From 7be3b891597c072c0abc2652f00ab1ea536c581f Mon Sep 17 00:00:00 2001 From: Lucas Dower Date: Tue, 24 Oct 2023 20:06:31 +0100 Subject: [PATCH] Optimised voxelisation, ~2x improvement --- Core/src/ots_voxel_mesh_converter.ts | 59 ++++++++--------- Core/src/ray.ts | 17 ++--- Core/src/util/array_util.ts | 18 ++++++ Core/tests/ray.test.ts | 94 ---------------------------- Editor/src/app_context.ts | 2 +- Editor/src/worker/worker_types.ts | 2 +- Sandbox/index.ts | 2 + 7 files changed, 53 insertions(+), 141 deletions(-) create mode 100644 Core/src/util/array_util.ts delete mode 100644 Core/tests/ray.test.ts diff --git a/Core/src/ots_voxel_mesh_converter.ts b/Core/src/ots_voxel_mesh_converter.ts index b22af2b..3354ed8 100644 --- a/Core/src/ots_voxel_mesh_converter.ts +++ b/Core/src/ots_voxel_mesh_converter.ts @@ -2,10 +2,11 @@ import { OtS_ReplaceMode, OtS_VoxelMesh } from './ots_voxel_mesh'; import { TAxis } from './util/type_util'; import { Vector3 } from './vector'; import { Triangle } from './triangle'; -import { rayIntersectTriangleFastX, rayIntersectTriangleFastY, rayIntersectTriangleFastZ } from './ray'; import { OtS_Colours, RGBA, RGBAUtil } from './colour'; import { OtS_Mesh, OtS_Triangle } from './ots_mesh'; import { UV } from './util'; +import { rayIntersectTriangleFastX, rayIntersectTriangleFastY, rayIntersectTriangleFastZ, RayIntersect } from './ray'; +import { findFirstTrueIndex } from './util/array_util'; export type OtS_VoxelMesh_ConverterConfig = { constraintAxis: TAxis, @@ -62,41 +63,35 @@ export class OtS_VoxelMesh_Converter { const rayOrigin = new Vector3(0, 0, 0); - rayOrigin.x = bounds.min.x - 1; - for (let y = bounds.min.y; y <= bounds.max.y; ++y) { - rayOrigin.y = y; - for (let z = bounds.min.z; z <= bounds.max.z; ++z) { - rayOrigin.z = z; - const intersection = rayIntersectTriangleFastX(rayOrigin, triangle); - if (intersection) { - this._handleRayHit(intersection, triangle, voxelMesh); + const edge1 = Vector3.sub(triangle.data.v1.position, triangle.data.v0.position); + const edge2 = Vector3.sub(triangle.data.v2.position, triangle.data.v0.position); + + const rasterisePlane = (a0: 'x' | 'y' | 'z', a1: 'x' | 'y' | 'z', a2: 'x' | 'y' | 'z', intersect: RayIntersect) => { + rayOrigin[a0] = bounds.min[a0] - 1; + for (let y = bounds.min[a1]; y <= bounds.max[a1]; ++y) { + rayOrigin[a1] = y; // 2 + let hasHit = false; + + const start = findFirstTrueIndex(bounds.max[a2] - bounds.min[a2] + 1, (index: number) => { + rayOrigin[a2] = bounds.min[a2] + index; + return intersect(rayOrigin, triangle, edge1, edge2) !== undefined; + }); + + for (let z = bounds.min[a2] + start; z <= bounds.max[a2]; ++z) { + rayOrigin[a2] = z; // 3 + const intersection = intersect(rayOrigin, triangle, edge1, edge2); + if (intersection) { + this._handleRayHit(intersection, triangle, voxelMesh); + } else if (hasHit) { + break; + } } } } - rayOrigin.y = bounds.min.y - 1; - for (let z = bounds.min.z; z <= bounds.max.z; ++z) { - rayOrigin.z = z; - for (let x = bounds.min.x; x <= bounds.max.x; ++x) { - rayOrigin.x = x; - const intersection = rayIntersectTriangleFastY(rayOrigin, triangle); - if (intersection) { - this._handleRayHit(intersection, triangle, voxelMesh); - } - } - } - - rayOrigin.z = bounds.min.z - 1; - for (let x = bounds.min.x; x <= bounds.max.x; ++x) { - rayOrigin.x = x; - for (let y = bounds.min.y; y <= bounds.max.y; ++y) { - rayOrigin.y = y; - const intersection = rayIntersectTriangleFastZ(rayOrigin, triangle); - if (intersection) { - this._handleRayHit(intersection, triangle, voxelMesh); - } - } - } + rasterisePlane('x', 'y', 'z', rayIntersectTriangleFastX); + rasterisePlane('y', 'z', 'x', rayIntersectTriangleFastY); + rasterisePlane('x', 'x', 'y', rayIntersectTriangleFastZ); } private _handleRayHit(intersection: Vector3, triangle: OtS_Triangle, voxelMesh: OtS_VoxelMesh) { diff --git a/Core/src/ray.ts b/Core/src/ray.ts index 49ac776..356431f 100644 --- a/Core/src/ray.ts +++ b/Core/src/ray.ts @@ -28,12 +28,9 @@ export interface Ray { axis: Axes } -export type RayIntersect = (origin: Vector3, v0: Vector3, v1: Vector3, v2: Vector3) => (Vector3 | undefined); - -export function rayIntersectTriangleFastX(origin: Vector3, triangle: OtS_Triangle): (Vector3 | undefined) { - const edge1 = Vector3.sub(triangle.data.v1.position, triangle.data.v0.position); - const edge2 = Vector3.sub(triangle.data.v2.position, triangle.data.v0.position); +export type RayIntersect = (origin: Vector3, triangle: OtS_Triangle, edge1: Vector3, edge2: Vector3) => (Vector3 | undefined); +export function rayIntersectTriangleFastX(origin: Vector3, triangle: OtS_Triangle, edge1: Vector3, edge2: Vector3): (Vector3 | undefined) { const h = new Vector3(0, -edge2.z, edge2.y); // Vector3.cross(rayDirection, edge2); const a = Vector3.dot(edge1, h); @@ -66,10 +63,7 @@ export function rayIntersectTriangleFastX(origin: Vector3, triangle: OtS_Triangl } } -export function rayIntersectTriangleFastY(origin: Vector3, triangle: OtS_Triangle): (Vector3 | undefined) { - const edge1 = Vector3.sub(triangle.data.v1.position, triangle.data.v0.position); - const edge2 = Vector3.sub(triangle.data.v2.position, triangle.data.v0.position); - +export function rayIntersectTriangleFastY(origin: Vector3, triangle: OtS_Triangle, edge1: Vector3, edge2: Vector3): (Vector3 | undefined) { const h = new Vector3(edge2.z, 0, -edge2.x); // Vector3.cross(rayDirection, edge2); const a = Vector3.dot(edge1, h); @@ -102,10 +96,7 @@ export function rayIntersectTriangleFastY(origin: Vector3, triangle: OtS_Triangl } } -export function rayIntersectTriangleFastZ(origin: Vector3, triangle: OtS_Triangle): (Vector3 | undefined) { - const edge1 = Vector3.sub(triangle.data.v1.position, triangle.data.v0.position); - const edge2 = Vector3.sub(triangle.data.v2.position, triangle.data.v0.position); - +export function rayIntersectTriangleFastZ(origin: Vector3, triangle: OtS_Triangle, edge1: Vector3, edge2: Vector3): (Vector3 | undefined) { const h = new Vector3(-edge2.y, edge2.x, 0); // Vector3.cross(rayDirection, edge2); const a = Vector3.dot(edge1, h); diff --git a/Core/src/util/array_util.ts b/Core/src/util/array_util.ts new file mode 100644 index 0000000..b4e126e --- /dev/null +++ b/Core/src/util/array_util.ts @@ -0,0 +1,18 @@ +export function findFirstTrueIndex(length: number, valueAt: (index: number) => boolean) { + let low = 0; + let high = length - 1; + let result = -1; + + while (low <= high) { + const mid = Math.floor((low + high) / 2); + + if (valueAt(mid) === true) { + result = mid; + high = mid - 1; + } else { + low = mid + 1; + } + } + + return result; +} \ No newline at end of file diff --git a/Core/tests/ray.test.ts b/Core/tests/ray.test.ts deleted file mode 100644 index 069d006..0000000 --- a/Core/tests/ray.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Axes, Ray, rayIntersectTriangle } from '../src/ray'; -import { Triangle } from '../src/triangle'; -import { ASSERT } from '../src/util/error_util'; -import { Vector3 } from '../src/vector'; - -test('rayIntersectTriangle x-axis #1', () => { - const ray: Ray = { - origin: new Vector3(-1, 0, 0), - axis: Axes.x, - }; - - const v0 = new Vector3(5, -1, -1); - const v1 = new Vector3(5, 0, 1); - const v2 = new Vector3(5, 1, -1); - - const intersects = rayIntersectTriangle(ray, v0, v1, v2); - expect(intersects).toBeDefined(); - ASSERT(intersects); - expect(intersects.equals(new Vector3(5, 0, 0))).toEqual(true); -}); - -test('rayIntersectTriangle x-axis #2', () => { - const ray: Ray = { - origin: new Vector3(1, 0, 0), - axis: Axes.x, - }; - - const v0 = new Vector3(0, -1, -1); - const v1 = new Vector3(0, 0, 1); - const v2 = new Vector3(0, 1, -1); - - const intersects = rayIntersectTriangle(ray, v0, v1, v2); - expect(intersects).toBeUndefined(); -}); - -test('rayIntersectTriangle y-axis #1', () => { - const ray: Ray = { - origin: new Vector3(0, -1, 0), - axis: Axes.y, - }; - - const v0 = new Vector3(-1, 6, -1); - const v1 = new Vector3(0, 6, 1); - const v2 = new Vector3(1, 6, -1); - - const intersects = rayIntersectTriangle(ray, v0, v1, v2); - expect(intersects).toBeDefined(); - ASSERT(intersects); - expect(intersects.equals(new Vector3(0, 6, 0))).toEqual(true); -}); - -test('rayIntersectTriangle y-axis #2', () => { - const ray: Ray = { - origin: new Vector3(0, 1, 0), - axis: Axes.y, - }; - - const v0 = new Vector3(-1, 0, -1); - const v1 = new Vector3(0, 0, 1); - const v2 = new Vector3(1, 0, -1); - - const intersects = rayIntersectTriangle(ray, v0, v1, v2); - expect(intersects).toBeUndefined(); -}); - -test('rayIntersectTriangle z-axis #1', () => { - const ray: Ray = { - origin: new Vector3(0, 0, -1), - axis: Axes.z, - }; - - const v0 = new Vector3(-1, -1, 7); - const v1 = new Vector3(0, 1, 7); - const v2 = new Vector3(1, -1, 7); - - const intersects = rayIntersectTriangle(ray, v0, v1, v2); - expect(intersects).toBeDefined(); - ASSERT(intersects); - expect(intersects.equals(new Vector3(0, 0, 7))).toEqual(true); -}); - -test('rayIntersectTriangle z-axis #2', () => { - const ray: Ray = { - origin: new Vector3(0, 0, 1), - axis: Axes.z, - }; - - const v0 = new Vector3(-1, -1, 0); - const v1 = new Vector3(0, 1, 0); - const v2 = new Vector3(1, -1, 0); - - const intersects = rayIntersectTriangle(ray, v0, v1, v2); - expect(intersects).toBeUndefined(); -}); diff --git a/Editor/src/app_context.ts b/Editor/src/app_context.ts index a3fa113..bc9101d 100644 --- a/Editor/src/app_context.ts +++ b/Editor/src/app_context.ts @@ -197,7 +197,7 @@ export class AppContext { params: { constraintAxis: components.constraintAxis.getValue(), size: components.size.getValue(), - useMultisampleColouring: components.multisampleColouring.getValue(), + //useMultisampleColouring: components.multisampleColouring.getValue(), enableAmbientOcclusion: components.ambientOcclusion.getValue(), voxelOverlapRule: components.voxelOverlapRule.getValue(), }, diff --git a/Editor/src/worker/worker_types.ts b/Editor/src/worker/worker_types.ts index dffc9c3..bbc0749 100644 --- a/Editor/src/worker/worker_types.ts +++ b/Editor/src/worker/worker_types.ts @@ -68,7 +68,7 @@ export namespace VoxeliseParams { export type Input = { constraintAxis: TAxis, size: number, - useMultisampleColouring: boolean, + useMultisampleColouring?: number, enableAmbientOcclusion: boolean, voxelOverlapRule: OtS_ReplaceMode, } diff --git a/Sandbox/index.ts b/Sandbox/index.ts index 7fae27b..2a7608e 100644 --- a/Sandbox/index.ts +++ b/Sandbox/index.ts @@ -96,6 +96,8 @@ import { OtS_Colours } from 'ots-core/src/colour'; const voxelMesh = voxelMeshConverter.process(mesh); console.timeEnd('Voxel Mesh'); + console.log('Voxel Count =', voxelMesh.getVoxelCount()); + // 4. Construct a block mesh from the block console.time('Block Mesh'); const blockMeshConverter = new OtS_BlockMesh_Converter();