mirror of
https://github.com/LucasDower/ObjToSchematic.git
synced 2025-02-23 13:49:07 +08:00
commit
760e22239b
@ -7,6 +7,8 @@ A tool to convert .obj files into Minecraft Schematics
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# 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`.
|
||||
|
@ -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 |
BIN
resources/greedy_meshing.png
Normal file
BIN
resources/greedy_meshing.png
Normal file
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
14
resources/tri.obj
Normal 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
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
@ -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()");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user