mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2024-12-21 03:09:14 +08:00
Added UI option for choosing voxeliser algorithm
This commit is contained in:
parent
d6cf5db3d2
commit
16488c260f
@ -10,6 +10,8 @@ import { VoxelMesh, VoxelMeshParams } from './voxel_mesh';
|
||||
import { BlockMesh, BlockMeshParams } from './block_mesh';
|
||||
import { TextureFiltering } from './texture';
|
||||
import { RayVoxeliser } from './voxelisers/ray-voxeliser';
|
||||
import { IVoxeliser } from './voxelisers/base-voxeliser';
|
||||
import { NormalCorrectedRayVoxeliser } from './voxelisers/normal-corrected-ray-voxeliser';
|
||||
|
||||
/* eslint-disable */
|
||||
export enum ActionReturnType {
|
||||
@ -170,7 +172,8 @@ export class AppContext {
|
||||
ambientOcclusionEnabled: uiElements.ambientOcclusion.getCachedValue() === 'on',
|
||||
};
|
||||
|
||||
this._loadedVoxelMesh = new RayVoxeliser().voxelise(this._loadedMesh, voxelMeshParams);
|
||||
const voxeliser: IVoxeliser = (uiElements.voxeliser.getCachedValue() === 'raybased' ? new RayVoxeliser() : new NormalCorrectedRayVoxeliser());
|
||||
this._loadedVoxelMesh = voxeliser.voxelise(this._loadedMesh, voxelMeshParams);
|
||||
Renderer.Get.useVoxelMesh(this._loadedVoxelMesh);
|
||||
}
|
||||
|
||||
|
56
src/ray.ts
56
src/ray.ts
@ -1,5 +1,5 @@
|
||||
import { Vector3 } from './vector';
|
||||
import { ASSERT, Bounds } from './util';
|
||||
import { ASSERT } from './util';
|
||||
|
||||
const EPSILON = 0.0000001;
|
||||
|
||||
@ -27,60 +27,6 @@ export interface Ray {
|
||||
axis: Axes
|
||||
}
|
||||
|
||||
export function generateRays(v0: Vector3, v1: Vector3, v2: Vector3, offset: Vector3): Array<Ray> {
|
||||
const bounds: Bounds = new Bounds(
|
||||
new Vector3(
|
||||
Math.ceil(Math.min(v0.x, v1.x, v2.x)),
|
||||
Math.ceil(Math.min(v0.y, v1.y, v2.y)),
|
||||
Math.ceil(Math.min(v0.z, v1.z, v2.z)),
|
||||
),
|
||||
new Vector3(
|
||||
Math.floor(Math.max(v0.x, v1.x, v2.x)),
|
||||
Math.floor(Math.max(v0.y, v1.y, v2.y)),
|
||||
Math.floor(Math.max(v0.z, v1.z, v2.z)),
|
||||
),
|
||||
);
|
||||
|
||||
const rayList: Array<Ray> = [];
|
||||
traverseX(rayList, bounds, offset);
|
||||
traverseY(rayList, bounds, offset);
|
||||
traverseZ(rayList, bounds, offset);
|
||||
return rayList;
|
||||
}
|
||||
|
||||
function traverseX(rayList: Array<Ray>, bounds: Bounds, offset: Vector3) {
|
||||
for (let y = bounds.min.y - offset.y; y <= bounds.max.y + offset.y; ++y) {
|
||||
for (let z = bounds.min.z - offset.z; z <= bounds.max.z + offset.z; ++z) {
|
||||
rayList.push({
|
||||
origin: new Vector3(bounds.min.x - 1, y, z),
|
||||
axis: Axes.x,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function traverseY(rayList: Array<Ray>, bounds: Bounds, offset: Vector3) {
|
||||
for (let x = bounds.min.x - offset.x; x <= bounds.max.x + offset.x; ++x) {
|
||||
for (let z = bounds.min.z - offset.z; z <= bounds.max.z + offset.z; ++z) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, bounds.min.y - 1, z),
|
||||
axis: Axes.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function traverseZ(rayList: Array<Ray>, bounds: Bounds, offset: Vector3) {
|
||||
for (let x = bounds.min.x - offset.x; x <= bounds.max.x + offset.x; ++x) {
|
||||
for (let y = bounds.min.y - offset.y; y <= bounds.max.y + offset.y; ++y) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, y, bounds.min.z - 1),
|
||||
axis: Axes.z,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function rayIntersectTriangle(ray: Ray, v0: Vector3, v1: Vector3, v2: Vector3): (Vector3 | undefined) {
|
||||
const edge1 = Vector3.sub(v1, v0);
|
||||
const edge2 = Vector3.sub(v2, v0);
|
||||
|
@ -58,6 +58,10 @@ export class UI {
|
||||
label: 'Build',
|
||||
elements: {
|
||||
'height': new SliderElement('Desired height', 3, 320, 0, 80),
|
||||
'voxeliser': new ComboBoxElement('Algorithm', [
|
||||
{ id: 'raybased', displayText: 'Ray-based' },
|
||||
{ id: 'normalcorrectedraybased', displayText: 'NCRB (beta)' },
|
||||
]),
|
||||
'ambientOcclusion': new ComboBoxElement('Ambient occlusion', [
|
||||
{ id: 'on', displayText: 'On (recommended)' },
|
||||
{ id: 'off', displayText: 'Off (faster)' },
|
||||
@ -71,7 +75,7 @@ export class UI {
|
||||
{ id: 'nearest', displayText: 'Nearest (faster)' },
|
||||
]),
|
||||
},
|
||||
elementsOrder: ['height', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering'],
|
||||
elementsOrder: ['height', 'voxeliser', 'ambientOcclusion', 'multisampleColouring', 'textureFiltering'],
|
||||
submitButton: new ButtonElement('Voxelise mesh', () => {
|
||||
this._appContext.do(Action.Voxelise);
|
||||
}),
|
||||
|
179
src/voxelisers/normal-corrected-ray-voxeliser.ts
Normal file
179
src/voxelisers/normal-corrected-ray-voxeliser.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { VoxelMeshParams, VoxelMesh } from '../voxel_mesh';
|
||||
import { AppConfig } from '../config';
|
||||
import { Mesh } from '../mesh';
|
||||
import { Axes, Ray, rayIntersectTriangle } from '../ray';
|
||||
import { Triangle, UVTriangle } from '../triangle';
|
||||
import { Bounds, RGB, UV } from '../util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IVoxeliser } from './base-voxeliser';
|
||||
import { DebugGeometryTemplates } from '../geometry';
|
||||
|
||||
/**
|
||||
* This voxeliser works by projecting rays onto each triangle
|
||||
* on each of the principle angles and testing for intersections
|
||||
*/
|
||||
export class NormalCorrectedRayVoxeliser extends IVoxeliser {
|
||||
private _mesh?: Mesh;
|
||||
private _voxelMesh?: VoxelMesh;
|
||||
private _voxelMeshParams?: VoxelMeshParams;
|
||||
private _scale!: number;
|
||||
private _size!: Vector3;
|
||||
private _offset!: Vector3;
|
||||
|
||||
public override voxelise(mesh: Mesh, voxelMeshParams: VoxelMeshParams): VoxelMesh {
|
||||
this._mesh = mesh;
|
||||
this._voxelMesh = new VoxelMesh(mesh, voxelMeshParams);
|
||||
this._voxelMeshParams = voxelMeshParams;
|
||||
|
||||
this._scale = (voxelMeshParams.desiredHeight) / Mesh.desiredHeight;
|
||||
const useMesh = mesh.copy();
|
||||
|
||||
useMesh.scaleMesh(this._scale);
|
||||
const bounds = useMesh.getBounds();
|
||||
this._size = Vector3.sub(bounds.max, bounds.min);
|
||||
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,
|
||||
);
|
||||
|
||||
for (let triIndex = 0; triIndex < useMesh.getTriangleCount(); ++triIndex) {
|
||||
const uvTriangle = useMesh.getUVTriangle(triIndex);
|
||||
const normals = useMesh.getNormals(triIndex);
|
||||
const material = useMesh.getMaterialByTriangle(triIndex);
|
||||
this._voxeliseTri(uvTriangle, material, normals);
|
||||
}
|
||||
|
||||
return this._voxelMesh;
|
||||
}
|
||||
|
||||
private _voxeliseTri(triangle: UVTriangle, materialName: string, normals: { v0: Vector3, v1: Vector3, v2: Vector3}) {
|
||||
const rayList = this._generateRays(triangle.v0, triangle.v1, triangle.v2,
|
||||
this._offset,
|
||||
);
|
||||
|
||||
rayList.forEach((ray) => {
|
||||
const intersection = rayIntersectTriangle(ray, triangle.v0, triangle.v1, triangle.v2);
|
||||
if (intersection) {
|
||||
const intersectionWorld = Vector3.divScalar(intersection, this._scale);
|
||||
this._voxelMesh!.debugBuffer.add(DebugGeometryTemplates.cross(
|
||||
intersectionWorld,
|
||||
0.1,
|
||||
RGB.magenta,
|
||||
));
|
||||
|
||||
// Move transition away from normal
|
||||
const norm = normals.v0.normalise();
|
||||
intersection.sub(Vector3.mulScalar(norm, 0.5));
|
||||
// Correct size parity
|
||||
intersection.add(this._offset);
|
||||
|
||||
let voxelPosition: Vector3;
|
||||
switch (ray.axis) {
|
||||
case Axes.x:
|
||||
voxelPosition = new Vector3(Math.round(intersection.x), intersection.y, intersection.z);
|
||||
break;
|
||||
case Axes.y:
|
||||
voxelPosition = new Vector3(intersection.x, Math.round(intersection.y), intersection.z);
|
||||
break;
|
||||
case Axes.z:
|
||||
voxelPosition = new Vector3(intersection.x, intersection.y, Math.round(intersection.z));
|
||||
break;
|
||||
}
|
||||
voxelPosition.round();
|
||||
|
||||
this._voxelMesh!.debugBuffer.add(DebugGeometryTemplates.arrow(
|
||||
intersectionWorld,
|
||||
Vector3.divScalar(voxelPosition, this._scale),
|
||||
RGB.magenta,
|
||||
));
|
||||
|
||||
let voxelColour: RGB;
|
||||
if (this._voxelMeshParams!.useMultisampleColouring) {
|
||||
const samples: RGB[] = [];
|
||||
for (let i = 0; i < AppConfig.MULTISAMPLE_COUNT; ++i) {
|
||||
const samplePosition = Vector3.add(voxelPosition, Vector3.random().addScalar(-0.5));
|
||||
samples.push(this._getVoxelColour(triangle, materialName, samplePosition));
|
||||
}
|
||||
voxelColour = RGB.averageFrom(samples);
|
||||
} else {
|
||||
voxelColour = this._getVoxelColour(triangle, materialName, voxelPosition);
|
||||
}
|
||||
|
||||
this._voxelMesh!.addVoxel(voxelPosition, voxelColour);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getVoxelColour(triangle: UVTriangle, materialName: string, location: Vector3): RGB {
|
||||
const area01 = new Triangle(triangle.v0, triangle.v1, location).getArea();
|
||||
const area12 = new Triangle(triangle.v1, triangle.v2, location).getArea();
|
||||
const area20 = new Triangle(triangle.v2, triangle.v0, location).getArea();
|
||||
const total = area01 + area12 + area20;
|
||||
|
||||
const w0 = area12 / total;
|
||||
const w1 = area20 / total;
|
||||
const w2 = area01 / total;
|
||||
|
||||
const uv = new UV(
|
||||
triangle.uv0.u * w0 + triangle.uv1.u * w1 + triangle.uv2.u * w2,
|
||||
triangle.uv0.v * w0 + triangle.uv1.v * w1 + triangle.uv2.v * w2,
|
||||
);
|
||||
|
||||
return this._mesh!.sampleMaterial(materialName, uv, this._voxelMeshParams!.textureFiltering);
|
||||
}
|
||||
|
||||
private _generateRays(v0: Vector3, v1: Vector3, v2: Vector3, offset: Vector3): Array<Ray> {
|
||||
const bounds: Bounds = new Bounds(
|
||||
new Vector3(
|
||||
Math.ceil(Math.min(v0.x, v1.x, v2.x)),
|
||||
Math.ceil(Math.min(v0.y, v1.y, v2.y)),
|
||||
Math.ceil(Math.min(v0.z, v1.z, v2.z)),
|
||||
),
|
||||
new Vector3(
|
||||
Math.floor(Math.max(v0.x, v1.x, v2.x)),
|
||||
Math.floor(Math.max(v0.y, v1.y, v2.y)),
|
||||
Math.floor(Math.max(v0.z, v1.z, v2.z)),
|
||||
),
|
||||
);
|
||||
|
||||
const rayList: Array<Ray> = [];
|
||||
this._traverseX(rayList, bounds, offset);
|
||||
this._traverseY(rayList, bounds, offset);
|
||||
this._traverseZ(rayList, bounds, offset);
|
||||
return rayList;
|
||||
}
|
||||
|
||||
private _traverseX(rayList: Array<Ray>, bounds: Bounds, offset: Vector3) {
|
||||
for (let y = bounds.min.y - offset.y; y <= bounds.max.y + offset.y; ++y) {
|
||||
for (let z = bounds.min.z - offset.z; z <= bounds.max.z + offset.z; ++z) {
|
||||
rayList.push({
|
||||
origin: new Vector3(bounds.min.x - 1, y, z),
|
||||
axis: Axes.x,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _traverseY(rayList: Array<Ray>, bounds: Bounds, offset: Vector3) {
|
||||
for (let x = bounds.min.x - offset.x; x <= bounds.max.x + offset.x; ++x) {
|
||||
for (let z = bounds.min.z - offset.z; z <= bounds.max.z + offset.z; ++z) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, bounds.min.y - 1, z),
|
||||
axis: Axes.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _traverseZ(rayList: Array<Ray>, bounds: Bounds, offset: Vector3) {
|
||||
for (let x = bounds.min.x - offset.x; x <= bounds.max.x + offset.x; ++x) {
|
||||
for (let y = bounds.min.y - offset.y; y <= bounds.max.y + offset.y; ++y) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, y, bounds.min.z - 1),
|
||||
axis: Axes.z,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { VoxelMeshParams, VoxelMesh } from '../voxel_mesh';
|
||||
import { AppConfig } from '../config';
|
||||
import { Mesh } from '../mesh';
|
||||
import { Axes, generateRays, rayIntersectTriangle } from '../ray';
|
||||
import { Axes, Ray, rayIntersectTriangle } from '../ray';
|
||||
import { Triangle, UVTriangle } from '../triangle';
|
||||
import { RGB, UV } from '../util';
|
||||
import { Bounds, RGB, UV } from '../util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IVoxeliser } from './base-voxeliser';
|
||||
import { DebugGeometryTemplates } from '../geometry';
|
||||
@ -17,7 +17,6 @@ export class RayVoxeliser extends IVoxeliser {
|
||||
private _voxelMesh?: VoxelMesh;
|
||||
private _voxelMeshParams?: VoxelMeshParams;
|
||||
private _scale!: number;
|
||||
private _size!: Vector3;
|
||||
private _offset!: Vector3;
|
||||
|
||||
public override voxelise(mesh: Mesh, voxelMeshParams: VoxelMeshParams): VoxelMesh {
|
||||
@ -25,49 +24,40 @@ export class RayVoxeliser extends IVoxeliser {
|
||||
this._voxelMesh = new VoxelMesh(mesh, voxelMeshParams);
|
||||
this._voxelMeshParams = voxelMeshParams;
|
||||
|
||||
this._scale = (voxelMeshParams.desiredHeight) / Mesh.desiredHeight;
|
||||
this._scale = (voxelMeshParams.desiredHeight - 1) / Mesh.desiredHeight;
|
||||
this._offset = (voxelMeshParams.desiredHeight % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
|
||||
const useMesh = mesh.copy();
|
||||
|
||||
useMesh.scaleMesh(this._scale);
|
||||
const bounds = useMesh.getBounds();
|
||||
this._size = Vector3.sub(bounds.max, bounds.min);
|
||||
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,
|
||||
);
|
||||
useMesh.translateMesh(this._offset);
|
||||
|
||||
for (let triIndex = 0; triIndex < useMesh.getTriangleCount(); ++triIndex) {
|
||||
const uvTriangle = useMesh.getUVTriangle(triIndex);
|
||||
const normals = useMesh.getNormals(triIndex);
|
||||
const material = useMesh.getMaterialByTriangle(triIndex);
|
||||
this._voxeliseTri(uvTriangle, material, normals);
|
||||
this._voxeliseTri(uvTriangle, material);
|
||||
}
|
||||
|
||||
return this._voxelMesh;
|
||||
}
|
||||
|
||||
private _voxeliseTri(triangle: UVTriangle, materialName: string, normals: { v0: Vector3, v1: Vector3, v2: Vector3}) {
|
||||
const rayList = generateRays(triangle.v0, triangle.v1, triangle.v2,
|
||||
this._offset,
|
||||
);
|
||||
private _voxeliseTri(triangle: UVTriangle, materialName: string) {
|
||||
const rayList = this._generateRays(triangle.v0, triangle.v1, triangle.v2);
|
||||
|
||||
rayList.forEach((ray) => {
|
||||
const rayOriginWorld = Vector3.divScalar(ray.origin, this._scale).sub(this._offset);
|
||||
this._voxelMesh!.debugBuffer.add(DebugGeometryTemplates.cross(
|
||||
rayOriginWorld,
|
||||
0.1,
|
||||
ray.axis === Axes.x ? RGB.red : (ray.axis === Axes.y ? RGB.green : RGB.blue),
|
||||
));
|
||||
|
||||
const intersection = rayIntersectTriangle(ray, triangle.v0, triangle.v1, triangle.v2);
|
||||
if (intersection) {
|
||||
const intersectionWorld = Vector3.divScalar(intersection, this._scale);
|
||||
this._voxelMesh!.debugBuffer.add(DebugGeometryTemplates.cross(
|
||||
intersectionWorld,
|
||||
0.1,
|
||||
RGB.magenta,
|
||||
this._voxelMesh!.debugBuffer.add(DebugGeometryTemplates.arrow(
|
||||
rayOriginWorld,
|
||||
Vector3.divScalar(intersection, this._scale).sub(this._offset),
|
||||
ray.axis === Axes.x ? RGB.red : (ray.axis === Axes.y ? RGB.green : RGB.blue),
|
||||
));
|
||||
|
||||
// Move transition away from normal
|
||||
const norm = normals.v0.normalise();
|
||||
intersection.sub(Vector3.mulScalar(norm, 0.5));
|
||||
// Correct size parity
|
||||
intersection.add(this._offset);
|
||||
|
||||
let voxelPosition: Vector3;
|
||||
switch (ray.axis) {
|
||||
case Axes.x:
|
||||
@ -80,13 +70,6 @@ export class RayVoxeliser extends IVoxeliser {
|
||||
voxelPosition = new Vector3(intersection.x, intersection.y, Math.round(intersection.z));
|
||||
break;
|
||||
}
|
||||
voxelPosition.round();
|
||||
|
||||
this._voxelMesh!.debugBuffer.add(DebugGeometryTemplates.arrow(
|
||||
intersectionWorld,
|
||||
Vector3.divScalar(voxelPosition, this._scale),
|
||||
RGB.magenta,
|
||||
));
|
||||
|
||||
let voxelColour: RGB;
|
||||
if (this._voxelMeshParams!.useMultisampleColouring) {
|
||||
@ -122,4 +105,58 @@ export class RayVoxeliser extends IVoxeliser {
|
||||
|
||||
return this._mesh!.sampleMaterial(materialName, uv, this._voxelMeshParams!.textureFiltering);
|
||||
}
|
||||
|
||||
private _generateRays(v0: Vector3, v1: Vector3, v2: Vector3): Array<Ray> {
|
||||
const bounds: Bounds = new Bounds(
|
||||
new Vector3(
|
||||
Math.floor(Math.min(v0.x, v1.x, v2.x)),
|
||||
Math.floor(Math.min(v0.y, v1.y, v2.y)),
|
||||
Math.floor(Math.min(v0.z, v1.z, v2.z)),
|
||||
),
|
||||
new Vector3(
|
||||
Math.ceil(Math.max(v0.x, v1.x, v2.x)),
|
||||
Math.ceil(Math.max(v0.y, v1.y, v2.y)),
|
||||
Math.ceil(Math.max(v0.z, v1.z, v2.z)),
|
||||
),
|
||||
);
|
||||
|
||||
const rayList: Array<Ray> = [];
|
||||
this._traverseX(rayList, bounds);
|
||||
this._traverseY(rayList, bounds);
|
||||
this._traverseZ(rayList, bounds);
|
||||
return rayList;
|
||||
}
|
||||
|
||||
private _traverseX(rayList: Array<Ray>, bounds: Bounds) {
|
||||
for (let y = bounds.min.y; y <= bounds.max.y; ++y) {
|
||||
for (let z = bounds.min.z; z <= bounds.max.z; ++z) {
|
||||
rayList.push({
|
||||
origin: new Vector3(bounds.min.x - 1, y, z),
|
||||
axis: Axes.x,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _traverseY(rayList: Array<Ray>, bounds: Bounds) {
|
||||
for (let x = bounds.min.x; x <= bounds.max.x; ++x) {
|
||||
for (let z = bounds.min.z; z <= bounds.max.z; ++z) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, bounds.min.y - 1, z),
|
||||
axis: Axes.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _traverseZ(rayList: Array<Ray>, bounds: Bounds) {
|
||||
for (let x = bounds.min.x; x <= bounds.max.x; ++x) {
|
||||
for (let y = bounds.min.y; y <= bounds.max.y; ++y) {
|
||||
rayList.push({
|
||||
origin: new Vector3(x, y, bounds.min.z - 1),
|
||||
axis: Axes.z,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user