Optimised voxelisation, ~2x improvement

This commit is contained in:
Lucas Dower 2023-10-24 20:06:31 +01:00
parent 6e81858e35
commit 7be3b89159
7 changed files with 53 additions and 141 deletions

View File

@ -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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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();
});

View File

@ -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(),
},

View File

@ -68,7 +68,7 @@ export namespace VoxeliseParams {
export type Input = {
constraintAxis: TAxis,
size: number,
useMultisampleColouring: boolean,
useMultisampleColouring?: number,
enableAmbientOcclusion: boolean,
voxelOverlapRule: OtS_ReplaceMode,
}

View File

@ -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();