@that-sky-project/that-sky-level 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +504 -0
- package/README.md +34 -0
- package/dist/cli.js +22 -0
- package/docs/img/blender_edit_obj_model.png +0 -0
- package/docs/materials.md +37 -0
- package/level_example/HTNH_Test/HTNH_Test.Objects.level.json +258 -0
- package/level_example/HTNH_Test/HTNH_Test.obj +46 -0
- package/level_example/HTNH_Test/Makefile +11 -0
- package/main.js +218 -0
- package/package.json +23 -0
- package/src/formats/triangleMesh.js +82 -0
- package/src/formats/wavefront.js +148 -0
- package/src/level/adjacency.js +466 -0
- package/src/level/enums/kFieldType.js +8 -0
- package/src/level/enums/kMaterial.js +39 -0
- package/src/level/meshes/levelGeo.js +283 -0
- package/src/level/meshes/levelLod.js +15 -0
- package/src/level/meshes/levelMeshes.js +141 -0
- package/src/level/meshes/levelToc.js +69 -0
- package/src/level/objects/levelObjects.js +683 -0
- package/src/level/objects/levelObjectsJson.js +256 -0
- package/src/meshopt/LICENSE +21 -0
- package/src/meshopt/meshopt_decoder.js +199 -0
- package/src/meshopt/meshopt_encoder.js +221 -0
- package/src/utils/binaryStream.js +806 -0
- package/src/utils/helperClasses.js +110 -0
- package/src/utils/logger.js +14 -0
- package/src/utils/normVec.js +67 -0
- package/src/utils/vector.js +232 -0
- package/webpack.config.js +31 -0
package/main.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const ps = require("process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const pl = require("path");
|
|
4
|
+
const arg = require("arg");
|
|
5
|
+
const MeshoptDecoder = require("./src/meshopt/meshopt_decoder.js");
|
|
6
|
+
const MeshoptEncoder = require("./src/meshopt/meshopt_encoder.js");
|
|
7
|
+
const { WavefrontObj } = require("./src/formats/wavefront.js");
|
|
8
|
+
const { LevelMeshes } = require("./src/level/meshes/levelMeshes.js");
|
|
9
|
+
const { LevelCvtAdjacency } = require("./src/level/adjacency.js");
|
|
10
|
+
const { LevelObjectsJson } = require("./src/level/objects/levelObjectsJson.js");
|
|
11
|
+
const { silence, printf } = require("./src/utils/logger.js");
|
|
12
|
+
|
|
13
|
+
const kEngineVersion = [0, 32, 2];
|
|
14
|
+
const kEditorVersion = [1, 0, 0];
|
|
15
|
+
|
|
16
|
+
const argv = arg({
|
|
17
|
+
// Show helps.
|
|
18
|
+
"--help": Boolean,
|
|
19
|
+
// Convert from .meshes to .obj
|
|
20
|
+
"--touch": Boolean,
|
|
21
|
+
// Convert from .obj to .meshes
|
|
22
|
+
"--convert": Boolean,
|
|
23
|
+
// Convert from .level.json to .level.bin
|
|
24
|
+
"--serialize": Boolean,
|
|
25
|
+
// Convert from .level.bin to .level.json
|
|
26
|
+
"--deserialize": Boolean,
|
|
27
|
+
|
|
28
|
+
// Merge chunks to a single object.
|
|
29
|
+
"-m": Boolean,
|
|
30
|
+
// Input file.
|
|
31
|
+
"-i": String,
|
|
32
|
+
// Output file.
|
|
33
|
+
"-o": String,
|
|
34
|
+
// Silence mode.
|
|
35
|
+
"-s": Boolean,
|
|
36
|
+
|
|
37
|
+
"-h": "--help",
|
|
38
|
+
"-T": "--touch",
|
|
39
|
+
"-C": "--convert",
|
|
40
|
+
"-S": "--serialize",
|
|
41
|
+
"-D": "--deserialize",
|
|
42
|
+
}, { permissive: true });
|
|
43
|
+
|
|
44
|
+
function readModelFile(path) {
|
|
45
|
+
var ext = pl.extname(path);
|
|
46
|
+
if (ext === ".obj") {
|
|
47
|
+
var raw = fs.readFileSync(path, "utf-8")
|
|
48
|
+
, result = new WavefrontObj();
|
|
49
|
+
result.fromFileBuffer(raw);
|
|
50
|
+
return result;
|
|
51
|
+
} else {
|
|
52
|
+
throw new Error("unrecognized file extname");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readMeshesFile(path) {
|
|
57
|
+
var ext = pl.extname(path);
|
|
58
|
+
if (ext === ".meshes") {
|
|
59
|
+
var raw = fs.readFileSync(path)
|
|
60
|
+
, result = new LevelMeshes();
|
|
61
|
+
result.fromFileBuffer(raw);
|
|
62
|
+
return result;
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error("unrecognized file extname");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function setDesc(desc, inputFile) {
|
|
69
|
+
desc.fileName = inputFile;
|
|
70
|
+
desc.timeStamp = Math.floor(new Date().getTime() / 1000);
|
|
71
|
+
desc.editor = "that-sky-level";
|
|
72
|
+
desc.editorVersion = kEditorVersion;
|
|
73
|
+
desc.engineVersion = kEngineVersion;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function touchObject(meshes, merge) {
|
|
77
|
+
var geo = meshes.geo
|
|
78
|
+
, obj = ""
|
|
79
|
+
, totalIdx = 0;
|
|
80
|
+
|
|
81
|
+
for (var vtx of geo.vertices)
|
|
82
|
+
obj += `v ${vtx.pos.x} ${vtx.pos.y} ${vtx.pos.z}\n`;
|
|
83
|
+
obj += "\n";
|
|
84
|
+
|
|
85
|
+
for (var vtx of geo.vertices)
|
|
86
|
+
obj += `vn ${vtx.normal.x} ${vtx.normal.y} ${vtx.normal.z}\n`;
|
|
87
|
+
obj += "\n";
|
|
88
|
+
|
|
89
|
+
if (merge)
|
|
90
|
+
obj += "o Chunks" + "\n";
|
|
91
|
+
for (var i = 0; i < geo.chunkCount; i++) {
|
|
92
|
+
if (!merge)
|
|
93
|
+
obj += "o Chunk_" + i + "\n";
|
|
94
|
+
var chunk = geo.chunks[i];
|
|
95
|
+
for (var j = 0; j < chunk.idxCount; j += 3) {
|
|
96
|
+
var fileIdx;
|
|
97
|
+
|
|
98
|
+
obj += "f ";
|
|
99
|
+
fileIdx = chunk.vtxStart + geo.localIndices[chunk.idxStart + j] + 1;
|
|
100
|
+
obj += `${fileIdx}//${fileIdx} `;
|
|
101
|
+
fileIdx = chunk.vtxStart + geo.localIndices[chunk.idxStart + j + 1] + 1;
|
|
102
|
+
obj += `${fileIdx}//${fileIdx} `;
|
|
103
|
+
fileIdx = chunk.vtxStart + geo.localIndices[chunk.idxStart + j + 2] + 1;
|
|
104
|
+
obj += `${fileIdx}//${fileIdx}\n`;
|
|
105
|
+
|
|
106
|
+
totalIdx += 3;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
result: obj,
|
|
112
|
+
totalVtx: geo.vertices.length,
|
|
113
|
+
totalIdx: totalIdx
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
!async function () {
|
|
118
|
+
await MeshoptDecoder.ready;
|
|
119
|
+
await MeshoptEncoder.ready;
|
|
120
|
+
|
|
121
|
+
silence(argv["-s"]);
|
|
122
|
+
|
|
123
|
+
if (argv["--convert"]) {
|
|
124
|
+
var input = argv["-i"]
|
|
125
|
+
, output = argv["-o"] || "./BstBaked.meshes";
|
|
126
|
+
|
|
127
|
+
if (!input)
|
|
128
|
+
throw new Error("no input file");
|
|
129
|
+
|
|
130
|
+
var rawMesh = readModelFile(input)
|
|
131
|
+
, converter = new LevelCvtAdjacency(rawMesh)
|
|
132
|
+
, meshes = new LevelMeshes();
|
|
133
|
+
|
|
134
|
+
converter.initialize(rawMesh);
|
|
135
|
+
meshes.geo = converter.convert();
|
|
136
|
+
setDesc(meshes.desc, input);
|
|
137
|
+
fs.writeFileSync(output, meshes.toFileBuffer());
|
|
138
|
+
printf("Converted:");
|
|
139
|
+
printf(" Chunks: " + meshes.geo.chunkCount);
|
|
140
|
+
printf(" Subchunks: " + meshes.geo.subchunkCount);
|
|
141
|
+
printf(" Vertices: " + meshes.geo.vertexCount);
|
|
142
|
+
printf(" Faces: " + meshes.geo.indexCount / 3);
|
|
143
|
+
return;
|
|
144
|
+
} else if (argv["--touch"]) {
|
|
145
|
+
var input = argv["-i"]
|
|
146
|
+
, output = argv["-o"];
|
|
147
|
+
|
|
148
|
+
if (!input)
|
|
149
|
+
throw new Error("no input file");
|
|
150
|
+
|
|
151
|
+
var meshes = readMeshesFile(input);
|
|
152
|
+
printf("File information:");
|
|
153
|
+
printf(" Version: 0x" + meshes.fileVersion.toString(16));
|
|
154
|
+
if (meshes.desc) {
|
|
155
|
+
printf(" Editor: " + meshes.desc.editor);
|
|
156
|
+
printf(" Editor version: " + meshes.desc.editorVersion);
|
|
157
|
+
printf(" Timestamp: " + meshes.desc.timeStamp);
|
|
158
|
+
printf(" Original file: " + meshes.desc.fileName);
|
|
159
|
+
} else {
|
|
160
|
+
printf(" Editor: -");
|
|
161
|
+
printf(" Editor version: -");
|
|
162
|
+
printf(" Timestamp: -");
|
|
163
|
+
printf(" Original file: -");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!output)
|
|
167
|
+
return;
|
|
168
|
+
|
|
169
|
+
var result = touchObject(meshes, argv["-m"]);
|
|
170
|
+
|
|
171
|
+
fs.writeFileSync(output, result.result);
|
|
172
|
+
printf("\nConverted:");
|
|
173
|
+
printf(" Vertices: " + result.totalVtx);
|
|
174
|
+
printf(" Indices: " + result.totalIdx);
|
|
175
|
+
} else if (argv["--serialize"]) {
|
|
176
|
+
var input = argv["-i"]
|
|
177
|
+
, output = argv["-o"] || "./Objects.level.bin";
|
|
178
|
+
|
|
179
|
+
if (!input)
|
|
180
|
+
throw new Error("no input file");
|
|
181
|
+
|
|
182
|
+
var file = fs.readFileSync(input, "utf-8");
|
|
183
|
+
fs.writeFileSync(output, LevelObjectsJson.write(JSON.parse(file)));
|
|
184
|
+
} else if (argv["--deserialize"]) {
|
|
185
|
+
var input = argv["-i"]
|
|
186
|
+
, output = argv["-o"] || "./Objects.level.json";
|
|
187
|
+
|
|
188
|
+
if (!input)
|
|
189
|
+
throw new Error("no input file");
|
|
190
|
+
|
|
191
|
+
var file = fs.readFileSync(input);
|
|
192
|
+
fs.writeFileSync(output, JSON.stringify(LevelObjectsJson.read(file), null, 2));
|
|
193
|
+
} else {
|
|
194
|
+
printf("that-sky-level");
|
|
195
|
+
printf("Copyright (c) 2026 That Sky Project");
|
|
196
|
+
printf("<https://www.github.com/that-sky-project/that-sky-level>");
|
|
197
|
+
printf(" - A Sky CotL level reader and writer.");
|
|
198
|
+
printf("");
|
|
199
|
+
printf("Usage:");
|
|
200
|
+
printf(" node main.js <-T|-C|-S|-D> -i <input> -o <output> [-m] [-s]");
|
|
201
|
+
printf(" node main.js --help");
|
|
202
|
+
printf("");
|
|
203
|
+
printf("Options:");
|
|
204
|
+
printf(" --touch, -T Convert from .meshes to .obj");
|
|
205
|
+
printf(" --convert, -C Convert from .obj to .meshes");
|
|
206
|
+
printf(" --serialize, -S Convert from .level.json to .level.bin");
|
|
207
|
+
printf(" --deserialize, -D Convert from .level.bin to .level.json");
|
|
208
|
+
printf(" --help, -h Show this help");
|
|
209
|
+
printf(" -i <file> Input file");
|
|
210
|
+
printf(" -o <file> Output file");
|
|
211
|
+
printf(" -s Disable logger");
|
|
212
|
+
printf(" -m Merge chunks when exporting to .obj");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}().catch(e => {
|
|
216
|
+
printf("tslevel: §c§lerror: §r%0", e.message || "" + e);
|
|
217
|
+
ps.exit(1);
|
|
218
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@that-sky-project/that-sky-level",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@htmonkeyg/tformat": "^1.0.1",
|
|
6
|
+
"arg": "^5.0.2",
|
|
7
|
+
"fbx-parser": "^2.1.3",
|
|
8
|
+
"long": "^5.3.2",
|
|
9
|
+
"lz4": "^0.6.5",
|
|
10
|
+
"parsenbt-js": "^3.1.3"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "npx webpack"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"tslevel": "./dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"terser-webpack-plugin": "^5.6.1",
|
|
20
|
+
"webpack": "^5.108.3",
|
|
21
|
+
"webpack-cli": "^7.1.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const { kMaterial } = require("../level/enums/kMaterial.js");
|
|
2
|
+
const { Vec3 } = require("../utils/vector.js");
|
|
3
|
+
|
|
4
|
+
class TriangleMeshVertex {
|
|
5
|
+
constructor(pos, normal) {
|
|
6
|
+
this.pos = pos || new Vec3();
|
|
7
|
+
this.normal = normal || new Vec3();
|
|
8
|
+
/** A list of vertices considered adjacent to the current vertex. */
|
|
9
|
+
this.nearby = new Set();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class TriangleMeshMaterial {
|
|
14
|
+
static Null = new TriangleMeshMaterial();
|
|
15
|
+
static Default = new TriangleMeshMaterial("kMaterial_Cliff");
|
|
16
|
+
|
|
17
|
+
static isNull(mat) {
|
|
18
|
+
return mat == TriangleMeshMaterial.Null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor(name) {
|
|
22
|
+
this.name = name || "";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getId() {
|
|
26
|
+
if (this.name.substring(0, 10) !== "kMaterial_")
|
|
27
|
+
return 0;
|
|
28
|
+
return kMaterial[this.name.substring(10)];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class TriangleMeshMaterialBarn {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.materials = new Map();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
initialize() {
|
|
38
|
+
this.materials.clear();
|
|
39
|
+
this.materials.set("", TriangleMeshMaterial.Null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
tryAddMaterial(name) {
|
|
43
|
+
if (this.materials.has(name))
|
|
44
|
+
return this.materials.get(name);
|
|
45
|
+
|
|
46
|
+
var result = new TriangleMeshMaterial(name);
|
|
47
|
+
this.materials.set(name, result);
|
|
48
|
+
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class TriangleMeshFace {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.materialRef = TriangleMeshMaterial.Null;
|
|
56
|
+
this.indices = [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class TriangleMesh {
|
|
61
|
+
constructor() {
|
|
62
|
+
this.cmdBuffer = [];
|
|
63
|
+
this.vtxBuffer = [];
|
|
64
|
+
this.materials = new TriangleMeshMaterialBarn();
|
|
65
|
+
|
|
66
|
+
this.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
clear() {
|
|
70
|
+
this.cmdBuffer = [];
|
|
71
|
+
this.vtxBuffer = [];
|
|
72
|
+
this.materials.initialize();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
TriangleMeshVertex,
|
|
78
|
+
TriangleMeshMaterial,
|
|
79
|
+
TriangleMeshMaterialBarn,
|
|
80
|
+
TriangleMeshFace,
|
|
81
|
+
TriangleMesh
|
|
82
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const { R8G8B8A8_SNORM } = require("../utils/normVec.js");
|
|
2
|
+
const { Vec3 } = require("../utils/vector");
|
|
3
|
+
const {
|
|
4
|
+
TriangleMesh,
|
|
5
|
+
TriangleMeshVertex,
|
|
6
|
+
TriangleMeshMaterial,
|
|
7
|
+
TriangleMeshMaterialBarn,
|
|
8
|
+
TriangleMeshFace
|
|
9
|
+
} = require("./triangleMesh.js");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse a Wavefront OBJ string into vertices, indices, and per-triangle
|
|
13
|
+
* material names. Only vertex positions and face indices are extracted.
|
|
14
|
+
* Returns an object with vertices (array of [x, y, z]), indices
|
|
15
|
+
* (flattened triangle index list), and materials (material name per
|
|
16
|
+
* triangle). If no usemtl is given, "" is used.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} objStr
|
|
19
|
+
* @param {TriangleMeshMaterialBarn} materialBarn
|
|
20
|
+
*/
|
|
21
|
+
function parseObj(objStr, materialBarn) {
|
|
22
|
+
// Split the file content into lines.
|
|
23
|
+
var lines = objStr.split("\n")
|
|
24
|
+
// Storage for raw vertex positions and normals.
|
|
25
|
+
, positions = []
|
|
26
|
+
, normals = []
|
|
27
|
+
, positionRefs = []
|
|
28
|
+
// Current material name, defaults to "".
|
|
29
|
+
, currentMtl = ""
|
|
30
|
+
// Map from combined position/normal keys to vertex indices.
|
|
31
|
+
, vertexMap = {}
|
|
32
|
+
// Output arrays.
|
|
33
|
+
, vertices = []
|
|
34
|
+
, faces = [];
|
|
35
|
+
|
|
36
|
+
// Process every line.
|
|
37
|
+
for (var i = 0; i < lines.length; i++) {
|
|
38
|
+
var line = lines[i].trim();
|
|
39
|
+
// Skip empty lines and comments.
|
|
40
|
+
if (line === "" || line.charAt(0) === "#")
|
|
41
|
+
continue;
|
|
42
|
+
|
|
43
|
+
var parts = line.split(/\s+/);
|
|
44
|
+
var keyword = parts[0];
|
|
45
|
+
|
|
46
|
+
if (keyword === "v") {
|
|
47
|
+
// Store a vertex position.
|
|
48
|
+
positionRefs[positions.length] = new Set();
|
|
49
|
+
positions.push(new Vec3(
|
|
50
|
+
parseFloat(parts[1]),
|
|
51
|
+
parseFloat(parts[2]),
|
|
52
|
+
parseFloat(parts[3])
|
|
53
|
+
));
|
|
54
|
+
} else if (keyword === "vn") {
|
|
55
|
+
// Store a vertex normal.
|
|
56
|
+
normals.push(new R8G8B8A8_SNORM(
|
|
57
|
+
parseFloat(parts[1]),
|
|
58
|
+
parseFloat(parts[2]),
|
|
59
|
+
parseFloat(parts[3])
|
|
60
|
+
));
|
|
61
|
+
} else if (keyword === "usemtl") {
|
|
62
|
+
// Switch active material.
|
|
63
|
+
currentMtl = parts[1] || "";
|
|
64
|
+
} else if (keyword === "f") {
|
|
65
|
+
// Parse a face definition.
|
|
66
|
+
var faceIndices = [];
|
|
67
|
+
var material = currentMtl;
|
|
68
|
+
|
|
69
|
+
// Loop through each vertex reference in the face.
|
|
70
|
+
for (var j = 1; j < parts.length; j++) {
|
|
71
|
+
var token = parts[j];
|
|
72
|
+
var subParts = token.split("/");
|
|
73
|
+
var posIdx = parseInt(subParts[0], 10);
|
|
74
|
+
var normIdx = null;
|
|
75
|
+
|
|
76
|
+
// Extract normal index when available (third component).
|
|
77
|
+
if (subParts.length >= 3 && subParts[2] !== "")
|
|
78
|
+
normIdx = parseInt(subParts[2], 10);
|
|
79
|
+
|
|
80
|
+
// Resolve negative indices relative to the end of the lists.
|
|
81
|
+
if (posIdx < 0)
|
|
82
|
+
posIdx = positions.length + 1 + posIdx;
|
|
83
|
+
if (normIdx !== null && normIdx < 0)
|
|
84
|
+
normIdx = normals.length + 1 + normIdx;
|
|
85
|
+
|
|
86
|
+
// Build a unique key based on position and normal pair.
|
|
87
|
+
var normKey = normIdx !== null
|
|
88
|
+
? normIdx
|
|
89
|
+
: "none";
|
|
90
|
+
var key = posIdx + "," + normKey;
|
|
91
|
+
|
|
92
|
+
// Add a new vertex if the pair hasn't been seen.
|
|
93
|
+
if (!vertexMap.hasOwnProperty(key)) {
|
|
94
|
+
if (posIdx - 1 > positions.length)
|
|
95
|
+
throw new Error("encountered invalid vertex pos");
|
|
96
|
+
if (normIdx - 1 > normals.length)
|
|
97
|
+
throw new Error("encountered invalid vertex normal");
|
|
98
|
+
|
|
99
|
+
var pos = positions[posIdx - 1]
|
|
100
|
+
, posRef = positionRefs[posIdx - 1]
|
|
101
|
+
, norm = (normIdx !== null && normals[normIdx - 1])
|
|
102
|
+
? normals[normIdx - 1]
|
|
103
|
+
: [0, 1, 0];
|
|
104
|
+
|
|
105
|
+
var vtx = new TriangleMeshVertex(pos, norm);
|
|
106
|
+
vtx.nearby = posRef;
|
|
107
|
+
|
|
108
|
+
vertexMap[key] = vertices.length;
|
|
109
|
+
posRef.add(vtx);
|
|
110
|
+
vertices.push(vtx);
|
|
111
|
+
}
|
|
112
|
+
// Record the index for the face.
|
|
113
|
+
faceIndices.push(vertexMap[key]);
|
|
114
|
+
}
|
|
115
|
+
// Store the face with its material.
|
|
116
|
+
var face = new TriangleMeshFace();
|
|
117
|
+
face.indices = faceIndices;
|
|
118
|
+
face.materialRef = materialBarn.tryAddMaterial(material);
|
|
119
|
+
faces.push(face);
|
|
120
|
+
}
|
|
121
|
+
// Other OBJ keywords (vt, g, o, s, mtllib, etc.) are ignored.
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Return the assembled vertices and indexed faces.
|
|
125
|
+
return { vertices: vertices, faces: faces };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class WavefrontObj extends TriangleMesh {
|
|
129
|
+
constructor() {
|
|
130
|
+
super();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {Buffer} fileBuffer
|
|
135
|
+
*/
|
|
136
|
+
fromFileBuffer(fileBuffer) {
|
|
137
|
+
this.clear();
|
|
138
|
+
|
|
139
|
+
var raw = parseObj(fileBuffer.toString("uit-8"), this.materials);
|
|
140
|
+
|
|
141
|
+
this.vtxBuffer = raw.vertices;
|
|
142
|
+
this.cmdBuffer = raw.faces;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
WavefrontObj
|
|
148
|
+
};
|