@adatechnology/game-terrain-three 0.0.0-stage → 0.1.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 +21 -0
- package/README.md +20 -2
- package/dist/index.d.ts +50 -0
- package/dist/index.global.js +682 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +652 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -3
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var GameTerrainThree = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
TERRAIN_AREA_GROUND: () => TERRAIN_AREA_GROUND,
|
|
25
|
+
TERRAIN_STRIPS: () => TERRAIN_STRIPS,
|
|
26
|
+
createSurfaceMaterial: () => createSurfaceMaterial,
|
|
27
|
+
createTerrainRenderer: () => createTerrainRenderer,
|
|
28
|
+
createTerrainTestModule: () => createTerrainTestModule
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// ../game-contracts/dist/index.js
|
|
32
|
+
var KIT_ORIGIN_KEY = "kitOrigin";
|
|
33
|
+
function markKitOrigin(object, origin) {
|
|
34
|
+
const node = object;
|
|
35
|
+
node.userData = node.userData ?? {};
|
|
36
|
+
node.userData[KIT_ORIGIN_KEY] = { ...origin };
|
|
37
|
+
return object;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ../game-terrain/dist/index.js
|
|
41
|
+
var TERRAIN_PATTERNS = ["plain", "grass", "soil", "sand", "asphalt", "concrete", "tile", "planks", "stone"];
|
|
42
|
+
|
|
43
|
+
// src/surface-material.ts
|
|
44
|
+
var patternIndex = (pattern) => Math.max(0, TERRAIN_PATTERNS.indexOf(pattern ?? "plain"));
|
|
45
|
+
var SURFACE_GLSL = (
|
|
46
|
+
/* glsl */
|
|
47
|
+
`
|
|
48
|
+
float tsHash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
49
|
+
float tsNoise(vec2 p) {
|
|
50
|
+
vec2 i = floor(p); vec2 f = fract(p); f = f * f * (3.0 - 2.0 * f);
|
|
51
|
+
return mix(mix(tsHash(i), tsHash(i + vec2(1.0, 0.0)), f.x), mix(tsHash(i + vec2(0.0, 1.0)), tsHash(i + vec2(1.0, 1.0)), f.x), f.y);
|
|
52
|
+
}
|
|
53
|
+
float tsFbm(vec2 p) { return tsNoise(p) * 0.55 + tsNoise(p * 2.3 + 7.1) * 0.3 + tsNoise(p * 5.1 + 3.3) * 0.15; }
|
|
54
|
+
float tsLine(float value, float period, float width) { float d = abs(fract(value / period + 0.5) - 0.5) * period; return 1.0 - smoothstep(width * 0.6, width, d); }
|
|
55
|
+
vec3 terrainSurface(vec3 base, vec2 xz, float kind) {
|
|
56
|
+
float k = floor(kind + 0.5);
|
|
57
|
+
if (k < 0.5) return base * (0.86 + 0.28 * tsFbm(xz * 9.0));
|
|
58
|
+
if (k < 1.5) {
|
|
59
|
+
float blades = tsNoise(xz * vec2(70.0, 55.0)) * 0.6 + tsNoise(xz * 22.0) * 0.4;
|
|
60
|
+
float patches = tsFbm(xz * 1.3);
|
|
61
|
+
return base * (0.55 + 0.8 * blades) * mix(vec3(1.25, 1.05, 0.7), vec3(0.8, 1.1, 0.85), patches);
|
|
62
|
+
}
|
|
63
|
+
if (k < 2.5) return base * (0.6 + 0.75 * tsFbm(xz * 6.0)) * (0.82 + 0.36 * tsNoise(xz * 48.0));
|
|
64
|
+
if (k < 3.5) return base * (0.88 + 0.12 * sin(xz.x * 22.0 + tsFbm(xz * 2.0) * 9.0)) * (0.92 + 0.16 * tsNoise(xz * 90.0));
|
|
65
|
+
if (k < 4.5) { float grain = tsNoise(xz * 120.0); return base * (0.55 + 1.1 * grain * grain) * (0.85 + 0.3 * tsFbm(xz * 0.8)); }
|
|
66
|
+
if (k < 5.5) {
|
|
67
|
+
float joint = max(tsLine(xz.x, 3.0, 0.012), tsLine(xz.y, 3.0, 0.012));
|
|
68
|
+
return base * (0.82 + 0.3 * tsFbm(xz * 3.5)) * (0.94 + 0.12 * tsNoise(xz * 130.0)) * (1.0 - 0.6 * joint);
|
|
69
|
+
}
|
|
70
|
+
if (k < 6.5) {
|
|
71
|
+
vec2 cell = floor(xz / 0.6);
|
|
72
|
+
float grout = max(tsLine(xz.x, 0.6, 0.006), tsLine(xz.y, 0.6, 0.006));
|
|
73
|
+
return mix(base * (0.93 + 0.14 * tsHash(cell)) * (0.97 + 0.06 * tsFbm(xz * 7.0)), vec3(0.14, 0.13, 0.12), grout);
|
|
74
|
+
}
|
|
75
|
+
if (k < 7.5) {
|
|
76
|
+
float row = floor(xz.y / 0.14);
|
|
77
|
+
float along = xz.x + tsHash(vec2(row, 3.0)) * 2.4;
|
|
78
|
+
float seam = max(tsLine(xz.y, 0.14, 0.004), tsLine(along, 2.4, 0.004));
|
|
79
|
+
float grain = tsNoise(vec2(along * 3.0, xz.y * 70.0)) * 0.6 + tsNoise(vec2(along * 11.0, xz.y * 180.0)) * 0.4;
|
|
80
|
+
return base * (0.72 + 0.56 * tsHash(vec2(row, floor(along / 2.4)))) * (0.75 + 0.5 * grain) * (1.0 - 0.75 * seam);
|
|
81
|
+
}
|
|
82
|
+
vec2 cell = floor(xz / 0.22);
|
|
83
|
+
float seams = max(tsLine(xz.x, 0.22, 0.012), tsLine(xz.y, 0.22, 0.012));
|
|
84
|
+
return base * (0.7 + 0.6 * tsHash(cell)) * (0.85 + 0.3 * tsFbm(xz * 16.0)) * (1.0 - 0.55 * seams);
|
|
85
|
+
}
|
|
86
|
+
`
|
|
87
|
+
);
|
|
88
|
+
function createSurfaceMaterial(THREE) {
|
|
89
|
+
const material = new THREE.MeshStandardMaterial({ roughness: 0.95, metalness: 0 });
|
|
90
|
+
material.onBeforeCompile = (shader) => {
|
|
91
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\nattribute float aPattern;\nattribute vec3 aBase;\nattribute vec2 aShade;\nflat varying float vPattern;\nflat varying vec3 vBase;\nvarying vec2 vShade;\nvarying vec2 vGroundXZ;").replace("#include <begin_vertex>", "#include <begin_vertex>\nvPattern = aPattern;\nvBase = aBase;\nvShade = aShade;\nvGroundXZ = position.xz;");
|
|
92
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>
|
|
93
|
+
flat varying float vPattern;
|
|
94
|
+
flat varying vec3 vBase;
|
|
95
|
+
varying vec2 vShade;
|
|
96
|
+
varying vec2 vGroundXZ;
|
|
97
|
+
${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment>\ndiffuseColor.rgb = mix(terrainSurface(vBase, vGroundXZ, vPattern), vec3(0.012, 0.011, 0.01), vShade.y) * vShade.x;");
|
|
98
|
+
};
|
|
99
|
+
material.customProgramCacheKey = () => "game-terrain-surface";
|
|
100
|
+
return material;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/chunk-geometry.ts
|
|
104
|
+
var DEPTH_SHADE = 1.6;
|
|
105
|
+
var MIN_DEPTH_LIGHT = 0.55;
|
|
106
|
+
var LOOSE_VISIBLE = 4e-3;
|
|
107
|
+
var THIN_SKIN = 0.1;
|
|
108
|
+
var SKIRT_QUADS = 4;
|
|
109
|
+
var BROKEN_DEPTH = 4e-3;
|
|
110
|
+
var BROKEN_TONE = 0.62;
|
|
111
|
+
function vertexLook(terrain, column, row, base, shade) {
|
|
112
|
+
const index = row * terrain.stride + column;
|
|
113
|
+
let material = terrain.vertexMaterial(column, row);
|
|
114
|
+
if (terrain.loose[index] > LOOSE_VISIBLE) {
|
|
115
|
+
const profile = terrain.profileList[terrain.profileIndex[index]];
|
|
116
|
+
const first = profile.strata[0];
|
|
117
|
+
material = terrain.material(first.thickness < THIN_SKIN && profile.strata[1] ? profile.strata[1].material : first.material);
|
|
118
|
+
}
|
|
119
|
+
const broken = material.pattern === "concrete" && terrain.dug[index] > BROKEN_DEPTH;
|
|
120
|
+
const tone = broken ? BROKEN_TONE : 1;
|
|
121
|
+
base[0] = material.color[0] * tone;
|
|
122
|
+
base[1] = material.color[1] * tone;
|
|
123
|
+
base[2] = material.color[2] * tone;
|
|
124
|
+
shade[0] = Math.max(MIN_DEPTH_LIGHT, 1 - terrain.dug[index] / DEPTH_SHADE);
|
|
125
|
+
shade[1] = terrain.scorch[index];
|
|
126
|
+
return patternIndex(broken ? "soil" : material.pattern);
|
|
127
|
+
}
|
|
128
|
+
function buildChunkGeometry(THREE, terrain, chunk, step) {
|
|
129
|
+
const [x0, z0] = terrain.bounds;
|
|
130
|
+
const { cellSize, columns, rows } = terrain;
|
|
131
|
+
const lastColumn = Math.min(columns, chunk.column + chunk.cells);
|
|
132
|
+
const lastRow = Math.min(rows, chunk.row + chunk.cells);
|
|
133
|
+
const across = Math.ceil((lastColumn - chunk.column) / step) + 1;
|
|
134
|
+
const down = Math.ceil((lastRow - chunk.row) / step) + 1;
|
|
135
|
+
const positions = new Float32Array(across * down * 3);
|
|
136
|
+
const normals = new Float32Array(across * down * 3);
|
|
137
|
+
const bases = new Float32Array(across * down * 3);
|
|
138
|
+
const shades = new Float32Array(across * down * 2);
|
|
139
|
+
const patterns = new Float32Array(across * down);
|
|
140
|
+
const base = [0, 0, 0];
|
|
141
|
+
const shade = [1, 0];
|
|
142
|
+
let at = 0;
|
|
143
|
+
for (let j = 0; j < down; j++) {
|
|
144
|
+
const row = Math.min(lastRow, chunk.row + j * step);
|
|
145
|
+
for (let i = 0; i < across; i++) {
|
|
146
|
+
const column = Math.min(lastColumn, chunk.column + i * step);
|
|
147
|
+
const height = terrain.vertexHeight(column, row);
|
|
148
|
+
positions[at * 3] = x0 + column * cellSize;
|
|
149
|
+
positions[at * 3 + 1] = height;
|
|
150
|
+
positions[at * 3 + 2] = z0 + row * cellSize;
|
|
151
|
+
const left = terrain.vertexHeight(Math.max(0, column - 1), row);
|
|
152
|
+
const right = terrain.vertexHeight(Math.min(columns, column + 1), row);
|
|
153
|
+
const back = terrain.vertexHeight(column, Math.max(0, row - 1));
|
|
154
|
+
const front = terrain.vertexHeight(column, Math.min(rows, row + 1));
|
|
155
|
+
const nx = left - right;
|
|
156
|
+
const ny = 2 * cellSize;
|
|
157
|
+
const nz = back - front;
|
|
158
|
+
const length = Math.hypot(nx, ny, nz);
|
|
159
|
+
normals[at * 3] = nx / length;
|
|
160
|
+
normals[at * 3 + 1] = ny / length;
|
|
161
|
+
normals[at * 3 + 2] = nz / length;
|
|
162
|
+
patterns[at] = vertexLook(terrain, column, row, base, shade);
|
|
163
|
+
bases.set(base, at * 3);
|
|
164
|
+
shades.set(shade, at * 2);
|
|
165
|
+
at++;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const indices = new Uint32Array((across - 1) * (down - 1) * 6);
|
|
169
|
+
let cursor = 0;
|
|
170
|
+
for (let j = 0; j < down - 1; j++) {
|
|
171
|
+
for (let i = 0; i < across - 1; i++) {
|
|
172
|
+
const a = j * across + i;
|
|
173
|
+
const b = a + 1;
|
|
174
|
+
const c = a + across;
|
|
175
|
+
const d = c + 1;
|
|
176
|
+
indices[cursor++] = a;
|
|
177
|
+
indices[cursor++] = c;
|
|
178
|
+
indices[cursor++] = b;
|
|
179
|
+
indices[cursor++] = c;
|
|
180
|
+
indices[cursor++] = d;
|
|
181
|
+
indices[cursor++] = b;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const geometry = new THREE.BufferGeometry();
|
|
185
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
186
|
+
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
|
|
187
|
+
geometry.setAttribute("aBase", new THREE.BufferAttribute(bases, 3));
|
|
188
|
+
geometry.setAttribute("aShade", new THREE.BufferAttribute(shades, 2));
|
|
189
|
+
geometry.setAttribute("aPattern", new THREE.BufferAttribute(patterns, 1));
|
|
190
|
+
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
191
|
+
return geometry;
|
|
192
|
+
}
|
|
193
|
+
function buildPristineGeometry(THREE, terrain, chunks, skirt) {
|
|
194
|
+
const [x0, z0] = terrain.bounds;
|
|
195
|
+
const { cellSize, columns, rows } = terrain;
|
|
196
|
+
const [, , x1, z1] = terrain.bounds;
|
|
197
|
+
const quads = chunks.length + SKIRT_QUADS;
|
|
198
|
+
const positions = new Float32Array(quads * 12);
|
|
199
|
+
const normals = new Float32Array(quads * 12);
|
|
200
|
+
const bases = new Float32Array(quads * 12);
|
|
201
|
+
const shades = new Float32Array(quads * 8);
|
|
202
|
+
const patterns = new Float32Array(quads * 4);
|
|
203
|
+
const indices = new Uint32Array(quads * 6);
|
|
204
|
+
const base = [0, 0, 0];
|
|
205
|
+
const shade = [1, 0];
|
|
206
|
+
chunks.forEach((chunk, n) => {
|
|
207
|
+
const lastColumn = Math.min(columns, chunk.column + chunk.cells);
|
|
208
|
+
const lastRow = Math.min(rows, chunk.row + chunk.cells);
|
|
209
|
+
const corners = [[chunk.column, chunk.row], [lastColumn, chunk.row], [chunk.column, lastRow], [lastColumn, lastRow]];
|
|
210
|
+
const pattern = vertexLook(terrain, chunk.column, chunk.row, base, shade);
|
|
211
|
+
corners.forEach(([column, row], corner) => {
|
|
212
|
+
const at = n * 4 + corner;
|
|
213
|
+
positions[at * 3] = x0 + column * cellSize;
|
|
214
|
+
positions[at * 3 + 1] = terrain.vertexHeight(column, row);
|
|
215
|
+
positions[at * 3 + 2] = z0 + row * cellSize;
|
|
216
|
+
normals[at * 3 + 1] = 1;
|
|
217
|
+
bases.set(base, at * 3);
|
|
218
|
+
shades.set(shade, at * 2);
|
|
219
|
+
patterns[at] = pattern;
|
|
220
|
+
});
|
|
221
|
+
const first = n * 4;
|
|
222
|
+
indices.set([first, first + 2, first + 1, first + 2, first + 3, first + 1], n * 6);
|
|
223
|
+
});
|
|
224
|
+
const surface = terrain.material(terrain.profileList.find((profile) => profile.id === terrain.layout().surface)?.strata[0]?.material ?? terrain.profileList[terrain.profileIndex[0]].strata[0].material);
|
|
225
|
+
const rects = [
|
|
226
|
+
[x0 - skirt, z0 - skirt, x1 + skirt, z0],
|
|
227
|
+
[x0 - skirt, z1, x1 + skirt, z1 + skirt],
|
|
228
|
+
[x0 - skirt, z0, x0, z1],
|
|
229
|
+
[x1, z0, x1 + skirt, z1]
|
|
230
|
+
];
|
|
231
|
+
rects.forEach(([ax, az, bx, bz], r) => {
|
|
232
|
+
const n = chunks.length + r;
|
|
233
|
+
const corners = [[ax, az], [bx, az], [ax, bz], [bx, bz]];
|
|
234
|
+
corners.forEach(([x, z], corner) => {
|
|
235
|
+
const at = n * 4 + corner;
|
|
236
|
+
positions[at * 3] = x;
|
|
237
|
+
positions[at * 3 + 2] = z;
|
|
238
|
+
normals[at * 3 + 1] = 1;
|
|
239
|
+
bases.set(surface.color, at * 3);
|
|
240
|
+
shades[at * 2] = 1;
|
|
241
|
+
patterns[at] = patternIndex(surface.pattern);
|
|
242
|
+
});
|
|
243
|
+
const first = n * 4;
|
|
244
|
+
indices.set([first, first + 2, first + 1, first + 2, first + 3, first + 1], n * 6);
|
|
245
|
+
});
|
|
246
|
+
const geometry = new THREE.BufferGeometry();
|
|
247
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
248
|
+
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
|
|
249
|
+
geometry.setAttribute("aBase", new THREE.BufferAttribute(bases, 3));
|
|
250
|
+
geometry.setAttribute("aShade", new THREE.BufferAttribute(shades, 2));
|
|
251
|
+
geometry.setAttribute("aPattern", new THREE.BufferAttribute(patterns, 1));
|
|
252
|
+
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
253
|
+
return geometry;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/layers.ts
|
|
257
|
+
var MARK_LIFT = 6e-3;
|
|
258
|
+
var MARK_REACH = 1.5;
|
|
259
|
+
var MARK_TEXTURE_SIZE = 64;
|
|
260
|
+
var MARK_DARKEN = 0.85;
|
|
261
|
+
var PIECE_SHAPE = { clods: [1, 0.85, 1], chips: [1, 0.6, 0.8], shards: [1.3, 0.2, 1], splinters: [0.3, 0.25, 2.4], dust: [1, 0.8, 1] };
|
|
262
|
+
function createMarkTexture(THREE) {
|
|
263
|
+
const size = MARK_TEXTURE_SIZE;
|
|
264
|
+
const data = new Uint8Array(size * size * 4);
|
|
265
|
+
for (let y = 0; y < size; y++) {
|
|
266
|
+
for (let x = 0; x < size; x++) {
|
|
267
|
+
const u = (x + 0.5) / size - 0.5;
|
|
268
|
+
const v = (y + 0.5) / size - 0.5;
|
|
269
|
+
const angle = Math.atan2(v, u);
|
|
270
|
+
const lobes = 0.86 + 0.09 * Math.sin(angle * 5) + 0.05 * Math.sin(angle * 13 + 1);
|
|
271
|
+
const q = Math.hypot(u, v) / (0.5 * lobes);
|
|
272
|
+
const shade = q < 0.55 ? 0.42 + q * 0.5 : 0.7 + (q - 0.55) * 0.8;
|
|
273
|
+
const at = (y * size + x) * 4;
|
|
274
|
+
data[at] = data[at + 1] = data[at + 2] = Math.round(Math.min(1, shade) * 255);
|
|
275
|
+
data[at + 3] = q < 0.8 ? 255 : q < 1 ? Math.round((1 - (q - 0.8) / 0.2) * 255) : 0;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
279
|
+
texture.magFilter = THREE.LinearFilter;
|
|
280
|
+
texture.minFilter = THREE.LinearFilter;
|
|
281
|
+
texture.needsUpdate = true;
|
|
282
|
+
return texture;
|
|
283
|
+
}
|
|
284
|
+
function createMarkLayer(THREE, terrain, drawDistance) {
|
|
285
|
+
const geometry = new THREE.PlaneGeometry(1, 1);
|
|
286
|
+
geometry.rotateX(-Math.PI / 2);
|
|
287
|
+
const texture = createMarkTexture(THREE);
|
|
288
|
+
const material = new THREE.MeshStandardMaterial({ map: texture, transparent: true, depthWrite: false, roughness: 1, polygonOffset: true, polygonOffsetFactor: -2, polygonOffsetUnits: -2 });
|
|
289
|
+
const mesh = new THREE.InstancedMesh(geometry, material, terrain.budget.maxMarks);
|
|
290
|
+
mesh.name = "terrain-marks";
|
|
291
|
+
mesh.count = 0;
|
|
292
|
+
mesh.frustumCulled = false;
|
|
293
|
+
mesh.receiveShadow = true;
|
|
294
|
+
mesh.renderOrder = 1;
|
|
295
|
+
const matrix = new THREE.Matrix4();
|
|
296
|
+
const rotation = new THREE.Matrix4();
|
|
297
|
+
const scale = new THREE.Matrix4();
|
|
298
|
+
const colour = new THREE.Color();
|
|
299
|
+
let seen = "";
|
|
300
|
+
let cameraX = Infinity;
|
|
301
|
+
let cameraZ = Infinity;
|
|
302
|
+
return {
|
|
303
|
+
mesh,
|
|
304
|
+
sync(camera) {
|
|
305
|
+
const marks = terrain.marks();
|
|
306
|
+
let blows = 0;
|
|
307
|
+
for (const mark of marks) blows += mark.count;
|
|
308
|
+
const stamp = `${terrain.marksVersion}:${blows}`;
|
|
309
|
+
if (stamp === seen && Math.hypot(camera.x - cameraX, camera.z - cameraZ) < 5) return;
|
|
310
|
+
seen = stamp;
|
|
311
|
+
cameraX = camera.x;
|
|
312
|
+
cameraZ = camera.z;
|
|
313
|
+
let count = 0;
|
|
314
|
+
for (const mark of marks) {
|
|
315
|
+
if (Math.hypot(mark.x - camera.x, mark.z - camera.z) > drawDistance) continue;
|
|
316
|
+
const size = mark.radius * 2 * MARK_REACH;
|
|
317
|
+
rotation.makeRotationY(mark.id * 2.399);
|
|
318
|
+
scale.makeScale(size, 1, size);
|
|
319
|
+
matrix.multiplyMatrices(rotation, scale);
|
|
320
|
+
matrix.setPosition(mark.x, terrain.heightAt(mark.x, mark.z) + MARK_LIFT, mark.z);
|
|
321
|
+
mesh.setMatrixAt(count, matrix);
|
|
322
|
+
const [r, g, b] = terrain.material(mark.material).color;
|
|
323
|
+
colour.setRGB(r * MARK_DARKEN + 0.04, g * MARK_DARKEN + 0.04, b * MARK_DARKEN + 0.04);
|
|
324
|
+
mesh.setColorAt(count, colour);
|
|
325
|
+
count++;
|
|
326
|
+
}
|
|
327
|
+
mesh.count = count;
|
|
328
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
329
|
+
if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
|
|
330
|
+
},
|
|
331
|
+
dispose() {
|
|
332
|
+
geometry.dispose();
|
|
333
|
+
material.dispose();
|
|
334
|
+
texture.dispose();
|
|
335
|
+
mesh.removeFromParent();
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function createDebrisLayer(THREE, terrain) {
|
|
340
|
+
const geometry = new THREE.IcosahedronGeometry(1, 0);
|
|
341
|
+
const material = new THREE.MeshStandardMaterial({ roughness: 0.95, flatShading: true });
|
|
342
|
+
const mesh = new THREE.InstancedMesh(geometry, material, terrain.budget.maxDebris);
|
|
343
|
+
mesh.name = "terrain-debris";
|
|
344
|
+
mesh.count = 0;
|
|
345
|
+
mesh.castShadow = true;
|
|
346
|
+
mesh.frustumCulled = false;
|
|
347
|
+
const matrix = new THREE.Matrix4();
|
|
348
|
+
const position = new THREE.Vector3();
|
|
349
|
+
const scale = new THREE.Vector3();
|
|
350
|
+
const quaternion = new THREE.Quaternion();
|
|
351
|
+
const euler = new THREE.Euler();
|
|
352
|
+
const colour = new THREE.Color();
|
|
353
|
+
return {
|
|
354
|
+
mesh,
|
|
355
|
+
sync() {
|
|
356
|
+
const pieces = terrain.debris();
|
|
357
|
+
let count = 0;
|
|
358
|
+
for (const piece of pieces) {
|
|
359
|
+
const [x, y, z] = piece.position;
|
|
360
|
+
const travelled = x * 1.7 + z * 1.3 + y;
|
|
361
|
+
euler.set(piece.spin[0] * travelled * 0.2 + piece.id, piece.spin[1] * travelled * 0.2, piece.spin[2] * travelled * 0.2);
|
|
362
|
+
const shape = PIECE_SHAPE[piece.kind];
|
|
363
|
+
scale.set(shape[0] * piece.radius, shape[1] * piece.radius, shape[2] * piece.radius);
|
|
364
|
+
matrix.compose(position.set(x, y, z), quaternion.setFromEuler(euler), scale);
|
|
365
|
+
mesh.setMatrixAt(count, matrix);
|
|
366
|
+
const [r, g, b] = terrain.material(piece.material).color;
|
|
367
|
+
const tone = 0.85 + piece.id * 37 % 30 / 100;
|
|
368
|
+
mesh.setColorAt(count, colour.setRGB(r * tone, g * tone, b * tone));
|
|
369
|
+
count++;
|
|
370
|
+
}
|
|
371
|
+
mesh.count = count;
|
|
372
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
373
|
+
if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
|
|
374
|
+
},
|
|
375
|
+
dispose() {
|
|
376
|
+
geometry.dispose();
|
|
377
|
+
material.dispose();
|
|
378
|
+
mesh.removeFromParent();
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/rebar.ts
|
|
384
|
+
var BAR_RADIUS = 6e-3;
|
|
385
|
+
var EXPOSED_MARGIN = 2e-3;
|
|
386
|
+
var MAX_SAG = 0.07;
|
|
387
|
+
function buildRebarGeometry(THREE, terrain, chunk) {
|
|
388
|
+
const [x0, z0] = terrain.bounds;
|
|
389
|
+
const { cellSize, columns, rows, stride } = terrain;
|
|
390
|
+
const lastColumn = Math.min(columns, chunk.column + chunk.cells);
|
|
391
|
+
const lastRow = Math.min(rows, chunk.row + chunk.cells);
|
|
392
|
+
const boxes = [];
|
|
393
|
+
const coverOf = (index) => {
|
|
394
|
+
let top = 0;
|
|
395
|
+
for (const stratum of terrain.profileList[terrain.profileIndex[index]].strata) {
|
|
396
|
+
if (stratum.reinforcement) return { depth: top + stratum.reinforcement.cover, spacing: stratum.reinforcement.spacing };
|
|
397
|
+
top += stratum.thickness;
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
};
|
|
401
|
+
const showing = (index, depth) => {
|
|
402
|
+
const profile = terrain.profileList[terrain.profileIndex[index]];
|
|
403
|
+
const limit = profile.strata.reduce((sum, stratum) => sum + stratum.thickness, 0);
|
|
404
|
+
return terrain.dug[index] > depth + EXPOSED_MARGIN && terrain.dug[index] <= limit + EXPOSED_MARGIN;
|
|
405
|
+
};
|
|
406
|
+
const onBar = (coordinate, spacing) => Math.floor(coordinate / spacing) !== Math.floor((coordinate + cellSize) / spacing);
|
|
407
|
+
const addBox = (ax, az, bx, bz, y) => {
|
|
408
|
+
boxes.push(Math.min(ax, bx) - BAR_RADIUS, y - BAR_RADIUS, Math.min(az, bz) - BAR_RADIUS, Math.max(ax, bx) + BAR_RADIUS, y + BAR_RADIUS, Math.max(az, bz) + BAR_RADIUS);
|
|
409
|
+
};
|
|
410
|
+
for (let row = chunk.row; row <= lastRow; row++) {
|
|
411
|
+
for (let column = chunk.column; column <= lastColumn; column++) {
|
|
412
|
+
const index = row * stride + column;
|
|
413
|
+
const cover = coverOf(index);
|
|
414
|
+
if (!cover || !showing(index, cover.depth)) continue;
|
|
415
|
+
const x = x0 + column * cellSize;
|
|
416
|
+
const z = z0 + row * cellSize;
|
|
417
|
+
const y = -cover.depth - BAR_RADIUS - Math.min(1, terrain.barBend[index]) * MAX_SAG;
|
|
418
|
+
if (onBar(z, cover.spacing) && column < lastColumn && showing(index + 1, cover.depth)) addBox(x, z, x + cellSize, z, y);
|
|
419
|
+
if (onBar(x, cover.spacing) && row < lastRow && showing(index + stride, cover.depth)) addBox(x, z, x, z + cellSize, y);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (boxes.length === 0) return null;
|
|
423
|
+
const count = boxes.length / 6;
|
|
424
|
+
const positions = new Float32Array(count * 24 * 3);
|
|
425
|
+
const normals = new Float32Array(count * 24 * 3);
|
|
426
|
+
const indices = new Uint32Array(count * 36);
|
|
427
|
+
const FACES = [
|
|
428
|
+
[[0, 4, 2, 0, 4, 5, 3, 4, 5, 3, 4, 2], [0, 1, 0]],
|
|
429
|
+
[[0, 1, 5, 0, 1, 2, 3, 1, 2, 3, 1, 5], [0, -1, 0]],
|
|
430
|
+
[[0, 1, 5, 3, 1, 5, 3, 4, 5, 0, 4, 5], [0, 0, 1]],
|
|
431
|
+
[[3, 1, 2, 0, 1, 2, 0, 4, 2, 3, 4, 2], [0, 0, -1]],
|
|
432
|
+
[[3, 1, 5, 3, 1, 2, 3, 4, 2, 3, 4, 5], [1, 0, 0]],
|
|
433
|
+
[[0, 1, 2, 0, 1, 5, 0, 4, 5, 0, 4, 2], [-1, 0, 0]]
|
|
434
|
+
];
|
|
435
|
+
let vertex = 0;
|
|
436
|
+
let cursor = 0;
|
|
437
|
+
for (let n = 0; n < count; n++) {
|
|
438
|
+
const box = boxes.slice(n * 6, n * 6 + 6);
|
|
439
|
+
for (const [corners, normal] of FACES) {
|
|
440
|
+
const first = vertex;
|
|
441
|
+
for (let c = 0; c < 4; c++) {
|
|
442
|
+
positions[vertex * 3] = box[corners[c * 3]];
|
|
443
|
+
positions[vertex * 3 + 1] = box[corners[c * 3 + 1]];
|
|
444
|
+
positions[vertex * 3 + 2] = box[corners[c * 3 + 2]];
|
|
445
|
+
normals.set(normal, vertex * 3);
|
|
446
|
+
vertex++;
|
|
447
|
+
}
|
|
448
|
+
indices.set([first, first + 1, first + 2, first, first + 2, first + 3], cursor);
|
|
449
|
+
cursor += 6;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const geometry = new THREE.BufferGeometry();
|
|
453
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
454
|
+
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
|
|
455
|
+
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
456
|
+
return geometry;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/renderer.ts
|
|
460
|
+
var DEFAULT_LOD_DISTANCE = 30;
|
|
461
|
+
var DEFAULT_MARK_DISTANCE = 45;
|
|
462
|
+
var DEFAULT_SKIRT = 300;
|
|
463
|
+
var LOD_CHECK_METRES = 4;
|
|
464
|
+
var REBAR_COLOUR = 4863272;
|
|
465
|
+
function createTerrainRenderer(THREE, options) {
|
|
466
|
+
const { terrain } = options;
|
|
467
|
+
const lodDistance = options.lodDistance ?? DEFAULT_LOD_DISTANCE;
|
|
468
|
+
const skirt = options.skirt ?? DEFAULT_SKIRT;
|
|
469
|
+
const group = markKitOrigin(new THREE.Group(), { owner: "game-terrain-three", kind: "ground" });
|
|
470
|
+
group.name = "floor";
|
|
471
|
+
const surface = createSurfaceMaterial(THREE);
|
|
472
|
+
const rebarMaterial = new THREE.MeshStandardMaterial({ color: REBAR_COLOUR, roughness: 0.75, metalness: 0.35 });
|
|
473
|
+
const pristineMesh = new THREE.Mesh(new THREE.BufferGeometry(), surface);
|
|
474
|
+
pristineMesh.receiveShadow = true;
|
|
475
|
+
pristineMesh.name = "terrain-pristine";
|
|
476
|
+
pristineMesh.frustumCulled = false;
|
|
477
|
+
group.add(pristineMesh);
|
|
478
|
+
const drawn = /* @__PURE__ */ new Map();
|
|
479
|
+
const marks = createMarkLayer(THREE, terrain, options.markDistance ?? DEFAULT_MARK_DISTANCE);
|
|
480
|
+
const debris = createDebrisLayer(THREE, terrain);
|
|
481
|
+
group.add(marks.mesh, debris.mesh);
|
|
482
|
+
let pristineStale = true;
|
|
483
|
+
let pristineCount = 0;
|
|
484
|
+
let lodX = Infinity;
|
|
485
|
+
let lodZ = Infinity;
|
|
486
|
+
let firstSync = true;
|
|
487
|
+
const distanceTo = (chunk, camera) => {
|
|
488
|
+
const half = chunk.cells * terrain.cellSize / 2;
|
|
489
|
+
const centreX = terrain.bounds[0] + chunk.column * terrain.cellSize + half;
|
|
490
|
+
const centreZ = terrain.bounds[1] + chunk.row * terrain.cellSize + half;
|
|
491
|
+
return Math.hypot(centreX - camera.x, centreZ - camera.z);
|
|
492
|
+
};
|
|
493
|
+
const stepFor = (distance) => distance > lodDistance * 2 ? 4 : distance > lodDistance ? 2 : 1;
|
|
494
|
+
function drop(index) {
|
|
495
|
+
const entry = drawn.get(index);
|
|
496
|
+
if (!entry) return;
|
|
497
|
+
group.remove(entry.mesh);
|
|
498
|
+
entry.mesh.geometry.dispose();
|
|
499
|
+
if (entry.rebar) {
|
|
500
|
+
group.remove(entry.rebar);
|
|
501
|
+
entry.rebar.geometry.dispose();
|
|
502
|
+
}
|
|
503
|
+
drawn.delete(index);
|
|
504
|
+
}
|
|
505
|
+
function draw(chunk, camera) {
|
|
506
|
+
drop(chunk.index);
|
|
507
|
+
const step = stepFor(distanceTo(chunk, camera));
|
|
508
|
+
const mesh = new THREE.Mesh(buildChunkGeometry(THREE, terrain, chunk, step), surface);
|
|
509
|
+
mesh.receiveShadow = true;
|
|
510
|
+
group.add(mesh);
|
|
511
|
+
const bars = buildRebarGeometry(THREE, terrain, chunk);
|
|
512
|
+
let rebar = null;
|
|
513
|
+
if (bars) {
|
|
514
|
+
rebar = new THREE.Mesh(bars, rebarMaterial);
|
|
515
|
+
rebar.castShadow = true;
|
|
516
|
+
group.add(rebar);
|
|
517
|
+
}
|
|
518
|
+
drawn.set(chunk.index, { mesh, rebar, step });
|
|
519
|
+
}
|
|
520
|
+
function rebuildPristine() {
|
|
521
|
+
const chunks = [];
|
|
522
|
+
for (let index = 0; index < terrain.chunksX * terrain.chunksZ; index++) {
|
|
523
|
+
const chunk = terrain.chunk(index);
|
|
524
|
+
if (chunk.pristine) chunks.push(chunk);
|
|
525
|
+
}
|
|
526
|
+
pristineMesh.geometry.dispose();
|
|
527
|
+
pristineMesh.geometry = buildPristineGeometry(THREE, terrain, chunks, skirt);
|
|
528
|
+
pristineCount = chunks.length;
|
|
529
|
+
pristineStale = false;
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
group,
|
|
533
|
+
sync(camera) {
|
|
534
|
+
const limit = firstSync ? Infinity : void 0;
|
|
535
|
+
firstSync = false;
|
|
536
|
+
for (const chunk of terrain.drainDirtyChunks(limit)) {
|
|
537
|
+
if (chunk.pristine) {
|
|
538
|
+
if (drawn.has(chunk.index)) drop(chunk.index);
|
|
539
|
+
pristineStale = true;
|
|
540
|
+
} else {
|
|
541
|
+
if (!drawn.has(chunk.index)) pristineStale = true;
|
|
542
|
+
draw(chunk, camera);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (pristineStale) rebuildPristine();
|
|
546
|
+
if (Math.hypot(camera.x - lodX, camera.z - lodZ) > LOD_CHECK_METRES) {
|
|
547
|
+
lodX = camera.x;
|
|
548
|
+
lodZ = camera.z;
|
|
549
|
+
for (const [index, entry] of drawn) {
|
|
550
|
+
const chunk = terrain.chunk(index);
|
|
551
|
+
if (stepFor(distanceTo(chunk, camera)) !== entry.step) draw(chunk, camera);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
marks.sync(camera);
|
|
555
|
+
debris.sync();
|
|
556
|
+
},
|
|
557
|
+
stats: () => ({ chunkMeshes: drawn.size, pristineChunks: pristineCount, marksDrawn: marks.mesh.count, debrisDrawn: debris.mesh.count }),
|
|
558
|
+
dispose() {
|
|
559
|
+
for (const index of [...drawn.keys()]) drop(index);
|
|
560
|
+
pristineMesh.geometry.dispose();
|
|
561
|
+
surface.dispose();
|
|
562
|
+
rebarMaterial.dispose();
|
|
563
|
+
marks.dispose();
|
|
564
|
+
debris.dispose();
|
|
565
|
+
group.removeFromParent();
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/locale.ts
|
|
571
|
+
var LOCALES = {
|
|
572
|
+
en: {
|
|
573
|
+
name: "Terrain",
|
|
574
|
+
group: "Terrain",
|
|
575
|
+
weight: "Concrete block (60 kg)",
|
|
576
|
+
surfaces: { dirt: "Dirt", lawn: "Lawn", sand: "Sand", road: "Asphalt", concrete_slab: "Concrete slab", suspended_slab: "Slab over a void", wood_floor: "Wood floor", tile_floor: "Tile", stone_paving: "Stone paving" }
|
|
577
|
+
},
|
|
578
|
+
"pt-BR": {
|
|
579
|
+
name: "Terreno",
|
|
580
|
+
group: "Terreno",
|
|
581
|
+
weight: "Bloco de concreto (60 kg)",
|
|
582
|
+
surfaces: { dirt: "Terra", lawn: "Grama", sand: "Areia", road: "Asfalto", concrete_slab: "Laje de concreto", suspended_slab: "Laje sobre v\xE3o", wood_floor: "Assoalho", tile_floor: "Cer\xE2mica", stone_paving: "Pedra" }
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
var locale = (id) => LOCALES[id] ?? LOCALES.en;
|
|
586
|
+
|
|
587
|
+
// src/test-module.ts
|
|
588
|
+
var TERRAIN_STRIPS = ["dirt", "lawn", "sand", "road", "concrete_slab", "suspended_slab", "wood_floor", "tile_floor", "stone_paving"];
|
|
589
|
+
var STRIP_WIDTH = 3;
|
|
590
|
+
var STRIP_Z0 = 4;
|
|
591
|
+
var STRIP_Z1 = 13;
|
|
592
|
+
var stripRect = (index) => {
|
|
593
|
+
const x0 = (index - TERRAIN_STRIPS.length / 2) * STRIP_WIDTH;
|
|
594
|
+
return [x0, STRIP_Z0, x0 + STRIP_WIDTH, STRIP_Z1];
|
|
595
|
+
};
|
|
596
|
+
var TERRAIN_AREA_GROUND = {
|
|
597
|
+
surface: "concrete_slab",
|
|
598
|
+
zones: TERRAIN_STRIPS.map((surface, index) => ({ rect: stripRect(index), surface }))
|
|
599
|
+
};
|
|
600
|
+
var WEIGHT_SIDE = 0.32;
|
|
601
|
+
var WEIGHT_HEIGHT = 4;
|
|
602
|
+
var LABEL_Y = 0.02;
|
|
603
|
+
var LABEL_WIDTH = 2.6;
|
|
604
|
+
var LABEL_HEIGHT = 0.5;
|
|
605
|
+
function weightSpec() {
|
|
606
|
+
return { name: "Weight", parts: [{ id: "block", material: "concrete", shape: { kind: "box", size: [WEIGHT_SIDE, WEIGHT_SIDE, WEIGHT_SIDE] }, position: [0, WEIGHT_SIDE / 2, 0] }], joints: [] };
|
|
607
|
+
}
|
|
608
|
+
function buildWeight(THREE) {
|
|
609
|
+
const mesh = new THREE.Mesh(new THREE.BoxGeometry(WEIGHT_SIDE, WEIGHT_SIDE, WEIGHT_SIDE), new THREE.MeshStandardMaterial({ color: 11842734, roughness: 0.9 }));
|
|
610
|
+
mesh.name = "block";
|
|
611
|
+
mesh.position.y = WEIGHT_SIDE / 2;
|
|
612
|
+
mesh.castShadow = true;
|
|
613
|
+
const root = markKitOrigin(new THREE.Group(), { owner: "game-terrain-three", kind: "item" });
|
|
614
|
+
root.add(mesh);
|
|
615
|
+
return root;
|
|
616
|
+
}
|
|
617
|
+
function buildLabel(THREE, text, x) {
|
|
618
|
+
if (typeof document === "undefined") return null;
|
|
619
|
+
const canvas = document.createElement("canvas");
|
|
620
|
+
canvas.width = 512;
|
|
621
|
+
canvas.height = 96;
|
|
622
|
+
const context = canvas.getContext("2d");
|
|
623
|
+
if (!context) return null;
|
|
624
|
+
context.fillStyle = "rgba(0,0,0,0.55)";
|
|
625
|
+
context.fillRect(0, 0, 512, 96);
|
|
626
|
+
context.fillStyle = "#fff";
|
|
627
|
+
context.font = "bold 54px sans-serif";
|
|
628
|
+
context.textAlign = "center";
|
|
629
|
+
context.textBaseline = "middle";
|
|
630
|
+
context.fillText(text, 256, 50);
|
|
631
|
+
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(LABEL_WIDTH, LABEL_HEIGHT), new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(canvas), transparent: true }));
|
|
632
|
+
markKitOrigin(mesh, { owner: "game-terrain-three", kind: "room" });
|
|
633
|
+
mesh.rotation.set(-Math.PI / 2, 0, Math.PI);
|
|
634
|
+
mesh.position.set(x, LABEL_Y, STRIP_Z0 - LABEL_HEIGHT);
|
|
635
|
+
return mesh;
|
|
636
|
+
}
|
|
637
|
+
function createTerrainTestModule(options = {}) {
|
|
638
|
+
let labels = [];
|
|
639
|
+
const text = locale(options.locale ?? "en");
|
|
640
|
+
return {
|
|
641
|
+
id: "terrain",
|
|
642
|
+
name: text.name,
|
|
643
|
+
group: text.group,
|
|
644
|
+
ground: TERRAIN_AREA_GROUND,
|
|
645
|
+
items() {
|
|
646
|
+
return [{ id: "terrain_weight", name: text.weight, build: (THREE) => buildWeight(THREE), spec: weightSpec, movable: true }];
|
|
647
|
+
},
|
|
648
|
+
area() {
|
|
649
|
+
for (const label of labels) label.visible = true;
|
|
650
|
+
return {
|
|
651
|
+
placements: TERRAIN_STRIPS.map((_, index) => {
|
|
652
|
+
const [x0] = stripRect(index);
|
|
653
|
+
return { item: "terrain_weight", position: [x0 + STRIP_WIDTH / 2, WEIGHT_HEIGHT, STRIP_Z1 - 1.2] };
|
|
654
|
+
}),
|
|
655
|
+
view: { eye: [0, 1.7, 1.5], target: [0, 0, 8] }
|
|
656
|
+
};
|
|
657
|
+
},
|
|
658
|
+
attach(host) {
|
|
659
|
+
const THREE = host.THREE;
|
|
660
|
+
const scene = host.scene;
|
|
661
|
+
labels = TERRAIN_STRIPS.flatMap((surface, index) => {
|
|
662
|
+
const [x0] = stripRect(index);
|
|
663
|
+
const label = buildLabel(THREE, text.surfaces[surface], x0 + STRIP_WIDTH / 2);
|
|
664
|
+
if (!label) return [];
|
|
665
|
+
label.visible = false;
|
|
666
|
+
scene.add(label);
|
|
667
|
+
return [label];
|
|
668
|
+
});
|
|
669
|
+
const stop = host.onClear?.(() => {
|
|
670
|
+
for (const label of labels) label.visible = false;
|
|
671
|
+
});
|
|
672
|
+
return () => {
|
|
673
|
+
stop?.();
|
|
674
|
+
for (const label of labels) label.removeFromParent();
|
|
675
|
+
labels = [];
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return __toCommonJS(index_exports);
|
|
681
|
+
})();
|
|
682
|
+
//# sourceMappingURL=index.global.js.map
|