mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2024-11-21 01:04:15 +08:00
Optimisations to ray-based voxeliser and occlusion manager (~1.55x), added linear allocator
This commit is contained in:
parent
f2579a9732
commit
33abd2d968
@ -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<Block> {
|
||||
const index = this._voxelMesh.getVoxelIndex(pos);
|
||||
const index = 0; //this._voxelMesh.getVoxelIndex(pos);
|
||||
if (index !== undefined) {
|
||||
return this._blocks[index];
|
||||
}
|
||||
|
48
src/linear_allocator.ts
Normal file
48
src/linear_allocator.ts
Normal file
@ -0,0 +1,48 @@
|
||||
export class LinearAllocator<T> {
|
||||
private _items: Array<T>;
|
||||
private _nextIndex: number;
|
||||
private _max: number;
|
||||
private _itemConstructor: () => T;
|
||||
|
||||
public constructor(getNewItem: () => T) {
|
||||
this._items = new Array<T>();
|
||||
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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<VoxeliseParams.Input, 'voxelOverlapRule' | 'enableAmbientOcclusion'>;
|
||||
|
||||
export class VoxelMesh {
|
||||
private _voxels: (Voxel & { collisions: number })[];
|
||||
private _voxelsHash: Map<number, number>;
|
||||
private _voxels: Map<number, Voxel>;
|
||||
private _bounds: Bounds;
|
||||
private _neighbourMap: Map<number, { value: number }>;
|
||||
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<Voxel> {
|
||||
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;
|
||||
|
@ -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<Ray>(() => {
|
||||
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<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)),
|
||||
),
|
||||
);
|
||||
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<Ray> = [];
|
||||
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<Ray> = [];
|
||||
this._traverseX(this._tmpBounds);
|
||||
this._traverseY(this._tmpBounds);
|
||||
this._traverseZ(this._tmpBounds);
|
||||
//return rayList;
|
||||
}
|
||||
|
||||
private _traverseX(rayList: Array<Ray>, 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<Ray>, 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<Ray>, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
27
tests/linear_allocator.test.ts
Normal file
27
tests/linear_allocator.test.ts
Normal file
@ -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<Vector3>(() => {
|
||||
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);
|
||||
});
|
@ -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', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user