diff --git a/src/block_mesh.ts b/src/block_mesh.ts index fdeb7ae..2937978 100644 --- a/src/block_mesh.ts +++ b/src/block_mesh.ts @@ -165,7 +165,7 @@ export class BlockMesh { const examinedBlock = this._blocks[examined.id]; const topBlockPosition = Vector3.add(examinedBlock.voxel.position, new Vector3(0, 1, 0)); - const topBlockIndex = this._voxelMesh.getVoxelIndex(topBlockPosition); + const topBlockIndex = 0; //this._voxelMesh.getVoxelIndex(topBlockPosition); if (topBlockIndex !== undefined && !AppRuntimeConstants.Get.TRANSPARENT_BLOCKS.includes(this._blocks[topBlockIndex].blockInfo.name)) { const block = atlasPalette.getBlock(examined.voxelColour, nonGrassLikeBlockCollection, examined.faceVisibility, examined.errWeight); @@ -211,7 +211,7 @@ export class BlockMesh { }); if (bestBlock !== undefined) { - const blockIndex = this._voxelMesh.getVoxelIndex(pos); + const blockIndex = 0; //this._voxelMesh.getVoxelIndex(pos); ASSERT(blockIndex !== undefined, 'Setting emissive block of block that doesn\'t exist'); this._blocks[blockIndex].blockInfo = bestBlock; @@ -222,7 +222,7 @@ export class BlockMesh { } public getBlockAt(pos: Vector3): TOptional { - const index = this._voxelMesh.getVoxelIndex(pos); + const index = 0; //this._voxelMesh.getVoxelIndex(pos); if (index !== undefined) { return this._blocks[index]; } diff --git a/src/linear_allocator.ts b/src/linear_allocator.ts new file mode 100644 index 0000000..1f5f376 --- /dev/null +++ b/src/linear_allocator.ts @@ -0,0 +1,48 @@ +export class LinearAllocator { + private _items: Array; + private _nextIndex: number; + private _max: number; + private _itemConstructor: () => T; + + public constructor(getNewItem: () => T) { + this._items = new Array(); + this._nextIndex = 0; + this._max = 0; + this._itemConstructor = getNewItem; + } + + private _add(item: T) { + this._items[this._nextIndex] = item; + ++this._nextIndex; + this._max = Math.max(this._max, this._nextIndex); + } + + public reset() { + this._nextIndex = 0; + } + + public get(index: number): T | undefined { + return this._items[index]; + } + + public size() { + return this._nextIndex; + } + + public place(): T { + if (this._nextIndex >= this._max) { + //console.log('Adding new item at index', this._nextIndex); + const newItem = this._itemConstructor(); + this._add(newItem); + return newItem; + } else { + ++this._nextIndex; + //console.log('Returning item at index', this._nextIndex - 1); + return this._items[this._nextIndex - 1]; + } + } + + public max() { + return this._max; + } +} diff --git a/src/occlusion.ts b/src/occlusion.ts index da0bf7a..c77380a 100644 --- a/src/occlusion.ts +++ b/src/occlusion.ts @@ -27,14 +27,14 @@ export class OcclusionManager { public getOcclusions(centre: Vector3, voxelMesh: VoxelMesh) { // Cache local neighbours - const neighbourData = voxelMesh.getNeighbourhoodMap().get(centre.hash()); + const neighbourData = voxelMesh.getNeighbours(centre); if (neighbourData === undefined) { // This voxel has no neighbours within a 1-block radius return this.getBlankOcclusions(); } for (let i = 0; i < 27; ++i) { - this._localNeighbourhoodCache[i] = (neighbourData.value & (1 << i)) > 0 ? 1 : 0; + this._localNeighbourhoodCache[i] = (neighbourData & (1 << i)) > 0 ? 1 : 0; } // For each face diff --git a/src/voxel_mesh.ts b/src/voxel_mesh.ts index 9fdd09e..2c1308d 100644 --- a/src/voxel_mesh.ts +++ b/src/voxel_mesh.ts @@ -10,9 +10,10 @@ import { Vector3 } from './vector'; import { RenderNextVoxelMeshChunkParams, VoxeliseParams } from './worker_types'; export interface Voxel { - position: Vector3; - colour: RGBA; - collisions: number; + position: Vector3, + colour: RGBA, + collisions: number, + neighbours: number, } export type TVoxelOverlapRule = 'first' | 'average'; @@ -20,30 +21,26 @@ export type TVoxelOverlapRule = 'first' | 'average'; export type TVoxelMeshParams = Pick; export class VoxelMesh { - private _voxels: (Voxel & { collisions: number })[]; - private _voxelsHash: Map; + private _voxels: Map; private _bounds: Bounds; - private _neighbourMap: Map; private _voxelMeshParams: TVoxelMeshParams; public constructor(voxelMeshParams: TVoxelMeshParams) { - this._voxels = []; - this._voxelsHash = new Map(); - this._neighbourMap = new Map(); + this._voxels = new Map(); this._bounds = Bounds.getInfiniteBounds(); this._voxelMeshParams = voxelMeshParams; this._recreateBuffer = true; } - public getVoxels() { - return this._voxels; + public getVoxels(): Voxel[] { + return Array.from(this._voxels.values()); } - public isVoxelAt(pos: Vector3) { - return this._voxelsHash.has(pos.hash()); + public isVoxelAt(pos: Vector3): boolean { + return this._voxels.has(pos.hash()); } - public isOpaqueVoxelAt(pos: Vector3) { + public isOpaqueVoxelAt(pos: Vector3): boolean { const voxel = this.getVoxelAt(pos); if (voxel) { return voxel.colour.a == 1.0; @@ -52,12 +49,7 @@ export class VoxelMesh { } public getVoxelAt(pos: Vector3): TOptional { - const voxelIndex = this._voxelsHash.get(pos.hash()); - if (voxelIndex !== undefined) { - const voxel = this._voxels[voxelIndex]; - ASSERT(voxel !== undefined); - return voxel; - } + return this._voxels.get(pos.hash()); } public static getFullFaceVisibility(): EFaceVisibility { @@ -93,27 +85,22 @@ export class VoxelMesh { } const pos = inPos.copy().round(); + const voxel = this._voxels.get(pos.hash()); - const hash = pos.hash(); - const voxelIndex = this._voxelsHash.get(hash); - if (voxelIndex !== undefined) { - // A voxel at this position already exists - const voxel = this._voxels[voxelIndex]; + if (voxel !== undefined) { voxel.colour.r = ((voxel.colour.r * voxel.collisions) + colour.r) / (voxel.collisions + 1); voxel.colour.g = ((voxel.colour.g * voxel.collisions) + colour.g) / (voxel.collisions + 1); voxel.colour.b = ((voxel.colour.b * voxel.collisions) + colour.b) / (voxel.collisions + 1); voxel.colour.a = ((voxel.colour.a * voxel.collisions) + colour.a) / (voxel.collisions + 1); ++voxel.collisions; } else { - // This is a new voxel - this._voxels.push({ + this._voxels.set(pos.hash(), { position: pos, colour: colour, collisions: 1, + neighbours: 0, }); - this._voxelsHash.set(hash, this._voxels.length - 1); this._bounds.extendByPoint(pos); - this._updateNeighbours(pos); } } @@ -121,15 +108,11 @@ export class VoxelMesh { return this._bounds; } - public getVoxelIndex(pos: Vector3) { - return this._voxelsHash.get(pos.hash()); + public getVoxelCount(): number { + return this._voxels.size; } - public getVoxelCount() { - return this._voxels.length; - } - - private _neighbours = [ + private static _Neighbours = [ new Vector3(1, 1, -1), new Vector3(0, 1, -1), new Vector3(-1, 1, -1), @@ -150,37 +133,45 @@ export class VoxelMesh { new Vector3(1, -1, 1), new Vector3(0, -1, 1), new Vector3(-1, -1, 1), - ]; + ].map((neighbourOffset) => { + const inverseOffset = neighbourOffset.copy().negate(); - private _updateNeighbours(pos: Vector3) { - if (this._voxelMeshParams.enableAmbientOcclusion) { - for (const neighbourOffset of this._neighbours) { - const neighbour = Vector3.add(pos, neighbourOffset); - const inverseOffset = neighbourOffset.copy().negate(); - const inverseIndex = OcclusionManager.getNeighbourIndex(inverseOffset); - // ASSERT(inverseIndex >= 0 && inverseIndex < 27); - const neighbourData = this.getNeighbours(neighbour); - neighbourData.value |= (1 << inverseIndex); - // ASSERT((this.getNeighbours(neighbour).value & (1 << inverseIndex)) !== 0); - } + return { + offset: neighbourOffset, + index: OcclusionManager.getNeighbourIndex(neighbourOffset), + inverseIndex: OcclusionManager.getNeighbourIndex(inverseOffset), + }; + }); + + /** + * Goes through each voxel and calculates what voxel neighbours it has. + * This is used for ambient occlusion. + * @note This does NOT check all 27 neighbours, i.e. it does not check voxels + * directly up, down, north, south, east, west as they're not needed. + */ + public calculateNeighbours() { + if (!this._voxelMeshParams.enableAmbientOcclusion) { + return; } + + const pos = new Vector3(0, 0, 0); + + this._voxels.forEach((voxel) => { + voxel.neighbours = 0; + + VoxelMesh._Neighbours.forEach((neighbour) => { + pos.setFrom(voxel.position); + pos.add(neighbour.offset); + + if (this.isVoxelAt(pos)) { + voxel.neighbours |= (1 << neighbour.index); + } + }); + }); } public getNeighbours(pos: Vector3) { - ASSERT(this._voxelMeshParams.enableAmbientOcclusion, 'Ambient occlusion is disabled'); - - const hash = pos.hash(); - const neighbours = this._neighbourMap.get(hash); - if (neighbours === undefined) { - this._neighbourMap.set(hash, { value: 0 }); - return this._neighbourMap.get(hash)!; - } else { - return neighbours; - } - } - - public getNeighbourhoodMap() { - return this._neighbourMap; + return this._voxels.get(pos.hash())?.neighbours ?? 0; } /* @@ -188,7 +179,7 @@ export class VoxelMesh { * Offset must be a vector that exists within this._neighbours defined above */ public hasNeighbour(pos: Vector3, offset: Vector3): boolean { - return (this.getNeighbours(pos).value & (1 << OcclusionManager.getNeighbourIndex(offset))) > 0; + return (this.getNeighbours(pos) & (1 << OcclusionManager.getNeighbourIndex(offset))) > 0; } private _renderParams?: RenderNextVoxelMeshChunkParams.Input; diff --git a/src/voxelisers/ray-voxeliser.ts b/src/voxelisers/ray-voxeliser.ts index 3a58d3f..c0b7af0 100644 --- a/src/voxelisers/ray-voxeliser.ts +++ b/src/voxelisers/ray-voxeliser.ts @@ -1,4 +1,5 @@ import { Bounds } from '../bounds'; +import { LinearAllocator } from '../linear_allocator'; import { Mesh } from '../mesh'; import { ProgressManager } from '../progress'; import { Axes, Ray, rayIntersectTriangle } from '../ray'; @@ -61,26 +62,40 @@ export class RayVoxeliser extends IVoxeliser { return this._voxelMesh; } + private _rayList = new LinearAllocator(() => { + const ray: Ray = { origin: new Vector3(0, 0, 0), axis: Axes.x }; + return ray; + }); private _voxeliseTri(triangle: UVTriangle, materialName: string) { - const rayList = this._generateRays(triangle.v0, triangle.v1, triangle.v2); + this._rayList.reset(); + this._generateRays(triangle.v0, triangle.v1, triangle.v2); ASSERT(this._mesh !== undefined); ASSERT(this._voxeliseParams !== undefined); ASSERT(this._voxelMesh !== undefined); - for (const ray of rayList) { + const voxelPosition = new Vector3(0, 0, 0); + const size = this._rayList.size(); + for (let i = 0; i < size; ++i) { + const ray = this._rayList.get(i)!; + const intersection = rayIntersectTriangle(ray, triangle.v0, triangle.v1, triangle.v2); if (intersection) { - let voxelPosition: Vector3; switch (ray.axis) { case Axes.x: - voxelPosition = new Vector3(Math.round(intersection.x), intersection.y, intersection.z); + voxelPosition.x = Math.round(intersection.x); + voxelPosition.y = intersection.y; + voxelPosition.z = intersection.z; break; case Axes.y: - voxelPosition = new Vector3(intersection.x, Math.round(intersection.y), intersection.z); + voxelPosition.x = intersection.x; + voxelPosition.y = Math.round(intersection.y); + voxelPosition.z = intersection.z; break; case Axes.z: - voxelPosition = new Vector3(intersection.x, intersection.y, Math.round(intersection.z)); + voxelPosition.x = intersection.x; + voxelPosition.y = intersection.y; + voxelPosition.z = Math.round(intersection.z); break; } @@ -97,56 +112,55 @@ export class RayVoxeliser extends IVoxeliser { }; } - private _generateRays(v0: Vector3, v1: Vector3, v2: Vector3): Array { - 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)), - ), - ); + private _tmpBounds: Bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(0, 0, 0)); + private _generateRays(v0: Vector3, v1: Vector3, v2: Vector3) { + this._tmpBounds.min.x = Math.floor(Math.min(v0.x, v1.x, v2.x)); + this._tmpBounds.min.y = Math.floor(Math.min(v0.y, v1.y, v2.y)); + this._tmpBounds.min.z = Math.floor(Math.min(v0.z, v1.z, v2.z)); - const rayList: Array = []; - this._traverseX(rayList, bounds); - this._traverseY(rayList, bounds); - this._traverseZ(rayList, bounds); - return rayList; + this._tmpBounds.max.x = Math.floor(Math.max(v0.x, v1.x, v2.x)); + this._tmpBounds.max.y = Math.floor(Math.max(v0.y, v1.y, v2.y)); + this._tmpBounds.max.z = Math.floor(Math.max(v0.z, v1.z, v2.z)); + + //const rayList: Array = []; + this._traverseX(this._tmpBounds); + this._traverseY(this._tmpBounds); + this._traverseZ(this._tmpBounds); + //return rayList; } - private _traverseX(rayList: Array, bounds: Bounds) { + private _traverseX(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, - }); + const ray = this._rayList.place(); + ray.origin.x = bounds.min.x - 1; + ray.origin.y = y; + ray.origin.z = z; + ray.axis = Axes.x; } } } - private _traverseY(rayList: Array, bounds: Bounds) { + private _traverseY(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, - }); + const ray = this._rayList.place(); + ray.origin.x = x; + ray.origin.y = bounds.min.y - 1; + ray.origin.z = z; + ray.axis = Axes.y; } } } - private _traverseZ(rayList: Array, bounds: Bounds) { + private _traverseZ(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, - }); + const ray = this._rayList.place(); + ray.origin.x = x; + ray.origin.y = y; + ray.origin.z = bounds.min.z - 1; + ray.axis = Axes.z; } } } diff --git a/src/worker_client.ts b/src/worker_client.ts index 2fc76a8..f5c03bf 100644 --- a/src/worker_client.ts +++ b/src/worker_client.ts @@ -120,6 +120,7 @@ export class WorkerClient { const voxeliser: IVoxeliser = VoxeliserFactory.GetVoxeliser(params.voxeliser); this._loadedVoxelMesh = voxeliser.voxelise(this._loadedMesh, params); + this._loadedVoxelMesh.calculateNeighbours(); this._voxelMeshChunkIndex = 0; diff --git a/tests/linear_allocator.test.ts b/tests/linear_allocator.test.ts new file mode 100644 index 0000000..a2c0b56 --- /dev/null +++ b/tests/linear_allocator.test.ts @@ -0,0 +1,27 @@ +import { LinearAllocator } from '../src/linear_allocator'; +import { Vector3 } from '../src/vector'; +import { TEST_PREAMBLE } from './preamble'; + +test('RegExpBuilder', () => { + TEST_PREAMBLE(); + + const vec = new LinearAllocator(() => { + return new Vector3(0, 0, 0); + }); + + expect(vec.size()).toBe(0); + expect(vec.max()).toBe(0); + const first = vec.place(); + first.x = 1; + expect(vec.size()).toBe(1); + expect(vec.max()).toBe(1); + const second = vec.place(); + second.x = 2; + expect(vec.size()).toBe(2); + expect(vec.max()).toBe(2); + vec.reset(); + expect(vec.size()).toBe(0); + expect(vec.max()).toBe(2); + const newFirst = vec.place(); + expect(newFirst.x).toBe(1); +}); diff --git a/tests/voxel_mesh.test.ts b/tests/voxel_mesh.test.ts index 75f3b72..ba2f8f5 100644 --- a/tests/voxel_mesh.test.ts +++ b/tests/voxel_mesh.test.ts @@ -12,18 +12,14 @@ test('Voxel neighbours', () => { enableAmbientOcclusion: true, }); - voxelMesh.addVoxel(new Vector3(1, 2, 3), RGBAColours.WHITE); + voxelMesh.addVoxel(new Vector3(0, 0, 0), RGBAColours.WHITE); + voxelMesh.addVoxel(new Vector3(1, 1, 0), RGBAColours.WHITE); + voxelMesh.calculateNeighbours(); - expect(voxelMesh.getNeighbours(new Vector3(1, 2, 3)).value).toBe(0); - - // Even though this neighbour does exist, it is not calculated as - // this relationship is never tested - expect(voxelMesh.hasNeighbour(new Vector3(1, 2, 3).add(new Vector3(1, 0, 0)), new Vector3(-1, 0, 0))).toBe(false); - - expect(voxelMesh.hasNeighbour(new Vector3(1, 2, 3).add(new Vector3(1, 1, 0)), new Vector3(-1, -1, 0))).toBe(true); - expect(voxelMesh.hasNeighbour(new Vector3(1, 2, 3).add(new Vector3(-1, -1, 0)), new Vector3(1, 1, 0))).toBe(true); - expect(voxelMesh.hasNeighbour(new Vector3(1, 2, 3).add(new Vector3(1, -1, 1)), new Vector3(-1, 1, -1))).toBe(true); - expect(voxelMesh.hasNeighbour(new Vector3(1, 2, 3).add(new Vector3(-1, 0, 1)), new Vector3(1, 0, -1))).toBe(true); + expect(voxelMesh.hasNeighbour(new Vector3(0, 0, 0), new Vector3(1, 1, 0))).toBe(true); + expect(voxelMesh.hasNeighbour(new Vector3(0, 0, 0), new Vector3(-1, -1, 0))).toBe(false); + expect(voxelMesh.hasNeighbour(new Vector3(1, 1, 0), new Vector3(-1, -1, 0))).toBe(true); + expect(voxelMesh.hasNeighbour(new Vector3(1, 1, 0), new Vector3(1, 1, 0))).toBe(false); }); test('Add voxel', () => {