Merge pull request #2 from LucasDower/greedy-meshing

Greedy meshing
This commit is contained in:
Lucas Dower 2021-07-06 16:48:28 +01:00 committed by GitHub
commit 760e22239b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 270 additions and 65 deletions

View File

@ -7,6 +7,8 @@ A tool to convert .obj files into Minecraft Schematics
![DebugPreview](/resources/debug_preview.png)
![MeshingPreview](/resources/greedy_meshing.png)
# Progress
0.1
* ✔️ **.json model loading**
@ -15,15 +17,15 @@ A tool to convert .obj files into Minecraft Schematics
* ✔️ **Basic .obj file loader UI**
0.2
* Greedy voxel meshing
* ✔️ **Greedy voxel meshing**
* Export to schematic
0.3
* Building guides
* Slice viewer
# Usage
Since no build has been released yet, you'll need to:
* Download and install [Node.js](https://nodejs.org/en/).
* Execute `git clone https://github.com/LucasDower/ObjToSchematic.git` in your command line.
* Navigate to `/ObjToSchematic-main`.

View File

@ -17,12 +17,11 @@
<button id="objBtn" class="btn btn-primary" type="button" id="inputGroupFileAddon04">Load</button>
</div>
<div class="input-group w-25 mx-auto">
<label class="input-group-text" for="inputGroupFileAddon03">Options</label>
<input type="number" min="0.1" step="0.1" value="1.0" class="form-control" id="voxelInput" aria-describedby="inputGroupFileAddon03" aria-label="Voxel size" disabled>
<label class="input-group-text" for="inputGroupFileAddon03">Voxel size</label>
<input type="number" min="0.1" step="0.1" value="0.1" class="form-control" id="voxelInput" aria-describedby="inputGroupFileAddon03" aria-label="Voxel size" disabled>
<button id="voxelBtn" class="btn btn-primary" type="button" id="inputGroupFileAddon03" disabled>Voxelise</button>
</div>
</div>
</nav>
<canvas id="c"></canvas>
</body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 75 KiB

14
resources/tri.obj Normal file
View File

@ -0,0 +1,14 @@
# Blender v2.93.1 OBJ File: ''
# www.blender.org
mtllib tri.mtl
o Plane_Plane.002
v -1.300944 -1.462577 0.805557
v 1.955046 2.129125 1.387971
v -0.654101 -0.666548 -2.193528
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vn -0.7422 0.6699 0.0177
usemtl None
s 1
f 1/1/1 2/2/1 3/3/1

View File

@ -10,8 +10,8 @@ class ArcballCamera {
this.zNear = zNear;
this.zFar = zFar;
this.actualDistance = 15.0;
this.actualAzimuth = 0.6;
this.actualDistance = 6.0;
this.actualAzimuth = -1.0;
this.actualElevation = 1.3;
this.cameraSmoothing = 0.025;
@ -28,7 +28,7 @@ class ArcballCamera {
this.mouseSensitivity = 0.005;
this.scrollSensitivity = 0.005;
this.zoomDistMin = 2.0;
this.zoomDistMin = 1.0;
this.zoomDistMax = 100.0;
this.isRotating = false;

View File

@ -3,13 +3,16 @@ const { Mesh } = require('./src/mesh.js');
const { VoxelManager } = require('./src/voxel_manager.js');
const { Vector3 } = require('./src/vector.js');
const voxelSize = 0.5;
//const voxelSize = document.querySelector("#voxelInput").value;
const voxelSize = 0.025;
let renderer = new Renderer(voxelSize);
const voxelManager = new VoxelManager(voxelSize);
const nav = document.querySelector("#nav");
let canvas = document.querySelector("#c");
const showMeshing = false;
const showFailedAABBs = true;
function resizeCanvas() {
canvas.height = window.innerHeight - 54;
@ -17,16 +20,6 @@ function resizeCanvas() {
}
resizeCanvas();
/*
const suzanneLeft = new Mesh('./resources/suzanne_left.obj');
voxelManager.voxeliseMesh(suzanneLeft);
renderer.registerVoxels(voxelManager.voxels);
const suzanneRight = new Mesh('./resources/suzanne_right.obj');
renderer.registerMesh(suzanneRight);
*/
let loadedMesh = null;
document.querySelector("#objBtn").addEventListener('click', () => {
@ -47,6 +40,7 @@ document.querySelector("#objBtn").addEventListener('click', () => {
renderer.compileRegister();
});
document.querySelector("#voxelBtn").addEventListener('click', () => {
const voxelSize = document.querySelector("#voxelInput").value;
@ -55,15 +49,33 @@ document.querySelector("#voxelBtn").addEventListener('click', () => {
renderer.setVoxelSize(voxelSize);
voxelManager.setVoxelSize(voxelSize);
voxelManager.voxeliseMesh(loadedMesh);
renderer.clear();
renderer.registerVoxels(voxelManager.voxels, false);
const mesh = voxelManager.buildMesh();
for (const box of mesh) {
renderer.registerBox(box.centre, box.size, false);
}
if (showMeshing) {
renderer.setStroke(new Vector3(1.0, 0.0, 0.0));
for (const box of mesh) {
renderer.registerBox(box.centre, box.size, true);
}
}
if (showFailedAABBs) {
renderer.setStroke(new Vector3(0.0, 0.0, 1.0));
for (const box of voxelManager.failedAABBs) {
renderer.registerBox(box.centre, box.size, true);
}
}
renderer.compileRegister();
});
function render(time) {
resizeCanvas();

View File

@ -1,10 +1,14 @@
const { Vector3 } = require('./vector.js');
const { VoxelManager } = require('./voxel_manager.js');
class HashMap {
class HashSet {
constructor(numBins) {
this.numBins = numBins;
this.bins = new Array(numBins);
for (let i = 0; i < numBins; ++i) {
this.bins[i] = [];
}
}
_getBin(key) {
@ -12,49 +16,22 @@ class HashMap {
return Math.abs(hash) % this.numBins;
}
add(key, value) {
add(key) {
const binIndex = this._getBin(key);
console.log(binIndex);
if (!this.bins[binIndex]) {
this.bins[binIndex] = [ {key: key, value: value} ];
} else {
this.bins[binIndex].push({key: key, value: value});
}
this.bins[binIndex].push(key);
}
get(key) {
contains(key) {
const binIndex = this._getBin(key);
if (!this.bins[binIndex]) {
return;
}
const list = this.bins[binIndex];
for (const item of list) {
if (item.key.equals(key)) {
return item.value;
if (item.equals(key)) {
return true;
}
}
return false;
}
}
const hashMap = new HashMap(4);
const v = new Vector3(2.0, 2.0, 3.0);
const v2 = new Vector3(2.0, 2.0, 5.0);
const v3 = new Vector3(-2.0, -2.0, -5.0);
const v4 = new Vector3(-2.0, -5.0, -7.0);
hashMap.add(v, true);
hashMap.add(v2, true);
hashMap.add(v3, true);
console.log(hashMap.bins[0]);
console.log(hashMap.bins[1]);
console.log(hashMap.bins[2]);
console.log(hashMap.bins[3]);
console.log(hashMap.get(v));
console.log(hashMap.get(v2));
console.log(hashMap.get(v3));
console.log(hashMap.get(v4));
module.exports.HashSet = HashSet;

View File

@ -32,6 +32,7 @@ class Renderer {
this._voxelSize = voxelSize;
this._voxelSizeVector = new Vector3(voxelSize, voxelSize, voxelSize);
this._cube = twgl.primitives.createCubeVertices(1.0);
this._registersOpen = true;
}
@ -118,7 +119,7 @@ class Renderer {
}
clear() {
console.log("clearing");
//console.log("clearing");
this._debugRegister = this._getEmptyDebugRegister();
this._register = this._getEmptyRegister();
@ -193,12 +194,21 @@ class Renderer {
]
};
} else {
let cube = twgl.primitives.createCubeVertices(this._voxelSize);
let cube = {
position: new Float32Array(72),
normal: new Float32Array(72),
indices: new Float32Array(72)
};
cube.position.set(this._cube.position);
cube.normal.set(this._cube.normal);
cube.indices.set(this._cube.indices);
for (let i = 0; i < 72; i += 3) {
cube.position[i + 0] += centre.x;
cube.position[i + 1] += centre.y;
cube.position[i + 2] += centre.z;
cube.position[i + 0] = (cube.position[i + 0] * size.x) + centre.x;
cube.position[i + 1] = (cube.position[i + 1] * size.y) + centre.y;
cube.position[i + 2] = (cube.position[i + 2] * size.z) + centre.z;
}
return cube;
@ -262,6 +272,7 @@ class Renderer {
// Use when drawing the same thing each frame
registerBox(centre, size, debug) {
const data = this._getBoxData(centre, size, debug);
//console.log(data);
this._addDataToRegister(data, debug);
}
@ -289,9 +300,16 @@ class Renderer {
}
registerVoxels(voxelCentres, debug) {
/*
for (let i = 0; i < voxelCentres.length; ++i) {
console.log(i / voxelCentres.length);
this.registerVoxel(voxelCentres[i], debug);
}
*/
for (const voxelCentre of voxelCentres) {
this.registerVoxel(voxelCentre, debug);
}
}
_cycleDebugRegister() {
@ -307,6 +325,10 @@ class Renderer {
//console.log("Cycling Registers");
}
_willDataOverflowBuffer(data) {
}
_addDataToRegister(data, debug) {
if (!this._registersOpen) {
console.error("Trying to register object when register is closed. Register before calling compileRegister()");

View File

@ -18,6 +18,14 @@ class Vector3 {
);
}
static addScalar(vec, scalar) {
return new Vector3(
vec.x + scalar,
vec.y + scalar,
vec.z + scalar
);
}
static sub(vecA, vecB) {
return new Vector3(
vecA.x - vecB.x,
@ -26,6 +34,14 @@ class Vector3 {
);
}
static subScalar(vec, scalar) {
return new Vector3(
vec.x - scalar,
vec.y - scalar,
vec.z - scalar
);
}
static dot(vecA, vecB) {
return vecA.x * vecB.x + vecA.y * vecB.y + vecA.z * vecB.z;
}

View File

@ -1,5 +1,6 @@
const { AABB, CubeAABB } = require("./aabb.js");
const { Vector3 } = require("./vector.js");
const { HashSet } = require('./hash_map.js');
class VoxelManager {
@ -7,6 +8,14 @@ class VoxelManager {
this._voxelSize = voxelSize;
this.voxels = [];
this.failedAABBs = [];
this.minX = Infinity; // JavaScript crack
this.minY = Infinity;
this.minZ = Infinity;
this.maxX = -Infinity;
this.maxY = -Infinity;
this.maxZ = -Infinity;
this.voxelsHash = new HashSet(2048);
}
setVoxelSize(voxelSize) {
@ -16,6 +25,14 @@ class VoxelManager {
clear() {
this.voxels = [];
this.failedAABBs = [];
this.minX = Infinity; // JavaScript crack
this.minY = Infinity;
this.minZ = Infinity;
this.maxX = -Infinity;
this.maxY = -Infinity;
this.maxZ = -Infinity;
this.voxelsHash = new HashSet(2048);
}
_getTriangleCubeAABB(triangle) {
@ -35,6 +52,151 @@ class VoxelManager {
return cubeAABB;
}
_voxelCentreToPosition(vec) {
//Vector3.round(Vector3.subScalar(Vector3.divScalar(vec, this._voxelSize), 0.5));
return new Vector3(
Math.round(vec.x / this._voxelSize),
Math.round(vec.y / this._voxelSize),
Math.round(vec.z / this._voxelSize)
);
}
_voxelPositionToCentre(vec) {
return new Vector3(
vec.x * this._voxelSize,
vec.y * this._voxelSize,
vec.z * this._voxelSize
);
}
addVoxel(vec) {
// (0.5, 0.5, 0.5) -> (0, 0, 0);
vec = Vector3.subScalar(vec, this._voxelSize / 2);
const pos = this._voxelCentreToPosition(vec);
if (this.voxelsHash.contains(pos)) {
return;
}
this.voxels.push(vec);
this.voxelsHash.add(pos, true);
this.minX = Math.min(this.minX, vec.x);
this.minY = Math.min(this.minY, vec.y);
this.minZ = Math.min(this.minZ, vec.z);
this.maxX = Math.max(this.maxX, vec.x);
this.maxY = Math.max(this.maxY, vec.y);
this.maxZ = Math.max(this.maxZ, vec.z);
}
_findXExtent(pos) {
let xEnd = pos.x + 1;
while (this.voxelsHash.contains(new Vector3(xEnd, pos.y, pos.z)) && !this.seen.contains(new Vector3(xEnd, pos.y, pos.z))) {
//console.log("Marking:", new Vector3(xEnd, y, z));
this.seen.add(new Vector3(xEnd, pos.y, pos.z));
++xEnd;
}
return xEnd - 1;
}
_findZExtent(pos, xEnd) {
let canMerge = true;
let zEnd = pos.z + 1;
do {
//console.log("zEnd:", z, zEnd);
for (let i = pos.x; i <= xEnd; ++i) {
const here = new Vector3(i, pos.y, zEnd);
if (!this.voxelsHash.contains(here) || this.seen.contains(here)) {
canMerge = false;
break;
}
}
if (canMerge) {
// Mark all as seen
for (let i = pos.x; i <= xEnd; ++i) {
const here = new Vector3(i, pos.y, zEnd);
//console.log("Marking:", new Vector3(xEnd, y, z));
this.seen.add(here);
}
++zEnd;
}
} while (canMerge);
return zEnd - 1;
}
_findYExtent(pos, xEnd, zEnd) {
let canMerge = true;
let yEnd = pos.y + 1;
do {
for (let i = pos.x; i <= xEnd; ++i) {
for (let j = pos.z; j <= zEnd; ++j) {
const here = new Vector3(i, yEnd, j);
if (!this.voxelsHash.contains(here) || this.seen.contains(here)) {
canMerge = false;
break;
}
}
}
if (canMerge) {
// Mark all as seen
for (let i = pos.x; i <= xEnd; ++i) {
for (let j = pos.z; j <= zEnd; ++j) {
const here = new Vector3(i, yEnd, j);
this.seen.add(here);
}
}
++yEnd;
}
} while (canMerge);
return yEnd - 1;
}
buildMesh() {
this.mesh = [];
this.seen = new HashSet(2048);
//console.log(this.voxelsHash);
const minPos = this._voxelCentreToPosition(new Vector3(this.minX, this.minY, this.minZ));
const maxPos = this._voxelCentreToPosition(new Vector3(this.maxX, this.maxY, this.maxZ));
for (let y = minPos.y; y <= maxPos.y; ++y) {
for (let z = minPos.z; z <= maxPos.z; ++z) {
for (let x = minPos.x; x <= maxPos.x; ++x) {
const pos = new Vector3(x, y, z);
if (this.seen.contains(pos) || !this.voxelsHash.contains(pos)) {
continue;
}
let xEnd = this._findXExtent(pos);
let zEnd = this._findZExtent(pos, xEnd);
let yEnd = this._findYExtent(pos, xEnd, zEnd);
let centre = new Vector3((xEnd + x)/2, (yEnd + y)/2, (zEnd + z)/2);
let size = new Vector3(xEnd - x + 1, yEnd - y + 1, zEnd - z + 1);
this.mesh.push({
centre: this._voxelPositionToCentre(centre),
size: this._voxelPositionToCentre(size)
});
}
}
}
//console.log("Mesh:", this.mesh);
return this.mesh;
}
voxeliseTriangle(triangle) {
const cubeAABB = this._getTriangleCubeAABB(triangle);
@ -51,7 +213,8 @@ class VoxelManager {
} else {
// We've reached the voxel level, stop
//renderer.registerBox(aabb.centre, aabb.size);
this.voxels.push(aabb.centre);
//this.voxels.push(aabb.centre);
this.addVoxel(aabb.centre);
}
} else {
this.failedAABBs.push(aabb);