Improved block selection, more robust .mtl parser

This commit is contained in:
Lucas Dower 2021-08-02 19:54:14 +01:00
parent 251520afad
commit a77c24bab2
7 changed files with 178 additions and 51 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 600 KiB

View File

@ -29,14 +29,15 @@ class AppContext {
const file = files[0];
if (!file.name.endsWith(".obj") && !file.name.endsWith(".OBJ")) {
this._showToast(`Could not load ${file.name}`, 'danger');
this._showToast("Files must be .obj format", 'danger');
return;
}
try {
this.loadedMesh = new Mesh(files[0].path);
} catch (err) {
this._showToast(`Could not load ${file.name}`, 'danger');
//this._showToast(`Could not load ${file.name}`, 'danger');
this._showToast(err.message, 'danger');
console.log(err);
return;
}
@ -112,6 +113,7 @@ class AppContext {
$("#toast").addClass(`bg-${style}`);
$("#toastText").html(text);
$("#toast").toast({ delay: 3000 });
$("#toast").toast('show');
}

View File

@ -1,24 +1,85 @@
const twgl = require('twgl.js');
const fs = require('fs');
const { Vector3 } = require('./vector');
const { HashMap } = require('./hash_map.js');
class BlockAtlas {
constructor(gl) {
constructor() {
const blocksJSONPath = "./resources/blocks.json";
const blocksTexturePath = "./resources/blocks.png";
const blocksJSONString = fs.readFileSync(blocksJSONPath);
this._blocks = JSON.parse(blocksJSONString);
/*
this._blocksWebGLTexture = twgl.createTexture(gl, {
src: blocksTexturePath,
mag: gl.NEAREST
});
*/
this._cachedBlocks = new HashMap(1024);
//this._buildLookupTable();
}
/*
_buildLookupTable() {
const numBlocks = Object.keys(this._blocks).length;
const numColours = 255 * 255 * 255;
if (numBlocks > 65535) {
throw Error(`Cannot pack ${numBlocks} blocks into 16-bit buffer`);
}
this.blockLookupTable = new Uint16Array(numColours); // 33.16Mb
let bufferIndex = 0;
let redSquaredDistances = {};
let greenSquaredDistances = {};
let squaredDistance = null;
let blockChoice = null;
let minimumSquaredDistance = null;
let blockIndices = {};
let index = 0;
for (const block in this._blocks) {
blockIndices[block] = index;
++index;
}
for (let r = 0; r < 256; ++r) {
// Cache the red distances to avoid recalculation for each G and B value.
for (const block in this._blocks) {
redSquaredDistances[block] = Math.pow(r/255 - this._blocks[block].colour.r, 2);
}
for (let g = 0; g < 256; ++g) {
// Cache the green distances to avoid recalculation for each B value.
for (const block in this._blocks) {
greenSquaredDistances[block] = Math.pow(g/255 - this._blocks[block].colour.g, 2);
}
for (let b = 0; b < 256; ++b) {
minimumSquaredDistance = Infinity;
blockChoice = null;
for (const block in this._blocks) {
squaredDistance = redSquaredDistances[block] +
greenSquaredDistances[block] +
Math.pow(b/255 - this._blocks[block].colour.b, 2);
if (squaredDistance < minimumSquaredDistance) {
minimumSquaredDistance = squaredDistance;
blockChoice = block;
}
}
this.blockLookupTable[bufferIndex] = blockIndices[blockChoice];
++bufferIndex;
}
}
break;
}
}
*/
getTexcoord(voxelColour) {
const block = this._getBlock(voxelColour);
@ -30,6 +91,11 @@ class BlockAtlas {
_getBlock(voxelColour) {
voxelColour = new Vector3(voxelColour[0], voxelColour[1], voxelColour[2]);
let cachedBlock = this._cachedBlocks.get(voxelColour);
if (cachedBlock) {
return cachedBlock;
}
let minDistance = Infinity;
let blockChoice = null;
@ -41,15 +107,14 @@ class BlockAtlas {
blockAvgColour.b
);
const distance = Vector3.sub(blockAvgColourVector, voxelColour).magnitude();
//console.log(block, blockAvgColourVector);
if (distance < minDistance) {
minDistance = distance;
blockChoice = block;
}
}
//console.log(voxelColour, "choosing", blockChoice);
this._cachedBlocks.add(voxelColour, blockChoice);
return blockChoice;
}

View File

@ -10,7 +10,7 @@ class ArcballCamera {
this.zNear = zNear;
this.zFar = zFar;
this.actualDistance = 6.0;
this.actualDistance = 18.0;
this.actualAzimuth = -1.0;
this.actualElevation = 1.3;

View File

@ -34,4 +34,45 @@ class HashSet {
}
}
module.exports.HashSet = HashSet;
class HashMap {
constructor(numBins) {
this.numBins = numBins;
this.bins = new Array(numBins);
}
_getBin(key) {
const hash = key.hash(); // A bit naughty
return Math.abs(hash) % this.numBins;
}
add(key, value) {
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});
}
}
get(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;
}
}
}
}
module.exports.HashSet = HashSet;
module.exports.HashMap = HashMap;

View File

@ -21,7 +21,6 @@ class Mesh {
//const mtl_path = obj_path.substring(0, obj_path.length - 3) + "mtl";
// Parse .obj
console.log(objPathString);
const wavefrontString = fs.readFileSync(objPathString).toString('utf8');
const parsedJSON = this._parseWavefrontObj(wavefrontString);
@ -33,7 +32,6 @@ class Mesh {
}
// Parse .mtl
console.log(this.mtlPath);
const materialString = fs.readFileSync(this.mtlPath).toString('utf8');
this._materials = this._parseMaterial(materialString);
@ -69,34 +67,57 @@ class Mesh {
let currentMaterialName = null;
let currentMaterialData = {};
lines.forEach((line) => {
for (let i = 0; i < lines.length; ++i) {
const line = lines[i];
const lineTokens = line.trim().split(/\s+/);
switch (lineTokens[0]) {
case "newmtl":
if (currentMaterialName) {
if (!("diffuseTexturePath" in currentMaterialData) && !("diffuseColour" in currentMaterialData)) {
currentMaterialData.diffuseColour = [1.0, 1.0, 1.0];
}
materialJSON[currentMaterialName] = currentMaterialData;
}
currentMaterialName = lineTokens[1];
currentMaterialData = {};
break;
case "Kd":
currentMaterialData.diffuseColour = lineTokens.slice(1).map(x => parseFloat(x));
if (!currentMaterialData.diffuseColour || currentMaterialData.diffuseColour.length != 3) {
throw Error(`Could not parse .mtl file. (Line ${i+1})`);
}
if (currentMaterialData.diffuseColour.some(x => Number.isNaN(x))) {
throw Error(`Could not parse .mtl file. (Line ${i+1})`);
}
break;
case "map_Kd":
if (!lineTokens[1]) {
throw Error(`No valid path to texture in .mtl file. (Line ${i+1})`);
}
let texturePath = lineTokens[1];
if (!path.isAbsolute(texturePath)) {
texturePath = path.join(this.objPath.dir, texturePath);
} else {
texturePath = path.parse(texturePath);
}
if (!fs.existsSync(texturePath)) {
console.error(texturePath);
throw Error(`Cannot load texture`);
throw Error(`Cannot load texture ${texturePath}`);
}
const _path = path.parse(texturePath);
if (_path.ext.toLowerCase() != ".png") {
throw Error(`Can only load .png textures`);
}
currentMaterialData.diffuseTexturePath = texturePath;
break;
}
});
}
if (!("diffuseTexturePath" in currentMaterialData) && !("diffuseColour" in currentMaterialData)) {
currentMaterialData.diffuseColour = [1.0, 1.0, 1.0];
}
materialJSON[currentMaterialName] = currentMaterialData;
return materialJSON;
@ -136,34 +157,33 @@ class Mesh {
continue;
}
if (currentLineTokens[0] === 'mtllib') {
this.mtlPath = currentLineTokens[1];
}
if (currentLineTokens[0] === 'usemtl') {
currentMaterial = currentLineTokens[1];
}
if (currentLineTokens[0] === 'f') {
// Get our 4 sets of vertex, uv, and normal indices for this face
for (let k = 1; k < 5; k++) {
// If there is no fourth face entry then this is specifying a triangle
// in this case we push `-1`
// Consumers of this module should check for `-1` before expanding face data
if (k === 4 && !currentLineTokens[4]) {
parsedJSON.vertexPositionIndices.push(-1);
parsedJSON.vertexUVIndices.push(-1);
parsedJSON.vertexNormalIndices.push(-1);
//parsedJSON.vertexMaterial.push(currentMaterial);
} else {
var indices = currentLineTokens[k].split('/');
parsedJSON.vertexPositionIndices.push(parseInt(indices[0], 10) - 1); // We zero index
parsedJSON.vertexUVIndices.push(parseInt(indices[1], 10) - 1); // our face indices
parsedJSON.vertexNormalIndices.push(parseInt(indices[2], 10) - 1); // by subtracting 1
parsedJSON.vertexMaterial.push(currentMaterial);
switch (currentLineTokens[0]) {
case "mtllib":
this.mtlPath = currentLineTokens[1];
break;
case "usemtl":
currentMaterial = currentLineTokens[1];
break;
case "f":
// Get our 4 sets of vertex, uv, and normal indices for this face
for (let k = 1; k < 5; k++) {
// If there is no fourth face entry then this is specifying a triangle
// in this case we push `-1`
// Consumers of this module should check for `-1` before expanding face data
if (k === 4 && !currentLineTokens[4]) {
parsedJSON.vertexPositionIndices.push(-1);
parsedJSON.vertexUVIndices.push(-1);
parsedJSON.vertexNormalIndices.push(-1);
//parsedJSON.vertexMaterial.push(currentMaterial);
} else {
var indices = currentLineTokens[k].split('/');
parsedJSON.vertexPositionIndices.push(parseInt(indices[0], 10) - 1); // We zero index
parsedJSON.vertexUVIndices.push(parseInt(indices[1], 10) - 1); // our face indices
parsedJSON.vertexNormalIndices.push(parseInt(indices[2], 10) - 1); // by subtracting 1
parsedJSON.vertexMaterial.push(currentMaterial);
}
}
}
}
}
}
return parsedJSON;

View File

@ -49,7 +49,7 @@ class Renderer {
this._registerData(data);
}
_registerVoxel(centre, voxelManager, blockTexcoord) {
_registerVoxel(centre, voxelManager, blockTexcoord) {
let occlusions = new Array(6);
// For each face
for (let f = 0; f < 6; ++f) {
@ -105,7 +105,7 @@ class Renderer {
//console.log(data);
materialBuffer.add(data);
});
console.log(mesh._materials[material]);
//console.log(mesh._materials[material]);
this._materialBuffers.push({
buffer: materialBuffer,
texture: mesh._materials[material].texture,
@ -319,7 +319,6 @@ class Renderer {
}
_getNewBuffers() {
console.log("Getting new buffer");
const bufferSize = 16384 * 16;
this._registerDebug = new SegmentedBuffer(bufferSize, [
{name: 'position', numComponents: 3},