Rewrite of OBJ parsing

This commit is contained in:
Lucas Dower 2021-09-11 23:34:38 +01:00
parent abd71d114d
commit 7056e62aa9
8 changed files with 312 additions and 350 deletions

View File

@ -69,6 +69,8 @@ export class AppContext {
try {
this._loadedMesh = new Mesh(files[0].path, this._gl);
this._loadedMesh.loadTextures(this._gl);
} catch (err: any) {
this._showToast(err.message, ToastColour.RED);
console.log(err);
@ -129,6 +131,7 @@ export class AppContext {
this._renderer.compile();
} catch (err: any) {
this._showToast(err.message, ToastColour.RED);
console.error(err);
return;
}

View File

@ -1,29 +1,8 @@
import * as twgl from "twgl.js";
import { Triangle } from "./triangle";
import { Vector3 } from "./vector";
import { UV } from "./util";
import { VoxelData } from "./buffer";
enum DataType {
PositionIndexData,
PositionTexcoordNormalIndexData
}
/*
export interface PositionIndexData {
type: DataType.PositionIndexData,
position: Float32Array,
indices: Uint16Array
}
export interface PositionTexcoordNormalIndexData extends PositionIndexData {
type: DataType.PositionTexcoordNormalIndexData,
texcoord: Float32Array,
normal: Float32Array
}
*/
export class GeometryTemplates {
@ -34,13 +13,14 @@ export class GeometryTemplates {
const b = triangle.v1;
const c = triangle.v2;
const n = triangle.normal;
//console.log(triangle);
if (debug) {
return {
position: new Float32Array([
a.x, a.y, a.z,
b.x, b.y, b.z,
c.x, c.y, c.z,
a.position.x, a.position.y, a.position.z,
b.position.x, b.position.y, b.position.z,
c.position.x, c.position.y, c.position.z,
]),
indices: new Uint16Array([
0, 1,
@ -51,14 +31,14 @@ export class GeometryTemplates {
} else {
return {
position: new Float32Array([
a.x, a.y, a.z,
b.x, b.y, b.z,
c.x, c.y, c.z,
a.position.x, a.position.y, a.position.z,
b.position.x, b.position.y, b.position.z,
c.position.x, c.position.y, c.position.z,
]),
texcoord: new Float32Array([
triangle.uv0.u, triangle.uv0.v,
triangle.uv1.u, triangle.uv1.v,
triangle.uv2.u, triangle.uv2.v
a.texcoord.u, a.texcoord.v,
b.texcoord.u, b.texcoord.v,
c.texcoord.u, c.texcoord.v
]),
normal: new Float32Array([
n.x, n.y, n.z,

View File

@ -1,36 +1,65 @@
import * as twgl from "twgl.js";
import * as fs from "fs";
import * as path from "path";
//import { expandVertexData } from "expand-vertex-data";
const expandVertexData: any = require("expand-vertex-data");
import { Triangle } from "./triangle";
import { Vector3 } from "./vector";
import { RGB } from "./util";
import { Texture } from "./texture";
import { RGB, UV } from "./util";
import { TextureFormat } from "./texture";
type VertexMap<T> = { [index: number]: T };
export class Vertex {
public position: Vector3;
public texcoord: UV
public normal: Vector3;
constructor(position: Vector3, texcoord: UV, normal: Vector3) {
this.position = position;
this.texcoord = texcoord;
if (!texcoord) {
this.texcoord = { u: 0, v: 0 };
}
this.normal = normal;
}
public static parseFromOBJ(
vertexToken: string,
vertexPositionMap: VertexMap<Vector3>,
vertexTexcoordMap: VertexMap<UV>,
vertexNormalMap: VertexMap<Vector3>,
) {
const tokens = vertexToken.split("/");
const positionIndex = parseFloat(tokens[0]);
const texcoordIndex = parseFloat(tokens[1]);
const normalIndex = parseFloat(tokens[2]);
const position = vertexPositionMap[positionIndex];
const texcoord = vertexTexcoordMap[texcoordIndex];
const normal = vertexNormalMap[normalIndex];
return new Vertex(position, texcoord, normal);
}
export enum TextureFormat {
PNG,
JPEG
}
interface objData {
vertexNormals: Array<number>;
vertexUVs: Array<number>;
vertexPositions: Array<number>;
vertexNormalIndices: Array<number>;
vertexUVIndices: Array<number>;
vertexPositionIndices: Array<number>;
vertexMaterial: Array<string>;
};
interface Materials {
[materialName: string]: (FillMaterial | TextureMaterial)
}
interface ParsedOBJ {
mtlPath: (string | undefined),
materials: Array<Material>
}
export interface FillMaterial {
readonly type: MaterialType.Fill
diffuseColour: RGB
readonly type: MaterialType.Fill,
diffuseColour: RGB
}
export interface TextureMaterial {
@ -50,72 +79,61 @@ export interface MaterialTriangles {
triangles: Array<Triangle>
}
class Material {
public name: string;
public faces: Array<Triangle>;
public materialData: (FillMaterial | TextureMaterial);
constructor(name: string) {
this.name = name;
this.faces = [];
let material: FillMaterial = { diffuseColour: { r: 1, g: 1, b: 1 }, type: MaterialType.Fill}
this.materialData = material;
}
public addFace(face: Triangle) {
this.faces.push(face);
}
public isFilled(): boolean {
return this.name !== "" && this.faces.length > 0;
}
public attachMTLData(materialData: (FillMaterial | TextureMaterial)) {
this.materialData = materialData;
}
}
export class Mesh {
//public materialTriangles: {[materialName: string]: Array<MaterialTriangles>};
public materialTriangles: Array<MaterialTriangles> = [];
private _gl: WebGLRenderingContext;
private objPath: path.ParsedPath;
private mtlPath?: string;
//private _materials: Materials;
private _data: {
position: Float32Array;
texcoord: Float32Array;
indices: Uint16Array;
materialNames: Array<string>;
}
private _materialIndices: {[materialName: string]: Array<number>}
public materials: Array<Material>
constructor(objPathString: string, gl: WebGLRenderingContext) {
this.objPath = path.parse(objPathString);
//this.materialTriangles: Array<MaterialTriangles>;
this._gl = gl;
// Parse .obj
const wavefrontString = fs.readFileSync(objPathString).toString('utf8');
const parsedJSON = this._parseWavefrontObj(wavefrontString);
if (this.mtlPath) {
if (!path.isAbsolute(this.mtlPath)) {
this.mtlPath = path.join(this.objPath.dir, this.mtlPath);
}
} else {
const parsedOBJ = this._parseOBJFile(wavefrontString);
// TODO: Create blank .mtl when not found
if (!parsedOBJ.mtlPath) {
throw Error("No .mtl file found.");
}
// Parse .mtl
const materialString = fs.readFileSync(this.mtlPath).toString('utf8');
const materials = this._parseMaterial(materialString);
// FIXME: Fix quad faces
//console.log(expandVertexData.expandVertexData(null, null));
const expanded = expandVertexData(parsedJSON, {facesToTriangles: true});
this._data = {
position: expanded.positions,
texcoord: expanded.uvs,
indices: expanded.positionIndices,
materialNames: parsedJSON.vertexMaterial
};
this._materialIndices = {};
for (let i = 0; i < parsedJSON.vertexMaterial.length; ++i) {
const materialName = parsedJSON.vertexMaterial[i];
const index = expanded.positionIndices[i];
if (this._materialIndices[materialName]) {
this._materialIndices[materialName].push(index);
} else {
this._materialIndices[materialName] = [index];
}
const objPath = path.parse(objPathString);
if (!path.isAbsolute(parsedOBJ.mtlPath)) {
parsedOBJ.mtlPath = path.join(objPath.dir, parsedOBJ.mtlPath);
}
// Parse .mtl
const materialString = fs.readFileSync(parsedOBJ.mtlPath).toString('utf8');
const parsedMTL = this._parseMaterial(materialString, objPath);
this._parseTriangles(materials);
this.materials = this._mergeMaterialData(parsedOBJ, parsedMTL);
this._centreMesh();
this._normaliseMesh();
this._loadTextures(materials);
}
private _addMaterial(materialsJSON: Materials, materialName: string, materialDiffuseColour: RGB, materialDiffuseTexturePath: string, materialFormat: TextureFormat) {
@ -135,13 +153,13 @@ export class Mesh {
}
// TODO: Rewrite
private _parseMaterial(materialString: string): Materials {
private _parseMaterial(materialString: string, objPath: path.ParsedPath): Materials {
var materialsJSON: Materials = {};
const lines = materialString.split('\n');
let materialName: string = "";
let materialDiffuseColour: RGB = {r: 1.0, g: 1.0, b: 1.0};
let materialDiffuseColour: RGB = { r: 1.0, g: 1.0, b: 1.0 };
let materialDiffuseTexturePath: string = "";
let materialTextureFormat: TextureFormat = TextureFormat.PNG;
@ -153,17 +171,17 @@ export class Mesh {
case "newmtl":
this._addMaterial(materialsJSON, materialName, materialDiffuseColour, materialDiffuseTexturePath, materialTextureFormat);
materialName = lineTokens[1];
materialDiffuseColour = {r: 0, g: 0, b: 0};
materialDiffuseColour = { r: 0, g: 0, b: 0 };
materialDiffuseTexturePath = ""
break;
case "Kd":
const diffuseColour = lineTokens.slice(1).map(x => parseFloat(x))
if (!diffuseColour || diffuseColour.length != 3) {
throw Error(`Could not parse .mtl file. (Line ${i+1})`);
throw Error(`Could not parse .mtl file. (Line ${i + 1})`);
}
if (diffuseColour.some(x => Number.isNaN(x))) {
throw Error(`Could not parse .mtl file. (Line ${i+1})`);
throw Error(`Could not parse .mtl file. (Line ${i + 1})`);
}
materialDiffuseColour = {
r: diffuseColour[0], g: diffuseColour[1], b: diffuseColour[2]
@ -172,11 +190,11 @@ export class Mesh {
case "map_Kd":
if (!lineTokens[1]) {
throw Error(`No valid path to texture in .mtl file. (Line ${i+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);
texturePath = path.join(objPath.dir, texturePath);
}
if (!fs.existsSync(texturePath)) {
console.error(texturePath);
@ -202,120 +220,113 @@ export class Mesh {
return materialsJSON;
}
/*
DISCLAIMER: This is a modified version of wavefront-obj-parser
to include .mtl data (https://www.npmjs.com/package/wavefront-obj-parser)
*/
// TODO: Just re-write this whole thing, wtf have I done, these types tho
_parseWavefrontObj(wavefrontString: string): objData {
const vertexInfoNameMap: {[key: string]: string} = {v: 'vertexPositions', vt: 'vertexUVs', vn: 'vertexNormals'};
private _mergeMaterialData(parsedOBJ: ParsedOBJ, parsedMTL: Materials): Array<Material> {
parsedOBJ.materials.forEach(material => {
material.attachMTLData(parsedMTL[material.name]);
});
var parsedJSON: {[key: string]: Array<number>} = {
vertexNormals: [],
vertexUVs: [],
vertexPositions: [],
vertexNormalIndices: [],
vertexUVIndices: [],
vertexPositionIndices: [],
};
var vertexMaterial: Array<string> = [];
return parsedOBJ.materials;
}
var linesInWavefrontObj = wavefrontString.split('\n');
var currentMaterial: string = "";
// Loop through and parse every line in our .obj file
for (let i = 0; i < linesInWavefrontObj.length; i++) {
const currentLine = linesInWavefrontObj[i];
// Tokenize our current line
const currentLineTokens = currentLine.trim().split(/\s+/);
// vertex position, vertex texture, or vertex normal
const vertexInfoType = vertexInfoNameMap[currentLineTokens[0]];
private _parseOBJFile(wavefrontString: string): ParsedOBJ {
const lines = wavefrontString.split("\n");
if (vertexInfoType) {
for (let k = 1; k < currentLineTokens.length; k++) {
parsedJSON[vertexInfoType].push(parseFloat(currentLineTokens[k]));
}
continue;
}
let mtlPath: (string | undefined);
switch (currentLineTokens[0]) {
let vertexPositionMap: VertexMap<Vector3> = {};
let vertexTexcoordMap: VertexMap<UV> = {};
let vertexNormalMap: VertexMap<Vector3> = {};
let vertexPositionIndex = 1;
let vertexTexcoordIndex = 1;
let vertexNormalIndex = 1;
let currentMaterial: Material = new Material("");
let materials: Array<Material> = [];
lines.forEach(line => {
const tokens = line.split(" ");
switch (tokens[0]) {
case "mtllib":
this.mtlPath = currentLineTokens[1];
mtlPath = tokens[1];
break;
case "v":
vertexPositionMap[vertexPositionIndex++] = Vector3.parse(tokens[1], tokens[2], tokens[3]);
break;
case "vn":
vertexNormalMap[vertexNormalIndex++] = Vector3.parse(tokens[1], tokens[2], tokens[3]);
break;
case "vt":
vertexTexcoordMap[vertexTexcoordIndex++] = { u: parseFloat(tokens[1]), v: parseFloat(tokens[2]) };
break;
case "usemtl":
currentMaterial = currentLineTokens[1];
if (currentMaterial.isFilled()) {
materials.push(currentMaterial);
}
currentMaterial = new Material(tokens[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
vertexMaterial.push(currentMaterial);
}
}
}
const v0 = Vertex.parseFromOBJ(tokens[1], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
const v1 = Vertex.parseFromOBJ(tokens[2], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
const v2 = Vertex.parseFromOBJ(tokens[3], vertexPositionMap, vertexTexcoordMap, vertexNormalMap);
const face = new Triangle(v0, v1, v2);
currentMaterial.addFace(face);
break;
}
});
if (currentMaterial.isFilled()) {
materials.push(currentMaterial);
}
return {
vertexNormals: parsedJSON["vertexNormals"],
vertexUVs: parsedJSON["vertexUVs"],
vertexPositions: parsedJSON["vertexPositions"],
vertexNormalIndices: parsedJSON["vertexNormalIndices"],
vertexUVIndices: parsedJSON["vertexUVIndices"],
vertexPositionIndices: parsedJSON["vertexPositionIndices"],
vertexMaterial: vertexMaterial
mtlPath: mtlPath,
materials: materials
}
}
// TODO: Factor in triagle's size, perform weighted sum
// TODO: Factor in triangle's size, perform weighted sum
// to prevent areas of dense triangles dominating
private _centreMesh() {
// Find the centre
let centre = new Vector3(0, 0, 0);
let count = 0;
this.materialTriangles.forEach(materialTriangle => {
materialTriangle.triangles.forEach(triangle => {
centre.add(triangle.getCentre());
this.materials.forEach(material => {
material.faces.forEach(face => {
centre.add(face.getCentre());
++count;
});
});
centre.divScalar(count);
console.log(centre);
// Translate each triangle
this.materialTriangles.forEach(materialTriangle => {
materialTriangle.triangles.forEach(triangle => {
triangle.v0.sub(centre);
triangle.v1.sub(centre);
triangle.v2.sub(centre);
triangle.buildAABB();
this.materials.forEach(material => {
material.faces.forEach(face => {
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();
});
});
}
/**
* (Optional) Scale model so each imported model is the same size
* Scale model so each imported model is the same size
*/
private _normaliseMesh() {
// Find the size
let a = new Vector3( Infinity, Infinity, Infinity);
let a = new Vector3(Infinity, Infinity, Infinity);
let b = new Vector3(-Infinity, -Infinity, -Infinity);
this.materialTriangles.forEach(materialTriangle => {
materialTriangle.triangles.forEach(triangle => {
const aabb = triangle.getAABB();
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);
@ -328,63 +339,27 @@ export class Mesh {
const size = Vector3.sub(b, a);
const targetSize = 8.0;
const scaleFactor = targetSize / Math.max(size.x, size.y, size.z);
// Scale each triangle
this.materialTriangles.forEach(materialTriangle => {
materialTriangle.triangles.forEach(triangle => {
triangle.v0.mulScalar(scaleFactor);
triangle.v1.mulScalar(scaleFactor);
triangle.v2.mulScalar(scaleFactor);
triangle.buildAABB();
this.materials.forEach(material => {
material.faces.forEach(face => {
face.v0.position.mulScalar(scaleFactor);
face.v1.position.mulScalar(scaleFactor);
face.v2.position.mulScalar(scaleFactor);
face.updateAABB();
});
});
}
_parseTriangles(materials: Materials) {
this.materialTriangles = [];
for (const materialName in this._materialIndices) {
let triangles = [];
const indices = this._materialIndices[materialName];
for (let i = 0; i < indices.length; i += 3) {
const i0 = indices[i];
const i1 = indices[i + 1];
const i2 = indices[i + 2];
const v0 = this._data.position.slice(3 * i0, 3 * i0 + 3);
const v1 = this._data.position.slice(3 * i1, 3 * i1 + 3);
const v2 = this._data.position.slice(3 * i2, 3 * i2 + 3);
const uv0 = this._data.texcoord.slice(2 * i0, 2 * i0 + 2);
const uv1 = this._data.texcoord.slice(2 * i1, 2 * i1 + 2);
const uv2 = this._data.texcoord.slice(2 * i2, 2 * i2 + 2);
const v0_ = new Vector3(v0[0], v0[1], v0[2]);
const v1_ = new Vector3(v1[0], v1[1], v1[2]);
const v2_ = new Vector3(v2[0], v2[1], v2[2]);
triangles.push(new Triangle(v0_, v1_, v2_, {u: uv0[0], v: uv0[1]}, {u: uv1[0], v: uv1[1]}, {u: uv2[0], v: uv2[1]}));
}
this.materialTriangles.push({
material: materials[materialName],
triangles: triangles
});
}
}
_loadTextures(materials: Materials) {
for (const materialName in materials) {
if (materials[materialName].type == MaterialType.Texture) {
const material = <TextureMaterial> materials[materialName];
material.texture = twgl.createTexture(this._gl, {
src: material.texturePath,
mag: this._gl.LINEAR
public loadTextures(gl: WebGLRenderingContext) {
this.materials.forEach(material => {
if (material.materialData?.type === MaterialType.Texture) {
material.materialData.texture = twgl.createTexture(gl, {
src: material.materialData.texturePath,
mag: gl.LINEAR
});
materials[materialName] = material;
}
}
});
}
}

View File

@ -15,8 +15,8 @@ import { Mesh, FillMaterial, TextureMaterial, MaterialType } from "./mesh";
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 _backgroundColour: RGB = { r: 0.1, g: 0.1, b: 0.1 };
private _strokeColour: RGB = { r: 1.0, g: 0.0, b: 0.0 };
private _fov: number = 30;
private _gl: WebGLRenderingContext;
private _camera: ArcballCamera;
@ -50,7 +50,7 @@ export class Renderer {
this._compiled = false;
//this._blockTexture = twgl.createTexture(this._gl, { src: "resources/blocks/stone.png", mag: this._gl.NEAREST });
this._materialBuffers = [];
@ -59,10 +59,10 @@ export class Renderer {
src: path.join(__dirname, "../resources/blocks.png"),
mag: this._gl.NEAREST
});
}
public set strokeColour(colour: RGB) {
@ -78,13 +78,13 @@ export class Renderer {
this._registerData(data);
}
private _registerVoxel(centre: Vector3, voxelManager: VoxelManager, blockTexcoord: UV) {
let occlusions = new Array<Array<number>>(6);
private _registerVoxel(centre: Vector3, voxelManager: VoxelManager, blockTexcoord: UV) {
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) {
@ -94,7 +94,7 @@ export class Renderer {
// 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);
occlusions[f] = occlusions[f].map(x => 1.0 - 0.2 * x);
}
let data: VoxelData = GeometryTemplates.getBoxBufferData(centre, false);
@ -121,29 +121,27 @@ export class Renderer {
this._registerData(data);
}
public registerMesh(mesh: Mesh) {
for (const material in mesh.materialTriangles) {
public registerMesh(mesh: Mesh) {
//console.log(mesh);
mesh.materials.forEach(material => {
const materialBuffer = new BottomlessBuffer([
{name: 'position', numComponents: 3},
{name: 'texcoord', numComponents: 2},
{name: 'normal', numComponents: 3}
{ name: 'position', numComponents: 3 },
{ name: 'texcoord', numComponents: 2 },
{ name: 'normal', numComponents: 3 }
]);
const materialTriangles = mesh.materialTriangles[material];
console.log(materialTriangles);
materialTriangles.triangles.forEach(triangle => {
const data = GeometryTemplates.getTriangleBufferData(triangle, false);
material.faces.forEach(face => {
const data = GeometryTemplates.getTriangleBufferData(face, false);
//console.log(data);
materialBuffer.add(data);
});
this._materialBuffers.push({
buffer: materialBuffer,
material: materialTriangles.material
material: material.materialData
});
}
console.log("MATERIAL BUFFERS:", this._materialBuffers);
});
}
registerVoxelMesh(voxelManager: VoxelManager) {
@ -156,8 +154,8 @@ export class Renderer {
this.registerBox(voxel.position);
});
} else {
// Setup arrays for calculating voxel ambient occlusion
// 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];
@ -170,7 +168,7 @@ export class Renderer {
});
*/
}
/*
const mesh = voxelManager.buildMesh();
for (const box of mesh) {
@ -215,7 +213,7 @@ export class Renderer {
u_texture: this._atlasTexture,
u_voxelSize: voxelSize
});
/*
// Draw default register
this._drawRegister(this._registerDefault, this._gl.TRIANGLES, shaderManager.shadedProgram, {
@ -256,45 +254,45 @@ export class Renderer {
this._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)]
[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)]
],
[
[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)]
[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)]
],
[
[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)]
[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)]
],
[
[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)]
[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)]
],
[
[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)]
[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)]
],
[
[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)]
[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)]
]
]
}
@ -306,7 +304,7 @@ export class Renderer {
this._registerDebug.add(data);
} else {
this._registerDefault.add(data);
}
}
}
_setupScene() {
@ -314,36 +312,36 @@ export class Renderer {
this._gl.viewport(0, 0, this._gl.canvas.width, this._gl.canvas.height);
this._camera.aspect = this._gl.canvas.width / this._gl.canvas.height;
this._gl.blendFuncSeparate(this._gl.SRC_ALPHA, this._gl.ONE_MINUS_SRC_ALPHA, this._gl.ONE, this._gl.ONE_MINUS_SRC_ALPHA);
this._gl.enable(this._gl.DEPTH_TEST);
//this._gl.enable(this._gl.CULL_FACE);
this._gl.enable(this._gl.BLEND);
this._gl.clearColor(this._backgroundColour.r, this._backgroundColour.g, this._backgroundColour.b, 1.0);
this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
this._camera.updateCameraPosition();
this._camera.updateCameraPosition();
}
_registerEvents() {
this._gl.canvas.addEventListener('mousedown', (e) => {
this._camera.isRotating = true;
});
this._gl.canvas.addEventListener('mouseup', (e) => {
this._camera.isRotating = false;
});
this._gl.canvas.addEventListener('mousemove', (e) => {
this._mouseManager.handleInput(<MouseEvent>e);
this._camera.updateCamera(this._mouseManager.getMouseDelta());
});
this._gl.canvas.addEventListener('wheel', (e) => {
this._camera.handleScroll(<WheelEvent>e);
});
}
_drawBuffer(drawMode: number, buffer: {numElements: number, buffer: twgl.BufferInfo}, shader: twgl.ProgramInfo, uniforms: any) {
_drawBuffer(drawMode: number, buffer: { numElements: number, buffer: twgl.BufferInfo }, shader: twgl.ProgramInfo, uniforms: any) {
this._gl.useProgram(shader.program);
twgl.setBuffersAndAttributes(this._gl, shader, buffer.buffer);
twgl.setUniforms(shader, uniforms);
@ -353,20 +351,20 @@ export class Renderer {
_getNewBuffers() {
const bufferSize = 16384 * 16;
this._registerDebug = new SegmentedBuffer(bufferSize, [
{name: 'position', numComponents: 3, insertIndex: 0},
{name: 'colour', numComponents: 3, insertIndex: 0}
{ name: 'position', numComponents: 3, insertIndex: 0 },
{ name: 'colour', numComponents: 3, insertIndex: 0 }
]);
this._registerVoxels = new SegmentedBuffer(bufferSize, [
{name: 'position', numComponents: 3, insertIndex: 0},
{name: 'normal', numComponents: 3, insertIndex: 0},
{name: 'occlusion', numComponents: 4, insertIndex: 0},
{name: 'texcoord', numComponents: 2, insertIndex: 0},
{name: 'blockTexcoord', numComponents: 2, insertIndex: 0},
{ name: 'position', numComponents: 3, insertIndex: 0 },
{ name: 'normal', numComponents: 3, insertIndex: 0 },
{ name: 'occlusion', numComponents: 4, insertIndex: 0 },
{ name: 'texcoord', numComponents: 2, insertIndex: 0 },
{ name: 'blockTexcoord', numComponents: 2, insertIndex: 0 },
]);
this._registerDefault = new SegmentedBuffer(bufferSize, [
{name: 'position', numComponents: 3, insertIndex: 0},
{ name: 'position', numComponents: 3, insertIndex: 0 },
//{name: 'colour', numComponents: 3},
{name: 'normal', numComponents: 3, insertIndex: 0}
{ name: 'normal', numComponents: 3, insertIndex: 0 }
]);
}

View File

@ -2,8 +2,11 @@ import * as fs from "fs";
import * as jpeg from "jpeg-js";
import { PNG } from "pngjs";
import { UV, RGBA } from "./util";
import { TextureFormat } from "./mesh";
export enum TextureFormat {
PNG,
JPEG
}
export class Texture {

View File

@ -2,48 +2,40 @@ 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";
export class Triangle {
public v0: Vector3;
public v1: Vector3;
public v2: Vector3;
public v0: Vertex;
public v1: Vertex;
public v2: Vertex;
public readonly normal: Vector3;
public readonly uv0: UV;
public readonly uv1: UV;
public readonly uv2: UV;
private aabb?: AABB;
private aabb!: AABB;
constructor(v0: Vector3, v1: Vector3, v2: Vector3, uv0: UV, uv1: UV, uv2: UV) {
constructor(v0: Vertex, v1: Vertex, v2: Vertex) {
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.uv0 = uv0;
this.uv1 = uv1;
this.uv2 = uv2;
const f0 = Vector3.sub(v1, v0);
const f1 = Vector3.sub(v0, v2);
const f0 = Vector3.sub(v1.position, v0.position);
const f1 = Vector3.sub(v0.position, v2.position);
this.normal = Vector3.cross(f0, f1).normalise();
this.buildAABB();
this.updateAABB();
}
public buildAABB() {
public updateAABB() {
const a = new Vector3(
Math.min(this.v0.x, this.v1.x, this.v2.x),
Math.min(this.v0.y, this.v1.y, this.v2.y),
Math.min(this.v0.z, this.v1.z, this.v2.z)
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.x, this.v1.x, this.v2.x),
Math.max(this.v0.y, this.v1.y, this.v2.y),
Math.max(this.v0.z, this.v1.z, this.v2.z)
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);
@ -53,19 +45,19 @@ export class Triangle {
}
public getAABB() {
return this.aabb;
return this.aabb!;
}
public insideAABB(aabb: AABB) {
return Vector3.lessThanEqualTo(aabb.a, this.aabb.a) && Vector3.lessThanEqualTo(this.aabb.b, aabb.b);
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, aabb.centre);
const v1 = Vector3.sub(this.v1, aabb.centre);
const v2 = Vector3.sub(this.v2, aabb.centre);
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);
@ -96,10 +88,11 @@ export class Triangle {
return true;
}
public getCentre() {
return Vector3.divScalar(Vector3.add(Vector3.add(this.v0, this.v1), this.v2), 3.0);
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);

View File

@ -25,6 +25,14 @@ export class Vector3 extends Hashable {
);
}
static parse(f1: string, f2: string, f3: string) {
return new Vector3(
parseFloat(f1),
parseFloat(f2),
parseFloat(f3)
)
}
add(vec: Vector3) {
this.x += vec.x;
this.y += vec.y;

View File

@ -313,9 +313,9 @@ export class VoxelManager {
return this._currentColour;
}
let distV0 = Vector3.sub(triangle.v0, centre).magnitude();
let distV1 = Vector3.sub(triangle.v1, centre).magnitude();
let distV2 = Vector3.sub(triangle.v2, centre).magnitude();
let distV0 = Vector3.sub(triangle.v0.position, centre).magnitude();
let distV1 = Vector3.sub(triangle.v1.position, centre).magnitude();
let distV2 = Vector3.sub(triangle.v2.position, centre).magnitude();
const k = distV0 + distV1 + distV2;
distV0 /= k;
@ -323,8 +323,8 @@ export class VoxelManager {
distV2 /= k;
const uv = {
u: triangle.uv0.u * distV0 + triangle.uv1.u * distV1 + triangle.uv2.u * distV2,
v: triangle.uv0.v * distV0 + triangle.uv1.v * distV1 + triangle.uv2.v * distV2,
u: triangle.v0.texcoord.u * distV0 + triangle.v1.texcoord.u * distV1 + triangle.v2.texcoord.u * distV2,
v: triangle.v0.texcoord.v * distV0 + triangle.v1.texcoord.v * distV1 + triangle.v2.texcoord.v * distV2,
}
return this._currentTexture.getRGBA(uv);
@ -398,23 +398,25 @@ export class VoxelManager {
}
voxeliseMesh(mesh: Mesh) {
for (const materialName in mesh.materialTriangles) {
const materialTriangles = mesh.materialTriangles[materialName];
mesh.materials.forEach(material => {
// Setup material
if (materialTriangles.material.type === MaterialType.Texture) {
console.log("VOXELISING", material.name);
if (material.materialData?.type === MaterialType.Texture) {
this._blockMode = MaterialType.Texture;
this._currentTexture = new Texture(materialTriangles.material.texturePath, materialTriangles.material.format);
this._currentTexture = new Texture(material.materialData.texturePath, material.materialData.format);
} else {
this._currentColour = materialTriangles.material.diffuseColour;
this._currentColour = material.materialData!.diffuseColour;
this._blockMode = MaterialType.Fill;
}
// Handle triangles
for (const triangle of materialTriangles.triangles) {
this.voxeliseTriangle(triangle);
}
}
material.faces.forEach(face => {
console.log("FACE");
this.voxeliseTriangle(face);
});
});
this.assignBlocks();
console.log(this.blockPalette);
}
}