mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2025-12-11 20:15:30 +01:00
Merge branch '0.7-constraint' into 0.7
This commit is contained in:
commit
6a99eb4a90
@ -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
|
||||
|
||||
@ -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(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -6,6 +6,7 @@ export enum EAppEvent {
|
||||
onTaskStart,
|
||||
onTaskProgress,
|
||||
onTaskEnd,
|
||||
onComboBoxChanged,
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}),
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -78,6 +78,7 @@ export class WorkerClient {
|
||||
|
||||
return {
|
||||
triangleCount: this._loadedMesh.getTriangleCount(),
|
||||
dimensions: this._loadedMesh.getBounds().getDimensions(),
|
||||
materials: this._loadedMesh.getMaterials(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
|
||||
},
|
||||
voxelise: {
|
||||
voxeliser: 'bvh-ray',
|
||||
desiredHeight: 80,
|
||||
constraintAxis: 'y',
|
||||
size: 80,
|
||||
useMultisampleColouring: false,
|
||||
textureFiltering: TextureFiltering.Linear,
|
||||
voxelOverlapRule: 'average',
|
||||
|
||||
@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
|
||||
},
|
||||
voxelise: {
|
||||
voxeliser: 'bvh-ray',
|
||||
desiredHeight: 80,
|
||||
constraintAxis: 'y',
|
||||
size: 80,
|
||||
useMultisampleColouring: false,
|
||||
textureFiltering: TextureFiltering.Linear,
|
||||
voxelOverlapRule: 'average',
|
||||
|
||||
@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
|
||||
},
|
||||
voxelise: {
|
||||
voxeliser: 'bvh-ray',
|
||||
desiredHeight: 80,
|
||||
constraintAxis: 'y',
|
||||
size: 80,
|
||||
useMultisampleColouring: false,
|
||||
textureFiltering: TextureFiltering.Linear,
|
||||
voxelOverlapRule: 'average',
|
||||
|
||||
@ -10,7 +10,8 @@ const baseConfig: THeadlessConfig = {
|
||||
},
|
||||
voxelise: {
|
||||
voxeliser: 'bvh-ray',
|
||||
desiredHeight: 80,
|
||||
constraintAxis: 'y',
|
||||
size: 80,
|
||||
useMultisampleColouring: false,
|
||||
textureFiltering: TextureFiltering.Linear,
|
||||
voxelOverlapRule: 'average',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user