@drawcall/charta 0.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.
Files changed (71) hide show
  1. package/LICENSE +7 -0
  2. package/dist/assets/loader.d.ts +21 -0
  3. package/dist/assets/loader.d.ts.map +1 -0
  4. package/dist/assets/loader.js +113 -0
  5. package/dist/errors.d.ts +11 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +27 -0
  8. package/dist/grammar.d.ts +29 -0
  9. package/dist/grammar.d.ts.map +1 -0
  10. package/dist/grammar.js +119 -0
  11. package/dist/grass/index.d.ts +25 -0
  12. package/dist/grass/index.d.ts.map +1 -0
  13. package/dist/grass/index.js +177 -0
  14. package/dist/grass/material.d.ts +10 -0
  15. package/dist/grass/material.d.ts.map +1 -0
  16. package/dist/grass/material.js +80 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +12 -0
  20. package/dist/interpreter.d.ts +47 -0
  21. package/dist/interpreter.d.ts.map +1 -0
  22. package/dist/interpreter.js +226 -0
  23. package/dist/locations.d.ts +16 -0
  24. package/dist/locations.d.ts.map +1 -0
  25. package/dist/locations.js +58 -0
  26. package/dist/parser.d.ts +9 -0
  27. package/dist/parser.d.ts.map +1 -0
  28. package/dist/parser.js +47 -0
  29. package/dist/pillars/index.d.ts +7 -0
  30. package/dist/pillars/index.d.ts.map +1 -0
  31. package/dist/pillars/index.js +154 -0
  32. package/dist/pillars/material.d.ts +3 -0
  33. package/dist/pillars/material.d.ts.map +1 -0
  34. package/dist/pillars/material.js +43 -0
  35. package/dist/place/index.d.ts +37 -0
  36. package/dist/place/index.d.ts.map +1 -0
  37. package/dist/place/index.js +216 -0
  38. package/dist/tiles/geometry.d.ts +46 -0
  39. package/dist/tiles/geometry.d.ts.map +1 -0
  40. package/dist/tiles/geometry.js +463 -0
  41. package/dist/tiles/index.d.ts +18 -0
  42. package/dist/tiles/index.d.ts.map +1 -0
  43. package/dist/tiles/index.js +121 -0
  44. package/dist/tiles/material.d.ts +6 -0
  45. package/dist/tiles/material.d.ts.map +1 -0
  46. package/dist/tiles/material.js +88 -0
  47. package/dist/utils/instanced-mesh-group.d.ts +17 -0
  48. package/dist/utils/instanced-mesh-group.d.ts.map +1 -0
  49. package/dist/utils/instanced-mesh-group.js +59 -0
  50. package/dist/utils/random.d.ts +4 -0
  51. package/dist/utils/random.d.ts.map +1 -0
  52. package/dist/utils/random.js +19 -0
  53. package/dist/utils/texture.d.ts +3 -0
  54. package/dist/utils/texture.d.ts.map +1 -0
  55. package/dist/utils/texture.js +30 -0
  56. package/dist/walls/index.d.ts +87 -0
  57. package/dist/walls/index.d.ts.map +1 -0
  58. package/dist/walls/index.js +376 -0
  59. package/dist/walls/material.d.ts +3 -0
  60. package/dist/walls/material.d.ts.map +1 -0
  61. package/dist/walls/material.js +67 -0
  62. package/dist/water/index.d.ts +10 -0
  63. package/dist/water/index.d.ts.map +1 -0
  64. package/dist/water/index.js +46 -0
  65. package/dist/water/material.d.ts +5 -0
  66. package/dist/water/material.d.ts.map +1 -0
  67. package/dist/water/material.js +46 -0
  68. package/dist/water/texture.d.ts +15 -0
  69. package/dist/water/texture.d.ts.map +1 -0
  70. package/dist/water/texture.js +201 -0
  71. package/package.json +39 -0
@@ -0,0 +1,463 @@
1
+ import { BufferGeometry, Float32BufferAttribute, Uint16BufferAttribute, } from 'three';
2
+ import { isLayerConnectedToWall } from '../walls/index.js';
3
+ const MAX_AUTO_CONNECT_HEIGHT_DIFF_RATIO = 1 / 4;
4
+ const MAX_CONNECTION_HEIGHT_DIFF_RATIO = 8;
5
+ function isBetterConnection(newConnection, oldConnection, tileSize) {
6
+ const getDistance = (c) => Math.abs(c[0].y - c[1].y);
7
+ const distNew = Math.max(tileSize, getDistance(newConnection));
8
+ const distOld = Math.max(tileSize, getDistance(oldConnection));
9
+ if (distNew !== distOld)
10
+ return distNew < distOld;
11
+ const sameTypeNew = newConnection[0].type === newConnection[1].type;
12
+ const sameTypeOld = oldConnection[0].type === oldConnection[1].type;
13
+ if (sameTypeNew !== sameTypeOld)
14
+ return sameTypeNew;
15
+ return getDistance(newConnection) < getDistance(oldConnection);
16
+ }
17
+ function isBlockedByWall(t1, t2, walls, tileSize) {
18
+ const closestWall1 = getClosestConnectedWall(t1, walls, tileSize);
19
+ const closestWall2 = getClosestConnectedWall(t2, walls, tileSize);
20
+ if (closestWall1 != null && closestWall2 != null) {
21
+ //both not null
22
+ if (closestWall1 != closestWall2) {
23
+ //if not connected to the same wall -> blocked
24
+ return true;
25
+ }
26
+ //block if not connected to the same part
27
+ return getClosestWallPart(closestWall1, t1) != getClosestWallPart(closestWall2, t2);
28
+ }
29
+ if (closestWall1 === closestWall2) {
30
+ //both null -> not blocked
31
+ return false;
32
+ }
33
+ //exactly one is null
34
+ const closestWall = closestWall1 ?? closestWall2;
35
+ //check if for the wall either t1 or t2 is connected to that the other tile is also rather connected to that part
36
+ if (getClosestWallPart(closestWall, t1) != getClosestWallPart(closestWall, t2)) {
37
+ return true;
38
+ }
39
+ //we differentiate between top and bottom connected walls since top connected walls tend to be human-made and therefore we block based on the texture id
40
+ if (getClosestWallPart(closestWall, t1) === 'bottomY') {
41
+ return false;
42
+ }
43
+ if (t1.textureId != t2.textureId) {
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+ function getClosestConnectedWall(tile, walls, tileSize) {
49
+ let distance = Infinity;
50
+ let result;
51
+ for (const wall of walls) {
52
+ if (!isLayerConnectedToWall(wall.topY, wall.bottomY, tile.y, tileSize)) {
53
+ continue;
54
+ }
55
+ let newDistance = Math.min(Math.abs(tile.y - wall.topY), Math.abs(tile.y - wall.bottomY));
56
+ if (newDistance < distance) {
57
+ result = wall;
58
+ distance = newDistance;
59
+ }
60
+ }
61
+ return result;
62
+ }
63
+ function getClosestWallPart(wall, tile) {
64
+ return Math.abs(wall.bottomY - tile.y) < Math.abs(wall.topY - tile.y) ? 'bottomY' : 'topY';
65
+ }
66
+ function isBlockingWall(wall, tile, tileSize) {
67
+ return isLayerConnectedToWall(wall.topY, wall.bottomY, tile.y, tileSize);
68
+ }
69
+ function getRelevantWallIndices(tile, vertexX, vertexZ, cellSizeX, cellSizeZ) {
70
+ const centerX = computeMeter('x', tile.cellIndexX, cellSizeX);
71
+ const centerZ = computeMeter('z', tile.cellIndexZ, cellSizeZ);
72
+ const dx = vertexX - centerX;
73
+ const dz = vertexZ - centerZ;
74
+ const eps = 1e-3;
75
+ const indices = [];
76
+ if (dz < -cellSizeZ * 0.25 + eps)
77
+ indices.push(0); // Top
78
+ if (dx > cellSizeX * 0.25 - eps)
79
+ indices.push(1); // Right
80
+ if (dz > cellSizeZ * 0.25 - eps)
81
+ indices.push(2); // Bottom
82
+ if (dx < -cellSizeX * 0.25 + eps)
83
+ indices.push(3); // Left
84
+ return indices;
85
+ }
86
+ function computeConnections(cell1, cell2, walls, tileSize) {
87
+ const connectedTiles = new Set();
88
+ const result = [];
89
+ while (true) {
90
+ let bestPotentialConnection;
91
+ for (const tile1 of cell1) {
92
+ for (const tile2 of cell2) {
93
+ if (result.some(([existingT1, existingT2]) => existingT1 === tile1 && existingT2 === tile2)) {
94
+ continue;
95
+ }
96
+ if (isBlockedByWall(tile1, tile2, walls, tileSize)) {
97
+ continue;
98
+ }
99
+ const heightDiff = Math.abs(tile1.y - tile2.y);
100
+ if (heightDiff > tileSize * MAX_AUTO_CONNECT_HEIGHT_DIFF_RATIO || tile1.type !== tile2.type) {
101
+ if (connectedTiles.has(tile1) || connectedTiles.has(tile2)) {
102
+ continue;
103
+ }
104
+ }
105
+ const newConnection = [tile1, tile2];
106
+ if (bestPotentialConnection != null && !isBetterConnection(newConnection, bestPotentialConnection, tileSize)) {
107
+ continue;
108
+ }
109
+ bestPotentialConnection = [tile1, tile2];
110
+ }
111
+ }
112
+ if (bestPotentialConnection == null ||
113
+ Math.abs(bestPotentialConnection[0].y - bestPotentialConnection[1].y) >=
114
+ MAX_CONNECTION_HEIGHT_DIFF_RATIO * tileSize) {
115
+ break;
116
+ }
117
+ connectedTiles.add(bestPotentialConnection[0]);
118
+ connectedTiles.add(bestPotentialConnection[1]);
119
+ result.push(bestPotentialConnection);
120
+ }
121
+ return result;
122
+ }
123
+ function computeConnectedTiles(originTile, connections, filter) {
124
+ // Build Adjacency List for O(1) lookup
125
+ const adjacency = new Map();
126
+ for (const [t1, t2] of connections) {
127
+ if (!adjacency.has(t1))
128
+ adjacency.set(t1, []);
129
+ if (!adjacency.has(t2))
130
+ adjacency.set(t2, []);
131
+ adjacency.get(t1).push(t2);
132
+ adjacency.get(t2).push(t1);
133
+ }
134
+ const visited = new Set([originTile]);
135
+ const queue = [originTile];
136
+ while (queue.length > 0) {
137
+ const current = queue.shift();
138
+ const neighbors = adjacency.get(current);
139
+ if (neighbors == null)
140
+ continue;
141
+ for (const neighbor of neighbors) {
142
+ if (visited.has(neighbor)) {
143
+ continue;
144
+ }
145
+ if (filter?.(neighbor) === false) {
146
+ continue;
147
+ }
148
+ visited.add(neighbor);
149
+ queue.push(neighbor);
150
+ }
151
+ }
152
+ return Array.from(visited);
153
+ }
154
+ const directions = {
155
+ Top: 0,
156
+ TopRight: 1,
157
+ Right: 2,
158
+ BottomRight: 3,
159
+ Bottom: 4,
160
+ BottomLeft: 5,
161
+ Left: 6,
162
+ TopLeft: 7,
163
+ };
164
+ const directionXConnectionIndexOffsetMap = {
165
+ [directions.Top]: [],
166
+ [directions.TopRight]: [
167
+ [0, -1],
168
+ [0, 0],
169
+ ],
170
+ [directions.Right]: [[0, 0]],
171
+ [directions.BottomRight]: [
172
+ [0, 0],
173
+ [0, 1],
174
+ ],
175
+ [directions.Bottom]: [],
176
+ [directions.BottomLeft]: [
177
+ [-1, 0],
178
+ [-1, 1],
179
+ ],
180
+ [directions.Left]: [[-1, 0]],
181
+ [directions.TopLeft]: [
182
+ [-1, -1],
183
+ [-1, 0],
184
+ ],
185
+ };
186
+ const directionZConnectionIndexOffsetMap = {
187
+ [directions.Top]: [[0, -1]],
188
+ [directions.TopRight]: [
189
+ [0, -1],
190
+ [1, -1],
191
+ ],
192
+ [directions.Right]: [],
193
+ [directions.BottomRight]: [
194
+ [0, 0],
195
+ [1, 0],
196
+ ],
197
+ [directions.Bottom]: [[0, 0]],
198
+ [directions.BottomLeft]: [
199
+ [0, 0],
200
+ [-1, 0],
201
+ ],
202
+ [directions.Left]: [],
203
+ [directions.TopLeft]: [
204
+ [0, -1],
205
+ [-1, -1],
206
+ ],
207
+ };
208
+ const directionXOffsetMap = {
209
+ [directions.Top]: 0,
210
+ [directions.TopRight]: 0.5,
211
+ [directions.Right]: 0.5,
212
+ [directions.BottomRight]: 0.5,
213
+ [directions.Bottom]: 0,
214
+ [directions.BottomLeft]: -0.5,
215
+ [directions.Left]: -0.5,
216
+ [directions.TopLeft]: -0.5,
217
+ };
218
+ const directionZOffsetMap = {
219
+ [directions.Top]: -0.5,
220
+ [directions.TopRight]: -0.5,
221
+ [directions.Right]: 0,
222
+ [directions.BottomRight]: 0.5,
223
+ [directions.Bottom]: 0.5,
224
+ [directions.BottomLeft]: 0.5,
225
+ [directions.Left]: 0,
226
+ [directions.TopLeft]: -0.5,
227
+ };
228
+ const wallDirections = ['top', 'right', 'bottom', 'left'];
229
+ function computeMeter(type, cellIndex, cellSize, direction) {
230
+ const offset = direction == null ? 0 : (type === 'x' ? directionXOffsetMap : directionZOffsetMap)[direction];
231
+ return cellSize * (cellIndex + 0.5 + offset);
232
+ }
233
+ export class TilesGeometry extends BufferGeometry {
234
+ tiles;
235
+ walls;
236
+ mapSizeX;
237
+ mapSizeZ;
238
+ cellSizeX;
239
+ cellSizeZ;
240
+ vertices;
241
+ positions = [];
242
+ uvs = [];
243
+ textureStrengths = [];
244
+ idCounter = 0;
245
+ xConnections;
246
+ zConnections;
247
+ constructor(tiles, walls, mapSizeX, mapSizeZ) {
248
+ super();
249
+ this.tiles = tiles;
250
+ this.walls = walls;
251
+ this.mapSizeX = mapSizeX;
252
+ this.mapSizeZ = mapSizeZ;
253
+ this.cellSizeZ = mapSizeZ / tiles.length;
254
+ this.cellSizeX = mapSizeX / tiles[0].length;
255
+ this.zConnections = new Array(tiles.length - 1)
256
+ .fill(undefined)
257
+ .map((_, z) => new Array(tiles[0].length)
258
+ .fill(undefined)
259
+ .map((_, x) => computeConnections(tiles[z][x], tiles[z + 1][x], [...(walls[z]?.[x]?.bottom ?? []), ...(walls[z + 1]?.[x]?.top ?? [])], this.cellSizeZ)));
260
+ this.xConnections = new Array(tiles.length)
261
+ .fill(undefined)
262
+ .map((_, z) => new Array(tiles[0].length - 1)
263
+ .fill(undefined)
264
+ .map((_, x) => computeConnections(tiles[z][x], tiles[z][x + 1], [...(walls[z]?.[x]?.right ?? []), ...(walls[z]?.[x + 1]?.left ?? [])], this.cellSizeX)));
265
+ this.vertices = new Array(tiles.length * 2 + 1)
266
+ .fill(undefined)
267
+ .map(() => new Array(tiles[0].length * 2 + 1).fill(undefined).map(() => []));
268
+ const indices = [];
269
+ for (let tileIZ = 0; tileIZ < tiles.length; tileIZ++) {
270
+ const tileRow = tiles[tileIZ];
271
+ for (let tileIX = 0; tileIX < tileRow.length; tileIX++) {
272
+ const tileStack = tileRow[tileIX];
273
+ for (let tileIY = 0; tileIY < tileStack.length; tileIY++) {
274
+ const centerVertexId = this.addVertex(tileIX, tileIZ, undefined, tileStack[tileIY]);
275
+ for (let i = 0; i < 8; i += 2) {
276
+ const a = centerVertexId;
277
+ const b = this.getConnectionVertexId(tileIX, tileIY, tileIZ, (i + 0));
278
+ const c = this.getConnectionVertexId(tileIX, tileIY, tileIZ, (i + 1));
279
+ const d = this.getConnectionVertexId(tileIX, tileIY, tileIZ, ((i + 2) % 8));
280
+ // First triangle
281
+ indices.push(a, c, b);
282
+ // Second triangle
283
+ indices.push(a, d, c);
284
+ }
285
+ }
286
+ }
287
+ }
288
+ // Set geometry attributes
289
+ this.setAttribute('position', new Float32BufferAttribute(this.positions, 3));
290
+ this.setAttribute('uv', new Float32BufferAttribute(this.uvs, 2));
291
+ this.setAttribute('textureStrength', new Float32BufferAttribute(this.textureStrengths, 16));
292
+ this.setIndex(new Uint16BufferAttribute(indices, 1));
293
+ this.computeVertexNormals();
294
+ this.computeTangents();
295
+ }
296
+ /**
297
+ * @param xIndex tile grid x index
298
+ * @param zIndex tile grid z index
299
+ * @param stackIndex index in the tile stack
300
+ * @param offsetX world unit offset from tile center X
301
+ * @param offsetZ world unit offset from tile center Z
302
+ * @param normalTarget optional target vector that will receive the interpolated face normal at the queried point
303
+ */
304
+ getHeight(xIndex, zIndex, stackIndex, offsetX, offsetZ, normalTarget) {
305
+ if (xIndex < 0 || xIndex >= this.tiles[0].length || zIndex < 0 || zIndex >= this.tiles.length) {
306
+ throw new Error(`Tile index out of bounds: (${xIndex}, ${zIndex}). Grid size: ${this.tiles[0].length}x${this.tiles.length}`);
307
+ }
308
+ const vertices = this.getVertices(xIndex, zIndex);
309
+ const vertex1 = vertices[stackIndex];
310
+ if (vertex1 == null) {
311
+ throw new Error(`No vertex found at stack index ${stackIndex} for tile (${xIndex}, ${zIndex}).`);
312
+ }
313
+ const centerX = computeMeter('x', xIndex, this.cellSizeX) - this.mapSizeX / 2;
314
+ const centerZ = computeMeter('z', zIndex, this.cellSizeZ) - this.mapSizeZ / 2;
315
+ const worldX = centerX + offsetX;
316
+ const worldZ = centerZ + offsetZ;
317
+ let tileOffsetX = offsetX;
318
+ let tileOffsetZ = offsetZ;
319
+ const l1Norm = Math.abs(tileOffsetX) + Math.abs(tileOffsetZ);
320
+ if (l1Norm > 0) {
321
+ tileOffsetX /= l1Norm;
322
+ tileOffsetZ /= l1Norm;
323
+ }
324
+ let triangleIndex = Math.abs(tileOffsetX) > Math.abs(tileOffsetZ) ? 1 : 0;
325
+ if (tileOffsetZ > 0) {
326
+ triangleIndex = 3 - triangleIndex;
327
+ }
328
+ if (tileOffsetX < 0) {
329
+ triangleIndex = 7 - triangleIndex;
330
+ }
331
+ const directionVertex2 = triangleIndex;
332
+ const directionVertex3 = ((triangleIndex + 1) % 8);
333
+ const tile = vertex1.relatedTiles[0];
334
+ const vertex2 = this.getVertices(xIndex, zIndex, directionVertex2).find((vertex) => vertex.relatedTiles.includes(tile));
335
+ const vertex3 = this.getVertices(xIndex, zIndex, directionVertex3).find((vertex) => vertex.relatedTiles.includes(tile));
336
+ if (vertex2 == null || vertex3 == null) {
337
+ throw new Error(`Failed to find vertices for triangle at tile (${xIndex}, ${zIndex}) stack ${stackIndex} direction ${triangleIndex}.`);
338
+ }
339
+ const [x1, y1_pos, z1] = vertex1.position;
340
+ const [x2, y2_pos, z2] = vertex2.position;
341
+ const [x3, y3_pos, z3] = vertex3.position;
342
+ const denom = (z2 - z3) * (x1 - x3) + (x3 - x2) * (z1 - z3);
343
+ let w1 = ((z2 - z3) * (worldX - x3) + (x3 - x2) * (worldZ - z3)) / denom;
344
+ let w2 = ((z3 - z1) * (worldX - x3) + (x1 - x3) * (worldZ - z3)) / denom;
345
+ // Handle potential precision issues or 0 denom (degenerate triangles shouldn't happen but safe to check?)
346
+ if (!Number.isFinite(w1))
347
+ w1 = 0;
348
+ if (!Number.isFinite(w2))
349
+ w2 = 0;
350
+ const w3 = 1 - w1 - w2;
351
+ if (normalTarget != null) {
352
+ const ux = x2 - x1;
353
+ const uy = y2_pos - y1_pos;
354
+ const uz = z2 - z1;
355
+ const vx = x3 - x1;
356
+ const vy = y3_pos - y1_pos;
357
+ const vz = z3 - z1;
358
+ const nx = uy * vz - uz * vy;
359
+ const ny = uz * vx - ux * vz;
360
+ const nz = ux * vy - uy * vx;
361
+ const lenSq = nx * nx + ny * ny + nz * nz;
362
+ if (lenSq > 0) {
363
+ normalTarget.set(nx, ny, nz).normalize().negate();
364
+ }
365
+ }
366
+ return w1 * y1_pos + w2 * y2_pos + w3 * y3_pos;
367
+ }
368
+ addVertex(cellIndexX, cellIndexZ, direction, tile, connections) {
369
+ const id = this.idCounter++;
370
+ const xMeter = computeMeter('x', cellIndexX, this.cellSizeX, direction);
371
+ const zMeter = computeMeter('z', cellIndexZ, this.cellSizeZ, direction);
372
+ let filteredRelatedTiles;
373
+ let relatedTiles;
374
+ if (connections == null) {
375
+ filteredRelatedTiles = [tile];
376
+ relatedTiles = [tile];
377
+ }
378
+ else {
379
+ relatedTiles = computeConnectedTiles(tile, connections);
380
+ const removeCoordinateSet = new Set();
381
+ let blockingTile;
382
+ for (const relatedTile of relatedTiles) {
383
+ const tileWalls = this.walls[relatedTile.cellIndexZ]?.[relatedTile.cellIndexX];
384
+ if (!tileWalls)
385
+ continue;
386
+ const relevantIndices = getRelevantWallIndices(relatedTile, xMeter, zMeter, this.cellSizeX, this.cellSizeZ);
387
+ let causesBlocking = false;
388
+ for (const i of relevantIndices) {
389
+ const dir = wallDirections[i];
390
+ const wallSize = i % 2 === 0 ? this.cellSizeZ : this.cellSizeX;
391
+ const hasBlockingWall = tileWalls[dir].some((wall) => isBlockingWall(wall, relatedTile, wallSize));
392
+ if (hasBlockingWall) {
393
+ causesBlocking = true;
394
+ // Remove neighbor in direction i
395
+ const nx = relatedTile.cellIndexX + directionXOffsetMap[(i * 2)] * 2;
396
+ const nz = relatedTile.cellIndexZ + directionZOffsetMap[(i * 2)] * 2;
397
+ removeCoordinateSet.add(`${nx},${nz}`);
398
+ // Check secondary blocking (diagonal)
399
+ const nextI = (i + 1) % 4;
400
+ const nextDir = wallDirections[nextI];
401
+ const nextWallSize = nextI % 2 === 0 ? this.cellSizeZ : this.cellSizeX;
402
+ const hasNextBlockingWall = tileWalls[nextDir].some((wall) => isBlockingWall(wall, relatedTile, nextWallSize));
403
+ if (hasNextBlockingWall) {
404
+ const diagDir = (i * 2 + 1);
405
+ const ndx = relatedTile.cellIndexX + directionXOffsetMap[diagDir] * 2;
406
+ const ndz = relatedTile.cellIndexZ + directionZOffsetMap[diagDir] * 2;
407
+ removeCoordinateSet.add(`${ndx},${ndz}`);
408
+ }
409
+ }
410
+ }
411
+ if (causesBlocking && blockingTile == null) {
412
+ blockingTile = relatedTile;
413
+ }
414
+ }
415
+ filteredRelatedTiles = computeConnectedTiles(blockingTile ?? tile, connections, (newTile) => !removeCoordinateSet.has(`${newTile.cellIndexX},${newTile.cellIndexZ}`));
416
+ }
417
+ const yMeter = filteredRelatedTiles.length === 0
418
+ ? tile.y
419
+ : filteredRelatedTiles.reduce((prev, t) => prev + t.y, 0) / filteredRelatedTiles.length;
420
+ const textureStrength = new Array(16).fill(0);
421
+ const strength = relatedTiles.length === 1 ? 1 : 0.5;
422
+ for (const { textureId } of relatedTiles) {
423
+ textureStrength[textureId] = strength;
424
+ }
425
+ this.textureStrengths.push(...textureStrength);
426
+ const position = [xMeter - this.mapSizeX * 0.5, yMeter, zMeter - this.mapSizeZ * 0.5];
427
+ this.positions.push(...position);
428
+ const uv = [xMeter, zMeter];
429
+ this.uvs.push(...uv);
430
+ this.getVertices(cellIndexX, cellIndexZ, direction).push({
431
+ position,
432
+ textureStrength,
433
+ uv,
434
+ relatedTiles,
435
+ id,
436
+ });
437
+ return id;
438
+ }
439
+ getVertices(cellIndexX, cellIndexZ, direction) {
440
+ let vertexIndexX = cellIndexX * 2 + 1;
441
+ let vertexIndexZ = cellIndexZ * 2 + 1;
442
+ if (direction != null) {
443
+ vertexIndexX += directionXOffsetMap[direction] * 2;
444
+ vertexIndexZ += directionZOffsetMap[direction] * 2;
445
+ }
446
+ return this.vertices[vertexIndexZ][vertexIndexX];
447
+ }
448
+ getConnectionVertexId(cellIndexX, cellIndeY, cellIndexZ, direction) {
449
+ const tile = this.tiles[cellIndexZ][cellIndexX][cellIndeY];
450
+ let vertexId = this.getVertices(cellIndexX, cellIndexZ, direction).find(({ relatedTiles: connectedTiles }) => connectedTiles.includes(tile))?.id;
451
+ if (vertexId == null) {
452
+ const connections = [];
453
+ for (const [xOffset, zOffset] of directionXConnectionIndexOffsetMap[direction]) {
454
+ connections.push(...(this.xConnections[zOffset + cellIndexZ]?.[xOffset + cellIndexX] ?? []));
455
+ }
456
+ for (const [xOffset, zOffset] of directionZConnectionIndexOffsetMap[direction]) {
457
+ connections.push(...(this.zConnections[zOffset + cellIndexZ]?.[xOffset + cellIndexX] ?? []));
458
+ }
459
+ vertexId = this.addVertex(cellIndexX, cellIndexZ, direction, tile, connections);
460
+ }
461
+ return vertexId;
462
+ }
463
+ }
@@ -0,0 +1,18 @@
1
+ import { Material, Mesh } from 'three';
2
+ import { Interpreter } from '../interpreter.js';
3
+ import { coerce } from 'zod';
4
+ export declare const groundSchema: import("zod").ZodObject<{
5
+ texture: import("zod").ZodString;
6
+ y: coerce.ZodCoercedNumber<unknown>;
7
+ }, import("zod/v4/core").$strip>;
8
+ export declare const ceilingSchema: import("zod").ZodObject<{
9
+ texture: import("zod").ZodString;
10
+ y: coerce.ZodCoercedNumber<unknown>;
11
+ }, import("zod/v4/core").$strip>;
12
+ export type TilesMeshOptions = {
13
+ materialSharpness?: number;
14
+ };
15
+ export declare class TilesMesh extends Mesh {
16
+ constructor(interpreter: Interpreter, material?: Material, options?: TilesMeshOptions);
17
+ }
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tiles/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,IAAI,EAOL,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAkB,MAAM,KAAK,CAAA;AAO5C,eAAO,MAAM,YAAY;;;gCAAoD,CAAA;AAC7E,eAAO,MAAM,aAAa;;;gCAAoD,CAAA;AAE9E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAyCD,qBAAa,SAAU,SAAQ,IAAI;gBACrB,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC,EAAE,OAAO,GAAE,gBAAqB;CA6GnH"}
@@ -0,0 +1,121 @@
1
+ import { Mesh, Texture, MeshBasicMaterial, } from 'three';
2
+ import { coerce, object, string } from 'zod';
3
+ import { TilesGeometry } from './geometry.js';
4
+ import { buildTilesMaterial } from './material.js';
5
+ import { ChartaError } from '../errors.js';
6
+ import { wallSchema } from '../walls/index.js';
7
+ import { buildTextureArrayFromAssets } from '../utils/texture.js';
8
+ export const groundSchema = object({ texture: string(), y: coerce.number() });
9
+ export const ceilingSchema = object({ texture: string(), y: coerce.number() });
10
+ function resolveHeights(position, items, interpreter, reverse) {
11
+ items = [...items];
12
+ if (reverse) {
13
+ items.reverse();
14
+ }
15
+ let lastY;
16
+ const result = [...items].map((item) => {
17
+ if (item.layerY != null) {
18
+ lastY = item.layerY;
19
+ return item.layerY;
20
+ }
21
+ if (item.explicitWallY != null) {
22
+ return item.explicitWallY;
23
+ }
24
+ if (lastY == null) {
25
+ interpreter.reportError(new ChartaError(`Wall at ${position.join('/')}: ${reverse ? 'missing topY and no susequent layer' : 'missing bottomY and no preceding layer'}`, interpreter.getSource(), item.loc));
26
+ return undefined;
27
+ }
28
+ return lastY;
29
+ });
30
+ if (reverse) {
31
+ result.reverse();
32
+ }
33
+ return result;
34
+ }
35
+ export class TilesMesh extends Mesh {
36
+ constructor(interpreter, material = new MeshBasicMaterial(), options = {}) {
37
+ super();
38
+ const rows = interpreter.getRows();
39
+ const cols = interpreter.getCols();
40
+ // Helper to manage texture indices on the fly
41
+ const textureIndex = new Map();
42
+ const getTextureIndex = (name, loc) => {
43
+ const texture = interpreter.getAsset(Texture, `${name}BaseColorTexture`, loc);
44
+ if (!texture)
45
+ return 0;
46
+ let idx = textureIndex.get(texture);
47
+ if (idx == null) {
48
+ idx = textureIndex.size;
49
+ textureIndex.set(texture, idx);
50
+ }
51
+ return idx;
52
+ };
53
+ // Initialize grid structures
54
+ const tiles = Array.from({ length: rows }, () => Array.from({ length: cols }, () => []));
55
+ const walls = Array.from({ length: rows }, () => Array.from({ length: cols }, () => ({
56
+ top: [],
57
+ bottom: [],
58
+ left: [],
59
+ right: [],
60
+ })));
61
+ for (let z = 0; z < rows; z++) {
62
+ for (let x = 0; x < cols; x++) {
63
+ // 1. Collect all entries for this cell
64
+ const calls = Array.from(interpreter.getCalls([z, x], {
65
+ ground: groundSchema,
66
+ ceiling: ceilingSchema,
67
+ wall: wallSchema,
68
+ }));
69
+ // 2. Compute wall heights using the helper
70
+ const bottomYs = resolveHeights([z, x], calls.map(([_1, parsed, _2, loc]) => ({
71
+ loc,
72
+ layerY: 'y' in parsed ? parsed.y : undefined,
73
+ explicitWallY: 'bottomY' in parsed ? parsed.bottomY : undefined,
74
+ })), interpreter, false);
75
+ const topYs = resolveHeights([z, x], calls.map(([_1, parsed, _2, loc]) => ({
76
+ loc,
77
+ layerY: 'y' in parsed ? parsed.y : undefined,
78
+ explicitWallY: 'topY' in parsed ? parsed.topY : undefined,
79
+ })), interpreter, true);
80
+ // 3. Populate data structures
81
+ for (let i = 0; i < calls.length; i++) {
82
+ const [name, parsed, _, loc] = calls[i];
83
+ if (name === 'wall') {
84
+ let bottomY = bottomYs[i];
85
+ let topY = topYs[i];
86
+ if (bottomY === undefined || topY === undefined)
87
+ continue;
88
+ // Normalize heights so bottom is numerically lower
89
+ if (topY < bottomY) {
90
+ ;
91
+ [bottomY, topY] = [topY, bottomY];
92
+ }
93
+ const dir = parsed.dir;
94
+ if (walls[z][x][dir]) {
95
+ walls[z][x][dir].push({ bottomY, topY });
96
+ }
97
+ }
98
+ else {
99
+ // Ground or Ceiling
100
+ tiles[z][x].push({
101
+ type: name,
102
+ y: parsed.y,
103
+ textureId: getTextureIndex(parsed.texture, loc),
104
+ cellIndexX: x,
105
+ cellIndexZ: z,
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ const cellSize = interpreter.getCellSize();
112
+ const geometry = new TilesGeometry(tiles, walls, cols * cellSize, rows * cellSize);
113
+ this.geometry = geometry;
114
+ interpreter.setAsset('tilesGeometry', geometry);
115
+ // Build texture array from interpreter assets using encountered material names in insertion order
116
+ const texturesInOrder = Array.from(textureIndex.keys()).sort((a, b) => textureIndex.get(a) - textureIndex.get(b));
117
+ if (texturesInOrder.length > 0) {
118
+ this.material = buildTilesMaterial(material, buildTextureArrayFromAssets(texturesInOrder));
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,6 @@
1
+ import { DataArrayTexture, Material } from "three";
2
+ /**
3
+ * Modifies an existing Three.js material to support texture array blending based on textureStrength
4
+ */
5
+ export declare function buildTilesMaterial<T extends Material>(material: T, textureArray: DataArrayTexture): T;
6
+ //# sourceMappingURL=material.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material.d.ts","sourceRoot":"","sources":["../../src/tiles/material.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEnD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,QAAQ,EACnD,QAAQ,EAAE,CAAC,EACX,YAAY,EAAE,gBAAgB,GAC7B,CAAC,CAoGH"}