@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.
@@ -0,0 +1,466 @@
1
+ /**
2
+ * Simple breadth first chunk allocation.
3
+ *
4
+ * Copyright (c) 2026 That Sky Project
5
+ *
6
+ * This program is released under LGPL 2.1, Refer to LICENSE for further
7
+ * informations.
8
+ *
9
+ * TODO: Reduce the memory usage of the allocation.
10
+ */
11
+
12
+ const { kMaterial } = require("./enums/kMaterial.js");
13
+ const {
14
+ TriangleMeshVertex,
15
+ TriangleMeshFace,
16
+ TriangleMeshMaterial,
17
+ TriangleMesh
18
+ } = require("../formats/triangleMesh.js");
19
+ const {
20
+ LevelGeo,
21
+ LevelGeoMeshVertex,
22
+ LevelGeoMeshVertexMaterial,
23
+ LevelGeoChunk,
24
+ LevelGeoSubchunk
25
+ } = require("./meshes/levelGeo.js");
26
+ const { R8G8B8A8_SNORM, R8G8B8A8_UNORM } = require("../utils/normVec.js");
27
+ const { Vec3 } = require("../utils/vector.js");
28
+
29
+ class LevelCvtAdjacencyVertex extends TriangleMeshVertex {
30
+ /**
31
+ * Create the vertex from another vertex.
32
+ * @param {TriangleMeshVertex} vtx
33
+ */
34
+ constructor(vtx) {
35
+ super();
36
+
37
+ this.normal = vtx.normal;
38
+ this.pos = vtx.pos;
39
+ this.materialRef = TriangleMeshMaterial.Null;
40
+ this.faces = new Set();
41
+ this.nearby = vtx.nearby;
42
+ }
43
+
44
+ /**
45
+ * @param {LevelCvtAdjacencyFace} face
46
+ * @returns {this}
47
+ */
48
+ assign(face) {
49
+ this.materialRef = face.materialRef;
50
+ this.faces.add(face);
51
+ return this;
52
+ }
53
+ }
54
+
55
+ class LevelCvtAdjacencyFace extends TriangleMeshFace {
56
+ /**
57
+ * @param {TriangleMeshFace} face
58
+ */
59
+ constructor(face) {
60
+ super();
61
+
62
+ this.materialRef = face.materialRef;
63
+ this.indices = face.indices.slice(0, 3);
64
+ this.vertices = [];
65
+ }
66
+
67
+ /**
68
+ * Assign vertices from given buffer.
69
+ * @param {LevelCvtAdjacencyVertex[]} vtxBuffer
70
+ * @returns {this}
71
+ */
72
+ from(vtxBuffer) {
73
+ var self = this;
74
+ this.vertices = this.indices.map(function (idx) {
75
+ // Assign a vertex to the face.
76
+ return vtxBuffer[idx].assign(self);
77
+ });
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Check whether the face has a material.
83
+ * @param {TriangleMeshMaterial} material
84
+ * @returns {boolean}
85
+ */
86
+ has(material) {
87
+ return this.vertices.some((vtx) => vtx.materialRef == material);
88
+ }
89
+ }
90
+
91
+ class LevelCvtAdjacencyChunk extends LevelGeoChunk {
92
+ constructor() {
93
+ super();
94
+
95
+ this.vertices = new Map();
96
+ this.activeSubchunks = new Map();
97
+ this.idxBuffer = [];
98
+ this.vtxBuffer = [];
99
+ this.subBuffer = [];
100
+ }
101
+
102
+ /**
103
+ * @param {LevelCvtAdjacencySubchunk} subchunk
104
+ * @param {TriangleMeshMaterial} material
105
+ */
106
+ beginSubchunk(subchunk, material) {
107
+ subchunk.materialId = material.getId() || kMaterial.Cliff;
108
+ subchunk.triangleStart = this.idxBuffer.length / 3;
109
+ subchunk.triangleCount = 1;
110
+ subchunk.vtxStart = 0;
111
+ }
112
+
113
+ /**
114
+ * @param {LevelCvtAdjacencySubchunk} subchunk
115
+ */
116
+ endSubchunk(subchunk) {
117
+ subchunk.triangleEnd = subchunk.triangleStart + subchunk.triangleCount - 1;
118
+ subchunk.vtxCount = this.vtxBuffer.length;
119
+ subchunk.vtxEnd = subchunk.vtxStart + subchunk.vtxCount - 1;
120
+ }
121
+
122
+ /**
123
+ * Mark the chunk as allocated.
124
+ */
125
+ done() {
126
+ for (var subchunk of this.activeSubchunks.values()) {
127
+ this.endSubchunk(subchunk);
128
+ this.subBuffer.push(subchunk);
129
+ }
130
+ this.activeSubchunks.clear();
131
+
132
+ this.idxCount = this.idxBuffer.length;
133
+ this.vtxCount = this.vtxBuffer.length;
134
+ this.subchunkCount = this.subBuffer.length;
135
+
136
+ var min = new Vec3(Infinity, Infinity, Infinity)
137
+ , max = new Vec3(-Infinity, -Infinity, -Infinity);
138
+ for (var vtx of this.vtxBuffer) {
139
+ min.x = Math.min(min.x, vtx.pos.x);
140
+ min.y = Math.min(min.y, vtx.pos.y);
141
+ min.z = Math.min(min.z, vtx.pos.z);
142
+ max.x = Math.max(max.x, vtx.pos.x);
143
+ max.y = Math.max(max.y, vtx.pos.y);
144
+ max.z = Math.max(max.z, vtx.pos.z);
145
+ }
146
+ this.min = min.sub(new Vec3(0.1, 0.1, 0.1));
147
+ this.max = max.add(new Vec3(0.1, 0.1, 0.1));
148
+ }
149
+
150
+ /**
151
+ * TODO: Support multi-material of single vertex.
152
+ * @param {LevelCvtAdjacencyFace} face
153
+ * @returns {boolean}
154
+ */
155
+ tryAssignActiveSubchunk(face) {
156
+ function addMaterial(material) {
157
+ if (!self.activeSubchunks.has(material)) {
158
+ var subchunk = new LevelCvtAdjacencySubchunk();
159
+ self.beginSubchunk(subchunk, material);
160
+ self.activeSubchunks.set(material, subchunk);
161
+ }
162
+ }
163
+
164
+ var self = this;
165
+
166
+ if (this.subBuffer.length + this.activeSubchunks.size > 252)
167
+ return false;
168
+
169
+ // Remove sub-chunks that specify materials not present on the current
170
+ // face from active list.
171
+ for (var m of this.activeSubchunks.keys()) {
172
+ if (
173
+ m != face.vertices[0].materialRef
174
+ && m != face.vertices[1].materialRef
175
+ && m != face.vertices[2].materialRef
176
+ ) {
177
+ var subchunk = this.activeSubchunks.get(m);
178
+ this.endSubchunk(subchunk);
179
+ this.subBuffer.push(subchunk);
180
+ this.activeSubchunks.delete(m);
181
+ } else {
182
+ this.activeSubchunks.get(m).triangleCount++;
183
+ }
184
+ }
185
+
186
+ addMaterial(face.vertices[0].materialRef);
187
+ addMaterial(face.vertices[1].materialRef);
188
+ addMaterial(face.vertices[2].materialRef);
189
+
190
+ return true;
191
+ }
192
+
193
+ /**
194
+ * @param {LevelCvtAdjacencyFace} face
195
+ * @returns {boolean}
196
+ */
197
+ tryAssignActiveSubchunk2(face) {
198
+ function addMaterial(material) {
199
+ if (!self.activeSubchunks.has(material)) {
200
+ var subchunk = new LevelCvtAdjacencySubchunk();
201
+ self.beginSubchunk(subchunk, material);
202
+ self.activeSubchunks.set(material, subchunk);
203
+ }
204
+ }
205
+
206
+ var self = this;
207
+
208
+ // Remove sub-chunks that specify materials not present on the current
209
+ // face from active list.
210
+ for (var m of this.activeSubchunks.keys()) {
211
+ if (
212
+ m == face.vertices[0].materialRef
213
+ || m == face.vertices[1].materialRef
214
+ || m == face.vertices[2].materialRef
215
+ ) {
216
+ this.activeSubchunks.get(m).triangleCount++;
217
+ }
218
+ }
219
+
220
+ addMaterial(face.vertices[0].materialRef);
221
+ addMaterial(face.vertices[1].materialRef);
222
+ addMaterial(face.vertices[2].materialRef);
223
+
224
+ return true;
225
+ }
226
+
227
+ /**
228
+ * @param {LevelCvtAdjacencyFace} face
229
+ * @returns {boolean}
230
+ */
231
+ tryAddFace(face) {
232
+ if (this.idxBuffer.length + 3 > 756)
233
+ return false;
234
+
235
+ var newVtxCount = [];
236
+ for (var vtx of face.vertices)
237
+ if (!this.vertices.has(vtx))
238
+ newVtxCount++;
239
+
240
+ if (this.vtxBuffer.length + newVtxCount > 252)
241
+ return false;
242
+
243
+ if (!this.tryAssignActiveSubchunk(face))
244
+ return false;
245
+
246
+ for (var vtx of face.vertices) {
247
+ var idx;
248
+ if (!this.vertices.has(vtx)) {
249
+ idx = this.vtxBuffer.length;
250
+ this.vertices.set(vtx, idx);
251
+ this.vtxBuffer.push(vtx);
252
+ } else {
253
+ idx = this.vertices.get(vtx);
254
+ }
255
+ this.idxBuffer.push(idx);
256
+ }
257
+
258
+ //this.tryAssignActiveSubchunk2(face);
259
+
260
+ return true;
261
+ }
262
+ }
263
+
264
+ class LevelCvtAdjacencySubchunk extends LevelGeoSubchunk {
265
+ constructor() {
266
+ super();
267
+ }
268
+ }
269
+
270
+ class LevelCvtAdjacency {
271
+ constructor(mesh) {
272
+ // - Mesh data.
273
+ this.meshVtx = new Set();
274
+ this.meshFaces = new Set();
275
+
276
+ // - Conversion data.
277
+ this.vtxBuffer = [];
278
+ this.idxBuffer = [];
279
+ this.chunks = [];
280
+ this.subchunks = [];
281
+ }
282
+
283
+ /**
284
+ * Initialize buffers.
285
+ * @param {TriangleMesh} mesh
286
+ */
287
+ initialize(mesh) {
288
+ var self = this
289
+ , cvtMap = new Map()
290
+ , vtxBuffer;
291
+
292
+ // Set vertex buffer.
293
+ vtxBuffer = mesh.vtxBuffer.map(function (vtx) {
294
+ var cvtVtx = new LevelCvtAdjacencyVertex(vtx);
295
+ cvtMap.set(vtx, cvtVtx);
296
+ return cvtVtx;
297
+ });
298
+ vtxBuffer.forEach(function (vtx) {
299
+ var nearby = new Set();
300
+ vtx.nearby.forEach(function (v) {
301
+ nearby.add(cvtMap.get(v))
302
+ });
303
+ vtx.nearby = nearby;
304
+ });
305
+ this.meshVtx = new Set(vtxBuffer);
306
+
307
+ // Set face buffer.
308
+ this.meshFaces = new Set(mesh.cmdBuffer.map(function (face) {
309
+ // Push vertices and set material of vertices.
310
+ return new LevelCvtAdjacencyFace(face).from(vtxBuffer);
311
+ }));
312
+ }
313
+
314
+ /**
315
+ * Assign vertices into chunks.
316
+ */
317
+ convert() {
318
+ var loop = new Set()
319
+ , unprocessedVtx = new Set(this.meshVtx)
320
+ , visitedFace = new Set()
321
+ , geo = new LevelGeo();
322
+
323
+ loop.add(unprocessedVtx.values().next().value);
324
+
325
+ while (unprocessedVtx.size) {
326
+ var chunk = this.assignChunk(loop, unprocessedVtx, visitedFace);
327
+ //this.assignSubChunk(chunk);
328
+ chunk.done();
329
+ this.chunks.push(chunk);
330
+ }
331
+
332
+ var localIndices = []
333
+ , vertices = []
334
+ , subchunks = [];
335
+
336
+ for (var chunk of this.chunks) {
337
+ chunk.idxStart = localIndices.length;
338
+ chunk.vtxStart = vertices.length;
339
+ chunk.subchunkStart = subchunks.length;
340
+
341
+ localIndices.push(...chunk.idxBuffer);
342
+ vertices.push(...chunk.vtxBuffer.map(function (vtx) {
343
+ var r = new LevelGeoMeshVertex();
344
+ r.pos = vtx.pos;
345
+ r.normal = vtx.normal;
346
+ r.material.setMaterial(vtx.materialRef.getId(), 1);
347
+ r.input2 = new R8G8B8A8_UNORM(0.99, 0.99, 0.99, 0.99);
348
+ r.input3 = new R8G8B8A8_UNORM(0.5, 0.5, 0.5, 0.5);
349
+ r.input4 = new R8G8B8A8_UNORM(0.04, 0.004, 0.004, 0.004);
350
+ return r;
351
+ }));
352
+ subchunks.push(...chunk.subBuffer);
353
+ }
354
+
355
+ geo.cloudChunkCount = 0;
356
+
357
+ geo.indexCount = localIndices.length;
358
+ geo.vertexCount = vertices.length;
359
+ geo.chunkCount = this.chunks.length;
360
+ geo.subchunkCount = subchunks.length;
361
+
362
+ geo.localIndices = localIndices;
363
+ geo.vertices = vertices;
364
+ geo.chunks = this.chunks;
365
+ geo.subchunks = subchunks;
366
+
367
+ return geo;
368
+ }
369
+
370
+ /**
371
+ * @param {Set<LevelCvtAdjacencyVertex>} start
372
+ * @param {Set<LevelCvtAdjacencyVertex>} unprocessedVtx
373
+ * @param {Set<LevelCvtAdjacencyFace>} visitedFace
374
+ * @returns {LevelCvtAdjacencyChunk}
375
+ */
376
+ assignChunk(start, unprocessedVtx, visitedFace, nextLoopVtx) {
377
+ function updateNextLoop(nextLoopVtx, face) {
378
+ unprocessedVtx.has(face.vertices[0]) && nextLoopVtx.add(face.vertices[0]);
379
+ unprocessedVtx.has(face.vertices[1]) && nextLoopVtx.add(face.vertices[1]);
380
+ unprocessedVtx.has(face.vertices[2]) && nextLoopVtx.add(face.vertices[2]);
381
+ }
382
+
383
+ function selectFace(vtx) {
384
+ var faces = new Set();
385
+ for (var v of vtx.nearby)
386
+ for (var f of v.faces)
387
+ faces.add(f);
388
+ return faces;
389
+ }
390
+
391
+ var recursiveVtx = new Set(start)
392
+ , chunk = new LevelCvtAdjacencyChunk()
393
+ , done = false;
394
+
395
+ // The first loop, iterates until the current chunk is fully allocated.
396
+ while (!done) {
397
+ // Record the vertices selected when entering the loop this time.
398
+ var nextLoopVtx = new Set();
399
+
400
+ // The second loop, iterates over the currently selected vertices.
401
+ for (var vtx of recursiveVtx) {
402
+ if (!unprocessedVtx.has(vtx))
403
+ // Skip processed vertices.
404
+ continue;
405
+
406
+ // The vertex is assigned, remove from the set.
407
+ unprocessedVtx.delete(vtx);
408
+
409
+ // The third loop, select all faces associated with a vertex (and all
410
+ // vertices sharing the same coordinates).
411
+ for (var face of selectFace(vtx)) {
412
+ if (visitedFace.has(face))
413
+ // Skip visited faces.
414
+ continue;
415
+
416
+ if (!chunk.tryAddFace(face)) {
417
+ done = true;
418
+ break;
419
+ }
420
+
421
+ // The face is assigned in this chunk allocation.
422
+ visitedFace.add(face);
423
+ // Add unvisited vertices of the face to the next loop.
424
+ updateNextLoop(nextLoopVtx, face);
425
+ }
426
+
427
+ if (done)
428
+ // Break the loop if the chunk is done.
429
+ break;
430
+ }
431
+
432
+ if (!done && !nextLoopVtx.size) {
433
+ // If the chunk is not yet fully allocated but there are no more contiguous
434
+ // vertices available for allocation, select a vertex from a new contiguous
435
+ // patch.
436
+ //
437
+ // If all vertices have already been allocated, return immediately.
438
+ if (!unprocessedVtx.size)
439
+ done = true;
440
+ else
441
+ nextLoopVtx.add(unprocessedVtx.values().next().value);
442
+ }
443
+
444
+ // Update the vertex list for the next iteration.
445
+ recursiveVtx = nextLoopVtx;
446
+ }
447
+
448
+ if (nextLoopVtx.size) {
449
+ // If contiguous vertices remain but the current chunk is fully allocated,
450
+ // record them for the next chunk.
451
+ start.clear();
452
+ for (var v of nextLoopVtx)
453
+ start.add(v);
454
+ }
455
+
456
+ return chunk;
457
+ }
458
+ }
459
+
460
+ module.exports = {
461
+ LevelCvtAdjacencyVertex,
462
+ LevelCvtAdjacencyFace,
463
+ LevelCvtAdjacencyChunk,
464
+ LevelCvtAdjacencySubchunk,
465
+ LevelCvtAdjacency
466
+ };
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ kFieldType: Object.freeze({
3
+ Raw: 0,
4
+ String: 1,
5
+ Ref: 2,
6
+ Array: 3
7
+ })
8
+ };
@@ -0,0 +1,39 @@
1
+ module.exports = {
2
+ kMaterial: Object.freeze({
3
+ None: 0,
4
+ Transparent: 2,
5
+ Void: 3,
6
+ Particle: 4,
7
+ WoodSlippery: 5,
8
+ VoidMinor: 6,
9
+ WoodPlank: 7,
10
+ Cliff: 16,
11
+ Soil: 17,
12
+ CliffLight: 18,
13
+ WallDamaged: 19,
14
+ Wall: 20,
15
+ Gold: 21,
16
+ Glacier: 22,
17
+ TileCeiling: 23,
18
+ TileFloor: 24,
19
+ TileWall: 25,
20
+ WallBrick: 26,
21
+ SoilWet: 27,
22
+ CliffWet: 28,
23
+ Bone: 29,
24
+ Wood: 30,
25
+ Ceramics: 31,
26
+ Sand: 32,
27
+ SandWet: 33,
28
+ SandLight: 34,
29
+ Snow: 35,
30
+ SandDeep: 36,
31
+ Mud: 37,
32
+ Grass: 48,
33
+ GrassWet: 49,
34
+ GrassLight: 50,
35
+ GrassMoss: 51,
36
+ Cloth: 52,
37
+ Cloud: 80,
38
+ })
39
+ };