Merge pull request #16 from LucasDower/voxeliser-rewrite, fixes #5

Voxeliser rewrite, fixes #5
This commit is contained in:
Lucas Dower 2021-11-14 13:08:45 +00:00 committed by GitHub
commit 84043f8de4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 305 additions and 318 deletions

View File

@ -1,49 +0,0 @@
import { Vector3 } from "./vector";
export class AABB {
public readonly centre: Vector3;
public readonly size: Vector3;
public readonly a: Vector3;
public readonly b: Vector3
constructor(centre: Vector3, size: Vector3) {
this.centre = centre;
this.size = size;
this.a = Vector3.sub(centre, Vector3.mulScalar(size, 0.5));
this.b = Vector3.add(centre, Vector3.mulScalar(size, 0.5));
}
}
export class CubeAABB extends AABB {
readonly width: number;
constructor(centre: Vector3, width: number) {
const sizeVector = new Vector3(width, width, width);
super(centre, sizeVector);
this.width = width;
}
public subdivide() {
const newWidth = this.width / 2;
const offset = this.width / 4;
return [
new CubeAABB(Vector3.add(this.centre, new Vector3(-offset, -offset, -offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3( offset, -offset, -offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3(-offset, offset, -offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3( offset, offset, -offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3(-offset, -offset, offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3( offset, -offset, offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3(-offset, offset, offset)), newWidth),
new CubeAABB(Vector3.add(this.centre, new Vector3( offset, offset, offset)), newWidth),
];
}
}

View File

@ -110,7 +110,7 @@ export class AppContext {
}
private _voxelise(): ReturnStatus {
const newVoxelSize = $("#inputVoxelSize").prop('value');
const newVoxelSize: number = parseFloat($("#inputVoxelSize").prop('value'));
if (newVoxelSize < 0.001) {
return { message: "Voxel size must be at least 0.001", style: ToastStyle.Failure };
}
@ -118,7 +118,6 @@ export class AppContext {
try {
const voxelManager = VoxelManager.Get;
voxelManager.clear();
voxelManager.setVoxelSize(this._voxelSize);
voxelManager.voxeliseMesh(this._loadedMesh!);

View File

@ -167,7 +167,7 @@ export class SegmentedBuffer {
}
if (this._willOverflow(data)) {
console.log("Cycling buffer...");
//console.log("Cycling buffer...");
this._cycle();
}
@ -303,7 +303,7 @@ export class BottomlessBuffer {
}
if (this._willOverflow(data)) {
console.log("Cycling buffer...");
//console.log("Cycling buffer...");
this._cycle();
}

View File

@ -73,6 +73,19 @@ export class HashMap<K extends Hashable, V> {
}
}
public has(item: K): boolean {
const binIndex = this._getBin(item);
const list = this._bins[binIndex];
for (const {key, value} of list) {
if (item.equals(key)) {
return true;
}
}
return false;
}
public add(key: K, value: V) {
const binIndex = this._getBin(key);
this._bins[binIndex].push({key, value});

View File

@ -21,6 +21,18 @@ export const clamp = (value: number, min: number, max: number) => {
return Math.max(Math.min(max, value), min);
}
export const floorToNearest = (value: number, base: number) => {
return Math.floor(value / base) * base;
}
export const ceilToNearest = (value: number, base: number) => {
return Math.ceil(value / base) * base;
}
export const roundToNearest = (value: number, base: number) => {
return Math.round(value / base) * base;
}
export const triangleArea = (a: number, b: number, c: number) => {
const p = (a + b + c) / 2;
return Math.sqrt(p * (p - a) * (p - b) * (p - c));

View File

@ -357,7 +357,6 @@ export class Mesh {
face.v0.position = Vector3.sub(face.v0.position, centre);
face.v1.position = Vector3.sub(face.v1.position, centre);
face.v2.position = Vector3.sub(face.v2.position, centre);
face.updateAABB();
});
});
}
@ -373,13 +372,13 @@ export class Mesh {
this.materials.forEach(material => {
material.faces.forEach(face => {
const aabb = face.getAABB();
a.x = Math.min(a.x, aabb.a.x);
a.y = Math.min(a.y, aabb.a.y);
a.z = Math.min(a.z, aabb.a.z);
b.x = Math.max(b.x, aabb.b.x);
b.y = Math.max(b.y, aabb.b.y);
b.z = Math.max(b.z, aabb.b.z);
const aabb = face.getBounds();
a.x = Math.min(a.x, aabb.minX);
a.y = Math.min(a.y, aabb.minY);
a.z = Math.min(a.z, aabb.minZ);
b.x = Math.max(b.x, aabb.maxX);
b.y = Math.max(b.y, aabb.maxY);
b.z = Math.max(b.z, aabb.maxZ);
});
});
@ -395,7 +394,6 @@ export class Mesh {
face.v0.position = Vector3.mulScalar(face.v0.position, scaleFactor);
face.v1.position = Vector3.mulScalar(face.v1.position, scaleFactor);
face.v2.position = Vector3.mulScalar(face.v2.position, scaleFactor);
face.updateAABB();
});
});
}

103
src/ray.ts Normal file
View File

@ -0,0 +1,103 @@
import { Vector3 } from "./vector";
import { Triangle } from "./triangle";
import { floorToNearest, ceilToNearest, xAxis, yAxis, zAxis } from "./math";
import { VoxelManager } from "./voxel_manager";
import { Bounds } from "./util";
const EPSILON = 0.0000001;
export enum Axes {
x, y, z
}
interface Ray {
origin: Vector3,
direction: Vector3,
axis: Axes
}
export function generateRays(v0: Vector3, v1: Vector3, v2: Vector3): Array<Ray> {
const bounds: Bounds = {
minX: Math.floor(Math.min(v0.x, v1.x, v2.x)),
minY: Math.floor(Math.min(v0.y, v1.y, v2.y)),
minZ: Math.floor(Math.min(v0.z, v1.z, v2.z)),
maxX: Math.ceil(Math.max(v0.x, v1.x, v2.x)),
maxY: Math.ceil(Math.max(v0.y, v1.y, v2.y)),
maxZ: Math.ceil(Math.max(v0.z, v1.z, v2.z)),
}
let rayList: Array<Ray> = [];
traverseX(rayList, bounds);
traverseY(rayList, bounds);
traverseZ(rayList, bounds);
return rayList;
}
function traverseX(rayList: Array<Ray>, bounds: Bounds) {
for (let y = bounds.minY; y <= bounds.maxY; ++y) {
for (let z = bounds.minZ; z <= bounds.maxZ; ++z) {
rayList.push({
origin: new Vector3(bounds.minX, y, z),
direction: new Vector3(1, 0, 0),
axis: Axes.x
});
}
}
}
function traverseY(rayList: Array<Ray>, bounds: Bounds) {
for (let x = bounds.minX; x <= bounds.maxX; ++x) {
for (let z = bounds.minZ; z <= bounds.maxZ; ++z) {
rayList.push({
origin: new Vector3(x, bounds.minY, z),
direction: new Vector3(0, 1, 0),
axis: Axes.y
});
}
}
}
function traverseZ(rayList: Array<Ray>, bounds: Bounds) {
for (let x = bounds.minX; x <= bounds.maxX; ++x) {
for (let y = bounds.minY; y <= bounds.maxY; ++y) {
rayList.push({
origin: new Vector3(x, y, bounds.minZ),
direction: new Vector3(0, 0, 1),
axis: Axes.z
});
}
}
}
export function rayIntersectTriangle(ray: Ray, v0: Vector3, v1: Vector3, v2: Vector3): (Vector3 | void) {
const edge1 = Vector3.sub(v1, v0);
const edge2 = Vector3.sub(v2, v0);
const h = Vector3.cross(ray.direction, edge2);
const a = Vector3.dot(edge1, h);
if (a > -EPSILON && a < EPSILON) {
return; // Ray is parallel to triangle
}
const f = 1.0 / a;
const s = Vector3.sub(ray.origin, v0);
const u = f * Vector3.dot(s, h);
if (u < 0.0 || u > 1.0) {
return;
}
const q = Vector3.cross(s, edge1);
const v = f * Vector3.dot(ray.direction, q);
if (v < 0.0 || u + v > 1.0) {
return;
}
const t = f * Vector3.dot(edge2, q);
if (t > EPSILON) {
return Vector3.add(ray.origin, Vector3.mulScalar(ray.direction, t));
}
}

View File

@ -13,6 +13,11 @@ import { Triangle } from "./triangle";
import { Mesh, FillMaterial, TextureMaterial, MaterialType } from "./mesh";
import { FaceInfo } from "./block_atlas";
/** Recommended, but slower */
const ENABLE_AMBIENT_OCCLUSION = true;
/** Darkens corner even if corner block does not exist, recommended */
const AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;
export class Renderer {
@ -21,8 +26,7 @@ export class Renderer {
private _backgroundColour: RGB = {r: 0.1, g: 0.1, b: 0.1};
private _strokeColour: RGB = {r: 1.0, g: 0.0, b: 0.0};
private _atlasTexture: WebGLTexture;
private _occlusionNeighbours!: Array<Array<Array<Vector3>>>; // Ew
//private _mouseManager: MouseManager
private _occlusionNeighboursIndices!: Array<Array<Array<number>>>; // Ew
private _debug: boolean = false;
private _compiled: boolean = false;
@ -54,12 +58,8 @@ export class Renderer {
src: path.join(__dirname, "../resources/blocks.png"),
mag: this._gl.NEAREST
});
}
public set strokeColour(colour: RGB) {
this._strokeColour = colour;
}
@ -73,23 +73,83 @@ export class Renderer {
this._registerData(data);
}
private _registerVoxel(centre: Vector3, voxelManager: VoxelManager, blockTexcoord: FaceInfo) {
let occlusions = new Array<Array<number>>(6);
private static _getNeighbourIndex(neighbour: Vector3) {
return 9*(neighbour.x+1) + 3*(neighbour.y+1) + (neighbour.z+1);
}
private static _faceNormal = [
new Vector3(1, 0, 0), new Vector3(-1, 0, 0),
new Vector3(0, 1, 0), new Vector3(0, -1, 0),
new Vector3(0, 0, 1), new Vector3(0, 0, -1),
]
private _calculateOcclusions(centre: Vector3) {
const voxelManager = VoxelManager.Get;
// Cache local neighbours
const localNeighbourhoodCache = Array<number>(27);
for (let i = -1; i <= 1; ++i) {
for (let j = -1; j <= 1; ++j) {
for (let k = -1; k <= 1; ++k) {
const neighbour = new Vector3(i, j, k);
const neighbourIndex = Renderer._getNeighbourIndex(neighbour);
localNeighbourhoodCache[neighbourIndex] = voxelManager.isVoxelAt(Vector3.add(centre, neighbour)) ? 1 : 0;
}
}
}
let occlusions = new Array<Array<number>>(6);
// For each face
for (let f = 0; f < 6; ++f) {
// For each vertex
occlusions[f] = [0, 0, 0, 0];
for (let v = 0; v < 4; ++v) {
// For each occlusion vertex
for (let o = 0; o < 3; ++o) {
occlusions[f][v] += voxelManager.isVoxelAt(Vector3.add(centre, this._occlusionNeighbours[f][v][o])) ? 1 : 0;
// Only compute ambient occlusion if this face is visible
const faceNormal = Renderer._faceNormal[f];
const faceNeighbourIndex = Renderer._getNeighbourIndex(faceNormal);
const faceVisible = localNeighbourhoodCache[faceNeighbourIndex] === 0;
if (faceVisible) {
for (let v = 0; v < 4; ++v) {
let numNeighbours = 0;
for (let i = 0; i < 2; ++i) {
const neighbourIndex = this._occlusionNeighboursIndices[f][v][i];
numNeighbours += localNeighbourhoodCache[neighbourIndex];
}
// If both edge blocks along this vertex exist,
// assume corner exists (even if it doesnt)
// (This is a stylistic choice)
if (numNeighbours == 2 && AMBIENT_OCCLUSION_OVERRIDE_CORNER) {
++numNeighbours;
} else {
const neighbourIndex = this._occlusionNeighboursIndices[f][v][2];
numNeighbours += localNeighbourhoodCache[neighbourIndex];
}
// Convert from occlusion denoting the occlusion factor to the
// attenuation in light value: 0 -> 1.0, 1 -> 0.8, 2 -> 0.6, 3 -> 0.4
occlusions[f][v] = 1.0 - 0.2 * numNeighbours;
}
}
// Convert from occlusion denoting the occlusion factor to the
// attenuation in light value: 0 -> 1.0, 1 -> 0.8, 2 -> 0.6, 3 -> 0.4
occlusions[f] = occlusions[f].map(x => 1.0 - 0.2 * x);
}
return occlusions;
}
private static _getBlankOcclusions() {
let blankOcclusions = new Array<Array<number>>(6);
for (let f = 0; f < 6; ++f) {
blankOcclusions[f] = [1, 1, 1, 1];
}
return blankOcclusions;
}
private _registerVoxel(centre: Vector3, voxelManager: VoxelManager, blockTexcoord: FaceInfo) {
let occlusions: number[][];
if (ENABLE_AMBIENT_OCCLUSION) {
occlusions = this._calculateOcclusions(centre);
} else {
occlusions = Renderer._getBlankOcclusions();
}
let data: VoxelData = GeometryTemplates.getBoxBufferData(centre, false);
@ -146,12 +206,9 @@ export class Renderer {
}
registerVoxelMesh() {
this._gl.enable(this._gl.CULL_FACE);
const voxelManager = VoxelManager.Get;
const voxelSize = voxelManager._voxelSize;
const sizeVector = new Vector3(1.0, 1.0, 1.0);
this._atlasSize = voxelManager.blockAtlas._atlasSize;
@ -161,26 +218,13 @@ export class Renderer {
});
} else {
// Setup arrays for calculating voxel ambient occlusion
for (let i = 0; i < voxelManager.voxels.length; ++i) {
const voxel = voxelManager.voxels[i];
//const colour = voxelManager.voxelColours[i];
const texcoord = voxelManager.voxelTexcoords[i];
this._registerVoxel(voxel.position, voxelManager, texcoord);
}
/*
voxelManager.voxels.forEach((voxel) => {
this._registerVoxel(voxel, voxelManager);
});
*/
}
/*
const mesh = voxelManager.buildMesh();
for (const box of mesh) {
this.registerBox(box.centre, box.size, false);
}
*/
}
clear() {
@ -223,15 +267,6 @@ export class Renderer {
u_atlasSize: this._atlasSize
});
/*
// Draw default register
this._drawRegister(this._registerDefault, this._gl.TRIANGLES, shaderManager.shadedProgram, {
u_lightWorldPos: this._camera.getCameraPosition(0.0, 0.0),
u_worldViewProjection: this._camera.getWorldViewProjection(),
u_worldInverseTranspose: this._camera.getWorldInverseTranspose()
});
*/
// Draw material registers
const camera = ArcballCamera.Get;
this._materialBuffers.forEach((materialBuffer) => {
@ -262,49 +297,66 @@ export class Renderer {
_setupOcclusions() {
// TODO: Find some for-loop to clean this up
this._occlusionNeighbours = [
// [Edge, Edge, Corrner]
const occlusionNeighbours = [
[
[new Vector3(1, 1, 0), new Vector3(1, 1, -1), new Vector3(1, 0, -1)],
[new Vector3(1, -1, 0), new Vector3(1, -1, -1), new Vector3(1, 0, -1)],
[new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(1, 0, 1)],
[new Vector3(1, -1, 0), new Vector3(1, -1, 1), new Vector3(1, 0, 1)]
// +X
[new Vector3(1, 1, 0), new Vector3(1, 0, -1), new Vector3(1, 1, -1)],
[new Vector3(1, -1, 0), new Vector3(1, 0, -1), new Vector3(1, -1, -1)],
[new Vector3(1, 1, 0), new Vector3(1, 0, 1), new Vector3(1, 1, 1)],
[new Vector3(1, -1, 0), new Vector3(1, 0, 1), new Vector3(1, -1, 1)]
],
[
[new Vector3(-1, 1, 0), new Vector3(-1, 1, 1), new Vector3(-1, 0, 1)],
[new Vector3(-1, -1, 0), new Vector3(-1, -1, 1), new Vector3(-1, 0, 1)],
[new Vector3(-1, 1, 0), new Vector3(-1, 1, -1), new Vector3(-1, 0, -1)],
[new Vector3(-1, -1, 0), new Vector3(-1, -1, -1), new Vector3(-1, 0, -1)]
// -X
[new Vector3(-1, 1, 0), new Vector3(-1, 0, 1), new Vector3(-1, 1, 1)],
[new Vector3(-1, -1, 0), new Vector3(-1, 0, 1), new Vector3(-1, -1, 1)],
[new Vector3(-1, 1, 0), new Vector3(-1, 0, -1), new Vector3(-1, 1, -1)],
[new Vector3(-1, -1, 0), new Vector3(-1, 0, -1), new Vector3(-1, -1, -1) ]
],
[
[new Vector3(-1, 1, 0), new Vector3(-1, 1, 1), new Vector3(0, 1, 1)],
[new Vector3(-1, 1, 0), new Vector3(-1, 1, -1), new Vector3(0, 1, -1)],
[new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(0, 1, 1)],
[new Vector3(1, 1, 0), new Vector3(1, 1, -1), new Vector3(0, 1, -1)]
// +Y
[new Vector3(-1, 1, 0), new Vector3(0, 1, 1), new Vector3(-1, 1, 1)],
[new Vector3(-1, 1, 0), new Vector3(0, 1, -1), new Vector3(-1, 1, -1)],
[new Vector3(1, 1, 0), new Vector3(0, 1, 1), new Vector3(1, 1, 1)],
[new Vector3(1, 1, 0), new Vector3(0, 1, -1), new Vector3(1, 1, -1)]
],
[
[new Vector3(-1, -1, 0), new Vector3(-1, -1, -1), new Vector3(0, -1, -1)],
[new Vector3(-1, -1, 0), new Vector3(-1, -1, 1), new Vector3(0, -1, 1)],
[new Vector3(1, -1, 0), new Vector3(1, -1, -1), new Vector3(0, -1, -1)],
[new Vector3(1, -1, 0), new Vector3(1, -1, 1), new Vector3(0, -1, 1)]
// -Y
[new Vector3(-1, -1, 0), new Vector3(0, -1, -1), new Vector3(-1, -1, -1)],
[new Vector3(-1, -1, 0), new Vector3(0, -1, 1), new Vector3(-1, -1, 1)],
[new Vector3(1, -1, 0), new Vector3(0, -1, -1), new Vector3(1, -1, -1)],
[new Vector3(1, -1, 0), new Vector3(0, -1, 1), new Vector3(1, -1, 1)]
],
[
[new Vector3(0, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 0, 1)],
[new Vector3(0, -1, 1), new Vector3(1, -1, 1), new Vector3(1, 0, 1)],
[new Vector3(0, 1, 1), new Vector3(-1, 1, 1), new Vector3(-1, 0, 1)],
[new Vector3(0, -1, 1), new Vector3(-1, -1, 1), new Vector3(-1, 0, 1)]
// + Z
[new Vector3(0, 1, 1), new Vector3(1, 0, 1), new Vector3(1, 1, 1)],
[new Vector3(0, -1, 1), new Vector3(1, 0, 1), new Vector3(1, -1, 1)],
[new Vector3(0, 1, 1), new Vector3(-1, 0, 1), new Vector3(-1, 1, 1)],
[new Vector3(0, -1, 1), new Vector3(-1, 0, 1), new Vector3(-1, -1, 1)]
],
[
[new Vector3(0, 1, -1), new Vector3(-1, 1, -1), new Vector3(-1, 0, -1)],
[new Vector3(0, -1, -1), new Vector3(-1, -1, -1), new Vector3(-1, 0, -1)],
[new Vector3(0, 1, -1), new Vector3(1, 1, -1), new Vector3(1, 0, -1)],
[new Vector3(0, -1, -1), new Vector3(1, -1, -1), new Vector3(1, 0, -1)]
// -Z
[new Vector3(0, 1, -1), new Vector3(-1, 0, -1), new Vector3(-1, 1, -1)],
[new Vector3(0, -1, -1), new Vector3(-1, 0, -1), new Vector3(-1, -1, -1)],
[new Vector3(0, 1, -1), new Vector3(1, 0, -1), new Vector3(1, 1, -1)],
[new Vector3(0, -1, -1), new Vector3(1, 0, -1), new Vector3(1, -1, -1)]
]
]
this._occlusionNeighboursIndices = new Array<Array<Array<number>>>();
for (let i = 0; i < 6; ++i) {
let row = new Array<Array<number>>();
for (let j = 0; j < 4; ++j) {
row.push(occlusionNeighbours[i][j].map(x => Renderer._getNeighbourIndex(x)));
}
this._occlusionNeighboursIndices.push(row);
}
console.log(this._occlusionNeighboursIndices);
}
_registerData(data: VoxelData) {

View File

@ -1,8 +1,6 @@
import { Vector3 } from "./vector";
import { AABB } from "./aabb";
import { xAxis, yAxis, zAxis, fastCrossXAxis, fastCrossYAxis, fastCrossZAxis } from "./math";
import { UV } from "./util";
import { Vertex } from "./mesh";
import { Bounds } from "./util";
export class Triangle {
@ -11,7 +9,6 @@ export class Triangle {
public v1: Vertex;
public v2: Vertex;
public readonly normal: Vector3;
private aabb?: AABB;
constructor(v0: Vertex, v1: Vertex, v2: Vertex) {
this.v0 = v0;
@ -21,88 +18,21 @@ export class Triangle {
const f0 = Vector3.sub(v1.position, v0.position);
const f1 = Vector3.sub(v0.position, v2.position);
this.normal = Vector3.cross(f0, f1).normalise();
this.updateAABB();
}
public updateAABB() {
const a = new Vector3(
Math.min(this.v0.position.x, this.v1.position.x, this.v2.position.x),
Math.min(this.v0.position.y, this.v1.position.y, this.v2.position.y),
Math.min(this.v0.position.z, this.v1.position.z, this.v2.position.z)
);
const b = new Vector3(
Math.max(this.v0.position.x, this.v1.position.x, this.v2.position.x),
Math.max(this.v0.position.y, this.v1.position.y, this.v2.position.y),
Math.max(this.v0.position.z, this.v1.position.z, this.v2.position.z)
);
const centre = Vector3.mulScalar(Vector3.add(a, b), 0.5);
const size = Vector3.abs(Vector3.sub(a, b));
this.aabb = new AABB(centre, size);
}
public getAABB() {
return this.aabb!;
}
public insideAABB(aabb: AABB) {
return Vector3.lessThanEqualTo(aabb.a, this.aabb!.a) && Vector3.lessThanEqualTo(this.aabb!.b, aabb.b);
}
public intersectAABB(aabb: AABB) {
const extents = Vector3.mulScalar(aabb.size, 0.5);
const v0 = Vector3.sub(this.v0.position, aabb.centre);
const v1 = Vector3.sub(this.v1.position, aabb.centre);
const v2 = Vector3.sub(this.v2.position, aabb.centre);
const f0 = Vector3.sub(v1, v0);
const f1 = Vector3.sub(v2, v1);
const f2 = Vector3.sub(v0, v2);
const axis = [
Vector3.cross(f0, f2),
xAxis,
yAxis,
zAxis,
fastCrossXAxis(f0),
fastCrossXAxis(f1),
fastCrossXAxis(f2),
fastCrossYAxis(f0),
fastCrossYAxis(f1),
fastCrossYAxis(f2),
fastCrossZAxis(f0),
fastCrossZAxis(f1),
fastCrossZAxis(f2)
];
for (const ax of axis) {
if (this._testAxis(v0, v1, v2, ax, extents)) {
return false;
}
}
return true;
}
public getCentre(): Vector3 {
return Vector3.divScalar(Vector3.add(Vector3.add(this.v0.position, this.v1.position), this.v2.position), 3.0);
}
private _testAxis(v0: Vector3, v1: Vector3, v2: Vector3, axis: Vector3, extents: Vector3) {
let p0 = Vector3.dot(v0, axis);
let p1 = Vector3.dot(v1, axis);
let p2 = Vector3.dot(v2, axis);
let r = extents.x * Math.abs(Vector3.dot(xAxis, axis)) +
extents.y * Math.abs(Vector3.dot(yAxis, axis)) +
extents.z * Math.abs(Vector3.dot(zAxis, axis));
return (Math.min(p0, p1, p2) > r || Math.max(p0, p1, p2) < -r);
public getBounds(): Bounds {
return {
minX: Math.min(this.v0.position.x, this.v1.position.x, this.v2.position.x),
minY: Math.min(this.v0.position.y, this.v1.position.y, this.v2.position.y),
minZ: Math.min(this.v0.position.z, this.v1.position.z, this.v2.position.z),
maxX: Math.max(this.v0.position.x, this.v1.position.x, this.v2.position.x),
maxY: Math.max(this.v0.position.y, this.v1.position.y, this.v2.position.y),
maxZ: Math.max(this.v0.position.z, this.v1.position.z, this.v2.position.z),
}
}
}

View File

@ -27,4 +27,13 @@ export interface RGBA {
g: number,
b: number,
a: number
}
export interface Bounds {
minX: number,
minY: number,
minZ: number,
maxX: number,
maxY: number,
maxZ: number,
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,3 @@
import { CubeAABB } from "./aabb";
import { Vector3 } from "./vector.js";
import { HashMap } from "./hash_map";
import { Texture } from "./texture";
@ -7,6 +6,7 @@ import { RGB } from "./util";
import { Triangle } from "./triangle";
import { Mesh, MaterialType } from "./mesh";
import { triangleArea } from "./math";
import { Axes, generateRays, rayIntersectTriangle } from "./ray";
interface Block {
position: Vector3;
@ -15,16 +15,10 @@ interface Block {
block?: string
}
interface TriangleCubeAABBs {
triangle: Triangle;
AABBs: Array<CubeAABB>;
}
export class VoxelManager {
public voxels: Array<Block>;
public voxelTexcoords: Array<FaceInfo>;
public triangleAABBs: Array<TriangleCubeAABBs>;
public _voxelSize: number;
private voxelsHash: HashMap<Vector3, Block>;
@ -47,7 +41,6 @@ export class VoxelManager {
this._voxelSize = voxelSize;
this.voxels = [];
this.voxelTexcoords = [];
this.triangleAABBs = [];
this.voxelsHash = new HashMap(2048);
this.blockAtlas = new BlockAtlas();
@ -69,45 +62,8 @@ export class VoxelManager {
this.voxelsHash = new HashMap(2048);
}
public clear() {
this.triangleAABBs = [];
this._clearVoxels();
}
private _snapToVoxelGrid(vec: Vector3) {
return vec.copy().divScalar(this._voxelSize).round().mulScalar(this._voxelSize);
}
private _getTriangleCubeAABB(triangle: Triangle) {
const triangleAABB = triangle.getAABB();
const gridSnappedCentre = this._snapToVoxelGrid(triangleAABB.centre);
let cubeAABB = new CubeAABB(gridSnappedCentre, this._voxelSize);
while (!triangle.insideAABB(cubeAABB)) {
cubeAABB = new CubeAABB(cubeAABB.centre, cubeAABB.width * 2.0);
}
return cubeAABB;
}
private _toGridPosition(vec: Vector3) {
return new Vector3(
Math.round(vec.x / this._voxelSize),
Math.round(vec.y / this._voxelSize),
Math.round(vec.z / this._voxelSize)
);
}
private _toModelPosition(vec: Vector3) {
return new Vector3(
vec.x * this._voxelSize,
vec.y * this._voxelSize,
vec.z * this._voxelSize
);
}
public isVoxelAt(pos: Vector3) {
return this.voxelsHash.get(pos) !== undefined;
return this.voxelsHash.has(pos);
}
public assignBlocks() {
@ -139,20 +95,7 @@ export class VoxelManager {
}
public addVoxel(vec: Vector3, block: BlockInfo) {
// (0.5, 0.5, 0.5) -> (0, 0, 0);
vec = Vector3.subScalar(vec, this._voxelSize / 2);
const pos = this._toGridPosition(vec);
// [HACK] FIXME: Fix misaligned voxels
// Some vec data is not not grid-snapped to voxelSize-spacing
const test = Vector3.divScalar(vec, this._voxelSize);
if ((test.x % 1 < 0.9 && test.x % 1 > 0.1) || (test.y % 1 < 0.9 && test.y % 1 > 0.1) || (test.z % 1 < 0.9 && test.z % 1 > 0.1)) {
console.warn("Misaligned voxel, skipping...");
return;
}
public addVoxel(pos: Vector3, block: BlockInfo) {
// Is there already a voxel in this position?
let voxel = this.voxelsHash.get(pos);
if (voxel !== undefined) {
@ -199,39 +142,43 @@ export class VoxelManager {
}
public voxeliseTriangle(triangle: Triangle) {
const cubeAABB = this._getTriangleCubeAABB(triangle);
const triangleAABBs = [];
const voxelSize = VoxelManager.Get._voxelSize;
const v0Scaled = Vector3.divScalar(triangle.v0.position, voxelSize);
const v1Scaled = Vector3.divScalar(triangle.v1.position, voxelSize);
const v2Scaled = Vector3.divScalar(triangle.v2.position, voxelSize);
let queue = [cubeAABB];
while (true) {
const aabb = queue.shift();
if (!aabb) {
break;
}
if (triangle.intersectAABB(aabb)) {
if (aabb.width > this._voxelSize) {
// Continue to subdivide
queue.push(...aabb.subdivide());
} else {
// We've reached the voxel level, stop
const voxelColour = this._getVoxelColour(triangle, aabb.centre);
const block = this.blockAtlas.getBlock(voxelColour);
const rayList = generateRays(v0Scaled, v1Scaled, v2Scaled);
this.addVoxel(aabb.centre, block);
triangleAABBs.push(aabb);
rayList.forEach(ray => {
const intersection = rayIntersectTriangle(ray, v0Scaled, v1Scaled, v2Scaled);
if (intersection) {
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;
}
}
}
this.triangleAABBs.push({triangle: triangle, AABBs: triangleAABBs});
const voxelColour = this._getVoxelColour(triangle, Vector3.mulScalar(voxelPosition, voxelSize));
const block = this.blockAtlas.getBlock(voxelColour);
this.addVoxel(voxelPosition, block);
}
});
}
voxeliseMesh(mesh: Mesh) {
this._clearVoxels();
mesh.materials.forEach(material => {
// Setup material
console.log("VOXELISING", material.name);
if (material.materialData?.type === MaterialType.Texture) {
this._blockMode = MaterialType.Texture;
this._currentTexture = new Texture(material.materialData.texturePath, material.materialData.format);