Merge pull request #67 from LucasDower/ortho

Added orthographic camera
This commit is contained in:
Lucas Dower 2022-08-28 17:06:02 +01:00 committed by GitHub
commit 76b364385e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 526 additions and 199 deletions

6
res/static/magnet.svg Normal file
View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-magnet" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#00abfb" fill="none" stroke-linecap="round" stroke-linejoin="round" id="magnet-svg">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M4 13v-8a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v8a2 2 0 0 0 6 0v-8a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v8a8 8 0 0 1 -16 0" />
<line x1="4" y1="8" x2="9" y2="8" />
<line x1="15" y1="8" x2="19" y2="8" />
</svg>

After

Width:  |  Height:  |  Size: 502 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rectangle" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#00abfb" fill="none" stroke-linecap="round" stroke-linejoin="round" id="orthographic-svg">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="3" y="5" width="18" height="14" rx="2" />
</svg>

After

Width:  |  Height:  |  Size: 361 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-perspective" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="#00abfb" fill="none" stroke-linecap="round" stroke-linejoin="round" id="perspective-svg">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M6.141 4.163l12 1.714a1 1 0 0 1 .859 .99v10.266a1 1 0 0 1 -.859 .99l-12 1.714a1 1 0 0 1 -1.141 -.99v-13.694a1 1 0 0 1 1.141 -.99z" />
</svg>

After

Width:  |  Height:  |  Size: 454 B

View File

@ -15,6 +15,7 @@ import { OutputStyle } from './ui/elements/output';
import { IExporter } from './exporters/base_exporter';
import { TVoxelisers, VoxeliseParams, VoxeliserFactory } from './voxelisers/voxelisers';
import { ExporterFactory, TExporters } from './exporters/exporters';
import { ArcballCamera } from './camera';
/* eslint-disable */
export enum EAction {
@ -81,8 +82,9 @@ export class AppContext {
this._ui.disable(EAction.Simplify);
Renderer.Get.toggleIsGridEnabled();
Renderer.Get.toggleIsAxesEnabled();
ArcballCamera.Get.setCameraMode('perspective');
ArcballCamera.Get.toggleAngleSnap();
}
public do(action: EAction) {
@ -223,6 +225,7 @@ export class AppContext {
public draw() {
Renderer.Get.update();
this._ui.tick();
Renderer.Get.draw();
}

View File

@ -1,9 +1,10 @@
import { m4, v3 } from 'twgl.js';
import { MouseManager } from './mouse';
import { degreesToRadians } from './math';
import { AppMath, between, clamp, degreesToRadians, roundToNearest } from './math';
import { Renderer } from './renderer';
import { SmoothVariable, SmoothVectorVariable } from './util';
import { LOG, SmoothVariable, SmoothVectorVariable } from './util';
import { Vector3 } from './vector';
import { AppConfig } from './config';
export class ArcballCamera {
public isUserRotating = false;
@ -13,6 +14,8 @@ export class ArcballCamera {
private readonly zNear: number;
private readonly zFar: number;
public aspect: number;
private _isPerspective: boolean = true;
private readonly _defaultDistance = 18.0;
private readonly _defaultAzimuth = -1.0;
@ -26,6 +29,10 @@ export class ArcballCamera {
private readonly up: v3.Vec3 = [0, 1, 0];
private eye: v3.Vec3 = [0, 0, 0];
private _azimuthRelief = 0.0;
private _elevationRelief = 0.0;
private _isAngleSnapped = false;
private mouseSensitivity = 0.005;
private scrollSensitivity = 0.005;
@ -43,21 +50,66 @@ export class ArcballCamera {
this.gl = Renderer.Get._gl;
this.aspect = this.gl.canvas.width / this.gl.canvas.height;
this._elevation.setClamp(0.01, Math.PI - 0.01);
this._elevation.setClamp(0.001, Math.PI - 0.001);
this._distance.setClamp(1.0, 100.0);
this.setCameraMode('perspective');
}
public isPerspective() {
return this._isPerspective;
}
public isOrthographic() {
return !this._isPerspective;
}
public isAlignedWithAxis(axis: 'x' | 'y' | 'z'): boolean {
const azimuth = Math.abs(this._azimuth.getTarget() % (Math.PI * 2));
const elevation = this._elevation.getTarget();
switch (axis) {
case 'x':
return AppMath.nearlyEqual(azimuth, AppMath.RADIANS_0) || AppMath.nearlyEqual(azimuth, AppMath.RADIANS_180);
case 'y':
return AppMath.nearlyEqual(elevation, AppMath.RADIANS_0, 0.002) || AppMath.nearlyEqual(elevation, AppMath.RADIANS_180, 0.002);
case 'z':
return AppMath.nearlyEqual(azimuth, AppMath.RADIANS_90) || AppMath.nearlyEqual(azimuth, AppMath.RADIANS_270);
}
}
public setCameraMode(mode: 'perspective' | 'orthographic') {
this._isPerspective = mode === 'perspective';
}
private _angleSnap = false;
public toggleAngleSnap() {
this._angleSnap = !this._angleSnap;
if (!this._angleSnap) {
this._isAngleSnapped = false;
this._azimuthRelief = 0.0;
this._elevationRelief = 0.0;
}
}
public isAngleSnapEnabled() {
return this._angleSnap;
}
public updateCamera() {
this.aspect = this.gl.canvas.width / this.gl.canvas.height;
const mouseDelta = MouseManager.Get.getMouseDelta();
mouseDelta.dx *= this.mouseSensitivity;
mouseDelta.dy *= this.mouseSensitivity;
if (this.isUserRotating) {
this._azimuth.addToTarget(mouseDelta.dx * this.mouseSensitivity);
this._elevation.addToTarget(mouseDelta.dy * this.mouseSensitivity);
this._azimuth.addToTarget(mouseDelta.dx);
this._elevation.addToTarget(mouseDelta.dy);
}
if (this.isUserTranslating) {
const my = mouseDelta.dy * this.mouseSensitivity;
const mx = mouseDelta.dx * this.mouseSensitivity;
const my = mouseDelta.dy;
const mx = mouseDelta.dx;
// Up-down
const dy = -Math.cos(this._elevation.getTarget() - Math.PI/2);
const df = Math.sin(this._elevation.getTarget() - Math.PI/2);
@ -72,6 +124,89 @@ export class ArcballCamera {
this._target.addToTarget(new Vector3(dx * mx, 0.0, dz * mx));
}
const axisSnapRadius = clamp(AppConfig.ANGLE_SNAP_RADIUS_DEGREES, 0.0, 90.0) * degreesToRadians;
if (this._shouldSnapCameraAngle()) {
let shouldSnapToAzimuth = false;
let shouldSnapToElevation = false;
let snapAngleAzimuth = 0.0;
let snapAngleElevation = 0.0;
const azimuth = this._azimuth.getTarget();
const elevation = this._elevation.getTarget();
const modAzimuth = Math.abs(azimuth % (90 * degreesToRadians));
if (modAzimuth < axisSnapRadius || modAzimuth > (90*degreesToRadians - axisSnapRadius)) {
shouldSnapToAzimuth = true;
snapAngleAzimuth = roundToNearest(azimuth, 90 * degreesToRadians);
}
const elevationSnapPoints = [0, 90, 180].map((x) => x * degreesToRadians);
for (const elevationSnapPoint of elevationSnapPoints) {
if (elevationSnapPoint - axisSnapRadius <= elevation && elevation <= elevationSnapPoint + axisSnapRadius) {
shouldSnapToElevation = true;
snapAngleElevation = elevationSnapPoint;
break;
}
}
if (shouldSnapToAzimuth && shouldSnapToElevation) {
this._azimuth.setTarget(snapAngleAzimuth);
this._elevation.setTarget(snapAngleElevation);
this._isAngleSnapped = true;
}
}
/*
if (this.isOrthographic()) {
const azimuth0 = between(this._azimuth.getTarget(), 0.0 - axisSnapRadius, 0.0 + axisSnapRadius);
const azimuth90 = between(this._azimuth.getTarget(), Math.PI/2 - axisSnapRadius, Math.PI/2 + axisSnapRadius);
const azimuth180 = between(this._azimuth.getTarget(), Math.PI - axisSnapRadius, Math.PI + axisSnapRadius);
const azimuth270 = between(this._azimuth.getTarget(), 3*Math.PI/2 - axisSnapRadius, 3*Math.PI/2 + axisSnapRadius);
const elevationTop = between(this._elevation.getTarget(), 0.0 - axisSnapRadius, 0.0 + axisSnapRadius);
const elevationMiddle = between(this._elevation.getTarget(), Math.PI/2 - axisSnapRadius, Math.PI/2 + axisSnapRadius);
const elevationBottom = between(this._elevation.getTarget(), Math.PI - axisSnapRadius, Math.PI + axisSnapRadius);
if (elevationMiddle) {
if (azimuth0) {
this._azimuth.setTarget(0);
this._elevation.setTarget(Math.PI/2);
this._isAngleSnapped = true;
} else if (azimuth90) {
this._azimuth.setTarget(90);
this._elevation.setTarget(Math.PI/2);
this._isAngleSnapped = true;
} else if (azimuth180) {
this._azimuth.setTarget(180);
this._elevation.setTarget(Math.PI/2);
this._isAngleSnapped = true;
} else if (azimuth270) {
this._azimuth.setTarget(270);
this._elevation.setTarget(Math.PI/2);
this._isAngleSnapped = true;
}
}
}
*/
if (this._isAngleSnapped && this.isUserRotating) {
this._azimuthRelief += mouseDelta.dx;
this._elevationRelief += mouseDelta.dy;
if (!between(this._azimuthRelief, -axisSnapRadius, axisSnapRadius) || !between(this._elevationRelief, -axisSnapRadius, axisSnapRadius)) {
this._azimuth.setTarget(this._azimuth.getTarget() + this._azimuthRelief * 2);
this._elevation.setTarget(this._elevation.getTarget() + this._elevationRelief * 2);
this._isAngleSnapped = false;
}
}
if (!this._isAngleSnapped) {
this._azimuthRelief = 0.0;
this._elevationRelief = 0.0;
}
// Move camera towards target location
this._distance.tick();
this._azimuth.tick();
@ -86,6 +221,10 @@ export class ArcballCamera {
];
}
private _shouldSnapCameraAngle() {
return this.isOrthographic() && this._angleSnap;
}
getCameraPosition(azimuthOffset: number, elevationOffset: number) {
const azimuth = this._azimuth.getActual() + azimuthOffset;
const elevation = this._elevation.getActual() + elevationOffset;
@ -114,7 +253,12 @@ export class ArcballCamera {
}
public getProjectionMatrix() {
return m4.perspective(this.fov, this.aspect, this.zNear, this.zFar);
if (this._isPerspective) {
return m4.perspective(this.fov, this.aspect, this.zNear, this.zFar);
} else {
const zoom = this._distance.getActual() / 3.6;
return m4.ortho(-zoom * this.aspect, zoom * this.aspect, -zoom, zoom, -1000, 1000);
}
}
public getCameraMatrix() {

View File

@ -1,27 +1,30 @@
// TODO: Replace with UI options
export namespace AppConfig {
/** Darkens corner even if corner block does not exist, recommended */
/** Darkens corner even if corner block does not exist, recommended. */
export const AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;
/** Enable logging to the console */
/** Enable logging to the console. */
export const LOGGING_ENABLED = true;
/** Enables runtime assertions, useful for debugging */
/** Enables runtime assertions, useful for debugging. */
export const ASSERTIONS_ENABLED = true;
/** Optimises rendering by not rendering triangles facing away from camera's view direction */
/** Optimises rendering by not rendering triangles facing away from camera's view direction. */
export const FACE_CULLING = false;
/** Enables extra runtimes checks that slow execution */
/** Enables extra runtimes checks that slow execution. */
export const DEBUG_ENABLED = true;
/** The number of samples used when sampling a voxel's colour from a textured material */
/** The number of samples used when sampling a voxel's colour from a textured material. */
export const MULTISAMPLE_COUNT = 16;
/** Max size of Node's old space in MBs */
/** Max size of Node's old space in MBs. */
export const OLD_SPACE_SIZE = 2048;
/** This value determines how much more important it is to closely match a block's transparency value than it's colour */
/** This value determines how much more important it is to closely match a block's transparency value than its colour. */
export const ALPHA_BIAS = 1.0;
/** The angle radius (in degrees) around a snapping point the viewport camera must be within to snap. Must be between 0.0 and 90.0 */
export const ANGLE_SNAP_RADIUS_DEGREES = 10.0;
}

View File

@ -2,13 +2,6 @@ import { ASSERT, LOG } from './util';
/* eslint-disable */
export enum EAppEvent {
onModelActiveChanged,
onModelAvailableChanged,
onGridEnabledChanged,
onAxesEnabledChanged,
onWireframeEnabledChanged,
onNormalsEnabledChanged,
onDevViewEnabledChanged,
}
/* eslint-enable */

View File

@ -219,36 +219,89 @@ export class DebugGeometryTemplates {
return MergeAttributeData(line, cone);
}
public static grid(dimensions: Vector3, spacing?: number): RenderBuffer {
public static COLOUR_MINOR: RGBA = { r: 0.5, g: 0.5, b: 0.5, a: 0.3 };
public static COLOUR_MAJOR: RGBA = { r: 1.0, g: 1.0, b: 1.0, a: 0.3 };
public static gridX(dimensions: Vector3, spacing?: number): RenderBuffer {
const buffer = new RenderBuffer([
{ name: 'position', numComponents: 3 },
{ name: 'colour', numComponents: 4 },
]);
buffer.add(DebugGeometryTemplates.line(
new Vector3(0, -dimensions.y / 2, -dimensions.z / 2),
new Vector3(0, -dimensions.y / 2, dimensions.z / 2),
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(0, dimensions.y / 2, -dimensions.z / 2),
new Vector3(0, dimensions.y / 2, dimensions.z / 2),
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(0, -dimensions.y / 2, -dimensions.z / 2),
new Vector3(0, dimensions.y / 2, -dimensions.z / 2),
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(0, -dimensions.y / 2, dimensions.z / 2),
new Vector3(0, dimensions.y / 2, dimensions.z / 2),
DebugGeometryTemplates.COLOUR_MAJOR,
));
if (spacing) {
ASSERT(spacing > 0.0);
for (let y = -dimensions.y / 2; y < dimensions.y / 2; y += spacing) {
buffer.add(DebugGeometryTemplates.line(
new Vector3(0, y, -dimensions.z / 2),
new Vector3(0, y, dimensions.z / 2),
DebugGeometryTemplates.COLOUR_MINOR,
));
}
for (let z = -dimensions.z / 2; z < dimensions.z / 2; z += spacing) {
buffer.add(DebugGeometryTemplates.line(
new Vector3(0, -dimensions.y / 2, z),
new Vector3(0, dimensions.y / 2, z),
DebugGeometryTemplates.COLOUR_MINOR,
));
}
}
return buffer;
}
public static gridY(dimensions: Vector3, spacing?: number): RenderBuffer {
const buffer = new RenderBuffer([
{ name: 'position', numComponents: 3 },
{ name: 'colour', numComponents: 4 },
]);
const COLOUR_MINOR: RGBA = { r: 0.5, g: 0.5, b: 0.5, a: 0.3 };
const COLOUR_MAJOR: RGBA = { r: 1.0, g: 1.0, b: 1.0, a: 0.3 };
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, 0, -dimensions.z / 2),
new Vector3(-dimensions.x / 2, 0, dimensions.z / 2),
COLOUR_MAJOR,
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(dimensions.x / 2, 0, -dimensions.z / 2),
new Vector3(dimensions.x / 2, 0, dimensions.z / 2),
COLOUR_MAJOR,
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, 0, -dimensions.z / 2),
new Vector3(dimensions.x / 2, 0, -dimensions.z / 2),
COLOUR_MAJOR,
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, 0, dimensions.z / 2),
new Vector3(dimensions.x / 2, 0, dimensions.z / 2),
COLOUR_MAJOR,
DebugGeometryTemplates.COLOUR_MAJOR,
));
if (spacing) {
@ -257,7 +310,7 @@ export class DebugGeometryTemplates {
buffer.add(DebugGeometryTemplates.line(
new Vector3(x, 0, -dimensions.z / 2),
new Vector3(x, 0, dimensions.z / 2),
COLOUR_MINOR,
DebugGeometryTemplates.COLOUR_MINOR,
));
}
@ -265,7 +318,59 @@ export class DebugGeometryTemplates {
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, 0, z),
new Vector3(dimensions.x / 2, 0, z),
COLOUR_MINOR,
DebugGeometryTemplates.COLOUR_MINOR,
));
}
}
return buffer;
}
public static gridZ(dimensions: Vector3, spacing?: number): RenderBuffer {
const buffer = new RenderBuffer([
{ name: 'position', numComponents: 3 },
{ name: 'colour', numComponents: 4 },
]);
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, -dimensions.y / 2, 0),
new Vector3(-dimensions.x / 2, dimensions.y / 2, 0),
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(dimensions.x / 2, -dimensions.y / 2, 0),
new Vector3(dimensions.x / 2, dimensions.y / 2, 0),
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, -dimensions.y / 2, 0),
new Vector3(dimensions.x / 2, -dimensions.y / 2, 0),
DebugGeometryTemplates.COLOUR_MAJOR,
));
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, dimensions.y / 2, 0),
new Vector3(dimensions.x / 2, dimensions.y / 2, 0),
DebugGeometryTemplates.COLOUR_MAJOR,
));
if (spacing) {
ASSERT(spacing > 0.0);
for (let x = -dimensions.x / 2; x < dimensions.x / 2; x += spacing) {
buffer.add(DebugGeometryTemplates.line(
new Vector3(x, -dimensions.y / 2, 0),
new Vector3(x, dimensions.y / 2, 0),
DebugGeometryTemplates.COLOUR_MINOR,
));
}
for (let y = -dimensions.y / 2; y < dimensions.y / 2; y += spacing) {
buffer.add(DebugGeometryTemplates.line(
new Vector3(-dimensions.x / 2, y, 0),
new Vector3(dimensions.x / 2, y, 0),
DebugGeometryTemplates.COLOUR_MINOR,
));
}
}

View File

@ -1,6 +1,20 @@
import { AppError, LOG_ERROR } from './util';
import { Vector3 } from './vector';
export namespace AppMath {
export const RADIANS_0 = degreesToRadians(0.0);
export const RADIANS_90 = degreesToRadians(90.0);
export const RADIANS_180 = degreesToRadians(180.0);
export const RADIANS_270 = degreesToRadians(270.0);
export function nearlyEqual(a: number, b: number, tolerance: number = 0.0001) {
return Math.abs(a - b) < tolerance;
}
export function degreesToRadians(degrees: number) {
return degrees * (Math.PI / 180.0);
}
}
export const argMax = (array: [number]) => {
return array.map((x, i) => [x, i]).reduce((r, a) => (a[0] > r[0] ? a : r))[1];
@ -34,6 +48,10 @@ export const roundToNearest = (value: number, base: number) => {
return Math.round(value / base) * base;
};
export const between = (value: number, min: number, max: number) => {
return min <= value && value <= max;
};
export const mapRange = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => {
return (value - fromMin)/(fromMax - fromMin) * (toMax - toMin) + toMin;
};

View File

@ -10,7 +10,6 @@ import { VoxelMesh } from './voxel_mesh';
import { BlockMesh } from './block_mesh';
import * as twgl from 'twgl.js';
import { EAppEvent, EventManager } from './event';
import { RGBA, RGBAUtil } from './colour';
import { Texture } from './texture';
@ -25,7 +24,6 @@ export enum MeshType {
/* eslint-disable */
enum EDebugBufferComponents {
Grid,
Wireframe,
Normals,
Bounds,
@ -59,6 +57,13 @@ export class Renderer {
private _isGridComponentEnabled: { [bufferComponent: string]: boolean };
private _axesEnabled: boolean;
private _gridBuffers: {
x: { [meshType: string]: RenderBuffer};
y: { [meshType: string]: RenderBuffer};
z: { [meshType: string]: RenderBuffer};
};
private _gridEnabled: boolean;
private static _instance: Renderer;
public static get Get() {
return this._instance || (this._instance = new this());
@ -73,6 +78,9 @@ export class Renderer {
this._modelsAvailable = 0;
this._materialBuffers = [];
this._gridBuffers = { x: {}, y: {}, z: {} };
this._gridEnabled = true;
this._debugBuffers = {};
this._debugBuffers[MeshType.None] = {};
this._debugBuffers[MeshType.TriangleMesh] = {};
@ -80,7 +88,6 @@ export class Renderer {
this._debugBuffers[MeshType.BlockMesh] = {};
this._isGridComponentEnabled = {};
this._isGridComponentEnabled[EDebugBufferComponents.Grid] = false;
this._axesEnabled = false;
this._axisBuffer = new RenderBuffer([
@ -117,50 +124,44 @@ export class Renderer {
// /////////////////////////////////////////////////////////////////////////
public toggleIsGridEnabled() {
const isEnabled = !this._isGridComponentEnabled[EDebugBufferComponents.Grid];
this._isGridComponentEnabled[EDebugBufferComponents.Grid] = isEnabled;
EventManager.Get.broadcast(EAppEvent.onGridEnabledChanged, isEnabled);
this._gridEnabled = !this._gridEnabled;
}
public isGridEnabled() {
return this._gridEnabled;
}
public isAxesEnabled() {
return this._axesEnabled;
}
public toggleIsAxesEnabled() {
this._axesEnabled = !this._axesEnabled;
EventManager.Get.broadcast(EAppEvent.onAxesEnabledChanged, this._axesEnabled);
}
public toggleIsWireframeEnabled() {
const isEnabled = !this._isGridComponentEnabled[EDebugBufferComponents.Wireframe];
this._isGridComponentEnabled[EDebugBufferComponents.Wireframe] = isEnabled;
EventManager.Get.broadcast(EAppEvent.onWireframeEnabledChanged, isEnabled);
}
public toggleIsNormalsEnabled() {
const isEnabled = !this._isGridComponentEnabled[EDebugBufferComponents.Normals];
this._isGridComponentEnabled[EDebugBufferComponents.Normals] = isEnabled;
EventManager.Get.broadcast(EAppEvent.onNormalsEnabledChanged, isEnabled);
}
public toggleIsDevDebugEnabled() {
const isEnabled = !this._isGridComponentEnabled[EDebugBufferComponents.Dev];
this._isGridComponentEnabled[EDebugBufferComponents.Dev] = isEnabled;
EventManager.Get.broadcast(EAppEvent.onDevViewEnabledChanged, isEnabled);
}
public clearMesh() {
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.TriangleMesh, false);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.VoxelMesh, false);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.BlockMesh, false);
this._materialBuffers = [];
this._modelsAvailable = 0;
this.setModelToUse(MeshType.None);
}
public useMesh(mesh: Mesh) {
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.TriangleMesh, false);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.VoxelMesh, false);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.BlockMesh, false);
public useMesh(mesh: Mesh) {
LOG('Using mesh');
this._materialBuffers = [];
@ -255,21 +256,16 @@ export class Renderer {
});
const dimensions = mesh.getBounds().getDimensions();
this._debugBuffers[MeshType.TriangleMesh][EDebugBufferComponents.Grid] = DebugGeometryTemplates.grid(dimensions);
// this._debugBuffers[MeshType.TriangleMesh][EDebugBufferComponents.Wireframe] = DebugGeometryTemplates.meshWireframe(mesh, new RGB(0.18, 0.52, 0.89).toRGBA());
// this._debugBuffers[MeshType.TriangleMesh][EDebugBufferComponents.Normals] = DebugGeometryTemplates.meshNormals(mesh, new RGB(0.89, 0.52, 0.18).toRGBA());
// delete this._debugBuffers[MeshType.TriangleMesh][EDebugBufferComponents.Dev];
this._gridBuffers.x[MeshType.TriangleMesh] = DebugGeometryTemplates.gridX(dimensions);
this._gridBuffers.y[MeshType.TriangleMesh] = DebugGeometryTemplates.gridY(dimensions);
this._gridBuffers.z[MeshType.TriangleMesh] = DebugGeometryTemplates.gridZ(dimensions);
this._modelsAvailable = 1;
this.setModelToUse(MeshType.TriangleMesh);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.TriangleMesh, true);
}
public useVoxelMesh(voxelMesh: VoxelMesh, voxelSize: number, ambientOcclusionEnabled: boolean) {
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.VoxelMesh, false);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.BlockMesh, false);
LOG('Using voxel mesh');
LOG(voxelMesh);
@ -285,18 +281,15 @@ export class Renderer {
);
dimensions.add(1);
this._debugBuffers[MeshType.VoxelMesh][EDebugBufferComponents.Grid] = DebugGeometryTemplates.grid(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
// this._debugBuffers[MeshType.VoxelMesh][EDebugBufferComponents.Wireframe] = DebugGeometryTemplates.voxelMeshWireframe(voxelMesh, new RGB(0.18, 0.52, 0.89).toRGBA(), this._voxelSize);
this._gridBuffers.x[MeshType.VoxelMesh] = DebugGeometryTemplates.gridX(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
this._gridBuffers.y[MeshType.VoxelMesh] = DebugGeometryTemplates.gridY(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
this._gridBuffers.z[MeshType.VoxelMesh] = DebugGeometryTemplates.gridZ(Vector3.mulScalar(dimensions, voxelSize), voxelSize);
this._modelsAvailable = 2;
this.setModelToUse(MeshType.VoxelMesh);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.VoxelMesh, true);
}
public useBlockMesh(blockMesh: BlockMesh) {
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.BlockMesh, false);
LOG('Using block mesh');
LOG(blockMesh);
this._blockBuffer = twgl.createBufferInfoFromArrays(this._gl, blockMesh.createBuffer());
@ -306,18 +299,17 @@ export class Renderer {
mag: this._gl.NEAREST,
});
this._debugBuffers[MeshType.BlockMesh][EDebugBufferComponents.Grid] = this._debugBuffers[MeshType.VoxelMesh][EDebugBufferComponents.Grid];
this._gridBuffers.y[MeshType.BlockMesh] = this._gridBuffers.y[MeshType.VoxelMesh];
this._modelsAvailable = 3;
this.setModelToUse(MeshType.BlockMesh);
EventManager.Get.broadcast(EAppEvent.onModelAvailableChanged, MeshType.BlockMesh, true);
}
// /////////////////////////////////////////////////////////////////////////
private _drawDebug() {
const debugComponents = [EDebugBufferComponents.Grid];
/*
const debugComponents = [EDebugBufferComponents.GridY];
for (const debugComp of debugComponents) {
if (this._isGridComponentEnabled[debugComp]) {
ASSERT(this._debugBuffers[this._meshToUse]);
@ -326,6 +318,9 @@ export class Renderer {
if (debugComp === EDebugBufferComponents.Dev) {
this._gl.disable(this._gl.DEPTH_TEST);
}
if (debugComp === EDebugBufferComponents.GridY && !ArcballCamera.Get.isAlignedWithAxis('y')) {
continue;
}
this._drawBuffer(this._gl.LINES, buffer.getWebGLBuffer(), ShaderManager.Get.debugProgram, {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
});
@ -333,6 +328,33 @@ export class Renderer {
}
}
}
*/
// Draw grid
if (this._gridEnabled) {
if (ArcballCamera.Get.isAlignedWithAxis('x') && !ArcballCamera.Get.isAlignedWithAxis('y') && !ArcballCamera.Get.isUserRotating) {
const gridBuffer = this._gridBuffers.x[this._meshToUse];
if (gridBuffer !== undefined) {
this._drawBuffer(this._gl.LINES, gridBuffer.getWebGLBuffer(), ShaderManager.Get.debugProgram, {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
});
}
} else if (ArcballCamera.Get.isAlignedWithAxis('z') && !ArcballCamera.Get.isAlignedWithAxis('y') && !ArcballCamera.Get.isUserRotating) {
const gridBuffer = this._gridBuffers.z[this._meshToUse];
if (gridBuffer !== undefined) {
this._drawBuffer(this._gl.LINES, gridBuffer.getWebGLBuffer(), ShaderManager.Get.debugProgram, {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
});
}
} else {
const gridBuffer = this._gridBuffers.y[this._meshToUse];
if (gridBuffer !== undefined) {
this._drawBuffer(this._gl.LINES, gridBuffer.getWebGLBuffer(), ShaderManager.Get.debugProgram, {
u_worldViewProjection: ArcballCamera.Get.getWorldViewProjection(),
});
}
}
}
// Draw axis
if (this._axesEnabled) {
this._gl.disable(this._gl.DEPTH_TEST);
@ -411,7 +433,6 @@ export class Renderer {
const isModelAvailable = this._modelsAvailable >= meshType;
if (isModelAvailable) {
this._meshToUse = meshType;
EventManager.Get.broadcast(EAppEvent.onModelActiveChanged, meshType);
}
}

View File

@ -2,7 +2,12 @@ import { ASSERT, getRandomID, STATIC_DIR } from '../../util';
import path from 'path';
import fs from 'fs';
import { EAppEvent, EventManager } from '../../event';
export type TToolbarBooleanProperty = 'enabled' | 'active';
export type TToolbarItemParams = {
icon: string;
}
export class ToolbarItemElement {
private _id: string;
@ -10,36 +15,44 @@ export class ToolbarItemElement {
private _iconPath: string;
private _isEnabled: boolean;
private _isActive: boolean;
private _onClick: () => void;
public constructor(iconName: string, onClick: () => void,
_activeChangedEvent?: EAppEvent, _activeChangedDelegate?: (...args: any[]) => boolean,
_enableChangedEvent?: EAppEvent, _enableChangedDelegate?: (...args: any[]) => boolean,
) {
this._id = getRandomID();
this._iconName = iconName;
this._iconPath = path.join(STATIC_DIR, iconName + '.svg');
this._isEnabled = false;
this._isActive = false;
this._onClick = onClick;
private _onClick?: () => void;
// Enabled/Disabled Event
if (_enableChangedEvent !== undefined && _enableChangedDelegate) {
EventManager.Get.add(_enableChangedEvent, (...args: any[]) => {
const isEnabled = _enableChangedDelegate(args);
this.setEnabled(isEnabled);
});
} else {
this._isEnabled = true;
}
public constructor(params: TToolbarItemParams) {
this._id = getRandomID();
// Active/Inactive Event
if (_activeChangedEvent !== undefined && _activeChangedDelegate) {
EventManager.Get.add(_activeChangedEvent, (...args: any[]) => {
const isActive = _activeChangedDelegate(args);
this.setActive(isActive);
});
this._iconName = params.icon;
this._iconPath = path.join(STATIC_DIR, params.icon + '.svg');
this._isEnabled = true;
this._isActive = false;
}
public tick() {
if (this._isEnabledDelegate !== undefined) {
this.setEnabled(this._isEnabledDelegate());
}
if (this._isActiveDelegate !== undefined) {
this.setActive(this._isActiveDelegate());
}
}
private _isActiveDelegate?: () => boolean;
public isActive(delegate: () => boolean) {
this._isActiveDelegate = delegate;
return this;
}
private _isEnabledDelegate?: () => boolean;
public isEnabled(delegate: () => boolean) {
this._isEnabledDelegate = delegate;
return this;
}
public onClick(delegate: () => void) {
this._onClick = delegate;
return this;
}
public generateHTML() {
@ -56,7 +69,7 @@ export class ToolbarItemElement {
ASSERT(element !== null);
element.addEventListener('click', () => {
if (this._isEnabled) {
if (this._isEnabled && this._onClick) {
this._onClick();
}
});

View File

@ -9,7 +9,6 @@ import { ASSERT, ATLASES_DIR, LOG, PALETTES_DIR } from '../util';
import fs from 'fs';
import { ToolbarItemElement } from './elements/toolbar_item';
import { EAppEvent } from '../event';
import { MeshType, Renderer } from '../renderer';
import { ArcballCamera } from '../camera';
import { TVoxelisers } from '../voxelisers/voxelisers';
@ -192,119 +191,117 @@ export class UI {
groups: {
'viewmode': {
elements: {
'mesh': new ToolbarItemElement('mesh', () => {
Renderer.Get.setModelToUse(MeshType.TriangleMesh);
},
EAppEvent.onModelActiveChanged, (...args: any[]) => {
const modelUsed = args[0][0][0] as MeshType;
return modelUsed === MeshType.TriangleMesh;
},
EAppEvent.onModelAvailableChanged, (...args: any[]) => {
const modelType = args[0][0][0] as MeshType;
const isCached = args[0][0][1] as boolean;
return modelType >= MeshType.TriangleMesh && isCached;
}),
'voxelMesh': new ToolbarItemElement('voxel', () => {
Renderer.Get.setModelToUse(MeshType.VoxelMesh);
}, EAppEvent.onModelActiveChanged, (...args: any[]) => {
const modelUsed = args[0][0][0] as MeshType;
return modelUsed === MeshType.VoxelMesh;
}, EAppEvent.onModelAvailableChanged, (...args: any[]) => {
const modelType = args[0][0][0] as MeshType;
const isCached = args[0][0][1] as boolean;
return modelType >= MeshType.VoxelMesh && isCached;
}),
'blockMesh': new ToolbarItemElement('block', () => {
Renderer.Get.setModelToUse(MeshType.BlockMesh);
}, EAppEvent.onModelActiveChanged, (...args: any[]) => {
const modelUsed = args[0][0][0] as MeshType;
return modelUsed === MeshType.BlockMesh;
}, EAppEvent.onModelAvailableChanged, (...args: any[]) => {
const modelType = args[0][0][0] as MeshType;
const isCached = args[0][0][1] as boolean;
return modelType >= MeshType.BlockMesh && isCached;
}),
'mesh': new ToolbarItemElement({ icon: 'mesh' })
.onClick(() => {
Renderer.Get.setModelToUse(MeshType.TriangleMesh);
})
.isActive(() => {
return Renderer.Get.getActiveMeshType() === MeshType.TriangleMesh;
})
.isEnabled(() => {
return Renderer.Get.getModelsAvailable() >= MeshType.TriangleMesh;
}),
'voxelMesh': new ToolbarItemElement({ icon: 'voxel' })
.onClick(() => {
Renderer.Get.setModelToUse(MeshType.VoxelMesh);
})
.isActive(() => {
return Renderer.Get.getActiveMeshType() === MeshType.VoxelMesh;
})
.isEnabled(() => {
return Renderer.Get.getModelsAvailable() >= MeshType.VoxelMesh;
}),
'blockMesh': new ToolbarItemElement({ icon: 'block' })
.onClick(() => {
Renderer.Get.setModelToUse(MeshType.BlockMesh);
})
.isActive(() => {
return Renderer.Get.getActiveMeshType() === MeshType.BlockMesh;
})
.isEnabled(() => {
return Renderer.Get.getModelsAvailable() >= MeshType.BlockMesh;
}),
},
elementsOrder: ['mesh', 'voxelMesh', 'blockMesh'],
},
'zoom': {
elements: {
'zoomOut': new ToolbarItemElement('minus', () => {
ArcballCamera.Get.onZoomOut();
}),
'zoomIn': new ToolbarItemElement('plus', () => {
ArcballCamera.Get.onZoomIn();
}),
'centre': new ToolbarItemElement('centre', () => {
ArcballCamera.Get.reset();
}),
},
elementsOrder: ['zoomOut', 'zoomIn', 'centre'],
},
'debug': {
elements: {
'grid': new ToolbarItemElement('grid', () => {
Renderer.Get.toggleIsGridEnabled();
}, EAppEvent.onGridEnabledChanged, (...args: any[]) => {
const isEnabled = args[0][0][0] as boolean;
return isEnabled;
}, EAppEvent.onModelActiveChanged, (...args: any[]) => {
return Renderer.Get.getActiveMeshType() !== MeshType.None;
}),
'axes': new ToolbarItemElement('axes', () => {
Renderer.Get.toggleIsAxesEnabled();
}, EAppEvent.onAxesEnabledChanged, (...args: any[]) => {
const isEnabled = args[0][0][0] as boolean;
return isEnabled;
}),
'grid': new ToolbarItemElement({ icon: 'grid' })
.onClick(() => {
Renderer.Get.toggleIsGridEnabled();
})
.isActive(() => {
return Renderer.Get.isGridEnabled();
})
.isEnabled(() => {
return Renderer.Get.getActiveMeshType() !== MeshType.None;
}),
'axes': new ToolbarItemElement({ icon: 'axes' })
.onClick(() => {
Renderer.Get.toggleIsAxesEnabled();
})
.isActive(() => {
return Renderer.Get.isAxesEnabled();
}),
},
elementsOrder: ['grid', 'axes'],
},
},
groupsOrder: ['viewmode', 'zoom', 'debug'],
groupsOrder: ['viewmode', 'debug'],
};
private _toolbarRight = {
groups: {
'debug': {
'zoom': {
elements: {
/*
'wireframe': new ToolbarItemElement('wireframe', () => {
Renderer.Get.toggleIsWireframeEnabled();
}, EAppEvent.onWireframeEnabledChanged, (...args: any[]) => {
const isEnabled = args[0][0][0] as boolean;
return isEnabled;
}, EAppEvent.onModelActiveChanged, (...args: any[]) => {
const modelUsed = args[0][0][0] as MeshType;
return modelUsed === MeshType.TriangleMesh || modelUsed === MeshType.VoxelMesh;
}),
'normals': new ToolbarItemElement('normal', () => {
Renderer.Get.toggleIsNormalsEnabled();
}, EAppEvent.onNormalsEnabledChanged, (...args: any[]) => {
const isEnabled = args[0][0][0] as boolean;
return isEnabled;
}, EAppEvent.onModelActiveChanged, (...args: any[]) => {
const modelUsed = args[0][0][0] as MeshType;
return modelUsed === MeshType.TriangleMesh;
}),
'dev': new ToolbarItemElement('debug', () => {
Renderer.Get.toggleIsDevDebugEnabled();
}, EAppEvent.onDevViewEnabledChanged, (...args: any[]) => {
const isEnabled = args[0][0][0] as boolean;
return isEnabled;
}, EAppEvent.onModelActiveChanged, (...args: any[]) => {
const modelUsed = args[0][0][0] as MeshType;
const devBufferAvailable = Renderer.Get.getModelsAvailable() >= 2;
return modelUsed === MeshType.TriangleMesh && devBufferAvailable;
}),
*/
'zoomOut': new ToolbarItemElement({ icon: 'minus' })
.onClick(() => {
ArcballCamera.Get.onZoomOut();
}),
'zoomIn': new ToolbarItemElement({ icon: 'plus' })
.onClick(() => {
ArcballCamera.Get.onZoomIn();
}),
'reset': new ToolbarItemElement({ icon: 'centre' })
.onClick(() => {
ArcballCamera.Get.reset();
}),
},
elementsOrder: [], // ['wireframe', 'normals', 'dev'],
elementsOrder: ['zoomOut', 'zoomIn', 'reset'],
},
'camera': {
elements: {
'perspective': new ToolbarItemElement({ icon: 'perspective' })
.onClick(() => {
ArcballCamera.Get.setCameraMode('perspective');
})
.isActive(() => {
return ArcballCamera.Get.isPerspective();
}),
'orthographic': new ToolbarItemElement({ icon: 'orthographic' })
.onClick(() => {
ArcballCamera.Get.setCameraMode('orthographic');
})
.isActive(() => {
return ArcballCamera.Get.isOrthographic();
}),
'angleSnap': new ToolbarItemElement({ icon: 'magnet' })
.onClick(() => {
ArcballCamera.Get.toggleAngleSnap();
})
.isActive(() => {
return ArcballCamera.Get.isAngleSnapEnabled();
})
.isEnabled(() => {
return ArcballCamera.Get.isOrthographic();
}),
},
elementsOrder: ['perspective', 'orthographic', 'angleSnap'],
},
},
groupsOrder: ['debug'],
groupsOrder: ['camera', 'zoom'],
};
private _uiDull: { [key: string]: Group } = this._ui;
@ -320,6 +317,22 @@ export class UI {
this._ui.assign.elements.fallable.addDescription('Read tooltips for more info');
}
public tick() {
for (const groupName in this._toolbarLeftDull) {
const toolbarGroup = this._toolbarLeftDull[groupName];
for (const toolbarItem of toolbarGroup.elementsOrder) {
toolbarGroup.elements[toolbarItem].tick();
}
}
for (const groupName in this._toolbarRightDull) {
const toolbarGroup = this._toolbarRightDull[groupName];
for (const toolbarItem of toolbarGroup.elementsOrder) {
toolbarGroup.elements[toolbarItem].tick();
}
}
}
public build() {
const groupHTML: { [key: string]: string } = {};
for (const groupName in this._ui) {

View File

@ -210,7 +210,7 @@ export class SmoothVariable {
}
public setTarget(target: number) {
this._target = target;
this._target = clamp(target, this._min, this._max);
}
public setActual(actual: number) {