forked from mirror/ObjToSchematic
Refactored mesh into mesh sections
This commit is contained in:
parent
8232fe8098
commit
61b32b2b0d
@ -1,10 +1,10 @@
|
||||
import { ImporterFactory } from './src/importers/importers';
|
||||
import { OtS_ImporterFactory } from './src/importers/importers';
|
||||
import { OtS_Texture } from './src/ots_texture';
|
||||
import { OtS_VoxelMesh } from './src/ots_voxel_mesh';
|
||||
import { OtS_VoxelMesh_Converter } from './src/ots_voxel_mesh_converter';
|
||||
|
||||
export default {
|
||||
getImporter: ImporterFactory.GetImporter,
|
||||
getImporter: OtS_ImporterFactory.GetImporter,
|
||||
texture: OtS_Texture,
|
||||
|
||||
voxelMeshConverter: OtS_VoxelMesh_Converter,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OtS_Mesh } from '../ots_mesh';
|
||||
|
||||
export abstract class IImporter {
|
||||
export abstract class OtS_Importer {
|
||||
public abstract import(file: ReadableStream<Uint8Array>): Promise<OtS_Mesh>;
|
||||
}
|
@ -4,25 +4,27 @@ import { GLTFLoader } from '@loaders.gl/gltf';
|
||||
import { RGBAColours, RGBAUtil } from '../colour';
|
||||
import { UV } from '../util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IImporter } from './base_importer';
|
||||
import { OtS_Mesh, TEMP_CONVERT_MESH, Tri } from '../ots_mesh';
|
||||
import { Material } from '../materials';
|
||||
import { OtS_Mesh } from '../ots_mesh';
|
||||
import { OtS_Texture } from '../ots_texture';
|
||||
import { OtS_Importer } from './base_importer';
|
||||
|
||||
export type TGltfImporterError =
|
||||
export type OtS_GltfImporterError =
|
||||
| { type: 'failed-to-parse' }
|
||||
| { type: 'unsupported-image-format' };
|
||||
|
||||
export class GltfImporterError extends Error {
|
||||
public error: TGltfImporterError;
|
||||
interface VertexIndices {
|
||||
v0: number;
|
||||
v1: number;
|
||||
v2: number;
|
||||
};
|
||||
|
||||
constructor(error: TGltfImporterError) {
|
||||
super();
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
export interface Tri {
|
||||
positionIndices: VertexIndices;
|
||||
texcoordIndices?: VertexIndices;
|
||||
material: string;
|
||||
};
|
||||
|
||||
export class GltfLoader extends IImporter {
|
||||
export class OtS_Importer_Gltf extends OtS_Importer {
|
||||
public override async import(file: ReadableStream<Uint8Array>): Promise<OtS_Mesh> {
|
||||
// TODO: StatusRework
|
||||
//StatusHandler.warning(LOC('import.gltf_experimental'));
|
||||
@ -31,7 +33,8 @@ export class GltfLoader extends IImporter {
|
||||
try {
|
||||
gltf = await parse(file, GLTFLoader, { loadImages: true });
|
||||
} catch (err) {
|
||||
throw new GltfImporterError({ type: 'failed-to-parse' });
|
||||
// TODO:
|
||||
//throw new GltfImporterError({ type: 'failed-to-parse' });
|
||||
}
|
||||
|
||||
return this._handleGLTF(gltf);
|
||||
@ -39,12 +42,11 @@ export class GltfLoader extends IImporter {
|
||||
|
||||
private _handleGLTF(gltf: any): OtS_Mesh {
|
||||
const meshVertices: Vector3[] = [];
|
||||
const meshNormals: Vector3[] = [];
|
||||
//const meshNormals: Vector3[] = [];
|
||||
const meshTexcoords: UV[] = [];
|
||||
const meshTriangles: Tri[] = [];
|
||||
const meshMaterials: Map<string, Material> = new Map();
|
||||
const meshMaterials: Map<string, { }> = new Map();
|
||||
meshMaterials.set('NONE', {
|
||||
name: 'NONE',
|
||||
type: 'solid',
|
||||
colour: RGBAUtil.copy(RGBAColours.WHITE),
|
||||
canBeTextured: false,
|
||||
@ -64,6 +66,7 @@ export class GltfLoader extends IImporter {
|
||||
));
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (attributes.NORMAL !== undefined) {
|
||||
const normals = attributes.NORMAL.value as Float32Array;
|
||||
for (let i = 0; i < normals.length; i += 3) {
|
||||
@ -74,6 +77,7 @@ export class GltfLoader extends IImporter {
|
||||
));
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (attributes.TEXCOORD_0 !== undefined) {
|
||||
const texcoords = attributes.TEXCOORD_0.value as Float32Array;
|
||||
for (let i = 0; i < texcoords.length; i += 2) {
|
||||
@ -102,7 +106,7 @@ export class GltfLoader extends IImporter {
|
||||
if (mimeType !== 'image/png' && mimeType !== 'image/jpeg') {
|
||||
// TODO: StatusRework
|
||||
//StatusHandler.warning(LOC('import.unsupported_image_type', { file_name: diffuseTexture.texture.source.id, file_type: mimeType }));
|
||||
throw new GltfImporterError({ type: 'unsupported-image-format' })
|
||||
//throw new GltfImporterError({ type: 'unsupported-image-format' })
|
||||
}
|
||||
|
||||
const base64 = btoa(
|
||||
@ -178,14 +182,14 @@ export class GltfLoader extends IImporter {
|
||||
meshTriangles.push({
|
||||
material: materialNameToUse,
|
||||
positionIndices: {
|
||||
x: maxIndex + indices[i * 3 + 0],
|
||||
y: maxIndex + indices[i * 3 + 1],
|
||||
z: maxIndex + indices[i * 3 + 2],
|
||||
v0: maxIndex + indices[i * 3 + 0],
|
||||
v1: maxIndex + indices[i * 3 + 1],
|
||||
v2: maxIndex + indices[i * 3 + 2],
|
||||
},
|
||||
texcoordIndices: {
|
||||
x: maxIndex + indices[i * 3 + 0],
|
||||
y: maxIndex + indices[i * 3 + 1],
|
||||
z: maxIndex + indices[i * 3 + 2],
|
||||
v0: maxIndex + indices[i * 3 + 0],
|
||||
v1: maxIndex + indices[i * 3 + 1],
|
||||
v2: maxIndex + indices[i * 3 + 2],
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -200,6 +204,10 @@ export class GltfLoader extends IImporter {
|
||||
});
|
||||
});
|
||||
|
||||
return TEMP_CONVERT_MESH(meshVertices, meshTexcoords, meshTriangles);
|
||||
const mesh = OtS_Mesh.create();
|
||||
|
||||
// TODO: Fix
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
@ -1,20 +1,16 @@
|
||||
import { ASSERT } from '../util/error_util';
|
||||
import { IImporter } from './base_importer';
|
||||
import { GltfLoader } from './gltf_loader';
|
||||
import { ObjImporter } from './obj_importer';
|
||||
import { OtS_Importer } from './base_importer';
|
||||
import { OtS_Importer_Gltf } from './gltf_importer';
|
||||
import { OtS_Importer_Obj } from './obj_importer';
|
||||
|
||||
export type TImporters = 'obj' | 'gltf';
|
||||
export type OtS_Importers = 'obj' | 'gltf';
|
||||
|
||||
|
||||
export class ImporterFactory {
|
||||
public static GetImporter(importer: TImporters): IImporter {
|
||||
export class OtS_ImporterFactory {
|
||||
public static GetImporter(importer: OtS_Importers): OtS_Importer {
|
||||
switch (importer) {
|
||||
case 'obj':
|
||||
return new ObjImporter();
|
||||
return new OtS_Importer_Obj();
|
||||
case 'gltf':
|
||||
return new GltfLoader();
|
||||
default:
|
||||
ASSERT(false);
|
||||
return new OtS_Importer_Gltf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,238 +1,80 @@
|
||||
import { RGBAColours, RGBAUtil } from '../colour';
|
||||
import { anyNaN } from '../math';
|
||||
import { OtS_Mesh, TEMP_CONVERT_MESH, Tri } from '../ots_mesh';
|
||||
import { OtS_Mesh } from '../ots_mesh';
|
||||
import { OtS_Texture } from '../ots_texture';
|
||||
import { UV } from '../util';
|
||||
import { ASSERT } from '../util/error_util';
|
||||
import { RegExpBuilder } from '../util/regex_util';
|
||||
import { REGEX_NZ_ANY } from '../util/regex_util';
|
||||
import { REGEX_NUMBER } from '../util/regex_util';
|
||||
import { Vector3 } from '../vector';
|
||||
import { IImporter } from './base_importer';
|
||||
import { OtS_Importer } from './base_importer';
|
||||
|
||||
type TObjImporterParser = {
|
||||
regex: RegExp,
|
||||
delegate: (match: { [key: string]: string }) => (null | TObjImporterError),
|
||||
}
|
||||
|
||||
export type TObjImporterError =
|
||||
export type OtS_ObjImporterError =
|
||||
| { type: 'invalid-encoding' }
|
||||
| { type: 'invalid-material-name', name: string }
|
||||
| { type: 'invalid-data' }
|
||||
| { type: 'failed-to-parse', line: string }
|
||||
| { type: 'failed-to-parse-essential-token', line: string }
|
||||
| { type: 'failed-to-parse-essential-token', line: string };
|
||||
|
||||
export class ObjImporterError extends Error {
|
||||
public error: TObjImporterError;
|
||||
|
||||
constructor(error: TObjImporterError) {
|
||||
super();
|
||||
this.error = error;
|
||||
}
|
||||
interface VertexIndices {
|
||||
v0: number;
|
||||
v1: number;
|
||||
v2: number;
|
||||
}
|
||||
|
||||
export class ObjImporter extends IImporter {
|
||||
export interface Tri {
|
||||
positionIndices: VertexIndices;
|
||||
texcoordIndices?: VertexIndices;
|
||||
material: string;
|
||||
}
|
||||
|
||||
export class OtS_Importer_Obj extends OtS_Importer {
|
||||
private _vertices: Vector3[] = [];
|
||||
private _normals: Vector3[] = [];
|
||||
private _uvs: UV[] = [];
|
||||
private _tris: Tri[] = [];
|
||||
private _currentMaterialName: string = 'DEFAULT_UNASSIGNED';
|
||||
|
||||
private _objParsers: TObjImporterParser[] = [
|
||||
{
|
||||
// e.g. 'usemtl my_material'
|
||||
regex: new RegExpBuilder().add(/^usemtl/).add(/ /).add(REGEX_NZ_ANY, 'name').toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
this._currentMaterialName = match.name.trim();
|
||||
|
||||
if (this._currentMaterialName.length === 0) {
|
||||
const err: TObjImporterError = { type: 'invalid-material-name', name: match.name };
|
||||
return err;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
// e.g. 'v 0.123 0.456 0.789'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/^v/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'x')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'y')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'z')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const x = parseFloat(match.x);
|
||||
const y = parseFloat(match.y);
|
||||
const z = parseFloat(match.z);
|
||||
|
||||
if (anyNaN(x, y, z)) {
|
||||
const err: TObjImporterError = { type: 'invalid-data' };
|
||||
return err;
|
||||
}
|
||||
|
||||
this._vertices.push(new Vector3(x, y, z));
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
// e.g. 'vn 0.123 0.456 0.789'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/^vn/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'x')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'y')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'z')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const x = parseFloat(match.x);
|
||||
const y = parseFloat(match.y);
|
||||
const z = parseFloat(match.z);
|
||||
|
||||
if (anyNaN(x, y, z)) {
|
||||
const err: TObjImporterError = { type: 'invalid-data' };
|
||||
return err;
|
||||
}
|
||||
|
||||
this._normals.push(new Vector3(x, y, z));
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
// e.g. 'vt 0.123 0.456'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/^vt/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'u')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'v')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const u = parseFloat(match.u);
|
||||
const v = parseFloat(match.v);
|
||||
|
||||
if (anyNaN(u, v)) {
|
||||
const err: TObjImporterError = { type: 'invalid-data' };
|
||||
return err;
|
||||
}
|
||||
|
||||
this._uvs.push({ u: u, v: v });
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
// e.g. 'f 1/2/3 ...' or 'f 1/2 ...' or 'f 1 ...'
|
||||
regex: new RegExpBuilder()
|
||||
.add(/^f/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(/.*/, 'line')
|
||||
.toRegExp(),
|
||||
delegate: (match: { [key: string]: string }) => {
|
||||
const line = match.line.trim();
|
||||
|
||||
const vertices = line.split(' ').filter((x) => {
|
||||
return x.length !== 0;
|
||||
});
|
||||
|
||||
if (vertices.length < 3) {
|
||||
const err: TObjImporterError = { type: 'invalid-data' };
|
||||
return err;
|
||||
}
|
||||
|
||||
const points: {
|
||||
positionIndex: number;
|
||||
texcoordIndex?: number;
|
||||
normalIndex?: number;
|
||||
}[] = [];
|
||||
|
||||
for (const vertex of vertices) {
|
||||
const vertexData = vertex.split('/');
|
||||
switch (vertexData.length) {
|
||||
case 1: {
|
||||
const index = parseInt(vertexData[0]);
|
||||
points.push({
|
||||
positionIndex: index,
|
||||
texcoordIndex: index,
|
||||
normalIndex: index,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const positionIndex = parseInt(vertexData[0]);
|
||||
const texcoordIndex = parseInt(vertexData[1]);
|
||||
points.push({
|
||||
positionIndex: positionIndex,
|
||||
texcoordIndex: texcoordIndex,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const positionIndex = parseInt(vertexData[0]);
|
||||
const texcoordIndex = parseInt(vertexData[1]);
|
||||
const normalIndex = parseInt(vertexData[2]);
|
||||
points.push({
|
||||
positionIndex: positionIndex,
|
||||
texcoordIndex: texcoordIndex,
|
||||
normalIndex: normalIndex,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
const err: TObjImporterError = { type: 'invalid-data' };
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
const pointBase = points[0];
|
||||
for (let i = 1; i < points.length - 1; ++i) {
|
||||
const pointA = points[i];
|
||||
const pointB = points[i + 1];
|
||||
const tri: Tri = {
|
||||
positionIndices: {
|
||||
x: pointBase.positionIndex - 1,
|
||||
y: pointA.positionIndex - 1,
|
||||
z: pointB.positionIndex - 1,
|
||||
},
|
||||
material: this._currentMaterialName,
|
||||
};
|
||||
if (pointBase.normalIndex || pointA.normalIndex || pointB.normalIndex) {
|
||||
ASSERT(pointBase.normalIndex && pointA.normalIndex && pointB.normalIndex);
|
||||
tri.normalIndices = {
|
||||
x: pointBase.normalIndex - 1,
|
||||
y: pointA.normalIndex - 1,
|
||||
z: pointB.normalIndex - 1,
|
||||
};
|
||||
}
|
||||
if (pointBase.texcoordIndex || pointA.texcoordIndex || pointB.texcoordIndex) {
|
||||
ASSERT(pointBase.texcoordIndex && pointA.texcoordIndex && pointB.texcoordIndex);
|
||||
tri.texcoordIndices = {
|
||||
x: pointBase.texcoordIndex - 1,
|
||||
y: pointA.texcoordIndex - 1,
|
||||
z: pointB.texcoordIndex - 1,
|
||||
};
|
||||
}
|
||||
this._tris.push(tri);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Parser context
|
||||
private _linesToParse: string[] = [];
|
||||
|
||||
private static _REGEX_USEMTL = new RegExpBuilder()
|
||||
.add(/^usemtl/)
|
||||
.add(/ /)
|
||||
.add(REGEX_NZ_ANY, 'name')
|
||||
.toRegExp();
|
||||
|
||||
private static _REGEX_VERTEX = new RegExpBuilder()
|
||||
.add(/^v/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'x')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'y')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'z')
|
||||
.toRegExp();
|
||||
|
||||
private static _REGEX_TEXCOORD = new RegExpBuilder()
|
||||
.add(/^vt/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'u')
|
||||
.addNonzeroWhitespace()
|
||||
.add(REGEX_NUMBER, 'v')
|
||||
.toRegExp();
|
||||
|
||||
private static _REGEX_FACE = new RegExpBuilder()
|
||||
.add(/^f/)
|
||||
.addNonzeroWhitespace()
|
||||
.add(/.*/, 'line')
|
||||
.toRegExp();
|
||||
|
||||
private _consumeLoadedLines(includeLast: boolean) {
|
||||
const size = includeLast ? this._linesToParse.length : this._linesToParse.length - 1;
|
||||
|
||||
|
||||
for (let i = 0; i < size; ++i) {
|
||||
const line = this._linesToParse[i];
|
||||
const { err } = this.parseOBJLine(line);
|
||||
if (err !== null) {
|
||||
throw new ObjImporterError(err);
|
||||
// TODO:
|
||||
//throw new ObjImporterError(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +84,7 @@ export class ObjImporter extends IImporter {
|
||||
public override async import(file: ReadableStream<Uint8Array>): Promise<OtS_Mesh> {
|
||||
const reader = file.getReader();
|
||||
const decoder = new TextDecoder(); //utf8
|
||||
|
||||
|
||||
let lastChunkEndedWithNewline = false;
|
||||
let result: ReadableStreamReadResult<Uint8Array>;
|
||||
do {
|
||||
@ -267,32 +109,232 @@ export class ObjImporter extends IImporter {
|
||||
lastChunkEndedWithNewline = lastChar === '\n' || lastChar === '\r\n';
|
||||
} while (!result.done);
|
||||
|
||||
return TEMP_CONVERT_MESH(this._vertices, this._uvs, this._tris);
|
||||
const materials = new Map<string, boolean>();
|
||||
this._tris.forEach((tri) => {
|
||||
if (!materials.has(tri.material)) {
|
||||
materials.set(tri.material, tri.texcoordIndices !== undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const mesh = OtS_Mesh.create();
|
||||
for (const [material, isTextureMaterial] of materials) {
|
||||
if (isTextureMaterial) {
|
||||
const positionData: number[] = [];
|
||||
const texcoordData: number[] = [];
|
||||
const indexData: number[] = [];
|
||||
let ni = 0;
|
||||
|
||||
this._tris.forEach((tri) => {
|
||||
if (tri.material === material) {
|
||||
const p0 = this._vertices[tri.positionIndices.v0];
|
||||
const p1 = this._vertices[tri.positionIndices.v1];
|
||||
const p2 = this._vertices[tri.positionIndices.v2];
|
||||
|
||||
ASSERT(tri.texcoordIndices !== undefined);
|
||||
const t0 = this._uvs[tri.texcoordIndices.v0];
|
||||
const t1 = this._uvs[tri.texcoordIndices.v1];
|
||||
const t2 = this._uvs[tri.texcoordIndices.v2];
|
||||
|
||||
positionData.push(p0.x, p0.y, p0.z);
|
||||
texcoordData.push(t0.u, t0.v);
|
||||
indexData.push(ni++);
|
||||
positionData.push(p1.x, p1.y, p1.z);
|
||||
texcoordData.push(t1.u, t1.v);
|
||||
indexData.push(ni++);
|
||||
positionData.push(p2.x, p2.y, p2.z);
|
||||
texcoordData.push(t2.u, t2.v);
|
||||
indexData.push(ni++);
|
||||
}
|
||||
});
|
||||
|
||||
mesh.addSection({
|
||||
type: 'textured',
|
||||
texture: OtS_Texture.CreateDebugTexture(),
|
||||
positionData: Float32Array.from(positionData),
|
||||
texcoordData: Float32Array.from(texcoordData),
|
||||
indexData: Uint32Array.from(indexData),
|
||||
});
|
||||
} else {
|
||||
const positionData: number[] = [];
|
||||
const indexData: number[] = [];
|
||||
let ni = 0;
|
||||
|
||||
this._tris.forEach((tri) => {
|
||||
if (tri.material === material) {
|
||||
const p0 = this._vertices[tri.positionIndices.v0];
|
||||
const p1 = this._vertices[tri.positionIndices.v1];
|
||||
const p2 = this._vertices[tri.positionIndices.v2];
|
||||
|
||||
positionData.push(p0.x, p0.y, p0.z);
|
||||
indexData.push(ni++);
|
||||
positionData.push(p1.x, p1.y, p1.z);
|
||||
indexData.push(ni++);
|
||||
positionData.push(p2.x, p2.y, p2.z);
|
||||
indexData.push(ni++);
|
||||
}
|
||||
});
|
||||
|
||||
mesh.addSection({
|
||||
type: 'solid',
|
||||
colour: RGBAUtil.copy(RGBAColours.WHITE),
|
||||
positionData: Float32Array.from(positionData),
|
||||
indexData: Uint32Array.from(indexData),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse the given line of an OBJ file.
|
||||
* Potentially returns an error if failed to do so.
|
||||
*/
|
||||
public parseOBJLine(line: string): { err: null | TObjImporterError} {
|
||||
const essentialTokens = ['usemtl ', 'v ', 'vt ', 'f ', 'vn '];
|
||||
|
||||
for (const parser of this._objParsers) {
|
||||
const match = parser.regex.exec(line);
|
||||
if (match && match.groups) {
|
||||
const err = parser.delegate(match.groups);
|
||||
return { err: err };
|
||||
}
|
||||
}
|
||||
|
||||
const beginsWithEssentialToken = essentialTokens.some((token) => {
|
||||
return line.startsWith(token);
|
||||
});
|
||||
|
||||
if (beginsWithEssentialToken) {
|
||||
return { err: { type: 'failed-to-parse-essential-token', line: line } }
|
||||
}
|
||||
public parseOBJLine(line: string): { err: null | OtS_ObjImporterError } {
|
||||
false
|
||||
|| this._tryParseAsUsemtl(line)
|
||||
|| this._tryParseAsVertex(line)
|
||||
|| this._tryParseAsTexcoord(line)
|
||||
|| this._tryParseAsFace(line);
|
||||
|
||||
return { err: null };
|
||||
}
|
||||
|
||||
// e.g. 'usemtl my_material'
|
||||
private _tryParseAsUsemtl(line: string): boolean {
|
||||
const match = OtS_Importer_Obj._REGEX_USEMTL.exec(line);
|
||||
if (match === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const materialName = match.groups?.name.trim();
|
||||
|
||||
if (materialName === undefined || materialName.length === 0) {
|
||||
throw 'Invalid material name'; // TODO: Error type
|
||||
}
|
||||
|
||||
this._currentMaterialName = materialName;
|
||||
return true;
|
||||
}
|
||||
|
||||
// e.g. 'v 0.123 0.456 0.789'
|
||||
private _tryParseAsVertex(line: string): boolean {
|
||||
const match = OtS_Importer_Obj._REGEX_VERTEX.exec(line);
|
||||
if (match === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const x = parseFloat(match.groups?.x ?? '');
|
||||
const y = parseFloat(match.groups?.y ?? '');
|
||||
const z = parseFloat(match.groups?.z ?? '');
|
||||
|
||||
if (isNaN(x) || isNaN(y) || isNaN(z)) {
|
||||
throw 'Invalid data'; // TODO: Error type
|
||||
}
|
||||
|
||||
this._vertices.push(new Vector3(x, y, z));
|
||||
return true;
|
||||
}
|
||||
|
||||
// e.g. 'vt 0.123 0.456'
|
||||
private _tryParseAsTexcoord(line: string): boolean {
|
||||
const match = OtS_Importer_Obj._REGEX_TEXCOORD.exec(line);
|
||||
if (match === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u = parseFloat(match.groups?.u ?? '');
|
||||
const v = parseFloat(match.groups?.v ?? '');
|
||||
|
||||
if (isNaN(u) || isNaN(v)) {
|
||||
throw 'Invalid data';
|
||||
}
|
||||
|
||||
this._uvs.push({ u: u, v: v });
|
||||
return true;
|
||||
}
|
||||
|
||||
private _tryParseAsFace(line: string): boolean {
|
||||
const match = OtS_Importer_Obj._REGEX_FACE.exec(line);
|
||||
if (match === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = match.groups?.line.trim();
|
||||
if (data === undefined) {
|
||||
throw 'Invalid data';
|
||||
}
|
||||
|
||||
const vertices = data.split(' ').filter((x) => {
|
||||
return x.length !== 0;
|
||||
});
|
||||
|
||||
if (vertices.length < 3) {
|
||||
throw 'Invalid data';
|
||||
}
|
||||
|
||||
const points: {
|
||||
positionIndex: number;
|
||||
texcoordIndex?: number;
|
||||
}[] = [];
|
||||
|
||||
for (const vertex of vertices) {
|
||||
const vertexData = vertex.split('/');
|
||||
switch (vertexData.length) {
|
||||
case 1: {
|
||||
const index = parseInt(vertexData[0]);
|
||||
points.push({
|
||||
positionIndex: index,
|
||||
texcoordIndex: index,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
const positionIndex = parseInt(vertexData[0]);
|
||||
const texcoordIndex = parseInt(vertexData[1]);
|
||||
points.push({
|
||||
positionIndex: positionIndex,
|
||||
texcoordIndex: texcoordIndex,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const positionIndex = parseInt(vertexData[0]);
|
||||
const texcoordIndex = parseInt(vertexData[1]);
|
||||
points.push({
|
||||
positionIndex: positionIndex,
|
||||
texcoordIndex: texcoordIndex,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw 'Invalid data';
|
||||
}
|
||||
}
|
||||
|
||||
const pointBase = points[0];
|
||||
for (let i = 1; i < points.length - 1; ++i) {
|
||||
const pointA = points[i];
|
||||
const pointB = points[i + 1];
|
||||
const tri: Tri = {
|
||||
positionIndices: {
|
||||
v0: pointBase.positionIndex - 1,
|
||||
v1: pointA.positionIndex - 1,
|
||||
v2: pointB.positionIndex - 1,
|
||||
},
|
||||
material: this._currentMaterialName,
|
||||
};
|
||||
if (pointBase.texcoordIndex || pointA.texcoordIndex || pointB.texcoordIndex) {
|
||||
ASSERT(pointBase.texcoordIndex && pointA.texcoordIndex && pointB.texcoordIndex);
|
||||
tri.texcoordIndices = {
|
||||
v0: pointBase.texcoordIndex - 1,
|
||||
v1: pointA.texcoordIndex - 1,
|
||||
v2: pointB.texcoordIndex - 1,
|
||||
};
|
||||
}
|
||||
this._tris.push(tri);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +1,43 @@
|
||||
import { RGBA, RGBAUtil } from './colour';
|
||||
import { OtS_Texture } from './ots_texture';
|
||||
|
||||
export type OtS_MaterialType = 'solid' | 'textured';
|
||||
// export type OtS_MaterialType = 'solid' | 'textured';
|
||||
|
||||
type BaseMaterial = {
|
||||
name: string,
|
||||
}
|
||||
// type BaseMaterial = {
|
||||
// //name: string,
|
||||
// }
|
||||
|
||||
export type SolidMaterial = BaseMaterial & {
|
||||
type: 'solid'
|
||||
colour: RGBA,
|
||||
canBeTextured: boolean,
|
||||
}
|
||||
export type TexturedMaterial = BaseMaterial & {
|
||||
type: 'textured',
|
||||
texture: OtS_Texture,
|
||||
}
|
||||
// export type SolidMaterial = BaseMaterial & {
|
||||
// type: 'solid'
|
||||
// //canBeTextured: boolean,
|
||||
// }
|
||||
// export type TexturedMaterial = BaseMaterial & {
|
||||
// type: 'textured',
|
||||
// texture: OtS_Texture,
|
||||
// }
|
||||
|
||||
export type Material = SolidMaterial | TexturedMaterial;
|
||||
// export type Material = SolidMaterial | TexturedMaterial;
|
||||
|
||||
export namespace OtS_Util {
|
||||
export function copySolidMaterial(material: SolidMaterial): SolidMaterial {
|
||||
return {
|
||||
type: 'solid',
|
||||
name: material.name,
|
||||
colour: RGBAUtil.copy(material.colour),
|
||||
canBeTextured: material.canBeTextured,
|
||||
};
|
||||
}
|
||||
// export namespace OtS_Util {
|
||||
// export function copySolidMaterial(material: SolidMaterial): SolidMaterial {
|
||||
// return {
|
||||
// type: 'solid',
|
||||
// colour: RGBAUtil.copy(material.colour),
|
||||
// };
|
||||
// }
|
||||
|
||||
export function copyTexturedMaterial(material: TexturedMaterial): TexturedMaterial {
|
||||
return {
|
||||
type: 'textured',
|
||||
name: material.name,
|
||||
texture: material.texture.copy(),
|
||||
}
|
||||
}
|
||||
// export function copyTexturedMaterial(material: TexturedMaterial): TexturedMaterial {
|
||||
// return {
|
||||
// type: 'textured',
|
||||
// texture: material.texture.copy(),
|
||||
// }
|
||||
// }
|
||||
|
||||
export function copyMaterial(material: Material): Material {
|
||||
if (material.type === 'solid') {
|
||||
return OtS_Util.copySolidMaterial(material);
|
||||
} else {
|
||||
return OtS_Util.copyTexturedMaterial(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
// export function copyMaterial(material: Material): Material {
|
||||
// if (material.type === 'solid') {
|
||||
// return OtS_Util.copySolidMaterial(material);
|
||||
// } else {
|
||||
// return OtS_Util.copyTexturedMaterial(material);
|
||||
// }
|
||||
// }
|
||||
// }
|
40
Core/src/ots_materials.ts
Normal file
40
Core/src/ots_materials.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { RGBA } from "./colour";
|
||||
//import { Material, OtS_Util } from "./materials";
|
||||
import { OtS_Texture } from "./ots_texture";
|
||||
|
||||
export type OtS_MeshSection = { positionData: Float32Array, indexData: Uint32Array } & (
|
||||
| { type: 'solid', colour: RGBA }
|
||||
| { type: 'colour', colourData: Float32Array }
|
||||
| { type: 'textured', texcoordData: Float32Array, texture: OtS_Texture });
|
||||
|
||||
/*
|
||||
export class OtS_MaterialSlots {
|
||||
private _slots: Map<number, Material>;
|
||||
|
||||
private constructor() {
|
||||
this._slots = new Map();
|
||||
}
|
||||
|
||||
public static create() {
|
||||
return new OtS_MaterialSlots();
|
||||
}
|
||||
|
||||
public setSlot(index: number, material: Material) {
|
||||
this._slots.set(index, material);
|
||||
}
|
||||
|
||||
public getSlot(index: number) {
|
||||
return this._slots.get(index);
|
||||
}
|
||||
|
||||
public copy() {
|
||||
const clone = OtS_MaterialSlots.create();
|
||||
|
||||
this._slots.forEach((value, key) => {
|
||||
clone.setSlot(key, OtS_Util.copyMaterial(value));
|
||||
});
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,70 +1,105 @@
|
||||
import { Bounds } from './bounds';
|
||||
import { RGBAColours, RGBAUtil } from './colour';
|
||||
import { Material, OtS_Util, SolidMaterial } from './materials';
|
||||
import { RGBA } from './colour';
|
||||
import { degreesToRadians } from './math';
|
||||
import { OtS_MeshSection } from './ots_materials';
|
||||
import { OtS_Texture } from './ots_texture';
|
||||
import { UV } from "./util";
|
||||
import { ASSERT } from './util/error_util';
|
||||
import { Result } from './util/type_util';
|
||||
import { Vector3 } from "./vector";
|
||||
|
||||
// TODO: Nuke
|
||||
interface VertexIndices {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
type OtS_VertexData<T> = {
|
||||
v0: T,
|
||||
v1: T,
|
||||
v2: T,
|
||||
}
|
||||
|
||||
// TODO: Nuke
|
||||
export interface Tri {
|
||||
positionIndices: VertexIndices;
|
||||
texcoordIndices?: VertexIndices;
|
||||
normalIndices?: VertexIndices;
|
||||
material: string;
|
||||
}
|
||||
export type OtS_Triangle =
|
||||
| { type: 'solid', colour: RGBA, data: OtS_VertexData<{ position: Vector3 }> }
|
||||
| { type: 'coloured', data: OtS_VertexData<{ position: Vector3, colour: RGBA }> }
|
||||
| { type: 'textured', texture: OtS_Texture, data: OtS_VertexData<{ position: Vector3, texcoord: UV }> }
|
||||
|
||||
export type OtS_Vertex = {
|
||||
position: Vector3,
|
||||
texcoord: UV,
|
||||
};
|
||||
|
||||
export type OtS_Triangle = {
|
||||
v0: OtS_Vertex,
|
||||
v1: OtS_Vertex,
|
||||
v2: OtS_Vertex,
|
||||
material: Material,
|
||||
};
|
||||
type OtS_MeshError = 'bad-height' | 'bad-material-match' | 'bad-geometry';
|
||||
|
||||
export class OtS_Mesh {
|
||||
private _materials: Material[];
|
||||
private _sections: OtS_MeshSection[];
|
||||
//private _geometry: OtS_Geometry;
|
||||
//private _materials: OtS_MaterialSlots;
|
||||
// [p0, p1, p2]
|
||||
private _positionData: Float32Array;
|
||||
//private _positionData: Float32Array;
|
||||
private static _POSITION_STRIDE = 3;
|
||||
// [u, v]
|
||||
private _texcoordData: Float32Array;
|
||||
//private _texcoordData: Float32Array;
|
||||
private static _TEXCOORD_STRIDE = 2;
|
||||
private static _COLOUR_STRIDE = 2;
|
||||
|
||||
//private static _MATERIAL_STRIDE = 4;
|
||||
// [position_index * 3, texcoord_index * 3, material_index]
|
||||
private _triangleData: Uint32Array;
|
||||
//private _triangleData: Uint32Array;
|
||||
private static _TRIANGLE_STRIDE = 7;
|
||||
|
||||
public constructor(positionData: Float32Array, texcoordData: Float32Array, triangleData: Uint32Array, materials: Material[]) {
|
||||
this._materials = materials;
|
||||
this._positionData = positionData;
|
||||
this._texcoordData = texcoordData;
|
||||
this._triangleData = triangleData;
|
||||
private constructor() {
|
||||
this._sections = [];
|
||||
}
|
||||
|
||||
public static create() {
|
||||
return new this();
|
||||
/*
|
||||
// TODO: Check non-zero height
|
||||
if (false) {
|
||||
return { ok: false, error: {
|
||||
code: 'bad-height',
|
||||
message: 'Geometry should have a non-zero height, consider rotating the mesh'
|
||||
}};
|
||||
}
|
||||
|
||||
// TODO: Check geometry using materials slots is valid, i.e. a triangle that uses a
|
||||
// textured material must have texcoords
|
||||
if (false) {
|
||||
return { ok: false, error: {
|
||||
code: 'bad-material-match',
|
||||
message: 'Material \'x\' uses a textured material but has no texcoords'
|
||||
}};
|
||||
}
|
||||
|
||||
// TODO: Check material slots used by geometry are defined
|
||||
|
||||
if (!geometry.hasAttribute('position')) {
|
||||
return { ok: false, error: {
|
||||
code: 'bad-geometry',
|
||||
message: 'Missing position data'
|
||||
}};
|
||||
}
|
||||
|
||||
const mesh = new this(geometry, materials);
|
||||
return { ok: true, value: mesh };
|
||||
*/
|
||||
}
|
||||
|
||||
public addSection(section: OtS_MeshSection): Result<void, OtS_MeshError> {
|
||||
// TODO: Validation
|
||||
this._sections.push(section);
|
||||
return { ok: true, value: undefined };
|
||||
}
|
||||
|
||||
public translate(x: number, y: number, z: number) {
|
||||
for (let i = 0; i < this._positionData.length; i += 3) {
|
||||
this._positionData[i + 0] += x;
|
||||
this._positionData[i + 1] += y;
|
||||
this._positionData[i + 2] += z;
|
||||
}
|
||||
this._sections.forEach((section) => {
|
||||
for (let i = 0; i < section.positionData.length; i += 3) {
|
||||
section.positionData[i + 0] += x;
|
||||
section.positionData[i + 1] += y;
|
||||
section.positionData[i + 2] += z;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public scale(s: number) {
|
||||
for (let i = 0; i < this._positionData.length; i += 3) {
|
||||
this._positionData[i + 0] *= s;
|
||||
this._positionData[i + 1] *= s;
|
||||
this._positionData[i + 2] *= s;
|
||||
}
|
||||
this._sections.forEach((section) => {
|
||||
for (let i = 0; i < section.positionData.length; i += 3) {
|
||||
section.positionData[i + 0] *= s;
|
||||
section.positionData[i + 1] *= s;
|
||||
section.positionData[i + 2] *= s;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public centre() {
|
||||
@ -72,16 +107,17 @@ export class OtS_Mesh {
|
||||
this.translate(-centre.x, -centre.y, -centre.z);
|
||||
}
|
||||
|
||||
public normalise() {
|
||||
public normalise(): boolean {
|
||||
const bounds = this.calcBounds();
|
||||
const size = Vector3.sub(bounds.max, bounds.min);
|
||||
const scaleFactor = 1.0 / size.y;
|
||||
|
||||
if (isNaN(scaleFactor) || !isFinite(scaleFactor)) {
|
||||
throw 'Could not normalize';
|
||||
return false;
|
||||
}
|
||||
|
||||
this.scale(scaleFactor);
|
||||
return true;
|
||||
}
|
||||
|
||||
public rotate(pitch: number, roll: number, yaw: number) {
|
||||
@ -106,174 +142,203 @@ export class OtS_Mesh {
|
||||
const Azy = cosb*sinc;
|
||||
const Azz = cosb*cosc;
|
||||
|
||||
for (let i = 0; i < this._positionData.length; i += 3) {
|
||||
const px = this._positionData[i + 0];
|
||||
const py = this._positionData[i + 1];
|
||||
const pz = this._positionData[i + 2];
|
||||
|
||||
this._positionData[i + 0] = Axx * px + Axy * py + Axz * pz;
|
||||
this._positionData[i + 1] = Ayx * px + Ayy * py + Ayz * pz;
|
||||
this._positionData[i + 2] = Azx * px + Azy * py + Azz * pz;
|
||||
}
|
||||
}
|
||||
|
||||
public setMaterial(newMaterial: Material): boolean {
|
||||
for (let i = 0; i < this._materials.length; ++i) {
|
||||
const oldMaterial = this._materials[i];
|
||||
if (oldMaterial.name === newMaterial.name) {
|
||||
if (oldMaterial.type === 'solid' && newMaterial.type === 'textured' && !oldMaterial.canBeTextured) {
|
||||
return false; // Assigning a texture material to a non-textureable material
|
||||
}
|
||||
|
||||
// TODO: Check newMaterial is valid
|
||||
|
||||
this._materials[i] = newMaterial;
|
||||
return true;
|
||||
this._sections.forEach((section) => {
|
||||
for (let i = 0; i < section.positionData.length; i += 3) {
|
||||
const px = section.positionData[i + 0];
|
||||
const py = section.positionData[i + 1];
|
||||
const pz = section.positionData[i + 2];
|
||||
|
||||
section.positionData[i + 0] = Axx * px + Axy * py + Axz * pz;
|
||||
section.positionData[i + 1] = Ayx * px + Ayy * py + Ayz * pz;
|
||||
section.positionData[i + 2] = Azx * px + Azy * py + Azz * pz;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No material found under that name
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @note Returns a reference to the underlying materials, modifying these is dangerous
|
||||
*/
|
||||
public getTriangles(): IterableIterator<OtS_Triangle> {
|
||||
const triangleCount = this._triangleData.length / OtS_Mesh._TRIANGLE_STRIDE;
|
||||
const sectionCount = this._sections.length;
|
||||
ASSERT(sectionCount > 0); // TODO: Don't assert,
|
||||
|
||||
let sectionIndex = 0;
|
||||
let triangleCount = this._sections[0].positionData.length / 3;
|
||||
let triangleIndex = 0;
|
||||
|
||||
const getVertex = (positionIndex: number, texcoordIndex: number) => {
|
||||
return {
|
||||
position: new Vector3(
|
||||
this._positionData[positionIndex * OtS_Mesh._POSITION_STRIDE + 0],
|
||||
this._positionData[positionIndex * OtS_Mesh._POSITION_STRIDE + 1],
|
||||
this._positionData[positionIndex * OtS_Mesh._POSITION_STRIDE + 2],
|
||||
),
|
||||
texcoord: {
|
||||
u: this._texcoordData[texcoordIndex * OtS_Mesh._TEXCOORD_STRIDE + 0],
|
||||
v: this._texcoordData[texcoordIndex * OtS_Mesh._TEXCOORD_STRIDE + 1],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[Symbol.iterator]: function () {
|
||||
return this;
|
||||
},
|
||||
next: () => {
|
||||
if (triangleIndex >= triangleCount && sectionIndex < sectionCount) {
|
||||
++sectionIndex;
|
||||
triangleCount = this._sections[sectionIndex].positionData.length / 3;
|
||||
}
|
||||
|
||||
if (triangleIndex < triangleCount) {
|
||||
const dataIndex = triangleIndex * OtS_Mesh._TRIANGLE_STRIDE;
|
||||
const section = this._sections[sectionIndex];
|
||||
|
||||
const positionIndex0 = this._triangleData[dataIndex + 0];
|
||||
const positionIndex1 = this._triangleData[dataIndex + 1];
|
||||
const positionIndex2 = this._triangleData[dataIndex + 2];
|
||||
const texcoordIndex0 = this._triangleData[dataIndex + 3];
|
||||
const texcoordIndex1 = this._triangleData[dataIndex + 4];
|
||||
const texcoordIndex2 = this._triangleData[dataIndex + 5];
|
||||
const materialIndex = this._triangleData[dataIndex + 6];
|
||||
|
||||
const triangle: OtS_Triangle = {
|
||||
material: this._materials[materialIndex],
|
||||
v0: getVertex(positionIndex0, texcoordIndex0),
|
||||
v1: getVertex(positionIndex1, texcoordIndex1),
|
||||
v2: getVertex(positionIndex2, texcoordIndex2),
|
||||
};
|
||||
const index0 = section.indexData[triangleIndex * 3 + 0];
|
||||
const index1 = section.indexData[triangleIndex * 3 + 1];
|
||||
const index2 = section.indexData[triangleIndex * 3 + 2];
|
||||
|
||||
++triangleIndex;
|
||||
|
||||
return { done: false, value: triangle };
|
||||
} else {
|
||||
return { done: true, value: undefined };
|
||||
switch (section.type) {
|
||||
case 'solid': {
|
||||
const triangle: OtS_Triangle = {
|
||||
type: 'solid',
|
||||
colour: section.colour,
|
||||
data: {
|
||||
v0: {
|
||||
position: new Vector3(
|
||||
section.positionData[index0 * 3 + 0],
|
||||
section.positionData[index0 * 3 + 1],
|
||||
section.positionData[index0 * 3 + 2],
|
||||
),
|
||||
},
|
||||
v1: {
|
||||
position: new Vector3(
|
||||
section.positionData[index1 * 3 + 0],
|
||||
section.positionData[index1 * 3 + 1],
|
||||
section.positionData[index1 * 3 + 2],
|
||||
),
|
||||
},
|
||||
v2: {
|
||||
position: new Vector3(
|
||||
section.positionData[index2 * 3 + 0],
|
||||
section.positionData[index2 * 3 + 1],
|
||||
section.positionData[index2 * 3 + 2],
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
return { done: false, value: triangle };
|
||||
}
|
||||
case 'colour': {
|
||||
const triangle: OtS_Triangle = {
|
||||
type: 'coloured',
|
||||
data: {
|
||||
v0: {
|
||||
position: new Vector3(
|
||||
section.positionData[index0 * 3 + 0],
|
||||
section.positionData[index0 * 3 + 1],
|
||||
section.positionData[index0 * 3 + 2],
|
||||
),
|
||||
colour: {
|
||||
r: section.colourData[index0 * 4 + 0],
|
||||
g: section.colourData[index0 * 4 + 1],
|
||||
b: section.colourData[index0 * 4 + 2],
|
||||
a: section.colourData[index0 * 4 + 3],
|
||||
},
|
||||
},
|
||||
v1: {
|
||||
position: new Vector3(
|
||||
section.positionData[index1 * 3 + 0],
|
||||
section.positionData[index1 * 3 + 1],
|
||||
section.positionData[index1 * 3 + 2],
|
||||
),
|
||||
colour: {
|
||||
r: section.colourData[index1 * 4 + 0],
|
||||
g: section.colourData[index1 * 4 + 1],
|
||||
b: section.colourData[index1 * 4 + 2],
|
||||
a: section.colourData[index1 * 4 + 3],
|
||||
},
|
||||
},
|
||||
v2: {
|
||||
position: new Vector3(
|
||||
section.positionData[index2 * 3 + 0],
|
||||
section.positionData[index2 * 3 + 1],
|
||||
section.positionData[index2 * 3 + 2],
|
||||
),
|
||||
colour: {
|
||||
r: section.colourData[index2 * 4 + 0],
|
||||
g: section.colourData[index2 * 4 + 1],
|
||||
b: section.colourData[index2 * 4 + 2],
|
||||
a: section.colourData[index2 * 4 + 3],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return { done: false, value: triangle };
|
||||
}
|
||||
case 'textured': {
|
||||
const triangle: OtS_Triangle = {
|
||||
type: 'textured',
|
||||
texture: section.texture,
|
||||
data: {
|
||||
v0: {
|
||||
position: new Vector3(
|
||||
section.positionData[index0 * 3 + 0],
|
||||
section.positionData[index0 * 3 + 1],
|
||||
section.positionData[index0 * 3 + 2],
|
||||
),
|
||||
texcoord: {
|
||||
u: section.texcoordData[index0 * 2 + 0],
|
||||
v: section.texcoordData[index0 * 2 + 1],
|
||||
},
|
||||
},
|
||||
v1: {
|
||||
position: new Vector3(
|
||||
section.positionData[index1 * 3 + 0],
|
||||
section.positionData[index1 * 3 + 1],
|
||||
section.positionData[index1 * 3 + 2],
|
||||
),
|
||||
texcoord: {
|
||||
u: section.texcoordData[index1 * 2 + 0],
|
||||
v: section.texcoordData[index1 * 2 + 1],
|
||||
},
|
||||
},
|
||||
v2: {
|
||||
position: new Vector3(
|
||||
section.positionData[index2 * 3 + 0],
|
||||
section.positionData[index2 * 3 + 1],
|
||||
section.positionData[index2 * 3 + 2],
|
||||
),
|
||||
texcoord: {
|
||||
u: section.texcoordData[index2 * 2 + 0],
|
||||
v: section.texcoordData[index2 * 2 + 1],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return { done: false, value: triangle };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { done: true, value: undefined };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public copy() {
|
||||
return new OtS_Mesh(
|
||||
this._positionData.slice(0),
|
||||
this._texcoordData.slice(0),
|
||||
this._triangleData.slice(0),
|
||||
this._materials.map((material) => {
|
||||
return OtS_Util.copyMaterial(material);
|
||||
}),
|
||||
);
|
||||
const clone = OtS_Mesh.create();
|
||||
|
||||
for (const section of this._sections) {
|
||||
const success = clone.addSection(section).ok;
|
||||
ASSERT(success);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public calcBounds() {
|
||||
const bounds = Bounds.getEmptyBounds();
|
||||
|
||||
|
||||
const vec = new Vector3(0, 0, 0);
|
||||
for (let i = 0; i < this._positionData.length; i += 3) {
|
||||
vec.set(
|
||||
this._positionData[i + 0],
|
||||
this._positionData[i + 1],
|
||||
this._positionData[i + 2],
|
||||
);
|
||||
bounds.extendByPoint(vec);
|
||||
}
|
||||
this._sections.forEach((section) => {
|
||||
for (let i = 0; i < section.positionData.length; i += 3) {
|
||||
vec.set(
|
||||
section.positionData[i + 0],
|
||||
section.positionData[i + 1],
|
||||
section.positionData[i + 2],
|
||||
);
|
||||
bounds.extendByPoint(vec);
|
||||
}
|
||||
});
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public getMaterials() {
|
||||
return this._materials.map((material) => {
|
||||
return OtS_Util.copyMaterial(material);
|
||||
});
|
||||
}
|
||||
|
||||
public getTriangleCount() {
|
||||
return this._triangleData.length / OtS_Mesh._TRIANGLE_STRIDE;
|
||||
}
|
||||
}
|
||||
|
||||
export function TEMP_CONVERT_MESH(vertices: Vector3[], uvs: UV[], tris: Tri[]): OtS_Mesh {
|
||||
const positionData = new Float32Array(3 * vertices.length);
|
||||
vertices.forEach((vertex, index) => {
|
||||
positionData[index * 3 + 0] = vertex.x;
|
||||
positionData[index * 3 + 1] = vertex.y;
|
||||
positionData[index * 3 + 2] = vertex.z;
|
||||
});
|
||||
|
||||
const texcoordData = new Float32Array(2 * uvs.length);
|
||||
uvs.forEach((uv, index) => {
|
||||
texcoordData[index * 2 + 0] = uv.u;
|
||||
texcoordData[index * 2 + 1] = uv.v;
|
||||
});
|
||||
|
||||
const materialNameToIndex = new Map<string, number>();
|
||||
const materials: Material[] = [];
|
||||
|
||||
const triangleData = new Uint32Array(7 * tris.length);
|
||||
tris.forEach((tri, index) => {
|
||||
const materialName = tri.material;
|
||||
let materialIndex = materialNameToIndex.get(materialName);
|
||||
if (materialIndex === undefined) {
|
||||
materialIndex = materialNameToIndex.size;
|
||||
materialNameToIndex.set(materialName, materialIndex);
|
||||
|
||||
const hasTexcoords = tri.texcoordIndices !== undefined;
|
||||
|
||||
const material: SolidMaterial = {
|
||||
name: materialName,
|
||||
canBeTextured: hasTexcoords,
|
||||
colour: RGBAUtil.copy(RGBAColours.WHITE),
|
||||
type: 'solid',
|
||||
}
|
||||
|
||||
materials.push(material);
|
||||
}
|
||||
|
||||
triangleData[index * 7 + 0] = tri.positionIndices.x;
|
||||
triangleData[index * 7 + 1] = tri.positionIndices.y;
|
||||
triangleData[index * 7 + 2] = tri.positionIndices.z;
|
||||
triangleData[index * 7 + 3] = tri.texcoordIndices?.x ?? 0;
|
||||
triangleData[index * 7 + 4] = tri.texcoordIndices?.y ?? 0;
|
||||
triangleData[index * 7 + 5] = tri.texcoordIndices?.z ?? 0;
|
||||
triangleData[index * 7 + 6] = materialIndex;
|
||||
});
|
||||
|
||||
return new OtS_Mesh(
|
||||
positionData,
|
||||
texcoordData,
|
||||
triangleData,
|
||||
materials,
|
||||
);
|
||||
}
|
@ -8,6 +8,8 @@ import { Bounds } from './bounds';
|
||||
import { RGBA, RGBAColours, RGBAUtil } from './colour';
|
||||
import { OtS_Mesh, OtS_Triangle } from './ots_mesh';
|
||||
import { ASSERT } from './util/error_util';
|
||||
import { OtS_Texture } from './ots_texture';
|
||||
import { UV } from './util';
|
||||
|
||||
export type OtS_VoxelMesh_ConverterConfig = {
|
||||
constraintAxis: TAxis,
|
||||
@ -70,14 +72,14 @@ export class OtS_VoxelMesh_Converter {
|
||||
|
||||
private _voxeliseTri(mesh: OtS_Mesh, voxelMesh: OtS_VoxelMesh, triangle: OtS_Triangle) {
|
||||
this._rays.reset();
|
||||
this._generateRays(triangle.v0.position, triangle.v1.position, triangle.v2.position);
|
||||
this._generateRays(triangle.data.v0.position, triangle.data.v1.position, triangle.data.v2.position);
|
||||
|
||||
const voxelPosition = new Vector3(0, 0, 0);
|
||||
const size = this._rays.size();
|
||||
for (let i = 0; i < size; ++i) {
|
||||
const ray = this._rays.get(i)!;
|
||||
|
||||
const intersection = rayIntersectTriangle(ray, triangle.v0.position, triangle.v1.position, triangle.v2.position);
|
||||
const intersection = rayIntersectTriangle(ray, triangle.data.v0.position, triangle.data.v1.position, triangle.data.v2.position);
|
||||
if (intersection) {
|
||||
switch (ray.axis) {
|
||||
case Axes.x:
|
||||
@ -122,31 +124,38 @@ export class OtS_VoxelMesh_Converter {
|
||||
}
|
||||
|
||||
private _getVoxelColour(mesh: OtS_Mesh, triangle: OtS_Triangle, location: Vector3): RGBA {
|
||||
if (triangle.material.type === 'solid') {
|
||||
return RGBAUtil.copy(triangle.material.colour);
|
||||
if (triangle.type === 'solid') {
|
||||
return triangle.colour;
|
||||
}
|
||||
|
||||
const area01 = Triangle.CalcArea(triangle.v0.position, triangle.v1.position, location);
|
||||
const area12 = Triangle.CalcArea(triangle.v1.position, triangle.v2.position, location);
|
||||
const area20 = Triangle.CalcArea(triangle.v2.position, triangle.v0.position, location);
|
||||
const area01 = Triangle.CalcArea(triangle.data.v0.position, triangle.data.v1.position, location);
|
||||
const area12 = Triangle.CalcArea(triangle.data.v1.position, triangle.data.v2.position, location);
|
||||
const area20 = Triangle.CalcArea(triangle.data.v2.position, triangle.data.v0.position, location);
|
||||
const total = area01 + area12 + area20;
|
||||
|
||||
const w0 = area12 / total;
|
||||
const w1 = area20 / total;
|
||||
const w2 = area01 / total;
|
||||
|
||||
const uv = {
|
||||
u: triangle.v0.texcoord.u * w0 + triangle.v1.texcoord.u * w1 + triangle.v2.texcoord.u * w2,
|
||||
v: triangle.v0.texcoord.v * w0 + triangle.v1.texcoord.v * w1 + triangle.v2.texcoord.v * w2,
|
||||
if (triangle.type === 'coloured') {
|
||||
return {
|
||||
r: triangle.data.v0.colour.r * w0 + triangle.data.v1.colour.r * w1 * triangle.data.v2.colour.r * w2,
|
||||
g: triangle.data.v0.colour.g * w0 + triangle.data.v1.colour.g * w1 * triangle.data.v2.colour.g * w2,
|
||||
b: triangle.data.v0.colour.b * w0 + triangle.data.v1.colour.b * w1 * triangle.data.v2.colour.b * w2,
|
||||
a: triangle.data.v0.colour.a * w0 + triangle.data.v1.colour.a * w1 * triangle.data.v2.colour.a * w2,
|
||||
};
|
||||
}
|
||||
|
||||
const texcoord: UV = {
|
||||
u: triangle.data.v0.texcoord.u * w0 + triangle.data.v1.texcoord.u * w1 + triangle.data.v2.texcoord.u * w2,
|
||||
v: triangle.data.v0.texcoord.v * w0 + triangle.data.v1.texcoord.v * w1 + triangle.data.v2.texcoord.v * w2,
|
||||
};
|
||||
|
||||
if (isNaN(uv.u) || isNaN(uv.v)) {
|
||||
if (isNaN(texcoord.u) || isNaN(texcoord.v)) {
|
||||
RGBAUtil.copy(RGBAColours.MAGENTA);
|
||||
}
|
||||
|
||||
ASSERT(triangle.material.type === 'textured');
|
||||
const texture = triangle.material.texture;
|
||||
return texture.sample(uv.u, uv.v);
|
||||
return triangle.texture.sample(texcoord.u, texcoord.v);
|
||||
}
|
||||
|
||||
private _generateRays(v0: Vector3, v1: Vector3, v2: Vector3) {
|
||||
|
@ -17,4 +17,8 @@ export type WrappedInfo = {}
|
||||
/** Wrapped simply wraps a payload with a list of warnings/info associated with it */
|
||||
export type Wrapped<T> = { payload: T, warnings: WrappedWarnings[], info: WrappedInfo[] };
|
||||
|
||||
export type BlockPalette = Set<string>;
|
||||
export type BlockPalette = Set<string>;
|
||||
|
||||
export type Result<T, E = Error> =
|
||||
| { ok: true, value: T }
|
||||
| { ok: false, error: { code: E, message?: string } };
|
@ -1,29 +1,58 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import path from 'node:path';
|
||||
import OTS from 'ots-core';
|
||||
|
||||
import { createOtSTexture, createReadableStream } from './src/util';
|
||||
import { RGBAColours, RGBAUtil } from 'ots-core/src/colour';
|
||||
import { OtS_Mesh } from 'ots-core/src/ots_mesh';
|
||||
import { OtS_Texture } from 'ots-core/src/ots_texture';
|
||||
|
||||
(async () => {
|
||||
// 1. Import a mesh
|
||||
const pathModel = path.join(__dirname, '../res/samples/skull.obj');
|
||||
const readableStream = createReadableStream(pathModel);
|
||||
const mesh = OtS_Mesh.create();
|
||||
{
|
||||
mesh.addSection({
|
||||
type: 'solid',
|
||||
colour: RGBAUtil.copy(RGBAColours.WHITE),
|
||||
positionData: Float32Array.from([
|
||||
0.0, 0.0, 0.0,
|
||||
1.0, 2.0, 3.0,
|
||||
4.0, 5.0, 6.0,
|
||||
]),
|
||||
indexData: Uint32Array.from([
|
||||
0, 1, 2
|
||||
]),
|
||||
});
|
||||
|
||||
const importer = OTS.getImporter('obj');
|
||||
const mesh = await importer.import(readableStream);
|
||||
mesh.addSection({
|
||||
type: 'colour',
|
||||
positionData: Float32Array.from([
|
||||
0.0, 10.0, 0.0,
|
||||
1.0, 12.0, 3.0,
|
||||
4.0, 15.0, 6.0,
|
||||
]),
|
||||
colourData: Float32Array.from([
|
||||
1.0, 0.0, 0.0, 1.0,
|
||||
1.0, 0.0, 0.0, 1.0,
|
||||
1.0, 0.0, 0.0, 1.0,
|
||||
]),
|
||||
indexData: Uint32Array.from([
|
||||
0, 1, 2
|
||||
]),
|
||||
});
|
||||
|
||||
// 2. Assign materials
|
||||
const pathTexture = path.join(__dirname, '../res/samples/skull.jpg');
|
||||
const texture = createOtSTexture(pathTexture);
|
||||
assert(texture !== undefined, `Could not parse ${pathTexture}`);
|
||||
|
||||
// Update the 'skull' material
|
||||
const success = mesh.setMaterial({
|
||||
type: 'textured',
|
||||
name: 'skull',
|
||||
texture: texture,
|
||||
});
|
||||
assert(success, 'Could not update skull material');
|
||||
mesh.addSection({
|
||||
type: 'textured',
|
||||
texture: new OtS_Texture(new Uint8ClampedArray(), 0, 0, 'nearest', 'repeat'),
|
||||
positionData: Float32Array.from([
|
||||
0.0, 20.0, 0.0,
|
||||
1.0, 22.0, 3.0,
|
||||
4.0, 25.0, 6.0,
|
||||
]),
|
||||
texcoordData: Float32Array.from([
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
0.0, 1.0,
|
||||
]),
|
||||
indexData: Uint32Array.from([
|
||||
0, 1, 2
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Construct a voxel mesh from the mesh
|
||||
const converter = new OTS.voxelMeshConverter();
|
||||
|
Loading…
Reference in New Issue
Block a user