Merge branch '0.7-constraint' into 0.7

This commit is contained in:
Lucas Dower 2022-11-17 21:45:18 +00:00
commit 6a99eb4a90
No known key found for this signature in database
GPG Key ID: B3EE6B8499593605
21 changed files with 167 additions and 31 deletions

View File

@ -14,6 +14,9 @@
"CAMERA_DEFAULT_ELEVATION_RADIANS": 1.3,
"CAMERA_SENSITIVITY_ROTATION": 0.005,
"CAMERA_SENSITIVITY_ZOOM": 0.005,
"CONSTRAINT_MAXIMUM_WIDTH": 1024,
"CONSTRAINT_MAXIMUM_HEIGHT": 380,
"CONSTRAINT_MAXIMUM_DEPTH": 1024,
"DITHER_MAGNITUDE": 32,
"SMOOTHNESS_MAX": 3.0,
"CAMERA_SMOOTHING": 0.1

View File

@ -20,6 +20,7 @@ import { ColourSpace, EAction } from './util';
import { ASSERT } from './util/error_util';
import { LOG_ERROR, Logger } from './util/log_util';
import { AppPaths, PathUtil } from './util/path_util';
import { Vector3 } from './vector';
import { TWorkerJob, WorkerController } from './worker_controller';
import { SetMaterialsParams, TFromWorkerMessage, TToWorkerMessage } from './worker_types';
@ -27,6 +28,7 @@ export class AppContext {
private _ui: UI;
private _workerController: WorkerController;
private _lastAction?: EAction;
public maxConstraint?: Vector3;
private _materialMap: MaterialMap;
public constructor() {
@ -190,6 +192,13 @@ export class AppContext {
ASSERT(payload.action === 'Import');
const outputElement = this._ui.getActionOutput(EAction.Import);
const dimensions = new Vector3(
payload.result.dimensions.x,
payload.result.dimensions.y,
payload.result.dimensions.z,
);
dimensions.mulScalar(380 / 8.0).floor();
this.maxConstraint = dimensions;
this._materialMap = payload.result.materials;
this._onMaterialMapChanged();
@ -373,8 +382,9 @@ export class AppContext {
const payload: TToWorkerMessage = {
action: 'Voxelise',
params: {
constraintAxis: uiElements.constraintAxis.getCachedValue(),
voxeliser: uiElements.voxeliser.getCachedValue(),
desiredHeight: uiElements.desiredHeight.getCachedValue(),
size: uiElements.size.getCachedValue(),
useMultisampleColouring: uiElements.multisampleColouring.getCachedValue(),
textureFiltering: uiElements.textureFiltering.getCachedValue() === 'linear' ? TextureFiltering.Linear : TextureFiltering.Nearest,
enableAmbientOcclusion: uiElements.ambientOcclusion.getCachedValue(),
@ -402,7 +412,7 @@ export class AppContext {
action: 'RenderNextVoxelMeshChunk',
params: {
enableAmbientOcclusion: uiElements.ambientOcclusion.getCachedValue(),
desiredHeight: uiElements.desiredHeight.getCachedValue(),
desiredHeight: uiElements.size.getCachedValue(),
},
};

View File

@ -30,6 +30,9 @@ export class AppConfig {
public readonly CAMERA_DEFAULT_ELEVATION_RADIANS: number;
public readonly CAMERA_SENSITIVITY_ROTATION: number;
public readonly CAMERA_SENSITIVITY_ZOOM: number;
public readonly CONSTRAINT_MAXIMUM_WIDTH: number;
public readonly CONSTRAINT_MAXIMUM_HEIGHT: number;
public readonly CONSTRAINT_MAXIMUM_DEPTH: number;
public readonly DITHER_MAGNITUDE: number;
public readonly SMOOTHNESS_MAX: number;
public readonly CAMERA_SMOOTHING: number;
@ -57,6 +60,9 @@ export class AppConfig {
this.CAMERA_DEFAULT_ELEVATION_RADIANS = configJSON.CAMERA_DEFAULT_ELEVATION_RADIANS;
this.CAMERA_SENSITIVITY_ROTATION = configJSON.CAMERA_SENSITIVITY_ROTATION;
this.CAMERA_SENSITIVITY_ZOOM = configJSON.CAMERA_SENSITIVITY_ZOOM;
this.CONSTRAINT_MAXIMUM_WIDTH = configJSON.CONSTRAINT_MAXIMUM_WIDTH;
this.CONSTRAINT_MAXIMUM_HEIGHT = configJSON.CONSTRAINT_MAXIMUM_HEIGHT;
this.CONSTRAINT_MAXIMUM_DEPTH = configJSON.CONSTRAINT_MAXIMUM_DEPTH;
this.DITHER_MAGNITUDE = configJSON.DITHER_MAGNITUDE;
this.SMOOTHNESS_MAX = configJSON.SMOOTHNESS_MAX;
this.CAMERA_SMOOTHING = configJSON.CAMERA_SMOOTHING;

View File

@ -6,6 +6,7 @@ export enum EAppEvent {
onTaskStart,
onTaskProgress,
onTaskEnd,
onComboBoxChanged,
}
/* eslint-enable */

View File

@ -1,3 +1,4 @@
import { EAppEvent, EventManager } from '../../event';
import { ASSERT } from '../../util/error_util';
import { LabelledElement } from './labelled_element';
@ -33,6 +34,7 @@ export class ComboBoxElement<T> extends LabelledElement<T> {
ASSERT(element !== null);
element.addEventListener('change', () => {
EventManager.Get.broadcast(EAppEvent.onComboBoxChanged, element.value);
if (this._onValueChangedDelegate) {
this._onValueChangedDelegate(element.value);
}

View File

@ -19,6 +19,7 @@ export class SliderElement extends LabelledElement<number> {
this._step = step;
this._dragging = false;
this._hovering = false;
this._customEvents = () => { };
}
public generateInnerHTML() {
@ -85,6 +86,20 @@ export class SliderElement extends LabelledElement<number> {
elementValue.addEventListener('change', () => {
this._onTypedValue();
});
this._customEvents(this);
}
private _customEvents: (slider: SliderElement) => void;
public registerCustomEvents(delegate: (slider: SliderElement) => void) {
this._customEvents = delegate;
return this;
}
public setMax(value: number) {
this._max = value;
this._value = clamp(this._value!, this._min, this._max);
this._onValueUpdated();
}
private _onTypedValue() {

View File

@ -3,6 +3,7 @@ import fs from 'fs';
import { AppContext } from '../app_context';
import { ArcballCamera } from '../camera';
import { AppConfig } from '../config';
import { EAppEvent, EventManager } from '../event';
import { TExporters } from '../exporters/exporters';
import { PaletteManager } from '../palette';
import { MeshType, Renderer } from '../renderer';
@ -10,6 +11,7 @@ import { EAction } from '../util';
import { ASSERT } from '../util/error_util';
import { LOG } from '../util/log_util';
import { AppPaths } from '../util/path_util';
import { TAxis } from '../util/type_util';
import { TDithering } from '../util/type_util';
import { TVoxelOverlapRule } from '../voxel_mesh';
import { TVoxelisers } from '../voxelisers/voxelisers';
@ -54,7 +56,32 @@ export class UI {
'voxelise': {
label: 'Voxelise',
elements: {
'desiredHeight': new SliderElement('Desired height', 3, 380, 0, 80, 1),
'constraintAxis': new ComboBoxElement<TAxis>('Constraint axis', [
{
id: 'y',
displayText: 'Y (height) (green)',
},
{
id: 'x',
displayText: 'X (width) (red)',
},
{
id: 'z',
displayText: 'Z (depth) (blue)',
},
]),
'size': new SliderElement('Size', 3, 380, 0, 80, 1)
.registerCustomEvents((slider: SliderElement) => {
EventManager.Get.add(EAppEvent.onComboBoxChanged, (value: any) => {
if (value[0] === 'x') {
slider.setMax(this._appContext.maxConstraint?.x ?? AppConfig.Get.CONSTRAINT_MAXIMUM_WIDTH);
} else if (value[0] === 'y') {
slider.setMax(this._appContext.maxConstraint?.y ?? AppConfig.Get.CONSTRAINT_MAXIMUM_HEIGHT);
} else {
slider.setMax(this._appContext.maxConstraint?.z ?? AppConfig.Get.CONSTRAINT_MAXIMUM_DEPTH);
}
});
}),
'voxeliser': new ComboBoxElement<TVoxelisers>('Algorithm', [
{
id: 'bvh-ray',
@ -98,7 +125,7 @@ export class UI {
},
]),
},
elementsOrder: ['desiredHeight', 'voxeliser', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering', 'voxelOverlapRule'],
elementsOrder: ['constraintAxis', 'size', 'voxeliser', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering', 'voxelOverlapRule'],
submitButton: new ButtonElement('Voxelise mesh', () => {
this._appContext.do(EAction.Voxelise);
}),

View File

@ -3,3 +3,5 @@ export type TBrand<K, T> = K & { __brand: T };
export type Vector3Hash = TBrand<number, 'Vector3Hash'>;
export type TDithering = 'off' | 'random' | 'ordered';
export type TAxis = 'x' | 'y' | 'z';

View File

@ -17,8 +17,24 @@ const bvhtree = require('bvh-tree');
export class BVHRayVoxeliserPlusThickness extends IVoxeliser {
protected override _voxelise(mesh: Mesh, voxeliseParams: VoxeliseParams.Input): VoxelMesh {
const voxelMesh = new VoxelMesh(voxeliseParams);
const scale = (voxeliseParams.desiredHeight - 1) / Mesh.desiredHeight;
const offset = (voxeliseParams.desiredHeight % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
const meshDimensions = mesh.getBounds().getDimensions();
let scale: number;
let offset = new Vector3(0.0, 0.0, 0.0);
switch (voxeliseParams.constraintAxis) {
case 'x':
scale = (voxeliseParams.size - 1) / meshDimensions.x;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.5, 0.0, 0.0) : new Vector3(0.0, 0.0, 0.0);
break;
case 'y':
scale = (voxeliseParams.size - 1) / meshDimensions.y;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
break;
case 'z':
scale = (voxeliseParams.size - 1) / meshDimensions.z;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.0, 0.0, 0.5) : new Vector3(0.0, 0.0, 0.0);
break;
}
mesh.setTransform((vertex: Vector3) => {
return vertex.copy().mulScalar(scale).add(offset);
@ -87,7 +103,7 @@ export class BVHRayVoxeliserPlusThickness extends IVoxeliser {
for (const intersection of intersections) {
const point = intersection.intersectionPoint;
const position = new Vector3(point.x, point.y, point.z);
// Shrinking towards the perpendicular vector
const triangle = mesh.getUVTriangle(intersection.triangleIndex);
const v0 = Vector3.sub(triangle.v1, triangle.v0);

View File

@ -2,7 +2,7 @@ import { Mesh } from '../mesh';
import { ProgressManager } from '../progress';
import { Axes, axesToDirection, Ray } from '../ray';
import { ASSERT } from '../util/error_util';
import { LOG } from '../util/log_util';
import { LOG, LOGF } from '../util/log_util';
import { Vector3 } from '../vector';
import { VoxelMesh } from '../voxel_mesh';
import { VoxeliseParams } from '../worker_types';
@ -17,8 +17,24 @@ const bvhtree = require('bvh-tree');
export class BVHRayVoxeliser extends IVoxeliser {
protected override _voxelise(mesh: Mesh, voxeliseParams: VoxeliseParams.Input): VoxelMesh {
const voxelMesh = new VoxelMesh(voxeliseParams);
const scale = (voxeliseParams.desiredHeight - 1) / Mesh.desiredHeight;
const offset = (voxeliseParams.desiredHeight % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
const meshDimensions = mesh.getBounds().getDimensions();
let scale: number;
let offset = new Vector3(0.0, 0.0, 0.0);
switch (voxeliseParams.constraintAxis) {
case 'x':
scale = (voxeliseParams.size - 1) / meshDimensions.x;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.5, 0.0, 0.0) : new Vector3(0.0, 0.0, 0.0);
break;
case 'y':
scale = (voxeliseParams.size - 1) / meshDimensions.y;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
break;
case 'z':
scale = (voxeliseParams.size - 1) / meshDimensions.z;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.0, 0.0, 0.5) : new Vector3(0.0, 0.0, 0.0);
break;
}
mesh.setTransform((vertex: Vector3) => {
return vertex.copy().mulScalar(scale).add(offset);

View File

@ -27,17 +27,30 @@ export class NormalCorrectedRayVoxeliser extends IVoxeliser {
this._voxelMesh = new VoxelMesh(voxeliseParams);
this._voxeliseParams = voxeliseParams;
const scale = (voxeliseParams.desiredHeight) / Mesh.desiredHeight;
const meshDimensions = mesh.getBounds().getDimensions();
let scale: number;
switch (voxeliseParams.constraintAxis) {
case 'x':
scale = voxeliseParams.size / meshDimensions.x;
break;
case 'y':
scale = voxeliseParams.size / meshDimensions.y;
break;
case 'z':
scale = voxeliseParams.size / meshDimensions.z;
break;
}
mesh.setTransform((vertex: Vector3) => {
return vertex.copy().mulScalar(scale);
});
const bounds = mesh.getBounds();
this._size = Vector3.sub(bounds.max, bounds.min);
this._size = Vector3.sub(bounds.max.copy().ceil(), bounds.min.copy().floor());
this._offset = new Vector3(
this._size.x % 2 < 0.001 ? 0.5 : 0.0,
this._size.y % 2 < 0.001 ? 0.5 : 0.0,
this._size.z % 2 < 0.001 ? 0.5 : 0.0,
this._size.x % 2 === 0 ? 0.5 : 0.0,
this._size.y % 2 === 0 ? 0.5 : 0.0,
this._size.z % 2 === 0 ? 0.5 : 0.0,
);
const numTris = mesh.getTriangleCount();

View File

@ -25,8 +25,23 @@ export class RayVoxeliser extends IVoxeliser {
this._voxelMesh = new VoxelMesh(voxeliseParams);
this._voxeliseParams = voxeliseParams;
const scale = (voxeliseParams.desiredHeight - 1) / Mesh.desiredHeight;
const offset = (voxeliseParams.desiredHeight % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
const meshDimensions = mesh.getBounds().getDimensions();
let scale: number;
let offset = new Vector3(0.0, 0.0, 0.0);
switch (voxeliseParams.constraintAxis) {
case 'x':
scale = (voxeliseParams.size - 1) / meshDimensions.x;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.5, 0.0, 0.0) : new Vector3(0.0, 0.0, 0.0);
break;
case 'y':
scale = (voxeliseParams.size - 1) / meshDimensions.y;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
break;
case 'z':
scale = (voxeliseParams.size - 1) / meshDimensions.z;
offset = (voxeliseParams.size % 2 === 0) ? new Vector3(0.0, 0.0, 0.5) : new Vector3(0.0, 0.0, 0.0);
break;
}
mesh.setTransform((vertex: Vector3) => {
return vertex.copy().mulScalar(scale).add(offset);

View File

@ -78,6 +78,7 @@ export class WorkerClient {
return {
triangleCount: this._loadedMesh.getTriangleCount(),
dimensions: this._loadedMesh.getBounds().getDimensions(),
materials: this._loadedMesh.getMaterials(),
};
}
@ -101,19 +102,19 @@ export class WorkerClient {
};
}
public voxelise(params: VoxeliseParams.Input): VoxeliseParams.Output {
ASSERT(this._loadedMesh !== undefined);
const voxeliser: IVoxeliser = VoxeliserFactory.GetVoxeliser(params.voxeliser);
this._loadedVoxelMesh = voxeliser.voxelise(this._loadedMesh, params);
this._voxelMeshChunkIndex = 0;
return {
};
}
private _voxelMeshChunkIndex = 0;
private _voxelMeshProgressHandle?: TTaskHandle;
public renderChunkedVoxelMesh(params: RenderNextVoxelMeshChunkParams.Input): RenderNextVoxelMeshChunkParams.Output {

View File

@ -7,6 +7,7 @@ import { StatusMessage } from './status';
import { TextureFiltering } from './texture';
import { ColourSpace } from './util';
import { AppError } from './util/error_util';
import { TAxis } from './util/type_util';
import { TDithering } from './util/type_util';
import { Vector3 } from './vector';
import { TVoxelOverlapRule } from './voxel_mesh';
@ -37,6 +38,7 @@ export namespace ImportParams {
export type Output = {
triangleCount: number,
dimensions: Vector3,
materials: MaterialMap
}
}
@ -54,8 +56,9 @@ export namespace RenderMeshParams {
export namespace VoxeliseParams {
export type Input = {
constraintAxis: TAxis,
voxeliser: TVoxelisers,
desiredHeight: number,
size: number,
useMultisampleColouring: boolean,
textureFiltering: TextureFiltering,
enableAmbientOcclusion: boolean,

View File

@ -18,7 +18,8 @@ test('Voxelise solid 2x2 cube', () => {
const voxeliser = new NormalCorrectedRayVoxeliser();
const voxelMesh = voxeliser.voxelise(mesh, {
desiredHeight: 2,
constraintAxis: 'y',
size: 2,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Nearest,
enableAmbientOcclusion: false,

View File

@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
},
voxelise: {
voxeliser: 'bvh-ray',
desiredHeight: 80,
constraintAxis: 'y',
size: 80,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',

View File

@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
},
voxelise: {
voxeliser: 'bvh-ray',
desiredHeight: 80,
constraintAxis: 'y',
size: 80,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',

View File

@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
},
voxelise: {
voxeliser: 'bvh-ray',
desiredHeight: 80,
constraintAxis: 'y',
size: 80,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',

View File

@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
},
voxelise: {
voxeliser: 'bvh-ray',
desiredHeight: 80,
constraintAxis: 'y',
size: 80,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',

View File

@ -7,8 +7,9 @@ export const headlessConfig: THeadlessConfig = {
filepath: '/Users/lucasdower/ObjToSchematic/res/samples/skull.obj', // Must be an absolute path
},
voxelise: {
constraintAxis: 'y',
voxeliser: 'bvh-ray',
desiredHeight: 80,
size: 80,
useMultisampleColouring: false,
textureFiltering: TextureFiltering.Linear,
voxelOverlapRule: 'average',

View File

@ -51,7 +51,7 @@ export function runHeadless(headlessConfig: THeadlessConfig) {
{
TIME_START('[TIMER] Exporter');
LOG_MAJOR('\nExporting...');
/**
* The OBJExporter is unique in that it uses the actual render buffer used by WebGL
* to create its data, in headless mode this render buffer is not created so we must
@ -62,7 +62,7 @@ export function runHeadless(headlessConfig: THeadlessConfig) {
do {
result = worker.renderChunkedVoxelMesh({
enableAmbientOcclusion: headlessConfig.voxelise.enableAmbientOcclusion,
desiredHeight: headlessConfig.voxelise.desiredHeight,
desiredHeight: headlessConfig.voxelise.size,
});
} while (result.moreVoxelsToBuffer);
}