@adatechnology/game-urban-three 0.1.0 → 0.2.1
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/README.md +7 -1
- package/dist/index.global.js +2383 -395
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +18 -11
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.global.js
CHANGED
|
@@ -36,10 +36,1528 @@ var GameUrbanThree = (() => {
|
|
|
36
36
|
loadUrbanTestModule: () => loadUrbanTestModule
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// ../game-shapes/dist/chunk-R4RY23RH.js
|
|
40
|
+
function canonicalJson(value) {
|
|
41
|
+
if (value === null) return "null";
|
|
42
|
+
if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
|
|
43
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
44
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
45
|
+
if (typeof value === "function") throw new Error("game-shapes: a function cannot be part of a shape (register it as a generator)");
|
|
46
|
+
if (ArrayBuffer.isView(value)) return canonicalJson(Array.from(value));
|
|
47
|
+
if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : canonicalJson(item)).join(",")}]`;
|
|
48
|
+
if (typeof value === "object") {
|
|
49
|
+
const record = value;
|
|
50
|
+
const keys = Object.keys(record).filter((key) => record[key] !== void 0).sort();
|
|
51
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
52
|
+
}
|
|
53
|
+
return "null";
|
|
54
|
+
}
|
|
55
|
+
function needsUint32(array) {
|
|
56
|
+
for (let i = array.length - 1; i >= 0; --i) if (array[i] >= 65535) return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
var indexArray = (indices) => needsUint32(indices) ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
60
|
+
function pack(type, parameters, a) {
|
|
61
|
+
return {
|
|
62
|
+
type,
|
|
63
|
+
parameters,
|
|
64
|
+
positions: new Float32Array(a.vertices),
|
|
65
|
+
normals: new Float32Array(a.normals),
|
|
66
|
+
uvs: new Float32Array(a.uvs),
|
|
67
|
+
...a.indices ? { indices: indexArray(a.indices) } : {},
|
|
68
|
+
groups: a.groups
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
var normalize3 = (x, y, z) => {
|
|
72
|
+
const length = Math.sqrt(x * x + y * y + z * z);
|
|
73
|
+
const s = 1 / (length || 1);
|
|
74
|
+
return [x * s, y * s, z * s];
|
|
75
|
+
};
|
|
76
|
+
function box(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1) {
|
|
77
|
+
const parameters = { width, height, depth, widthSegments, heightSegments, depthSegments };
|
|
78
|
+
widthSegments = Math.floor(widthSegments);
|
|
79
|
+
heightSegments = Math.floor(heightSegments);
|
|
80
|
+
depthSegments = Math.floor(depthSegments);
|
|
81
|
+
const indices = [];
|
|
82
|
+
const vertices = [];
|
|
83
|
+
const normals = [];
|
|
84
|
+
const uvs = [];
|
|
85
|
+
const groups = [];
|
|
86
|
+
let numberOfVertices = 0;
|
|
87
|
+
let groupStart = 0;
|
|
88
|
+
const buildPlane = (u, v, w, udir, vdir, width2, height2, depth2, gridX, gridY, materialIndex) => {
|
|
89
|
+
const segmentWidth = width2 / gridX;
|
|
90
|
+
const segmentHeight = height2 / gridY;
|
|
91
|
+
const widthHalf = width2 / 2;
|
|
92
|
+
const heightHalf = height2 / 2;
|
|
93
|
+
const depthHalf = depth2 / 2;
|
|
94
|
+
const gridX1 = gridX + 1;
|
|
95
|
+
const gridY1 = gridY + 1;
|
|
96
|
+
let vertexCounter = 0;
|
|
97
|
+
let groupCount = 0;
|
|
98
|
+
const vector = { x: 0, y: 0, z: 0 };
|
|
99
|
+
for (let iy = 0; iy < gridY1; iy++) {
|
|
100
|
+
const y = iy * segmentHeight - heightHalf;
|
|
101
|
+
for (let ix = 0; ix < gridX1; ix++) {
|
|
102
|
+
const x = ix * segmentWidth - widthHalf;
|
|
103
|
+
vector[u] = x * udir;
|
|
104
|
+
vector[v] = y * vdir;
|
|
105
|
+
vector[w] = depthHalf;
|
|
106
|
+
vertices.push(vector.x, vector.y, vector.z);
|
|
107
|
+
vector[u] = 0;
|
|
108
|
+
vector[v] = 0;
|
|
109
|
+
vector[w] = depth2 > 0 ? 1 : -1;
|
|
110
|
+
normals.push(vector.x, vector.y, vector.z);
|
|
111
|
+
uvs.push(ix / gridX);
|
|
112
|
+
uvs.push(1 - iy / gridY);
|
|
113
|
+
vertexCounter += 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (let iy = 0; iy < gridY; iy++) {
|
|
117
|
+
for (let ix = 0; ix < gridX; ix++) {
|
|
118
|
+
const a = numberOfVertices + ix + gridX1 * iy;
|
|
119
|
+
const b = numberOfVertices + ix + gridX1 * (iy + 1);
|
|
120
|
+
const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
|
|
121
|
+
const d = numberOfVertices + (ix + 1) + gridX1 * iy;
|
|
122
|
+
indices.push(a, b, d);
|
|
123
|
+
indices.push(b, c, d);
|
|
124
|
+
groupCount += 6;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
groups.push({ start: groupStart, count: groupCount, materialIndex });
|
|
128
|
+
groupStart += groupCount;
|
|
129
|
+
numberOfVertices += vertexCounter;
|
|
130
|
+
};
|
|
131
|
+
buildPlane("z", "y", "x", -1, -1, depth, height, width, depthSegments, heightSegments, 0);
|
|
132
|
+
buildPlane("z", "y", "x", 1, -1, depth, height, -width, depthSegments, heightSegments, 1);
|
|
133
|
+
buildPlane("x", "z", "y", 1, 1, width, depth, height, widthSegments, depthSegments, 2);
|
|
134
|
+
buildPlane("x", "z", "y", 1, -1, width, depth, -height, widthSegments, depthSegments, 3);
|
|
135
|
+
buildPlane("x", "y", "z", 1, -1, width, height, depth, widthSegments, heightSegments, 4);
|
|
136
|
+
buildPlane("x", "y", "z", -1, -1, width, height, -depth, widthSegments, heightSegments, 5);
|
|
137
|
+
return pack("BoxGeometry", parameters, { vertices, normals, uvs, indices, groups });
|
|
138
|
+
}
|
|
139
|
+
function cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {
|
|
140
|
+
radialSegments = Math.floor(radialSegments);
|
|
141
|
+
heightSegments = Math.floor(heightSegments);
|
|
142
|
+
const indices = [];
|
|
143
|
+
const vertices = [];
|
|
144
|
+
const normals = [];
|
|
145
|
+
const uvs = [];
|
|
146
|
+
const groups = [];
|
|
147
|
+
let index = 0;
|
|
148
|
+
const indexArray2 = [];
|
|
149
|
+
const halfHeight = height / 2;
|
|
150
|
+
let groupStart = 0;
|
|
151
|
+
const generateTorso = () => {
|
|
152
|
+
let groupCount = 0;
|
|
153
|
+
const slope = (radiusBottom - radiusTop) / height;
|
|
154
|
+
for (let y = 0; y <= heightSegments; y++) {
|
|
155
|
+
const indexRow = [];
|
|
156
|
+
const v = y / heightSegments;
|
|
157
|
+
const radius = v * (radiusBottom - radiusTop) + radiusTop;
|
|
158
|
+
for (let x = 0; x <= radialSegments; x++) {
|
|
159
|
+
const u = x / radialSegments;
|
|
160
|
+
const theta = u * thetaLength + thetaStart;
|
|
161
|
+
const sinTheta = Math.sin(theta);
|
|
162
|
+
const cosTheta = Math.cos(theta);
|
|
163
|
+
vertices.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta);
|
|
164
|
+
const n = normalize3(sinTheta, slope, cosTheta);
|
|
165
|
+
normals.push(n[0], n[1], n[2]);
|
|
166
|
+
uvs.push(u, 1 - v);
|
|
167
|
+
indexRow.push(index++);
|
|
168
|
+
}
|
|
169
|
+
indexArray2.push(indexRow);
|
|
170
|
+
}
|
|
171
|
+
for (let x = 0; x < radialSegments; x++) {
|
|
172
|
+
for (let y = 0; y < heightSegments; y++) {
|
|
173
|
+
const a = indexArray2[y][x];
|
|
174
|
+
const b = indexArray2[y + 1][x];
|
|
175
|
+
const c = indexArray2[y + 1][x + 1];
|
|
176
|
+
const d = indexArray2[y][x + 1];
|
|
177
|
+
indices.push(a, b, d);
|
|
178
|
+
indices.push(b, c, d);
|
|
179
|
+
groupCount += 6;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
groups.push({ start: groupStart, count: groupCount, materialIndex: 0 });
|
|
183
|
+
groupStart += groupCount;
|
|
184
|
+
};
|
|
185
|
+
const generateCap = (top) => {
|
|
186
|
+
const centerIndexStart = index;
|
|
187
|
+
let groupCount = 0;
|
|
188
|
+
const radius = top === true ? radiusTop : radiusBottom;
|
|
189
|
+
const sign2 = top === true ? 1 : -1;
|
|
190
|
+
for (let x = 1; x <= radialSegments; x++) {
|
|
191
|
+
vertices.push(0, halfHeight * sign2, 0);
|
|
192
|
+
normals.push(0, sign2, 0);
|
|
193
|
+
uvs.push(0.5, 0.5);
|
|
194
|
+
index++;
|
|
195
|
+
}
|
|
196
|
+
const centerIndexEnd = index;
|
|
197
|
+
for (let x = 0; x <= radialSegments; x++) {
|
|
198
|
+
const u = x / radialSegments;
|
|
199
|
+
const theta = u * thetaLength + thetaStart;
|
|
200
|
+
const cosTheta = Math.cos(theta);
|
|
201
|
+
const sinTheta = Math.sin(theta);
|
|
202
|
+
vertices.push(radius * sinTheta, halfHeight * sign2, radius * cosTheta);
|
|
203
|
+
normals.push(0, sign2, 0);
|
|
204
|
+
uvs.push(cosTheta * 0.5 + 0.5, sinTheta * 0.5 * sign2 + 0.5);
|
|
205
|
+
index++;
|
|
206
|
+
}
|
|
207
|
+
for (let x = 0; x < radialSegments; x++) {
|
|
208
|
+
const c = centerIndexStart + x;
|
|
209
|
+
const i = centerIndexEnd + x;
|
|
210
|
+
if (top === true) indices.push(i, i + 1, c);
|
|
211
|
+
else indices.push(i + 1, i, c);
|
|
212
|
+
groupCount += 3;
|
|
213
|
+
}
|
|
214
|
+
groups.push({ start: groupStart, count: groupCount, materialIndex: top === true ? 1 : 2 });
|
|
215
|
+
groupStart += groupCount;
|
|
216
|
+
};
|
|
217
|
+
generateTorso();
|
|
218
|
+
if (openEnded === false) {
|
|
219
|
+
if (radiusTop > 0) generateCap(true);
|
|
220
|
+
if (radiusBottom > 0) generateCap(false);
|
|
221
|
+
}
|
|
222
|
+
return { vertices, normals, uvs, indices, groups };
|
|
223
|
+
}
|
|
224
|
+
function cylinder(radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
225
|
+
const parameters = { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength };
|
|
226
|
+
return pack("CylinderGeometry", parameters, cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
|
|
227
|
+
}
|
|
228
|
+
function cone(radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
229
|
+
const parameters = { radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength };
|
|
230
|
+
return pack("ConeGeometry", parameters, cylinderArrays(0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
|
|
231
|
+
}
|
|
232
|
+
function sphere(radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI) {
|
|
233
|
+
const parameters = { radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength };
|
|
234
|
+
widthSegments = Math.max(3, Math.floor(widthSegments));
|
|
235
|
+
heightSegments = Math.max(2, Math.floor(heightSegments));
|
|
236
|
+
const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI);
|
|
237
|
+
let index = 0;
|
|
238
|
+
const grid = [];
|
|
239
|
+
const indices = [];
|
|
240
|
+
const vertices = [];
|
|
241
|
+
const normals = [];
|
|
242
|
+
const uvs = [];
|
|
243
|
+
for (let iy = 0; iy <= heightSegments; iy++) {
|
|
244
|
+
const verticesRow = [];
|
|
245
|
+
const v = iy / heightSegments;
|
|
246
|
+
let uOffset = 0;
|
|
247
|
+
if (iy === 0 && thetaStart === 0) uOffset = 0.5 / widthSegments;
|
|
248
|
+
else if (iy === heightSegments && thetaEnd === Math.PI) uOffset = -0.5 / widthSegments;
|
|
249
|
+
for (let ix = 0; ix <= widthSegments; ix++) {
|
|
250
|
+
const u = ix / widthSegments;
|
|
251
|
+
const x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
|
|
252
|
+
const y = radius * Math.cos(thetaStart + v * thetaLength);
|
|
253
|
+
const z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
|
|
254
|
+
vertices.push(x, y, z);
|
|
255
|
+
const n = normalize3(x, y, z);
|
|
256
|
+
normals.push(n[0], n[1], n[2]);
|
|
257
|
+
uvs.push(u + uOffset, 1 - v);
|
|
258
|
+
verticesRow.push(index++);
|
|
259
|
+
}
|
|
260
|
+
grid.push(verticesRow);
|
|
261
|
+
}
|
|
262
|
+
for (let iy = 0; iy < heightSegments; iy++) {
|
|
263
|
+
for (let ix = 0; ix < widthSegments; ix++) {
|
|
264
|
+
const a = grid[iy][ix + 1];
|
|
265
|
+
const b = grid[iy][ix];
|
|
266
|
+
const c = grid[iy + 1][ix];
|
|
267
|
+
const d = grid[iy + 1][ix + 1];
|
|
268
|
+
if (iy !== 0 || thetaStart > 0) indices.push(a, b, d);
|
|
269
|
+
if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return pack("SphereGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
273
|
+
}
|
|
274
|
+
function plane(width = 1, height = 1, widthSegments = 1, heightSegments = 1) {
|
|
275
|
+
const parameters = { width, height, widthSegments, heightSegments };
|
|
276
|
+
const widthHalf = width / 2;
|
|
277
|
+
const heightHalf = height / 2;
|
|
278
|
+
const gridX = Math.floor(widthSegments);
|
|
279
|
+
const gridY = Math.floor(heightSegments);
|
|
280
|
+
const gridX1 = gridX + 1;
|
|
281
|
+
const gridY1 = gridY + 1;
|
|
282
|
+
const segmentWidth = width / gridX;
|
|
283
|
+
const segmentHeight = height / gridY;
|
|
284
|
+
const indices = [];
|
|
285
|
+
const vertices = [];
|
|
286
|
+
const normals = [];
|
|
287
|
+
const uvs = [];
|
|
288
|
+
for (let iy = 0; iy < gridY1; iy++) {
|
|
289
|
+
const y = iy * segmentHeight - heightHalf;
|
|
290
|
+
for (let ix = 0; ix < gridX1; ix++) {
|
|
291
|
+
const x = ix * segmentWidth - widthHalf;
|
|
292
|
+
vertices.push(x, -y, 0);
|
|
293
|
+
normals.push(0, 0, 1);
|
|
294
|
+
uvs.push(ix / gridX);
|
|
295
|
+
uvs.push(1 - iy / gridY);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (let iy = 0; iy < gridY; iy++) {
|
|
299
|
+
for (let ix = 0; ix < gridX; ix++) {
|
|
300
|
+
const a = ix + gridX1 * iy;
|
|
301
|
+
const b = ix + gridX1 * (iy + 1);
|
|
302
|
+
const c = ix + 1 + gridX1 * (iy + 1);
|
|
303
|
+
const d = ix + 1 + gridX1 * iy;
|
|
304
|
+
indices.push(a, b, d);
|
|
305
|
+
indices.push(b, c, d);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return pack("PlaneGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
309
|
+
}
|
|
310
|
+
function circle(radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
311
|
+
const parameters = { radius, segments, thetaStart, thetaLength };
|
|
312
|
+
segments = Math.max(3, segments);
|
|
313
|
+
const indices = [];
|
|
314
|
+
const vertices = [];
|
|
315
|
+
const normals = [];
|
|
316
|
+
const uvs = [];
|
|
317
|
+
vertices.push(0, 0, 0);
|
|
318
|
+
normals.push(0, 0, 1);
|
|
319
|
+
uvs.push(0.5, 0.5);
|
|
320
|
+
for (let s = 0, i = 3; s <= segments; s++, i += 3) {
|
|
321
|
+
const segment = thetaStart + s / segments * thetaLength;
|
|
322
|
+
vertices.push(radius * Math.cos(segment), radius * Math.sin(segment), 0);
|
|
323
|
+
normals.push(0, 0, 1);
|
|
324
|
+
uvs.push((vertices[i] / radius + 1) / 2, (vertices[i + 1] / radius + 1) / 2);
|
|
325
|
+
}
|
|
326
|
+
for (let i = 1; i <= segments; i++) indices.push(i, i + 1, 0);
|
|
327
|
+
return pack("CircleGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
328
|
+
}
|
|
329
|
+
function ring(innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
330
|
+
const parameters = { innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength };
|
|
331
|
+
thetaSegments = Math.max(3, thetaSegments);
|
|
332
|
+
phiSegments = Math.max(1, phiSegments);
|
|
333
|
+
const indices = [];
|
|
334
|
+
const vertices = [];
|
|
335
|
+
const normals = [];
|
|
336
|
+
const uvs = [];
|
|
337
|
+
let radius = innerRadius;
|
|
338
|
+
const radiusStep = (outerRadius - innerRadius) / phiSegments;
|
|
339
|
+
for (let j = 0; j <= phiSegments; j++) {
|
|
340
|
+
for (let i = 0; i <= thetaSegments; i++) {
|
|
341
|
+
const segment = thetaStart + i / thetaSegments * thetaLength;
|
|
342
|
+
const x = radius * Math.cos(segment);
|
|
343
|
+
const y = radius * Math.sin(segment);
|
|
344
|
+
vertices.push(x, y, 0);
|
|
345
|
+
normals.push(0, 0, 1);
|
|
346
|
+
uvs.push((x / outerRadius + 1) / 2, (y / outerRadius + 1) / 2);
|
|
347
|
+
}
|
|
348
|
+
radius += radiusStep;
|
|
349
|
+
}
|
|
350
|
+
for (let j = 0; j < phiSegments; j++) {
|
|
351
|
+
const thetaSegmentLevel = j * (thetaSegments + 1);
|
|
352
|
+
for (let i = 0; i < thetaSegments; i++) {
|
|
353
|
+
const segment = i + thetaSegmentLevel;
|
|
354
|
+
const a = segment;
|
|
355
|
+
const b = segment + thetaSegments + 1;
|
|
356
|
+
const c = segment + thetaSegments + 2;
|
|
357
|
+
const d = segment + 1;
|
|
358
|
+
indices.push(a, b, d);
|
|
359
|
+
indices.push(b, c, d);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return pack("RingGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
363
|
+
}
|
|
364
|
+
function lathe(points, segments = 12, phiStart = 0, phiLength = Math.PI * 2) {
|
|
365
|
+
const parameters = { points: points.map(([x, y]) => ({ x, y })), segments, phiStart, phiLength };
|
|
366
|
+
segments = Math.floor(segments);
|
|
367
|
+
phiLength = Math.max(0, Math.min(Math.PI * 2, phiLength));
|
|
368
|
+
const indices = [];
|
|
369
|
+
const vertices = [];
|
|
370
|
+
const uvs = [];
|
|
371
|
+
const initNormals = [];
|
|
372
|
+
const normals = [];
|
|
373
|
+
const inverseSegments = 1 / segments;
|
|
374
|
+
const normal = { x: 0, y: 0, z: 0 };
|
|
375
|
+
const curNormal = { x: 0, y: 0, z: 0 };
|
|
376
|
+
const prevNormal = { x: 0, y: 0, z: 0 };
|
|
377
|
+
let dx = 0;
|
|
378
|
+
let dy = 0;
|
|
379
|
+
const last = points.length - 1;
|
|
380
|
+
const px = (j) => points[j][0];
|
|
381
|
+
const py = (j) => points[j][1];
|
|
382
|
+
const setNormalized = () => {
|
|
383
|
+
const n = normalize3(normal.x, normal.y, normal.z);
|
|
384
|
+
normal.x = n[0];
|
|
385
|
+
normal.y = n[1];
|
|
386
|
+
normal.z = n[2];
|
|
387
|
+
};
|
|
388
|
+
for (let j = 0; j <= last; j++) {
|
|
389
|
+
if (j === 0) {
|
|
390
|
+
dx = px(j + 1) - px(j);
|
|
391
|
+
dy = py(j + 1) - py(j);
|
|
392
|
+
normal.x = dy * 1;
|
|
393
|
+
normal.y = -dx;
|
|
394
|
+
normal.z = dy * 0;
|
|
395
|
+
prevNormal.x = normal.x;
|
|
396
|
+
prevNormal.y = normal.y;
|
|
397
|
+
prevNormal.z = normal.z;
|
|
398
|
+
setNormalized();
|
|
399
|
+
initNormals.push(normal.x, normal.y, normal.z);
|
|
400
|
+
} else if (j === last) {
|
|
401
|
+
initNormals.push(prevNormal.x, prevNormal.y, prevNormal.z);
|
|
402
|
+
} else {
|
|
403
|
+
dx = px(j + 1) - px(j);
|
|
404
|
+
dy = py(j + 1) - py(j);
|
|
405
|
+
normal.x = dy * 1;
|
|
406
|
+
normal.y = -dx;
|
|
407
|
+
normal.z = dy * 0;
|
|
408
|
+
curNormal.x = normal.x;
|
|
409
|
+
curNormal.y = normal.y;
|
|
410
|
+
curNormal.z = normal.z;
|
|
411
|
+
normal.x += prevNormal.x;
|
|
412
|
+
normal.y += prevNormal.y;
|
|
413
|
+
normal.z += prevNormal.z;
|
|
414
|
+
setNormalized();
|
|
415
|
+
initNormals.push(normal.x, normal.y, normal.z);
|
|
416
|
+
prevNormal.x = curNormal.x;
|
|
417
|
+
prevNormal.y = curNormal.y;
|
|
418
|
+
prevNormal.z = curNormal.z;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
for (let i = 0; i <= segments; i++) {
|
|
422
|
+
const phi = phiStart + i * inverseSegments * phiLength;
|
|
423
|
+
const sin = Math.sin(phi);
|
|
424
|
+
const cos = Math.cos(phi);
|
|
425
|
+
for (let j = 0; j <= last; j++) {
|
|
426
|
+
vertices.push(px(j) * sin, py(j), px(j) * cos);
|
|
427
|
+
uvs.push(i / segments, j / (points.length - 1));
|
|
428
|
+
const x = initNormals[3 * j + 0] * sin;
|
|
429
|
+
const y = initNormals[3 * j + 1];
|
|
430
|
+
const z = initNormals[3 * j + 0] * cos;
|
|
431
|
+
normals.push(x, y, z);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
for (let i = 0; i < segments; i++) {
|
|
435
|
+
for (let j = 0; j < points.length - 1; j++) {
|
|
436
|
+
const base = j + i * points.length;
|
|
437
|
+
const a = base;
|
|
438
|
+
const b = base + points.length;
|
|
439
|
+
const c = base + points.length + 1;
|
|
440
|
+
const d = base + 1;
|
|
441
|
+
indices.push(a, b, d);
|
|
442
|
+
indices.push(c, d, b);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return pack("LatheGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
446
|
+
}
|
|
447
|
+
function torus(radius = 1, tube2 = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2) {
|
|
448
|
+
const parameters = { radius, tube: tube2, radialSegments, tubularSegments, arc };
|
|
449
|
+
radialSegments = Math.floor(radialSegments);
|
|
450
|
+
tubularSegments = Math.floor(tubularSegments);
|
|
451
|
+
const indices = [];
|
|
452
|
+
const vertices = [];
|
|
453
|
+
const normals = [];
|
|
454
|
+
const uvs = [];
|
|
455
|
+
for (let j = 0; j <= radialSegments; j++) {
|
|
456
|
+
for (let i = 0; i <= tubularSegments; i++) {
|
|
457
|
+
const u = i / tubularSegments * arc;
|
|
458
|
+
const v = j / radialSegments * Math.PI * 2;
|
|
459
|
+
const x = (radius + tube2 * Math.cos(v)) * Math.cos(u);
|
|
460
|
+
const y = (radius + tube2 * Math.cos(v)) * Math.sin(u);
|
|
461
|
+
const z = tube2 * Math.sin(v);
|
|
462
|
+
vertices.push(x, y, z);
|
|
463
|
+
const cx = radius * Math.cos(u);
|
|
464
|
+
const cy = radius * Math.sin(u);
|
|
465
|
+
const n = normalize3(x - cx, y - cy, z - 0);
|
|
466
|
+
normals.push(n[0], n[1], n[2]);
|
|
467
|
+
uvs.push(i / tubularSegments);
|
|
468
|
+
uvs.push(j / radialSegments);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
for (let j = 1; j <= radialSegments; j++) {
|
|
472
|
+
for (let i = 1; i <= tubularSegments; i++) {
|
|
473
|
+
const a = (tubularSegments + 1) * j + i - 1;
|
|
474
|
+
const b = (tubularSegments + 1) * (j - 1) + i - 1;
|
|
475
|
+
const c = (tubularSegments + 1) * (j - 1) + i;
|
|
476
|
+
const d = (tubularSegments + 1) * j + i;
|
|
477
|
+
indices.push(a, b, d);
|
|
478
|
+
indices.push(b, c, d);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return pack("TorusGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
482
|
+
}
|
|
483
|
+
function difference(size, subtract) {
|
|
484
|
+
const [w, h, d] = size;
|
|
485
|
+
const hw = w / 2;
|
|
486
|
+
const hh = h / 2;
|
|
487
|
+
const hd = d / 2;
|
|
488
|
+
const holes = subtract.map((hole) => ({ x0: Math.max(-hw, Math.min(hole.min[0], hole.max[0])), x1: Math.min(hw, Math.max(hole.min[0], hole.max[0])), y0: Math.max(-hh, Math.min(hole.min[1], hole.max[1])), y1: Math.min(hh, Math.max(hole.min[1], hole.max[1])) })).filter((hole) => hole.x1 > hole.x0 && hole.y1 > hole.y0);
|
|
489
|
+
const cuts = (lo, hi, values) => [.../* @__PURE__ */ new Set([lo, hi, ...values.filter((v) => v > lo && v < hi)])].sort((a, b) => a - b);
|
|
490
|
+
const xs = cuts(-hw, hw, holes.flatMap((hole) => [hole.x0, hole.x1]));
|
|
491
|
+
const ys = cuts(-hh, hh, holes.flatMap((hole) => [hole.y0, hole.y1]));
|
|
492
|
+
const nx = xs.length - 1;
|
|
493
|
+
const ny = ys.length - 1;
|
|
494
|
+
const solid = (i, j) => {
|
|
495
|
+
if (i < 0 || j < 0 || i >= nx || j >= ny) return false;
|
|
496
|
+
const cx = (xs[i] + xs[i + 1]) / 2;
|
|
497
|
+
const cy = (ys[j] + ys[j + 1]) / 2;
|
|
498
|
+
return !holes.some((hole) => cx > hole.x0 && cx < hole.x1 && cy > hole.y0 && cy < hole.y1);
|
|
499
|
+
};
|
|
500
|
+
const positions = [];
|
|
501
|
+
const normals = [];
|
|
502
|
+
const uvs = [];
|
|
503
|
+
const indices = [];
|
|
504
|
+
const quad = (corners, normal, uv) => {
|
|
505
|
+
const base = positions.length / 3;
|
|
506
|
+
for (let k = 0; k < 4; k++) {
|
|
507
|
+
positions.push(...corners[k]);
|
|
508
|
+
normals.push(...normal);
|
|
509
|
+
uvs.push(...uv[k]);
|
|
510
|
+
}
|
|
511
|
+
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
512
|
+
};
|
|
513
|
+
const fu = (x) => (x + hw) / w;
|
|
514
|
+
const fv = (y) => (y + hh) / h;
|
|
515
|
+
for (let j = 0; j < ny; j++) {
|
|
516
|
+
for (let i = 0; i < nx; i++) {
|
|
517
|
+
if (!solid(i, j)) continue;
|
|
518
|
+
const x0 = xs[i];
|
|
519
|
+
const x1 = xs[i + 1];
|
|
520
|
+
const y0 = ys[j];
|
|
521
|
+
const y1 = ys[j + 1];
|
|
522
|
+
quad([[x0, y0, hd], [x1, y0, hd], [x1, y1, hd], [x0, y1, hd]], [0, 0, 1], [[fu(x0), fv(y0)], [fu(x1), fv(y0)], [fu(x1), fv(y1)], [fu(x0), fv(y1)]]);
|
|
523
|
+
quad([[x1, y0, -hd], [x0, y0, -hd], [x0, y1, -hd], [x1, y1, -hd]], [0, 0, -1], [[1 - fu(x1), fv(y0)], [1 - fu(x0), fv(y0)], [1 - fu(x0), fv(y1)], [1 - fu(x1), fv(y1)]]);
|
|
524
|
+
const zu = (z) => (z + hd) / d;
|
|
525
|
+
if (!solid(i - 1, j)) quad([[x0, y0, -hd], [x0, y0, hd], [x0, y1, hd], [x0, y1, -hd]], [-1, 0, 0], [[zu(-hd), fv(y0)], [zu(hd), fv(y0)], [zu(hd), fv(y1)], [zu(-hd), fv(y1)]]);
|
|
526
|
+
if (!solid(i + 1, j)) quad([[x1, y0, hd], [x1, y0, -hd], [x1, y1, -hd], [x1, y1, hd]], [1, 0, 0], [[zu(hd), fv(y0)], [zu(-hd), fv(y0)], [zu(-hd), fv(y1)], [zu(hd), fv(y1)]]);
|
|
527
|
+
if (!solid(i, j - 1)) quad([[x0, y0, -hd], [x1, y0, -hd], [x1, y0, hd], [x0, y0, hd]], [0, -1, 0], [[fu(x0), zu(-hd)], [fu(x1), zu(-hd)], [fu(x1), zu(hd)], [fu(x0), zu(hd)]]);
|
|
528
|
+
if (!solid(i, j + 1)) quad([[x0, y1, hd], [x1, y1, hd], [x1, y1, -hd], [x0, y1, -hd]], [0, 1, 0], [[fu(x0), zu(hd)], [fu(x1), zu(hd)], [fu(x1), zu(-hd)], [fu(x0), zu(-hd)]]);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
type: "BufferGeometry",
|
|
533
|
+
parameters: { size: [w, h, d], subtract: holes.map((hole) => ({ min: [hole.x0, hole.y0], max: [hole.x1, hole.y1] })) },
|
|
534
|
+
positions: new Float32Array(positions),
|
|
535
|
+
normals: new Float32Array(normals),
|
|
536
|
+
uvs: new Float32Array(uvs),
|
|
537
|
+
indices: indexArray(indices),
|
|
538
|
+
groups: []
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
var Earcut = {
|
|
542
|
+
triangulate: function(data, holeIndices, dim = 2) {
|
|
543
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
544
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
545
|
+
let outerNode = linkedList(data, 0, outerLen, dim, true);
|
|
546
|
+
const triangles = [];
|
|
547
|
+
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
|
|
548
|
+
let minX, minY, maxX, maxY, x, y, invSize;
|
|
549
|
+
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
550
|
+
if (data.length > 80 * dim) {
|
|
551
|
+
minX = maxX = data[0];
|
|
552
|
+
minY = maxY = data[1];
|
|
553
|
+
for (let i = dim; i < outerLen; i += dim) {
|
|
554
|
+
x = data[i];
|
|
555
|
+
y = data[i + 1];
|
|
556
|
+
if (x < minX) minX = x;
|
|
557
|
+
if (y < minY) minY = y;
|
|
558
|
+
if (x > maxX) maxX = x;
|
|
559
|
+
if (y > maxY) maxY = y;
|
|
560
|
+
}
|
|
561
|
+
invSize = Math.max(maxX - minX, maxY - minY);
|
|
562
|
+
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
563
|
+
}
|
|
564
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
|
|
565
|
+
return triangles;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
function linkedList(data, start, end, dim, clockwise) {
|
|
569
|
+
let i, last;
|
|
570
|
+
if (clockwise === signedArea(data, start, end, dim) > 0) {
|
|
571
|
+
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
572
|
+
} else {
|
|
573
|
+
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
574
|
+
}
|
|
575
|
+
if (last && equals(last, last.next)) {
|
|
576
|
+
removeNode(last);
|
|
577
|
+
last = last.next;
|
|
578
|
+
}
|
|
579
|
+
return last;
|
|
580
|
+
}
|
|
581
|
+
function filterPoints(start, end) {
|
|
582
|
+
if (!start) return start;
|
|
583
|
+
if (!end) end = start;
|
|
584
|
+
let p = start, again;
|
|
585
|
+
do {
|
|
586
|
+
again = false;
|
|
587
|
+
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
|
|
588
|
+
removeNode(p);
|
|
589
|
+
p = end = p.prev;
|
|
590
|
+
if (p === p.next) break;
|
|
591
|
+
again = true;
|
|
592
|
+
} else {
|
|
593
|
+
p = p.next;
|
|
594
|
+
}
|
|
595
|
+
} while (again || p !== end);
|
|
596
|
+
return end;
|
|
597
|
+
}
|
|
598
|
+
function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
599
|
+
if (!ear) return;
|
|
600
|
+
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
|
|
601
|
+
let stop = ear, prev, next;
|
|
602
|
+
while (ear.prev !== ear.next) {
|
|
603
|
+
prev = ear.prev;
|
|
604
|
+
next = ear.next;
|
|
605
|
+
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
|
|
606
|
+
triangles.push(prev.i / dim | 0);
|
|
607
|
+
triangles.push(ear.i / dim | 0);
|
|
608
|
+
triangles.push(next.i / dim | 0);
|
|
609
|
+
removeNode(ear);
|
|
610
|
+
ear = next.next;
|
|
611
|
+
stop = next.next;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
ear = next;
|
|
615
|
+
if (ear === stop) {
|
|
616
|
+
if (!pass) {
|
|
617
|
+
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
|
|
618
|
+
} else if (pass === 1) {
|
|
619
|
+
ear = cureLocalIntersections(filterPoints(ear), triangles, dim);
|
|
620
|
+
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
|
|
621
|
+
} else if (pass === 2) {
|
|
622
|
+
splitEarcut(ear, triangles, dim, minX, minY, invSize);
|
|
623
|
+
}
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function isEar(ear) {
|
|
629
|
+
const a = ear.prev, b = ear, c = ear.next;
|
|
630
|
+
if (area(a, b, c) >= 0) return false;
|
|
631
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
632
|
+
const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx, y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy, x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx, y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy;
|
|
633
|
+
let p = c.next;
|
|
634
|
+
while (p !== a) {
|
|
635
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
636
|
+
p = p.next;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
function isEarHashed(ear, minX, minY, invSize) {
|
|
641
|
+
const a = ear.prev, b = ear, c = ear.next;
|
|
642
|
+
if (area(a, b, c) >= 0) return false;
|
|
643
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
644
|
+
const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx, y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy, x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx, y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy;
|
|
645
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize), maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
646
|
+
let p = ear.prevZ, n = ear.nextZ;
|
|
647
|
+
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
648
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
649
|
+
p = p.prevZ;
|
|
650
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
651
|
+
n = n.nextZ;
|
|
652
|
+
}
|
|
653
|
+
while (p && p.z >= minZ) {
|
|
654
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
655
|
+
p = p.prevZ;
|
|
656
|
+
}
|
|
657
|
+
while (n && n.z <= maxZ) {
|
|
658
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
659
|
+
n = n.nextZ;
|
|
660
|
+
}
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
function cureLocalIntersections(start, triangles, dim) {
|
|
664
|
+
let p = start;
|
|
665
|
+
do {
|
|
666
|
+
const a = p.prev, b = p.next.next;
|
|
667
|
+
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
668
|
+
triangles.push(a.i / dim | 0);
|
|
669
|
+
triangles.push(p.i / dim | 0);
|
|
670
|
+
triangles.push(b.i / dim | 0);
|
|
671
|
+
removeNode(p);
|
|
672
|
+
removeNode(p.next);
|
|
673
|
+
p = start = b;
|
|
674
|
+
}
|
|
675
|
+
p = p.next;
|
|
676
|
+
} while (p !== start);
|
|
677
|
+
return filterPoints(p);
|
|
678
|
+
}
|
|
679
|
+
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
680
|
+
let a = start;
|
|
681
|
+
do {
|
|
682
|
+
let b = a.next.next;
|
|
683
|
+
while (b !== a.prev) {
|
|
684
|
+
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
685
|
+
let c = splitPolygon(a, b);
|
|
686
|
+
a = filterPoints(a, a.next);
|
|
687
|
+
c = filterPoints(c, c.next);
|
|
688
|
+
earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
|
|
689
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
b = b.next;
|
|
693
|
+
}
|
|
694
|
+
a = a.next;
|
|
695
|
+
} while (a !== start);
|
|
696
|
+
}
|
|
697
|
+
function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
698
|
+
const queue = [];
|
|
699
|
+
let i, len2, start, end, list2;
|
|
700
|
+
for (i = 0, len2 = holeIndices.length; i < len2; i++) {
|
|
701
|
+
start = holeIndices[i] * dim;
|
|
702
|
+
end = i < len2 - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
703
|
+
list2 = linkedList(data, start, end, dim, false);
|
|
704
|
+
if (list2 === list2.next) list2.steiner = true;
|
|
705
|
+
queue.push(getLeftmost(list2));
|
|
706
|
+
}
|
|
707
|
+
queue.sort(compareX);
|
|
708
|
+
for (i = 0; i < queue.length; i++) {
|
|
709
|
+
outerNode = eliminateHole(queue[i], outerNode);
|
|
710
|
+
}
|
|
711
|
+
return outerNode;
|
|
712
|
+
}
|
|
713
|
+
function compareX(a, b) {
|
|
714
|
+
return a.x - b.x;
|
|
715
|
+
}
|
|
716
|
+
function eliminateHole(hole, outerNode) {
|
|
717
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
718
|
+
if (!bridge) {
|
|
719
|
+
return outerNode;
|
|
720
|
+
}
|
|
721
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
722
|
+
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
723
|
+
return filterPoints(bridge, bridge.next);
|
|
724
|
+
}
|
|
725
|
+
function findHoleBridge(hole, outerNode) {
|
|
726
|
+
let p = outerNode, qx = -Infinity, m;
|
|
727
|
+
const hx = hole.x, hy = hole.y;
|
|
728
|
+
do {
|
|
729
|
+
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
730
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
731
|
+
if (x <= hx && x > qx) {
|
|
732
|
+
qx = x;
|
|
733
|
+
m = p.x < p.next.x ? p : p.next;
|
|
734
|
+
if (x === hx) return m;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
p = p.next;
|
|
738
|
+
} while (p !== outerNode);
|
|
739
|
+
if (!m) return null;
|
|
740
|
+
const stop = m, mx = m.x, my = m.y;
|
|
741
|
+
let tanMin = Infinity, tan;
|
|
742
|
+
p = m;
|
|
743
|
+
do {
|
|
744
|
+
if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
745
|
+
tan = Math.abs(hy - p.y) / (hx - p.x);
|
|
746
|
+
if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) {
|
|
747
|
+
m = p;
|
|
748
|
+
tanMin = tan;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
p = p.next;
|
|
752
|
+
} while (p !== stop);
|
|
753
|
+
return m;
|
|
754
|
+
}
|
|
755
|
+
function sectorContainsSector(m, p) {
|
|
756
|
+
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
|
|
757
|
+
}
|
|
758
|
+
function indexCurve(start, minX, minY, invSize) {
|
|
759
|
+
let p = start;
|
|
760
|
+
do {
|
|
761
|
+
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
762
|
+
p.prevZ = p.prev;
|
|
763
|
+
p.nextZ = p.next;
|
|
764
|
+
p = p.next;
|
|
765
|
+
} while (p !== start);
|
|
766
|
+
p.prevZ.nextZ = null;
|
|
767
|
+
p.prevZ = null;
|
|
768
|
+
sortLinked(p);
|
|
769
|
+
}
|
|
770
|
+
function sortLinked(list2) {
|
|
771
|
+
let i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;
|
|
772
|
+
do {
|
|
773
|
+
p = list2;
|
|
774
|
+
list2 = null;
|
|
775
|
+
tail = null;
|
|
776
|
+
numMerges = 0;
|
|
777
|
+
while (p) {
|
|
778
|
+
numMerges++;
|
|
779
|
+
q = p;
|
|
780
|
+
pSize = 0;
|
|
781
|
+
for (i = 0; i < inSize; i++) {
|
|
782
|
+
pSize++;
|
|
783
|
+
q = q.nextZ;
|
|
784
|
+
if (!q) break;
|
|
785
|
+
}
|
|
786
|
+
qSize = inSize;
|
|
787
|
+
while (pSize > 0 || qSize > 0 && q) {
|
|
788
|
+
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
|
|
789
|
+
e = p;
|
|
790
|
+
p = p.nextZ;
|
|
791
|
+
pSize--;
|
|
792
|
+
} else {
|
|
793
|
+
e = q;
|
|
794
|
+
q = q.nextZ;
|
|
795
|
+
qSize--;
|
|
796
|
+
}
|
|
797
|
+
if (tail) tail.nextZ = e;
|
|
798
|
+
else list2 = e;
|
|
799
|
+
e.prevZ = tail;
|
|
800
|
+
tail = e;
|
|
801
|
+
}
|
|
802
|
+
p = q;
|
|
803
|
+
}
|
|
804
|
+
tail.nextZ = null;
|
|
805
|
+
inSize *= 2;
|
|
806
|
+
} while (numMerges > 1);
|
|
807
|
+
return list2;
|
|
808
|
+
}
|
|
809
|
+
function zOrder(x, y, minX, minY, invSize) {
|
|
810
|
+
x = (x - minX) * invSize | 0;
|
|
811
|
+
y = (y - minY) * invSize | 0;
|
|
812
|
+
x = (x | x << 8) & 16711935;
|
|
813
|
+
x = (x | x << 4) & 252645135;
|
|
814
|
+
x = (x | x << 2) & 858993459;
|
|
815
|
+
x = (x | x << 1) & 1431655765;
|
|
816
|
+
y = (y | y << 8) & 16711935;
|
|
817
|
+
y = (y | y << 4) & 252645135;
|
|
818
|
+
y = (y | y << 2) & 858993459;
|
|
819
|
+
y = (y | y << 1) & 1431655765;
|
|
820
|
+
return x | y << 1;
|
|
821
|
+
}
|
|
822
|
+
function getLeftmost(start) {
|
|
823
|
+
let p = start, leftmost = start;
|
|
824
|
+
do {
|
|
825
|
+
if (p.x < leftmost.x || p.x === leftmost.x && p.y < leftmost.y) leftmost = p;
|
|
826
|
+
p = p.next;
|
|
827
|
+
} while (p !== start);
|
|
828
|
+
return leftmost;
|
|
829
|
+
}
|
|
830
|
+
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
831
|
+
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && (ax - px) * (by - py) >= (bx - px) * (ay - py) && (bx - px) * (cy - py) >= (cx - px) * (by - py);
|
|
832
|
+
}
|
|
833
|
+
function isValidDiagonal(a, b) {
|
|
834
|
+
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
|
|
835
|
+
(locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
|
|
836
|
+
(area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
|
|
837
|
+
equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0);
|
|
838
|
+
}
|
|
839
|
+
function area(p, q, r) {
|
|
840
|
+
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
841
|
+
}
|
|
842
|
+
function equals(p1, p2) {
|
|
843
|
+
return p1.x === p2.x && p1.y === p2.y;
|
|
844
|
+
}
|
|
845
|
+
function intersects(p1, q1, p2, q2) {
|
|
846
|
+
const o1 = sign(area(p1, q1, p2));
|
|
847
|
+
const o2 = sign(area(p1, q1, q2));
|
|
848
|
+
const o3 = sign(area(p2, q2, p1));
|
|
849
|
+
const o4 = sign(area(p2, q2, q1));
|
|
850
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
851
|
+
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
|
|
852
|
+
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
|
|
853
|
+
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
|
|
854
|
+
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
function onSegment(p, q, r) {
|
|
858
|
+
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
|
|
859
|
+
}
|
|
860
|
+
function sign(num2) {
|
|
861
|
+
return num2 > 0 ? 1 : num2 < 0 ? -1 : 0;
|
|
862
|
+
}
|
|
863
|
+
function intersectsPolygon(a, b) {
|
|
864
|
+
let p = a;
|
|
865
|
+
do {
|
|
866
|
+
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) return true;
|
|
867
|
+
p = p.next;
|
|
868
|
+
} while (p !== a);
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
function locallyInside(a, b) {
|
|
872
|
+
return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
|
|
873
|
+
}
|
|
874
|
+
function middleInside(a, b) {
|
|
875
|
+
let p = a, inside = false;
|
|
876
|
+
const px = (a.x + b.x) / 2, py = (a.y + b.y) / 2;
|
|
877
|
+
do {
|
|
878
|
+
if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)
|
|
879
|
+
inside = !inside;
|
|
880
|
+
p = p.next;
|
|
881
|
+
} while (p !== a);
|
|
882
|
+
return inside;
|
|
883
|
+
}
|
|
884
|
+
function splitPolygon(a, b) {
|
|
885
|
+
const a2 = new Node(a.i, a.x, a.y), b2 = new Node(b.i, b.x, b.y), an = a.next, bp = b.prev;
|
|
886
|
+
a.next = b;
|
|
887
|
+
b.prev = a;
|
|
888
|
+
a2.next = an;
|
|
889
|
+
an.prev = a2;
|
|
890
|
+
b2.next = a2;
|
|
891
|
+
a2.prev = b2;
|
|
892
|
+
bp.next = b2;
|
|
893
|
+
b2.prev = bp;
|
|
894
|
+
return b2;
|
|
895
|
+
}
|
|
896
|
+
function insertNode(i, x, y, last) {
|
|
897
|
+
const p = new Node(i, x, y);
|
|
898
|
+
if (!last) {
|
|
899
|
+
p.prev = p;
|
|
900
|
+
p.next = p;
|
|
901
|
+
} else {
|
|
902
|
+
p.next = last.next;
|
|
903
|
+
p.prev = last;
|
|
904
|
+
last.next.prev = p;
|
|
905
|
+
last.next = p;
|
|
906
|
+
}
|
|
907
|
+
return p;
|
|
908
|
+
}
|
|
909
|
+
function removeNode(p) {
|
|
910
|
+
p.next.prev = p.prev;
|
|
911
|
+
p.prev.next = p.next;
|
|
912
|
+
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
|
|
913
|
+
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
914
|
+
}
|
|
915
|
+
function Node(i, x, y) {
|
|
916
|
+
this.i = i;
|
|
917
|
+
this.x = x;
|
|
918
|
+
this.y = y;
|
|
919
|
+
this.prev = null;
|
|
920
|
+
this.next = null;
|
|
921
|
+
this.z = 0;
|
|
922
|
+
this.prevZ = null;
|
|
923
|
+
this.nextZ = null;
|
|
924
|
+
this.steiner = false;
|
|
925
|
+
}
|
|
926
|
+
function signedArea(data, start, end, dim) {
|
|
927
|
+
let sum2 = 0;
|
|
928
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
929
|
+
sum2 += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
930
|
+
j = i;
|
|
931
|
+
}
|
|
932
|
+
return sum2;
|
|
933
|
+
}
|
|
934
|
+
var equals2 = (a, b) => a.x === b.x && a.y === b.y;
|
|
935
|
+
var linePoint = (v1, v2, t) => t === 1 ? { x: v2.x, y: v2.y } : { x: (v2.x - v1.x) * t + v1.x, y: (v2.y - v1.y) * t + v1.y };
|
|
936
|
+
function pathPoints(points) {
|
|
937
|
+
const out = [];
|
|
938
|
+
let last;
|
|
939
|
+
for (let i = 1; i < points.length; i++) {
|
|
940
|
+
const v1 = { x: points[i - 1][0], y: points[i - 1][1] };
|
|
941
|
+
const v2 = { x: points[i][0], y: points[i][1] };
|
|
942
|
+
for (let d = 0; d <= 1; d++) {
|
|
943
|
+
const point = linePoint(v1, v2, d / 1);
|
|
944
|
+
if (last && equals2(last, point)) continue;
|
|
945
|
+
out.push(point);
|
|
946
|
+
last = point;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return out;
|
|
950
|
+
}
|
|
951
|
+
function area2(contour) {
|
|
952
|
+
const n = contour.length;
|
|
953
|
+
let a = 0;
|
|
954
|
+
for (let p = n - 1, q = 0; q < n; p = q++) a += contour[p].x * contour[q].y - contour[q].x * contour[p].y;
|
|
955
|
+
return a * 0.5;
|
|
956
|
+
}
|
|
957
|
+
var isClockWise = (pts) => area2(pts) < 0;
|
|
958
|
+
function removeDupEndPts(points) {
|
|
959
|
+
const l = points.length;
|
|
960
|
+
if (l > 2 && equals2(points[l - 1], points[0])) points.pop();
|
|
961
|
+
}
|
|
962
|
+
function triangulateShape(contour, holes) {
|
|
963
|
+
const vertices = [];
|
|
964
|
+
const holeIndices = [];
|
|
965
|
+
const faces = [];
|
|
966
|
+
removeDupEndPts(contour);
|
|
967
|
+
for (const p of contour) vertices.push(p.x, p.y);
|
|
968
|
+
let holeIndex = contour.length;
|
|
969
|
+
holes.forEach(removeDupEndPts);
|
|
970
|
+
for (let i = 0; i < holes.length; i++) {
|
|
971
|
+
holeIndices.push(holeIndex);
|
|
972
|
+
holeIndex += holes[i].length;
|
|
973
|
+
for (const p of holes[i]) vertices.push(p.x, p.y);
|
|
974
|
+
}
|
|
975
|
+
const triangles = Earcut.triangulate(vertices, holeIndices);
|
|
976
|
+
for (let i = 0; i < triangles.length; i += 3) faces.push(triangles.slice(i, i + 3));
|
|
977
|
+
return faces;
|
|
978
|
+
}
|
|
979
|
+
function computeVertexNormals(positions, indices) {
|
|
980
|
+
const normals = new Float32Array(positions.length);
|
|
981
|
+
const get = (array, i) => [array[i * 3], array[i * 3 + 1], array[i * 3 + 2]];
|
|
982
|
+
const face = (vA, vB, vC) => {
|
|
983
|
+
const pA = get(positions, vA);
|
|
984
|
+
const pB = get(positions, vB);
|
|
985
|
+
const pC = get(positions, vC);
|
|
986
|
+
const cb = [pC[0] - pB[0], pC[1] - pB[1], pC[2] - pB[2]];
|
|
987
|
+
const ab = [pA[0] - pB[0], pA[1] - pB[1], pA[2] - pB[2]];
|
|
988
|
+
return [cb[1] * ab[2] - cb[2] * ab[1], cb[2] * ab[0] - cb[0] * ab[2], cb[0] * ab[1] - cb[1] * ab[0]];
|
|
989
|
+
};
|
|
990
|
+
const set = (i, x, y, z) => {
|
|
991
|
+
normals[i * 3] = x;
|
|
992
|
+
normals[i * 3 + 1] = y;
|
|
993
|
+
normals[i * 3 + 2] = z;
|
|
994
|
+
};
|
|
995
|
+
if (indices) {
|
|
996
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
997
|
+
const vA = indices[i];
|
|
998
|
+
const vB = indices[i + 1];
|
|
999
|
+
const vC = indices[i + 2];
|
|
1000
|
+
const cb = face(vA, vB, vC);
|
|
1001
|
+
for (const v of [vA, vB, vC]) {
|
|
1002
|
+
const n = get(normals, v);
|
|
1003
|
+
set(v, n[0] + cb[0], n[1] + cb[1], n[2] + cb[2]);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
} else {
|
|
1007
|
+
for (let i = 0; i < positions.length / 3; i += 3) {
|
|
1008
|
+
const cb = face(i, i + 1, i + 2);
|
|
1009
|
+
set(i, cb[0], cb[1], cb[2]);
|
|
1010
|
+
set(i + 1, cb[0], cb[1], cb[2]);
|
|
1011
|
+
set(i + 2, cb[0], cb[1], cb[2]);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
for (let i = 0; i < normals.length / 3; i++) {
|
|
1015
|
+
const [x, y, z] = get(normals, i);
|
|
1016
|
+
const s = 1 / (Math.sqrt(x * x + y * y + z * z) || 1);
|
|
1017
|
+
set(i, x * s, y * s, z * s);
|
|
1018
|
+
}
|
|
1019
|
+
return normals;
|
|
1020
|
+
}
|
|
1021
|
+
function extrude(outline, depth = 1, steps = 1, bevel = void 0) {
|
|
1022
|
+
const parameters = { shape: outline, depth, steps, bevel: bevel ?? {} };
|
|
1023
|
+
const verticesArray = [];
|
|
1024
|
+
const uvArray = [];
|
|
1025
|
+
const groups = [];
|
|
1026
|
+
const placeholder = [];
|
|
1027
|
+
const bevelEnabled = bevel !== false;
|
|
1028
|
+
const spec = bevel === false || bevel === void 0 ? {} : bevel;
|
|
1029
|
+
let bevelThickness = spec.thickness !== void 0 ? spec.thickness : 0.2;
|
|
1030
|
+
let bevelSize = spec.size !== void 0 ? spec.size : bevelThickness - 0.1;
|
|
1031
|
+
let bevelOffset = spec.offset !== void 0 ? spec.offset : 0;
|
|
1032
|
+
let bevelSegments = spec.segments !== void 0 ? spec.segments : 3;
|
|
1033
|
+
if (!bevelEnabled) {
|
|
1034
|
+
bevelSegments = 0;
|
|
1035
|
+
bevelThickness = 0;
|
|
1036
|
+
bevelSize = 0;
|
|
1037
|
+
bevelOffset = 0;
|
|
1038
|
+
}
|
|
1039
|
+
let vertices = pathPoints(outline.outer);
|
|
1040
|
+
const holes = (outline.holes ?? []).map(pathPoints);
|
|
1041
|
+
const reverse = !isClockWise(vertices);
|
|
1042
|
+
if (reverse) {
|
|
1043
|
+
vertices = vertices.reverse();
|
|
1044
|
+
for (let h = 0; h < holes.length; h++) {
|
|
1045
|
+
const ahole = holes[h];
|
|
1046
|
+
if (isClockWise(ahole)) holes[h] = ahole.reverse();
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
const faces = triangulateShape(vertices, holes);
|
|
1050
|
+
const contour = vertices;
|
|
1051
|
+
for (const ahole of holes) vertices = vertices.concat(ahole);
|
|
1052
|
+
const scalePt2 = (pt, vec, size) => ({ x: pt.x + vec.x * size, y: pt.y + vec.y * size });
|
|
1053
|
+
const vlen = vertices.length;
|
|
1054
|
+
const flen = faces.length;
|
|
1055
|
+
const getBevelVec = (inPt, inPrev, inNext) => {
|
|
1056
|
+
let vTransX;
|
|
1057
|
+
let vTransY;
|
|
1058
|
+
let shrinkBy;
|
|
1059
|
+
const vPrevX = inPt.x - inPrev.x;
|
|
1060
|
+
const vPrevY = inPt.y - inPrev.y;
|
|
1061
|
+
const vNextX = inNext.x - inPt.x;
|
|
1062
|
+
const vNextY = inNext.y - inPt.y;
|
|
1063
|
+
const vPrevLensq = vPrevX * vPrevX + vPrevY * vPrevY;
|
|
1064
|
+
const collinear0 = vPrevX * vNextY - vPrevY * vNextX;
|
|
1065
|
+
if (Math.abs(collinear0) > Number.EPSILON) {
|
|
1066
|
+
const vPrevLen = Math.sqrt(vPrevLensq);
|
|
1067
|
+
const vNextLen = Math.sqrt(vNextX * vNextX + vNextY * vNextY);
|
|
1068
|
+
const ptPrevShiftX = inPrev.x - vPrevY / vPrevLen;
|
|
1069
|
+
const ptPrevShiftY = inPrev.y + vPrevX / vPrevLen;
|
|
1070
|
+
const ptNextShiftX = inNext.x - vNextY / vNextLen;
|
|
1071
|
+
const ptNextShiftY = inNext.y + vNextX / vNextLen;
|
|
1072
|
+
const sf = ((ptNextShiftX - ptPrevShiftX) * vNextY - (ptNextShiftY - ptPrevShiftY) * vNextX) / (vPrevX * vNextY - vPrevY * vNextX);
|
|
1073
|
+
vTransX = ptPrevShiftX + vPrevX * sf - inPt.x;
|
|
1074
|
+
vTransY = ptPrevShiftY + vPrevY * sf - inPt.y;
|
|
1075
|
+
const vTransLensq = vTransX * vTransX + vTransY * vTransY;
|
|
1076
|
+
if (vTransLensq <= 2) return { x: vTransX, y: vTransY };
|
|
1077
|
+
shrinkBy = Math.sqrt(vTransLensq / 2);
|
|
1078
|
+
} else {
|
|
1079
|
+
let directionEq = false;
|
|
1080
|
+
if (vPrevX > Number.EPSILON) {
|
|
1081
|
+
if (vNextX > Number.EPSILON) directionEq = true;
|
|
1082
|
+
} else if (vPrevX < -Number.EPSILON) {
|
|
1083
|
+
if (vNextX < -Number.EPSILON) directionEq = true;
|
|
1084
|
+
} else if (Math.sign(vPrevY) === Math.sign(vNextY)) {
|
|
1085
|
+
directionEq = true;
|
|
1086
|
+
}
|
|
1087
|
+
if (directionEq) {
|
|
1088
|
+
vTransX = -vPrevY;
|
|
1089
|
+
vTransY = vPrevX;
|
|
1090
|
+
shrinkBy = Math.sqrt(vPrevLensq);
|
|
1091
|
+
} else {
|
|
1092
|
+
vTransX = vPrevX;
|
|
1093
|
+
vTransY = vPrevY;
|
|
1094
|
+
shrinkBy = Math.sqrt(vPrevLensq / 2);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return { x: vTransX / shrinkBy, y: vTransY / shrinkBy };
|
|
1098
|
+
};
|
|
1099
|
+
const movements = (ring2) => {
|
|
1100
|
+
const out = [];
|
|
1101
|
+
for (let i = 0, il = ring2.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {
|
|
1102
|
+
if (j === il) j = 0;
|
|
1103
|
+
if (k === il) k = 0;
|
|
1104
|
+
out[i] = getBevelVec(ring2[i], ring2[j], ring2[k]);
|
|
1105
|
+
}
|
|
1106
|
+
return out;
|
|
1107
|
+
};
|
|
1108
|
+
const contourMovements = movements(contour);
|
|
1109
|
+
const holesMovements = [];
|
|
1110
|
+
let verticesMovements = contourMovements.concat();
|
|
1111
|
+
for (const ahole of holes) {
|
|
1112
|
+
const oneHoleMovements = movements(ahole);
|
|
1113
|
+
holesMovements.push(oneHoleMovements);
|
|
1114
|
+
verticesMovements = verticesMovements.concat(oneHoleMovements);
|
|
1115
|
+
}
|
|
1116
|
+
const v = (x, y, z) => {
|
|
1117
|
+
placeholder.push(x);
|
|
1118
|
+
placeholder.push(y);
|
|
1119
|
+
placeholder.push(z);
|
|
1120
|
+
};
|
|
1121
|
+
const bevelLayer = (b, zOf) => {
|
|
1122
|
+
const t = b / bevelSegments;
|
|
1123
|
+
const z = bevelThickness * Math.cos(t * Math.PI / 2);
|
|
1124
|
+
const bs2 = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;
|
|
1125
|
+
for (let i = 0; i < contour.length; i++) {
|
|
1126
|
+
const vert = scalePt2(contour[i], contourMovements[i], bs2);
|
|
1127
|
+
v(vert.x, vert.y, zOf(z));
|
|
1128
|
+
}
|
|
1129
|
+
for (let h = 0; h < holes.length; h++) {
|
|
1130
|
+
const ahole = holes[h];
|
|
1131
|
+
const oneHoleMovements = holesMovements[h];
|
|
1132
|
+
for (let i = 0; i < ahole.length; i++) {
|
|
1133
|
+
const vert = scalePt2(ahole[i], oneHoleMovements[i], bs2);
|
|
1134
|
+
v(vert.x, vert.y, zOf(z));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
for (let b = 0; b < bevelSegments; b++) bevelLayer(b, (z) => -z);
|
|
1139
|
+
const bs = bevelSize + bevelOffset;
|
|
1140
|
+
for (let i = 0; i < vlen; i++) {
|
|
1141
|
+
const vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];
|
|
1142
|
+
v(vert.x, vert.y, 0);
|
|
1143
|
+
}
|
|
1144
|
+
for (let s = 1; s <= steps; s++) {
|
|
1145
|
+
for (let i = 0; i < vlen; i++) {
|
|
1146
|
+
const vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];
|
|
1147
|
+
v(vert.x, vert.y, depth / steps * s);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
for (let b = bevelSegments - 1; b >= 0; b--) bevelLayer(b, (z) => depth + z);
|
|
1151
|
+
const addVertex = (index) => {
|
|
1152
|
+
verticesArray.push(placeholder[index * 3 + 0]);
|
|
1153
|
+
verticesArray.push(placeholder[index * 3 + 1]);
|
|
1154
|
+
verticesArray.push(placeholder[index * 3 + 2]);
|
|
1155
|
+
};
|
|
1156
|
+
const addUV = (x, y) => {
|
|
1157
|
+
uvArray.push(x);
|
|
1158
|
+
uvArray.push(y);
|
|
1159
|
+
};
|
|
1160
|
+
const at = (index, axis) => verticesArray[index * 3 + axis];
|
|
1161
|
+
const f3 = (a, b, c) => {
|
|
1162
|
+
addVertex(a);
|
|
1163
|
+
addVertex(b);
|
|
1164
|
+
addVertex(c);
|
|
1165
|
+
const nextIndex = verticesArray.length / 3;
|
|
1166
|
+
for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at(index, 0), at(index, 1));
|
|
1167
|
+
};
|
|
1168
|
+
const f4 = (a, b, c, d) => {
|
|
1169
|
+
addVertex(a);
|
|
1170
|
+
addVertex(b);
|
|
1171
|
+
addVertex(d);
|
|
1172
|
+
addVertex(b);
|
|
1173
|
+
addVertex(c);
|
|
1174
|
+
addVertex(d);
|
|
1175
|
+
const nextIndex = verticesArray.length / 3;
|
|
1176
|
+
const iA = nextIndex - 6;
|
|
1177
|
+
const iB = nextIndex - 3;
|
|
1178
|
+
const iC = nextIndex - 2;
|
|
1179
|
+
const iD = nextIndex - 1;
|
|
1180
|
+
const byX = Math.abs(at(iA, 1) - at(iB, 1)) < Math.abs(at(iA, 0) - at(iB, 0));
|
|
1181
|
+
const uv = (index) => [byX ? at(index, 0) : at(index, 1), 1 - at(index, 2)];
|
|
1182
|
+
const uvs = [uv(iA), uv(iB), uv(iC), uv(iD)];
|
|
1183
|
+
for (const k of [0, 1, 3, 1, 2, 3]) addUV(...uvs[k]);
|
|
1184
|
+
};
|
|
1185
|
+
let start = verticesArray.length / 3;
|
|
1186
|
+
if (bevelEnabled) {
|
|
1187
|
+
let offset = 0;
|
|
1188
|
+
for (let i = 0; i < flen; i++) {
|
|
1189
|
+
const face = faces[i];
|
|
1190
|
+
f3(face[2] + offset, face[1] + offset, face[0] + offset);
|
|
1191
|
+
}
|
|
1192
|
+
const layer = steps + bevelSegments * 2;
|
|
1193
|
+
offset = vlen * layer;
|
|
1194
|
+
for (let i = 0; i < flen; i++) {
|
|
1195
|
+
const face = faces[i];
|
|
1196
|
+
f3(face[0] + offset, face[1] + offset, face[2] + offset);
|
|
1197
|
+
}
|
|
1198
|
+
} else {
|
|
1199
|
+
for (let i = 0; i < flen; i++) {
|
|
1200
|
+
const face = faces[i];
|
|
1201
|
+
f3(face[2], face[1], face[0]);
|
|
1202
|
+
}
|
|
1203
|
+
for (let i = 0; i < flen; i++) {
|
|
1204
|
+
const face = faces[i];
|
|
1205
|
+
f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 0 });
|
|
1209
|
+
start = verticesArray.length / 3;
|
|
1210
|
+
const sidewalls = (ring2, layeroffset2) => {
|
|
1211
|
+
let i = ring2.length;
|
|
1212
|
+
while (--i >= 0) {
|
|
1213
|
+
const j = i;
|
|
1214
|
+
let k = i - 1;
|
|
1215
|
+
if (k < 0) k = ring2.length - 1;
|
|
1216
|
+
for (let s = 0, sl = steps + bevelSegments * 2; s < sl; s++) {
|
|
1217
|
+
const slen1 = vlen * s;
|
|
1218
|
+
const slen2 = vlen * (s + 1);
|
|
1219
|
+
f4(layeroffset2 + j + slen1, layeroffset2 + k + slen1, layeroffset2 + k + slen2, layeroffset2 + j + slen2);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
let layeroffset = 0;
|
|
1224
|
+
sidewalls(contour, layeroffset);
|
|
1225
|
+
layeroffset += contour.length;
|
|
1226
|
+
for (const ahole of holes) {
|
|
1227
|
+
sidewalls(ahole, layeroffset);
|
|
1228
|
+
layeroffset += ahole.length;
|
|
1229
|
+
}
|
|
1230
|
+
groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 1 });
|
|
1231
|
+
const positions = new Float32Array(verticesArray);
|
|
1232
|
+
return { type: "ExtrudeGeometry", parameters, positions, normals: computeVertexNormals(positions), uvs: new Float32Array(uvArray), groups };
|
|
1233
|
+
}
|
|
1234
|
+
var PARAMETER_KEYS = {
|
|
1235
|
+
box: ["size", "segments"],
|
|
1236
|
+
cylinder: ["radiusTop", "radiusBottom", "height", "radialSegments", "heightSegments", "openEnded", "thetaStart", "thetaLength"],
|
|
1237
|
+
cone: ["radius", "height", "radialSegments", "heightSegments", "openEnded", "thetaStart", "thetaLength"],
|
|
1238
|
+
sphere: ["radius", "widthSegments", "heightSegments", "phiStart", "phiLength", "thetaStart", "thetaLength"],
|
|
1239
|
+
plane: ["width", "height", "widthSegments", "heightSegments"],
|
|
1240
|
+
circle: ["radius", "segments", "thetaStart", "thetaLength"],
|
|
1241
|
+
ring: ["innerRadius", "outerRadius", "thetaSegments", "phiSegments", "thetaStart", "thetaLength"],
|
|
1242
|
+
lathe: ["points", "segments", "phiStart", "phiLength"],
|
|
1243
|
+
torus: ["radius", "tube", "radialSegments", "tubularSegments", "arc"],
|
|
1244
|
+
extrude: ["shape", "depth", "steps", "bevel"],
|
|
1245
|
+
difference: ["size", "subtract"],
|
|
1246
|
+
mesh: ["positions", "normals", "uvs", "indices"]
|
|
1247
|
+
};
|
|
1248
|
+
var SEGMENT_KEYS = {
|
|
1249
|
+
cylinder: ["radialSegments"],
|
|
1250
|
+
cone: ["radialSegments"],
|
|
1251
|
+
sphere: ["widthSegments", "heightSegments"],
|
|
1252
|
+
circle: ["segments"],
|
|
1253
|
+
ring: ["thetaSegments"],
|
|
1254
|
+
lathe: ["segments"],
|
|
1255
|
+
torus: ["radialSegments", "tubularSegments"]
|
|
1256
|
+
};
|
|
1257
|
+
var SEGMENT_DEFAULTS = { radialSegments: 32, widthSegments: 32, heightSegments: 16, segments: 12, thetaSegments: 32, tubularSegments: 48 };
|
|
1258
|
+
var segmentDefault = (kind, key) => kind === "circle" && key === "segments" ? 32 : kind === "torus" && key === "radialSegments" ? 12 : SEGMENT_DEFAULTS[key];
|
|
1259
|
+
function primitiveParameters(node, options = {}) {
|
|
1260
|
+
const out = {};
|
|
1261
|
+
const source = node;
|
|
1262
|
+
for (const key of PARAMETER_KEYS[node.kind]) if (source[key] !== void 0) out[key] = source[key];
|
|
1263
|
+
const detail = options.detail ?? 1;
|
|
1264
|
+
if (detail !== 1) {
|
|
1265
|
+
const floor = options.minSegments ?? 3;
|
|
1266
|
+
for (const key of SEGMENT_KEYS[node.kind] ?? []) {
|
|
1267
|
+
const given = out[key] ?? segmentDefault(node.kind, key);
|
|
1268
|
+
out[key] = Math.max(floor, Math.round(given * detail));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return out;
|
|
1272
|
+
}
|
|
1273
|
+
function mesh(p) {
|
|
1274
|
+
const positions = new Float32Array(p.positions);
|
|
1275
|
+
const indices = p.indices ? indexArray(p.indices) : void 0;
|
|
1276
|
+
return {
|
|
1277
|
+
type: "BufferGeometry",
|
|
1278
|
+
parameters: {},
|
|
1279
|
+
positions,
|
|
1280
|
+
normals: p.normals ? new Float32Array(p.normals) : computeVertexNormals(positions, indices),
|
|
1281
|
+
uvs: p.uvs ? new Float32Array(p.uvs) : new Float32Array(positions.length / 3 * 2),
|
|
1282
|
+
...indices ? { indices } : {},
|
|
1283
|
+
groups: []
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
function make(kind, p) {
|
|
1287
|
+
switch (kind) {
|
|
1288
|
+
case "box": {
|
|
1289
|
+
const size = p.size ?? [1, 1, 1];
|
|
1290
|
+
const segments = p.segments ?? [1, 1, 1];
|
|
1291
|
+
return box(size[0], size[1], size[2], segments[0], segments[1], segments[2]);
|
|
1292
|
+
}
|
|
1293
|
+
case "cylinder":
|
|
1294
|
+
return cylinder(p.radiusTop, p.radiusBottom, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength);
|
|
1295
|
+
case "cone":
|
|
1296
|
+
return cone(p.radius, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength);
|
|
1297
|
+
case "sphere":
|
|
1298
|
+
return sphere(p.radius, p.widthSegments, p.heightSegments, p.phiStart, p.phiLength, p.thetaStart, p.thetaLength);
|
|
1299
|
+
case "plane":
|
|
1300
|
+
return plane(p.width, p.height, p.widthSegments, p.heightSegments);
|
|
1301
|
+
case "circle":
|
|
1302
|
+
return circle(p.radius, p.segments, p.thetaStart, p.thetaLength);
|
|
1303
|
+
case "ring":
|
|
1304
|
+
return ring(p.innerRadius, p.outerRadius, p.thetaSegments, p.phiSegments, p.thetaStart, p.thetaLength);
|
|
1305
|
+
case "lathe":
|
|
1306
|
+
return lathe(p.points, p.segments, p.phiStart, p.phiLength);
|
|
1307
|
+
case "torus":
|
|
1308
|
+
return torus(p.radius, p.tube, p.radialSegments, p.tubularSegments, p.arc);
|
|
1309
|
+
case "extrude":
|
|
1310
|
+
return extrude(p.shape, p.depth, p.steps, p.bevel);
|
|
1311
|
+
case "difference":
|
|
1312
|
+
return difference(p.size, p.subtract ?? []);
|
|
1313
|
+
case "mesh":
|
|
1314
|
+
return mesh(p);
|
|
1315
|
+
default:
|
|
1316
|
+
throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
var templates = /* @__PURE__ */ new Map();
|
|
1320
|
+
var templateLimit = 4096;
|
|
1321
|
+
var stats = { hits: 0, misses: 0 };
|
|
1322
|
+
function buildPrimitive(node, options = {}) {
|
|
1323
|
+
const parameters = primitiveParameters(node, options);
|
|
1324
|
+
if (node.kind === "mesh" || templateLimit === 0) return make(node.kind, parameters);
|
|
1325
|
+
const key = `${node.kind}:${canonicalJson(parameters)}`;
|
|
1326
|
+
const cached = templates.get(key);
|
|
1327
|
+
if (cached) {
|
|
1328
|
+
stats.hits++;
|
|
1329
|
+
templates.delete(key);
|
|
1330
|
+
templates.set(key, cached);
|
|
1331
|
+
return cached;
|
|
1332
|
+
}
|
|
1333
|
+
stats.misses++;
|
|
1334
|
+
const built = make(node.kind, parameters);
|
|
1335
|
+
templates.set(key, built);
|
|
1336
|
+
if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
|
|
1337
|
+
return built;
|
|
1338
|
+
}
|
|
1339
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? null : "expected a finite number";
|
|
1340
|
+
var bool = (v) => typeof v === "boolean" ? null : "expected a boolean";
|
|
1341
|
+
var str = (v) => typeof v === "string" ? null : "expected a string";
|
|
1342
|
+
var int = (min) => (v) => typeof v === "number" && Number.isInteger(v) && v >= min ? null : `expected an integer >= ${min}`;
|
|
1343
|
+
var range = (lo, hi, loOpen = false) => (v) => typeof v === "number" && (loOpen ? v > lo : v >= lo) && v <= hi ? null : `expected a number in ${loOpen ? "(" : "["}${lo}, ${hi}]`;
|
|
1344
|
+
var tuple = (n, item = num) => (v) => {
|
|
1345
|
+
if (!Array.isArray(v) || v.length !== n) return `expected an array of ${n}`;
|
|
1346
|
+
for (const x of v) if (item(x)) return `expected an array of ${n} numbers`;
|
|
1347
|
+
return null;
|
|
1348
|
+
};
|
|
1349
|
+
var list = (item, min = 0) => (v) => {
|
|
1350
|
+
if (!Array.isArray(v)) return "expected an array";
|
|
1351
|
+
if (v.length < min) return `expected at least ${min} items`;
|
|
1352
|
+
for (let i = 0; i < v.length; i++) {
|
|
1353
|
+
const e = item(v[i]);
|
|
1354
|
+
if (e) return `[${i}]: ${e}`;
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
};
|
|
1358
|
+
var color = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 16777215 || typeof v === "string" && /^#[0-9a-fA-F]{6}$/.test(v) ? null : 'expected 0xRRGGBB or "#rrggbb"';
|
|
1359
|
+
var oneOf = (values) => (v) => values.includes(v) ? null : `expected one of ${values.map((x) => JSON.stringify(x)).join(", ")}`;
|
|
1360
|
+
var object = (fields, required = []) => (v) => {
|
|
1361
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return "expected an object";
|
|
1362
|
+
const record = v;
|
|
1363
|
+
for (const key of required) if (record[key] === void 0) return `${key}: required`;
|
|
1364
|
+
for (const key of Object.keys(record)) {
|
|
1365
|
+
if (record[key] === void 0) continue;
|
|
1366
|
+
const check = fields[key];
|
|
1367
|
+
if (!check) return `${key}: unknown field`;
|
|
1368
|
+
const e = check(record[key]);
|
|
1369
|
+
if (e) return `${key}: ${e}`;
|
|
1370
|
+
}
|
|
1371
|
+
return null;
|
|
1372
|
+
};
|
|
1373
|
+
var ORDERS = ["XYZ", "YXZ", "ZXY", "ZYX", "YZX", "XZY"];
|
|
1374
|
+
var TRANSFORM = {
|
|
1375
|
+
position: tuple(3),
|
|
1376
|
+
rotation: tuple(3),
|
|
1377
|
+
rotationOrder: oneOf(ORDERS),
|
|
1378
|
+
scale: (v) => typeof v === "number" ? num(v) : tuple(3)(v),
|
|
1379
|
+
heading: num,
|
|
1380
|
+
matrix: tuple(16)
|
|
1381
|
+
};
|
|
1382
|
+
var UV = object({ u0: num, v0: num, u1: num, v1: num }, ["u0", "v0", "u1", "v1"]);
|
|
1383
|
+
var INSTANCE = object({ ...TRANSFORM, color });
|
|
1384
|
+
var COMMON = {
|
|
1385
|
+
...TRANSFORM,
|
|
1386
|
+
kind: str,
|
|
1387
|
+
name: str,
|
|
1388
|
+
part: str,
|
|
1389
|
+
material: str,
|
|
1390
|
+
color,
|
|
1391
|
+
uv: UV,
|
|
1392
|
+
instances: list(INSTANCE),
|
|
1393
|
+
minDetail: range(0, 1, true),
|
|
1394
|
+
tags: list(str)
|
|
1395
|
+
};
|
|
1396
|
+
var vec2 = tuple(2);
|
|
1397
|
+
var BEVEL = (v) => v === false ? null : object({ thickness: num, size: num, offset: num, segments: int(1) })(v);
|
|
1398
|
+
var KINDS = {
|
|
1399
|
+
group: { fields: {} },
|
|
1400
|
+
box: { fields: { size: tuple(3), segments: tuple(3, int(1)) } },
|
|
1401
|
+
cylinder: { fields: { radiusTop: num, radiusBottom: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },
|
|
1402
|
+
cone: { fields: { radius: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },
|
|
1403
|
+
sphere: { fields: { radius: num, widthSegments: int(3), heightSegments: int(2), phiStart: num, phiLength: num, thetaStart: num, thetaLength: num } },
|
|
1404
|
+
plane: { fields: { width: num, height: num, widthSegments: int(1), heightSegments: int(1) } },
|
|
1405
|
+
circle: { fields: { radius: num, segments: int(3), thetaStart: num, thetaLength: num } },
|
|
1406
|
+
ring: { fields: { innerRadius: num, outerRadius: num, thetaSegments: int(3), phiSegments: int(1), thetaStart: num, thetaLength: num } },
|
|
1407
|
+
lathe: { fields: { points: list(vec2, 2), segments: int(1), phiStart: num, phiLength: num }, required: ["points"] },
|
|
1408
|
+
torus: { fields: { radius: num, tube: num, radialSegments: int(1), tubularSegments: int(1), arc: num } },
|
|
1409
|
+
extrude: {
|
|
1410
|
+
fields: { shape: object({ outer: list(vec2, 3), holes: list(list(vec2, 3)) }, ["outer"]), depth: num, steps: int(1), bevel: BEVEL },
|
|
1411
|
+
required: ["shape"]
|
|
1412
|
+
},
|
|
1413
|
+
difference: { fields: { size: tuple(3), subtract: list(object({ min: vec2, max: vec2 }, ["min", "max"])) }, required: ["size", "subtract"] },
|
|
1414
|
+
mesh: {
|
|
1415
|
+
fields: {
|
|
1416
|
+
positions: (v) => list(num)(v) ?? (v.length % 3 === 0 ? null : "expected a multiple of 3 numbers"),
|
|
1417
|
+
normals: list(num),
|
|
1418
|
+
uvs: list(num),
|
|
1419
|
+
indices: list(int(0))
|
|
1420
|
+
},
|
|
1421
|
+
required: ["positions"]
|
|
1422
|
+
},
|
|
1423
|
+
point: { fields: { data: () => null } },
|
|
1424
|
+
custom: { fields: { generator: str, params: () => null }, required: ["generator"] }
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// ../game-shapes-three/dist/index.js
|
|
1428
|
+
var UV_BEFORE_NORMAL = /* @__PURE__ */ new Set(["LatheGeometry", "ExtrudeGeometry"]);
|
|
1429
|
+
var INDEX_FIRST = /* @__PURE__ */ new Set(["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry"]);
|
|
1430
|
+
var isPrimitive = (value) => typeof value.type === "string";
|
|
1431
|
+
function threeParameters(THREE, p) {
|
|
1432
|
+
if (p.type === "LatheGeometry") {
|
|
1433
|
+
const points = p.parameters.points.map(({ x, y }) => new THREE.Vector2(x, y));
|
|
1434
|
+
return { ...p.parameters, points };
|
|
1435
|
+
}
|
|
1436
|
+
return { ...p.parameters };
|
|
1437
|
+
}
|
|
1438
|
+
function toGeometry(THREE, source, options = {}) {
|
|
1439
|
+
const copy = options.copy !== false;
|
|
1440
|
+
const primitive = isPrimitive(source) ? source : null;
|
|
1441
|
+
const ThreeClass = primitive && primitive.type !== "BufferGeometry" ? THREE[primitive.type] : void 0;
|
|
1442
|
+
const geometry = ThreeClass ? Reflect.construct(THREE.BufferGeometry, [], ThreeClass) : new THREE.BufferGeometry();
|
|
1443
|
+
if (primitive && ThreeClass) {
|
|
1444
|
+
Object.assign(geometry, { type: primitive.type, parameters: threeParameters(THREE, primitive) });
|
|
1445
|
+
}
|
|
1446
|
+
const attribute = (array, size) => copy ? new THREE.Float32BufferAttribute(array, size) : new THREE.BufferAttribute(array, size);
|
|
1447
|
+
const index = source.indices;
|
|
1448
|
+
const setIndex = () => {
|
|
1449
|
+
if (!index) return;
|
|
1450
|
+
geometry.setIndex(copy ? index instanceof Uint32Array ? new THREE.Uint32BufferAttribute(index, 1) : new THREE.Uint16BufferAttribute(index, 1) : new THREE.BufferAttribute(index, 1));
|
|
1451
|
+
};
|
|
1452
|
+
const type = primitive?.type ?? "BufferGeometry";
|
|
1453
|
+
if (INDEX_FIRST.has(type)) setIndex();
|
|
1454
|
+
geometry.setAttribute("position", attribute(source.positions, 3));
|
|
1455
|
+
if (UV_BEFORE_NORMAL.has(type)) {
|
|
1456
|
+
geometry.setAttribute("uv", attribute(source.uvs, 2));
|
|
1457
|
+
geometry.setAttribute("normal", attribute(source.normals, 3));
|
|
1458
|
+
} else {
|
|
1459
|
+
geometry.setAttribute("normal", attribute(source.normals, 3));
|
|
1460
|
+
geometry.setAttribute("uv", attribute(source.uvs, 2));
|
|
1461
|
+
}
|
|
1462
|
+
if (!primitive && source.colors) geometry.setAttribute("color", attribute(source.colors, 3));
|
|
1463
|
+
if (!INDEX_FIRST.has(type)) setIndex();
|
|
1464
|
+
if (primitive) for (const group of primitive.groups) geometry.addGroup(group.start, group.count, group.materialIndex);
|
|
1465
|
+
return geometry;
|
|
1466
|
+
}
|
|
1467
|
+
var SHAPE_GEOMETRY_TYPES = ["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry", "ExtrudeGeometry"];
|
|
1468
|
+
function linePoints(path) {
|
|
1469
|
+
if (path.autoClose || path.curves.length === 0) return null;
|
|
1470
|
+
const points = [];
|
|
1471
|
+
for (const [index, curve] of path.curves.entries()) {
|
|
1472
|
+
if (!curve.isLineCurve || !curve.v1 || !curve.v2) return null;
|
|
1473
|
+
if (index === 0) points.push([curve.v1.x, curve.v1.y]);
|
|
1474
|
+
else {
|
|
1475
|
+
const last = points[points.length - 1];
|
|
1476
|
+
if (last[0] !== curve.v1.x || last[1] !== curve.v1.y) return null;
|
|
1477
|
+
}
|
|
1478
|
+
points.push([curve.v2.x, curve.v2.y]);
|
|
1479
|
+
}
|
|
1480
|
+
return points;
|
|
1481
|
+
}
|
|
1482
|
+
function extrudeOf(shapes, options = {}) {
|
|
1483
|
+
if (Array.isArray(shapes) || !shapes || options.extrudePath !== void 0 || options.UVGenerator !== void 0) return null;
|
|
1484
|
+
const shape = shapes;
|
|
1485
|
+
const outer = linePoints(shape);
|
|
1486
|
+
if (!outer) return null;
|
|
1487
|
+
const holes = [];
|
|
1488
|
+
for (const hole of shape.holes ?? []) {
|
|
1489
|
+
const points = linePoints(hole);
|
|
1490
|
+
if (!points) return null;
|
|
1491
|
+
holes.push(points);
|
|
1492
|
+
}
|
|
1493
|
+
const outline = holes.length ? { outer, holes } : { outer };
|
|
1494
|
+
const bevel = options.bevelEnabled === false ? false : {
|
|
1495
|
+
...options.bevelThickness !== void 0 ? { thickness: options.bevelThickness } : {},
|
|
1496
|
+
...options.bevelSize !== void 0 ? { size: options.bevelSize } : {},
|
|
1497
|
+
...options.bevelOffset !== void 0 ? { offset: options.bevelOffset } : {},
|
|
1498
|
+
...options.bevelSegments !== void 0 ? { segments: options.bevelSegments } : {}
|
|
1499
|
+
};
|
|
1500
|
+
return extrude(outline, options.depth, options.steps, bevel);
|
|
1501
|
+
}
|
|
1502
|
+
var namespaces = /* @__PURE__ */ new WeakMap();
|
|
1503
|
+
var shims = /* @__PURE__ */ new WeakSet();
|
|
1504
|
+
function shapeGeometries(THREE, options = {}) {
|
|
1505
|
+
if (shims.has(THREE)) return THREE;
|
|
1506
|
+
const cache2 = options.cache !== false;
|
|
1507
|
+
const key = cache2 ? "cache" : "nocache";
|
|
1508
|
+
let byKey = namespaces.get(THREE);
|
|
1509
|
+
if (!byKey) {
|
|
1510
|
+
byKey = /* @__PURE__ */ new Map();
|
|
1511
|
+
namespaces.set(THREE, byKey);
|
|
1512
|
+
}
|
|
1513
|
+
const existing = byKey.get(key);
|
|
1514
|
+
if (existing) return existing;
|
|
1515
|
+
const made = (p) => toGeometry(THREE, p);
|
|
1516
|
+
const viaCache = (node, direct) => cache2 ? buildPrimitive(node) : direct();
|
|
1517
|
+
const defined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
1518
|
+
const constructors = {
|
|
1519
|
+
BoxGeometry: (width, height, depth, ws = 1, hs = 1, ds = 1) => made(viaCache({ kind: "box", size: [width ?? 1, height ?? 1, depth ?? 1], segments: [ws, hs, ds] }, () => box(width, height, depth, ws, hs, ds))),
|
|
1520
|
+
CylinderGeometry: (radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) => made(viaCache({ kind: "cylinder", ...defined({ radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cylinder(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),
|
|
1521
|
+
ConeGeometry: (radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) => made(viaCache({ kind: "cone", ...defined({ radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cone(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),
|
|
1522
|
+
SphereGeometry: (radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) => made(viaCache({ kind: "sphere", ...defined({ radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength }) }, () => sphere(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength))),
|
|
1523
|
+
PlaneGeometry: (width, height, widthSegments, heightSegments) => made(viaCache({ kind: "plane", ...defined({ width, height, widthSegments, heightSegments }) }, () => plane(width, height, widthSegments, heightSegments))),
|
|
1524
|
+
CircleGeometry: (radius, segments, thetaStart, thetaLength) => made(viaCache({ kind: "circle", ...defined({ radius, segments, thetaStart, thetaLength }) }, () => circle(radius, segments, thetaStart, thetaLength))),
|
|
1525
|
+
RingGeometry: (innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) => made(viaCache({ kind: "ring", ...defined({ innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength }) }, () => ring(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength))),
|
|
1526
|
+
LatheGeometry: (points, segments, phiStart, phiLength) => {
|
|
1527
|
+
if (!points) return new THREE.LatheGeometry(void 0, segments, phiStart, phiLength);
|
|
1528
|
+
const profile = points.map((p) => [p.x, p.y]);
|
|
1529
|
+
return made(viaCache({ kind: "lathe", points: profile, ...defined({ segments, phiStart, phiLength }) }, () => lathe(profile, segments, phiStart, phiLength)));
|
|
1530
|
+
},
|
|
1531
|
+
TorusGeometry: (radius, tube2, radialSegments, tubularSegments, arc) => made(viaCache({ kind: "torus", ...defined({ radius, tube: tube2, radialSegments, tubularSegments, arc }) }, () => torus(radius, tube2, radialSegments, tubularSegments, arc))),
|
|
1532
|
+
ExtrudeGeometry: (shapes, extrudeOptions) => {
|
|
1533
|
+
const buffers = shapes === void 0 ? null : extrudeOf(shapes, extrudeOptions);
|
|
1534
|
+
if (!buffers) return new THREE.ExtrudeGeometry(shapes, extrudeOptions);
|
|
1535
|
+
const geometry = toGeometry(THREE, buffers);
|
|
1536
|
+
geometry.parameters = { shapes, options: extrudeOptions ?? {} };
|
|
1537
|
+
return geometry;
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
const namespace = { ...THREE };
|
|
1541
|
+
for (const type of SHAPE_GEOMETRY_TYPES) {
|
|
1542
|
+
const make2 = constructors[type];
|
|
1543
|
+
const Constructor = function(...args) {
|
|
1544
|
+
return make2(...args);
|
|
1545
|
+
};
|
|
1546
|
+
Constructor.prototype = THREE[type].prototype;
|
|
1547
|
+
Object.defineProperty(Constructor, "name", { value: type });
|
|
1548
|
+
namespace[type] = Constructor;
|
|
1549
|
+
}
|
|
1550
|
+
const result = namespace;
|
|
1551
|
+
byKey.set(key, result);
|
|
1552
|
+
shims.add(result);
|
|
1553
|
+
return result;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
39
1556
|
// src/rounded-box.ts
|
|
40
1557
|
function createRoundedBox(THREE, width, height, depth, radius) {
|
|
41
|
-
|
|
42
|
-
|
|
1558
|
+
const T = shapeGeometries(THREE);
|
|
1559
|
+
if (radius === 0) return new T.BoxGeometry(width, height, depth);
|
|
1560
|
+
const geometry = new T.BoxGeometry(2, 2, 2, 4, 4, 4);
|
|
43
1561
|
const position = geometry.attributes.position;
|
|
44
1562
|
const normal = geometry.attributes.normal;
|
|
45
1563
|
const half = [width / 2, height / 2, depth / 2];
|
|
@@ -76,7 +1594,7 @@ var GameUrbanThree = (() => {
|
|
|
76
1594
|
shape.lineTo(width / 2, 0);
|
|
77
1595
|
shape.lineTo(0, height);
|
|
78
1596
|
shape.lineTo(-width / 2, 0);
|
|
79
|
-
const geometry = new THREE.ExtrudeGeometry(shape, { depth, bevelEnabled: false });
|
|
1597
|
+
const geometry = new (shapeGeometries(THREE)).ExtrudeGeometry(shape, { depth, bevelEnabled: false });
|
|
80
1598
|
geometry.translate(0, 0, -depth / 2);
|
|
81
1599
|
return geometry;
|
|
82
1600
|
}
|
|
@@ -95,11 +1613,11 @@ var GameUrbanThree = (() => {
|
|
|
95
1613
|
g.textAlign = "center";
|
|
96
1614
|
g.textBaseline = "middle";
|
|
97
1615
|
const base = g.font;
|
|
98
|
-
lines.forEach((
|
|
1616
|
+
lines.forEach((text2, index) => {
|
|
99
1617
|
g.font = base;
|
|
100
|
-
const measured = g.measureText(
|
|
1618
|
+
const measured = g.measureText(text2).width;
|
|
101
1619
|
if (measured > o.width * FIT) g.font = base.replace(/(\d+(?:\.\d+)?)px/, (_match, size) => (Number(size) * o.width * FIT / measured).toFixed(1) + "px");
|
|
102
|
-
g.fillText(
|
|
1620
|
+
g.fillText(text2, o.width / 2, o.height / 2 + (index - (lines.length - 1) / 2) * (o.height / lines.length));
|
|
103
1621
|
});
|
|
104
1622
|
const texture = new THREE.CanvasTexture(canvas);
|
|
105
1623
|
texture.anisotropy = 4;
|
|
@@ -115,25 +1633,26 @@ var GameUrbanThree = (() => {
|
|
|
115
1633
|
var SIGN_FONT = '700 56px "Arial Narrow", Arial, sans-serif';
|
|
116
1634
|
var SIGN_CANVAS = { width: 512, height: 128 };
|
|
117
1635
|
function createUrbanThree(THREE, options = {}) {
|
|
1636
|
+
const T = shapeGeometries(THREE);
|
|
118
1637
|
const geometries = /* @__PURE__ */ new Map();
|
|
119
1638
|
const materials = /* @__PURE__ */ new Map();
|
|
120
1639
|
const disposables = [];
|
|
121
|
-
const standard = (
|
|
1640
|
+
const standard = (color2, style, flat = false) => {
|
|
122
1641
|
const roughness = style?.roughness ?? DEFAULT_ROUGHNESS;
|
|
123
1642
|
const metalness = style?.metalness ?? DEFAULT_METALNESS;
|
|
124
|
-
const key = `s|${
|
|
1643
|
+
const key = `s|${color2}|${roughness}|${metalness}|${flat ? 1 : 0}`;
|
|
125
1644
|
let material = materials.get(key);
|
|
126
1645
|
if (!material) {
|
|
127
|
-
material = new THREE.MeshStandardMaterial({ color, roughness, metalness, flatShading: flat });
|
|
1646
|
+
material = new THREE.MeshStandardMaterial({ color: color2, roughness, metalness, flatShading: flat });
|
|
128
1647
|
materials.set(key, material);
|
|
129
1648
|
}
|
|
130
1649
|
return material;
|
|
131
1650
|
};
|
|
132
|
-
const glowing = (
|
|
133
|
-
const key = `b|${
|
|
1651
|
+
const glowing = (color2) => {
|
|
1652
|
+
const key = `b|${color2}`;
|
|
134
1653
|
let material = materials.get(key);
|
|
135
1654
|
if (!material) {
|
|
136
|
-
material = new THREE.MeshBasicMaterial({ color });
|
|
1655
|
+
material = new THREE.MeshBasicMaterial({ color: color2 });
|
|
137
1656
|
materials.set(key, material);
|
|
138
1657
|
}
|
|
139
1658
|
return material;
|
|
@@ -147,9 +1666,9 @@ var GameUrbanThree = (() => {
|
|
|
147
1666
|
}
|
|
148
1667
|
return geometry;
|
|
149
1668
|
};
|
|
150
|
-
const shadows = (
|
|
151
|
-
|
|
152
|
-
|
|
1669
|
+
const shadows = (mesh2, style, cast, receive) => {
|
|
1670
|
+
mesh2.castShadow = style?.castShadow ?? cast;
|
|
1671
|
+
mesh2.receiveShadow = style?.receiveShadow ?? receive;
|
|
153
1672
|
};
|
|
154
1673
|
const texture = (lines, width, height, background, foreground, font) => {
|
|
155
1674
|
if (!options.document) throw new Error("createUrbanThree needs a document (option) to draw signs and labels");
|
|
@@ -158,12 +1677,12 @@ var GameUrbanThree = (() => {
|
|
|
158
1677
|
return map;
|
|
159
1678
|
};
|
|
160
1679
|
const textPlane = (part, map) => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (part.ry)
|
|
165
|
-
disposables.push(
|
|
166
|
-
return
|
|
1680
|
+
const mesh2 = new THREE.Mesh(new T.PlaneGeometry(part.width, part.height), new THREE.MeshBasicMaterial({ map, transparent: true }));
|
|
1681
|
+
mesh2.userData.own = true;
|
|
1682
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1683
|
+
if (part.ry) mesh2.rotation.y = part.ry;
|
|
1684
|
+
disposables.push(mesh2.geometry, mesh2.material);
|
|
1685
|
+
return mesh2;
|
|
167
1686
|
};
|
|
168
1687
|
const drawPart = (part, animators, lights) => {
|
|
169
1688
|
switch (part.shape) {
|
|
@@ -174,94 +1693,94 @@ var GameUrbanThree = (() => {
|
|
|
174
1693
|
return [group];
|
|
175
1694
|
}
|
|
176
1695
|
case "box": {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
if (part.ry)
|
|
180
|
-
if (part.rz)
|
|
181
|
-
shadows(
|
|
182
|
-
return [
|
|
1696
|
+
const mesh2 = new THREE.Mesh(roundedBox(part.width, part.height, part.depth, part.radius), part.emissive ? glowing(part.color) : standard(part.color, part.style));
|
|
1697
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1698
|
+
if (part.ry) mesh2.rotation.y = part.ry;
|
|
1699
|
+
if (part.rz) mesh2.rotation.z = part.rz;
|
|
1700
|
+
shadows(mesh2, part.style, true, true);
|
|
1701
|
+
return [mesh2];
|
|
183
1702
|
}
|
|
184
1703
|
case "prism": {
|
|
185
1704
|
const geometry = createPrism(THREE, part.width, part.height, part.depth);
|
|
186
1705
|
disposables.push(geometry);
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
if (part.ry)
|
|
190
|
-
shadows(
|
|
191
|
-
return [
|
|
1706
|
+
const mesh2 = new THREE.Mesh(geometry, standard(part.color, part.style, true));
|
|
1707
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1708
|
+
if (part.ry) mesh2.rotation.y = part.ry;
|
|
1709
|
+
shadows(mesh2, part.style, true, true);
|
|
1710
|
+
return [mesh2];
|
|
192
1711
|
}
|
|
193
1712
|
case "cylinder": {
|
|
194
|
-
const geometry = new
|
|
1713
|
+
const geometry = new T.CylinderGeometry(part.radiusTop, part.radiusBottom, part.height, part.segments);
|
|
195
1714
|
disposables.push(geometry);
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
shadows(
|
|
199
|
-
return [
|
|
1715
|
+
const mesh2 = new THREE.Mesh(geometry, standard(part.color, part.style));
|
|
1716
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1717
|
+
shadows(mesh2, part.style, true, true);
|
|
1718
|
+
return [mesh2];
|
|
200
1719
|
}
|
|
201
1720
|
case "cone": {
|
|
202
|
-
const geometry = new
|
|
1721
|
+
const geometry = new T.ConeGeometry(part.radius, part.height, part.segments);
|
|
203
1722
|
disposables.push(geometry);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
shadows(
|
|
207
|
-
return [
|
|
1723
|
+
const mesh2 = new THREE.Mesh(geometry, standard(part.color, part.style, true));
|
|
1724
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1725
|
+
shadows(mesh2, part.style, true, true);
|
|
1726
|
+
return [mesh2];
|
|
208
1727
|
}
|
|
209
1728
|
case "crown": {
|
|
210
1729
|
const geometry = new THREE.DodecahedronGeometry(part.radius, 0);
|
|
211
1730
|
disposables.push(geometry);
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
shadows(
|
|
215
|
-
return [
|
|
1731
|
+
const mesh2 = new THREE.Mesh(geometry, standard(part.color, part.style));
|
|
1732
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1733
|
+
shadows(mesh2, part.style, true, false);
|
|
1734
|
+
return [mesh2];
|
|
216
1735
|
}
|
|
217
1736
|
case "terrain": {
|
|
218
1737
|
const { grid } = part;
|
|
219
|
-
const geometry = new
|
|
1738
|
+
const geometry = new T.PlaneGeometry(grid.size, grid.size, grid.segments, grid.segments);
|
|
220
1739
|
geometry.rotateX(-Math.PI / 2);
|
|
221
1740
|
const position = geometry.attributes.position;
|
|
222
1741
|
const colors2 = new Float32Array(position.count * 3);
|
|
223
|
-
const
|
|
1742
|
+
const color2 = new THREE.Color();
|
|
224
1743
|
for (let index = 0; index < position.count; index++) {
|
|
225
1744
|
position.setXYZ(index, grid.x[index], grid.heights[index], grid.z[index]);
|
|
226
|
-
|
|
227
|
-
colors2[index * 3] =
|
|
228
|
-
colors2[index * 3 + 1] =
|
|
229
|
-
colors2[index * 3 + 2] =
|
|
1745
|
+
color2.setHex(grid.colors[index]);
|
|
1746
|
+
colors2[index * 3] = color2.r;
|
|
1747
|
+
colors2[index * 3 + 1] = color2.g;
|
|
1748
|
+
colors2[index * 3 + 2] = color2.b;
|
|
230
1749
|
}
|
|
231
1750
|
geometry.setAttribute("color", new THREE.BufferAttribute(colors2, 3));
|
|
232
1751
|
geometry.computeVertexNormals();
|
|
233
1752
|
disposables.push(geometry);
|
|
234
1753
|
const material = new THREE.MeshStandardMaterial({ vertexColors: true, flatShading: true, roughness: 1 });
|
|
235
1754
|
disposables.push(material);
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
shadows(
|
|
239
|
-
return [
|
|
1755
|
+
const mesh2 = new THREE.Mesh(geometry, material);
|
|
1756
|
+
mesh2.userData.own = true;
|
|
1757
|
+
shadows(mesh2, part.style, true, true);
|
|
1758
|
+
return [mesh2];
|
|
240
1759
|
}
|
|
241
1760
|
case "vault": {
|
|
242
|
-
const geometry = new
|
|
1761
|
+
const geometry = new T.CylinderGeometry(part.radius, part.radius, part.length, part.segments, 1, false, 0, part.arc);
|
|
243
1762
|
disposables.push(geometry);
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
shadows(
|
|
249
|
-
return [
|
|
1763
|
+
const mesh2 = new THREE.Mesh(geometry, standard(part.color, part.style));
|
|
1764
|
+
mesh2.rotation.set(part.rotation.x, part.rotation.y, part.rotation.z);
|
|
1765
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1766
|
+
mesh2.scale.set(part.scale.x, part.scale.y, part.scale.z);
|
|
1767
|
+
shadows(mesh2, part.style, true, false);
|
|
1768
|
+
return [mesh2];
|
|
250
1769
|
}
|
|
251
1770
|
case "plane": {
|
|
252
|
-
const geometry = new
|
|
1771
|
+
const geometry = new T.PlaneGeometry(part.width, part.depth).rotateX(-Math.PI / 2);
|
|
253
1772
|
disposables.push(geometry);
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
shadows(
|
|
257
|
-
return [
|
|
1773
|
+
const mesh2 = new THREE.Mesh(geometry, standard(part.color, part.style));
|
|
1774
|
+
mesh2.position.set(part.x, part.y, part.z);
|
|
1775
|
+
shadows(mesh2, part.style, false, true);
|
|
1776
|
+
return [mesh2];
|
|
258
1777
|
}
|
|
259
1778
|
case "line": {
|
|
260
1779
|
const geometry = new THREE.BufferGeometry().setFromPoints(part.points.map(([x, y, z]) => new THREE.Vector3(x, y, z)));
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
disposables.push(geometry,
|
|
264
|
-
return [
|
|
1780
|
+
const mesh2 = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: part.color }));
|
|
1781
|
+
mesh2.userData.own = true;
|
|
1782
|
+
disposables.push(geometry, mesh2.material);
|
|
1783
|
+
return [mesh2];
|
|
265
1784
|
}
|
|
266
1785
|
case "sign":
|
|
267
1786
|
return [textPlane(part, texture([part.text], SIGN_CANVAS.width, SIGN_CANVAS.height, part.background, part.foreground, SIGN_FONT))];
|
|
@@ -272,8 +1791,8 @@ var GameUrbanThree = (() => {
|
|
|
272
1791
|
holder.position.set(part.x, part.y, part.z);
|
|
273
1792
|
if (part.ry) holder.rotation.y = part.ry;
|
|
274
1793
|
if (part.scale !== 1) holder.scale.setScalar(part.scale);
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
1794
|
+
const object2 = options.resolvers?.[part.kind]?.(part);
|
|
1795
|
+
if (object2) holder.add(object2);
|
|
277
1796
|
holder.userData.placement = { kind: part.kind, id: part.id };
|
|
278
1797
|
return [holder];
|
|
279
1798
|
}
|
|
@@ -305,39 +1824,39 @@ var GameUrbanThree = (() => {
|
|
|
305
1824
|
}
|
|
306
1825
|
}
|
|
307
1826
|
};
|
|
308
|
-
const instancedItems = (
|
|
309
|
-
const
|
|
1827
|
+
const instancedItems = (list2) => {
|
|
1828
|
+
const mesh2 = new THREE.InstancedMesh(roundedBox(1, 1, 1, ITEM_ROUNDNESS), standard(16777215), list2.length);
|
|
310
1829
|
const matrix = new THREE.Matrix4();
|
|
311
1830
|
const quaternion = new THREE.Quaternion();
|
|
312
1831
|
const euler = new THREE.Euler();
|
|
313
1832
|
const position = new THREE.Vector3();
|
|
314
1833
|
const scale = new THREE.Vector3();
|
|
315
|
-
const
|
|
316
|
-
|
|
1834
|
+
const color2 = new THREE.Color();
|
|
1835
|
+
list2.forEach((entry, index) => {
|
|
317
1836
|
euler.set(entry.rx, entry.ry, entry.rz);
|
|
318
1837
|
quaternion.setFromEuler(euler);
|
|
319
1838
|
matrix.compose(position.set(entry.x, entry.y + entry.height / 2, entry.z), quaternion, scale.set(entry.width, entry.height, entry.depth));
|
|
320
|
-
|
|
321
|
-
|
|
1839
|
+
mesh2.setMatrixAt(index, matrix);
|
|
1840
|
+
mesh2.setColorAt(index, color2.set(entry.color));
|
|
322
1841
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return
|
|
1842
|
+
mesh2.castShadow = true;
|
|
1843
|
+
mesh2.receiveShadow = true;
|
|
1844
|
+
return mesh2;
|
|
326
1845
|
};
|
|
327
|
-
const instancedWindows = (
|
|
328
|
-
const geometry = new
|
|
1846
|
+
const instancedWindows = (list2, material) => {
|
|
1847
|
+
const geometry = new T.PlaneGeometry(1, 1);
|
|
329
1848
|
disposables.push(geometry);
|
|
330
|
-
const
|
|
1849
|
+
const mesh2 = new THREE.InstancedMesh(geometry, material, list2.length);
|
|
331
1850
|
const matrix = new THREE.Matrix4();
|
|
332
1851
|
const quaternion = new THREE.Quaternion();
|
|
333
1852
|
const axis = new THREE.Vector3(0, 1, 0);
|
|
334
1853
|
const scale = new THREE.Vector3();
|
|
335
|
-
|
|
1854
|
+
list2.forEach((entry, index) => {
|
|
336
1855
|
quaternion.setFromAxisAngle(axis, entry.ry);
|
|
337
1856
|
matrix.compose(new THREE.Vector3(entry.x, entry.y, entry.z), quaternion, scale.set(entry.width, entry.height, 1));
|
|
338
|
-
|
|
1857
|
+
mesh2.setMatrixAt(index, matrix);
|
|
339
1858
|
});
|
|
340
|
-
return
|
|
1859
|
+
return mesh2;
|
|
341
1860
|
};
|
|
342
1861
|
function draw(scene, drawOptions = {}) {
|
|
343
1862
|
const root = drawOptions.root ?? new THREE.Group();
|
|
@@ -590,7 +2109,7 @@ var GameUrbanThree = (() => {
|
|
|
590
2109
|
const [h, s, v] = toHsv(c);
|
|
591
2110
|
return fromHsv(h - amt * 0.03, clamp(s - amt * 0.12, 0, 1), clamp(v + amt * 0.35, 0, 1));
|
|
592
2111
|
}
|
|
593
|
-
function
|
|
2112
|
+
function srgbToLinear2(v) {
|
|
594
2113
|
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
595
2114
|
}
|
|
596
2115
|
function linearToSrgb(v) {
|
|
@@ -772,7 +2291,7 @@ var GameUrbanThree = (() => {
|
|
|
772
2291
|
this.groupTriangles();
|
|
773
2292
|
const n = this.pos.length / 3;
|
|
774
2293
|
const colors2 = new Float32Array(n * 3);
|
|
775
|
-
for (let i = 0; i < n * 3; i++) colors2[i] =
|
|
2294
|
+
for (let i = 0; i < n * 3; i++) colors2[i] = srgbToLinear2(this.col[i]);
|
|
776
2295
|
return {
|
|
777
2296
|
positions: new Float32Array(this.pos),
|
|
778
2297
|
normals: new Float32Array(this.nor),
|
|
@@ -973,16 +2492,16 @@ var GameUrbanThree = (() => {
|
|
|
973
2492
|
const f = frames[k];
|
|
974
2493
|
const r = o.radii[k];
|
|
975
2494
|
const t = k / (n - 1);
|
|
976
|
-
const
|
|
2495
|
+
const ring2 = [];
|
|
977
2496
|
for (let i = 0; i < S; i++) {
|
|
978
2497
|
const a = i / S * TAU;
|
|
979
2498
|
const ca = Math.cos(a);
|
|
980
2499
|
const sa = Math.sin(a);
|
|
981
2500
|
const dir = [f.n[0] * ca + f.b[0] * sa, f.n[1] * ca + f.b[1] * sa, f.n[2] * ca + f.b[2] * sa];
|
|
982
2501
|
const rr = r * (1 + (o.rough ? o.rough(k, i) : 0));
|
|
983
|
-
|
|
2502
|
+
ring2.push({ p: add(path[k], mul(dir, rr)), nr: dir, c: o.color(t, i / S) });
|
|
984
2503
|
}
|
|
985
|
-
rings.push(
|
|
2504
|
+
rings.push(ring2);
|
|
986
2505
|
}
|
|
987
2506
|
const sw = (k) => o.sway(k / (n - 1));
|
|
988
2507
|
if (smooth2) {
|
|
@@ -1066,7 +2585,7 @@ var GameUrbanThree = (() => {
|
|
|
1066
2585
|
const swayAt = (k) => o.sway(k / (N - 1));
|
|
1067
2586
|
for (let k = 0; k < N; k++) {
|
|
1068
2587
|
const f = fr[k];
|
|
1069
|
-
const
|
|
2588
|
+
const ring2 = [];
|
|
1070
2589
|
for (let i = 0; i < S; i++) {
|
|
1071
2590
|
const ridge = i % 2 === 0;
|
|
1072
2591
|
const rib = i >> 1;
|
|
@@ -1076,10 +2595,10 @@ var GameUrbanThree = (() => {
|
|
|
1076
2595
|
const rr = radii[k] * (ridge ? 1 + wob : o.valley);
|
|
1077
2596
|
const p = add(pts[k], mul(dir, rr));
|
|
1078
2597
|
const c = o.color(Math.min(1, k / (path.length - 1)), ridge, rib);
|
|
1079
|
-
|
|
2598
|
+
ring2.push({ p, nr: dir, c });
|
|
1080
2599
|
if (ridge && k < path.length) info.ridges.push({ p, n: dir, t: k / (path.length - 1), rib, ring: k });
|
|
1081
2600
|
}
|
|
1082
|
-
rings.push(
|
|
2601
|
+
rings.push(ring2);
|
|
1083
2602
|
}
|
|
1084
2603
|
for (let k = 0; k < N - 1; k++) {
|
|
1085
2604
|
for (let i = 0; i < S; i++) {
|
|
@@ -1401,7 +2920,7 @@ var GameUrbanThree = (() => {
|
|
|
1401
2920
|
return { p, r, members: g.members };
|
|
1402
2921
|
});
|
|
1403
2922
|
}
|
|
1404
|
-
function octa(b, center, radii,
|
|
2923
|
+
function octa(b, center, radii, color2, sway) {
|
|
1405
2924
|
const pts = [
|
|
1406
2925
|
[radii[0], 0, 0],
|
|
1407
2926
|
[0, 0, radii[2]],
|
|
@@ -1414,11 +2933,11 @@ var GameUrbanThree = (() => {
|
|
|
1414
2933
|
const a = pts[i];
|
|
1415
2934
|
const c = pts[(i + 1) % 4];
|
|
1416
2935
|
const out = sub(add(a, c), mul(center, 2));
|
|
1417
|
-
b.flatTri(a, c, top,
|
|
1418
|
-
b.flatTri(c, a, bot,
|
|
2936
|
+
b.flatTri(a, c, top, color2(0.5), color2(0.5), color2(1), sway, sway, sway, add(out, [0, 1, 0]));
|
|
2937
|
+
b.flatTri(c, a, bot, color2(0.5), color2(0.5), color2(0), sway, sway, sway, add(out, [0, -1, 0]));
|
|
1419
2938
|
}
|
|
1420
2939
|
}
|
|
1421
|
-
function blossom(b, center, normal, size,
|
|
2940
|
+
function blossom(b, center, normal, size, color2, heart, sway, petals = 5, twist = 0) {
|
|
1422
2941
|
const f = frameFromAxis(center, normal, twist);
|
|
1423
2942
|
const peak = toWorld(f, [0, size * 0.3, 0]);
|
|
1424
2943
|
for (let i = 0; i < petals; i++) {
|
|
@@ -1428,7 +2947,7 @@ var GameUrbanThree = (() => {
|
|
|
1428
2947
|
const p0 = toWorld(f, [Math.cos(a0) * size * 0.75, 0, Math.sin(a0) * size * 0.75]);
|
|
1429
2948
|
const p1 = toWorld(f, [Math.cos(a1) * size * 0.75, 0, Math.sin(a1) * size * 0.75]);
|
|
1430
2949
|
const pm = toWorld(f, [Math.cos(am) * size * 1.1, size * 0.08, Math.sin(am) * size * 1.1]);
|
|
1431
|
-
const c = i % 2 ? shade(
|
|
2950
|
+
const c = i % 2 ? shade(color2, 0.93) : color2;
|
|
1432
2951
|
b.flatTri(peak, p0, pm, heart, c, lighten(c, 0.4), sway, sway, sway, normal);
|
|
1433
2952
|
b.flatTri(peak, pm, p1, heart, lighten(c, 0.4), c, sway, sway, sway, normal);
|
|
1434
2953
|
}
|
|
@@ -2318,8 +3837,8 @@ var GameUrbanThree = (() => {
|
|
|
2318
3837
|
const nb = ctx.pick(6, 5, 4);
|
|
2319
3838
|
for (let i = 0; i < nb; i++) {
|
|
2320
3839
|
const a = i * 2.2 + 0.4;
|
|
2321
|
-
const
|
|
2322
|
-
blobs.push({ c: [Math.cos(a) *
|
|
3840
|
+
const ring2 = i === 0 ? 0 : moundR * 0.5;
|
|
3841
|
+
blobs.push({ c: [Math.cos(a) * ring2, moundH * (i === 0 ? 0.55 : 0.28 + 0.07 * (i % 3)), Math.sin(a) * ring2], r: moundR * (i === 0 ? 0.62 : 0.52), sq: 0.95 });
|
|
2323
3842
|
}
|
|
2324
3843
|
}
|
|
2325
3844
|
for (const bl of blobs) clump(ctx.foliage, { center: bl.c, radius: bl.r, squash: bl.sq, detail: ctx.pick(1, 1, 0), seed: rng.seed(), base: dead ? mix(leafBase, [0.42, 0.32, 0.2], 0.7) : dry ? mix(leafBase, [0.55, 0.48, 0.22], 0.45) : mix(leafBase, [0.25, 0.55, 0.22], 0.25), swayK: 0.3, noise: 0.16, flatBottom: 0.8, ao: 0.5 });
|
|
@@ -2686,7 +4205,7 @@ var GameUrbanThree = (() => {
|
|
|
2686
4205
|
}
|
|
2687
4206
|
}
|
|
2688
4207
|
if (o.teeth && o.teeth.count > 0) {
|
|
2689
|
-
const { count, color, size } = o.teeth;
|
|
4208
|
+
const { count, color: color2, size } = o.teeth;
|
|
2690
4209
|
for (let k = 0; k < count; k++) {
|
|
2691
4210
|
const t0 = 0.12 + 0.7 * k / count;
|
|
2692
4211
|
const t1 = t0 + 0.05;
|
|
@@ -2697,7 +4216,7 @@ var GameUrbanThree = (() => {
|
|
|
2697
4216
|
const e1 = sd < 0 ? rows[i1].L : rows[i1].R;
|
|
2698
4217
|
const out = mul(right, sd * width * size);
|
|
2699
4218
|
const tip = add(add(mul(add(e0, e1), 0.5), out), mul(d, length * 0.02));
|
|
2700
|
-
b.flatTri(e0, tip, e1,
|
|
4219
|
+
b.flatTri(e0, tip, e1, color2, shade(color2, 1.3), color2, rows[i0].s, rows[i0].s, rows[i1].s, add(out, n2));
|
|
2701
4220
|
}
|
|
2702
4221
|
}
|
|
2703
4222
|
}
|
|
@@ -2726,8 +4245,8 @@ var GameUrbanThree = (() => {
|
|
|
2726
4245
|
const az = i / n * TAU * 1 + rng.range(-0.3, 0.3);
|
|
2727
4246
|
const el = rng.range(1.2, 1.5);
|
|
2728
4247
|
const len2 = h * rng.range(0.55, 1) * (ctx.condition === "dead" ? 0.6 : 1);
|
|
2729
|
-
const
|
|
2730
|
-
const base0 = [Math.cos(az) *
|
|
4248
|
+
const ring2 = rng.range(0, 0.08);
|
|
4249
|
+
const base0 = [Math.cos(az) * ring2, 0, Math.sin(az) * ring2];
|
|
2731
4250
|
const band = rng.range(0, TAU);
|
|
2732
4251
|
fleshyLeaf(ctx.foliage, base0, dirFromAngles(az, el), dirFromAngles(az + Math.PI, 0.2), len2, h * 0.16 * rng.range(0.85, 1.15), {
|
|
2733
4252
|
rows: ctx.pick(7, 4, 2),
|
|
@@ -2749,10 +4268,10 @@ var GameUrbanThree = (() => {
|
|
|
2749
4268
|
const base = tintFlesh(rgb(spec.color ?? 7315306), ctx.condition);
|
|
2750
4269
|
const edge = rgb(spec.edgeColor ?? 15261896);
|
|
2751
4270
|
for (let i = 0; i < n; i++) {
|
|
2752
|
-
const
|
|
4271
|
+
const ring2 = i < n * 0.4 ? 0 : i < n * 0.75 ? 1 : 2;
|
|
2753
4272
|
const az = i * 2.4 + rng.range(-0.2, 0.2);
|
|
2754
|
-
const el = [1.15, 0.85, 0.55][
|
|
2755
|
-
const len2 = h * [0.75, 0.95, 0.85][
|
|
4273
|
+
const el = [1.15, 0.85, 0.55][ring2] + rng.range(-0.1, 0.1);
|
|
4274
|
+
const len2 = h * [0.75, 0.95, 0.85][ring2] * rng.range(0.9, 1.1);
|
|
2756
4275
|
const c = jitter(base, rng, 0.01, 0.06, 0.04);
|
|
2757
4276
|
fleshyLeaf(ctx.foliage, [0, 0.02, 0], dirFromAngles(az, el), [0, 1, 0], len2, h * 0.26, {
|
|
2758
4277
|
rows: ctx.pick(3, 2, 2),
|
|
@@ -2839,7 +4358,7 @@ var GameUrbanThree = (() => {
|
|
|
2839
4358
|
}
|
|
2840
4359
|
return { collider: null, bendable: true };
|
|
2841
4360
|
}
|
|
2842
|
-
function spineTuft(b, p, n, count, length,
|
|
4361
|
+
function spineTuft(b, p, n, count, length, color2, rng, sway = 0) {
|
|
2843
4362
|
const nn = norm(n);
|
|
2844
4363
|
const t1 = perp(nn);
|
|
2845
4364
|
const t2 = norm(cross(nn, t1));
|
|
@@ -2852,15 +4371,15 @@ var GameUrbanThree = (() => {
|
|
|
2852
4371
|
const side = mul(norm(cross(dir, nn)), w);
|
|
2853
4372
|
const left = add(p, side);
|
|
2854
4373
|
const right = add(p, mul(side, -1));
|
|
2855
|
-
b.flatTri(left, right, tip, shade(
|
|
4374
|
+
b.flatTri(left, right, tip, shade(color2, 0.8), shade(color2, 0.8), color2, sway, sway, sway, nn);
|
|
2856
4375
|
}
|
|
2857
4376
|
}
|
|
2858
|
-
function areole(b, p, n, size,
|
|
4377
|
+
function areole(b, p, n, size, color2, sway = 0) {
|
|
2859
4378
|
const nn = norm(n);
|
|
2860
4379
|
const t1 = mul(perp(nn), size);
|
|
2861
4380
|
const t2 = mul(norm(cross(nn, perp(nn))), size);
|
|
2862
4381
|
const c = add(p, mul(nn, size * 0.1));
|
|
2863
|
-
b.flatTri(add(c, t1), add(c, add(mul(t1, -0.5), t2)), add(c, add(mul(t1, -0.5), mul(t2, -1))),
|
|
4382
|
+
b.flatTri(add(c, t1), add(c, add(mul(t1, -0.5), t2)), add(c, add(mul(t1, -0.5), mul(t2, -1))), color2, color2, color2, sway, sway, sway, nn);
|
|
2864
4383
|
}
|
|
2865
4384
|
function generateColumnarCactus(spec, ctx) {
|
|
2866
4385
|
const rng = ctx.rng;
|
|
@@ -2897,7 +4416,7 @@ var GameUrbanThree = (() => {
|
|
|
2897
4416
|
dome: domeSteps,
|
|
2898
4417
|
color: (t, ridge, rib) => bodyColor(t, ridge, rib + seedK),
|
|
2899
4418
|
sway: () => 0,
|
|
2900
|
-
rough: (
|
|
4419
|
+
rough: (ring2, rib) => Math.sin(ring2 * 1.7 + rib * 2.3 + seedK) * 0.03
|
|
2901
4420
|
});
|
|
2902
4421
|
const last = path[path.length - 1];
|
|
2903
4422
|
const prev = path[path.length - 2];
|
|
@@ -3109,7 +4628,7 @@ var GameUrbanThree = (() => {
|
|
|
3109
4628
|
return shade(base, k);
|
|
3110
4629
|
},
|
|
3111
4630
|
sway: () => 0,
|
|
3112
|
-
rough: (
|
|
4631
|
+
rough: (ring2, rib) => Math.sin(ring2 * 2.1 + rib * 1.3) * 0.02
|
|
3113
4632
|
});
|
|
3114
4633
|
if (ctx.lod === 0) {
|
|
3115
4634
|
for (const rd of res.ridges) {
|
|
@@ -3264,9 +4783,9 @@ var GameUrbanThree = (() => {
|
|
|
3264
4783
|
sides,
|
|
3265
4784
|
radii,
|
|
3266
4785
|
color: (t, a) => {
|
|
3267
|
-
const
|
|
4786
|
+
const ring2 = 0.84 + 0.16 * Math.cos(t * stemH * scars * TAU);
|
|
3268
4787
|
const stripe = 0.94 + 0.06 * Math.cos(a * TAU * 3 + t * 4);
|
|
3269
|
-
let c = shade(trunkC,
|
|
4788
|
+
let c = shade(trunkC, ring2 * stripe * (0.78 + 0.22 * Math.min(1, t * 6)));
|
|
3270
4789
|
if (variant === "royal") c = mix(c, [0.62, 0.62, 0.55], 0.25);
|
|
3271
4790
|
if (dead) c = mix(c, [0.4, 0.37, 0.32], 0.5);
|
|
3272
4791
|
return c;
|
|
@@ -3291,34 +4810,34 @@ var GameUrbanThree = (() => {
|
|
|
3291
4810
|
for (let i = 0; i < n; i++) {
|
|
3292
4811
|
if (ctx.lod === 2 && variant === "fan" && i % 2 === 1) continue;
|
|
3293
4812
|
const f = srng.fork(i);
|
|
3294
|
-
const
|
|
4813
|
+
const ring2 = i % 3;
|
|
3295
4814
|
const az2 = i / n * TAU * 1 + f.range(-0.2, 0.2) + si;
|
|
3296
4815
|
let elev;
|
|
3297
4816
|
let droop;
|
|
3298
4817
|
if (variant === "royal") {
|
|
3299
|
-
elev = [1.1, 0.65, 0.2][
|
|
3300
|
-
droop = [0.35, 0.65, 1][
|
|
4818
|
+
elev = [1.1, 0.65, 0.2][ring2] + f.range(-0.1, 0.1);
|
|
4819
|
+
droop = [0.35, 0.65, 1][ring2];
|
|
3301
4820
|
} else if (variant === "date") {
|
|
3302
|
-
elev = [1.15, 0.8, 0.45][
|
|
3303
|
-
droop = [0.3, 0.5, 0.75][
|
|
4821
|
+
elev = [1.15, 0.8, 0.45][ring2] + f.range(-0.08, 0.08);
|
|
4822
|
+
droop = [0.3, 0.5, 0.75][ring2];
|
|
3304
4823
|
} else if (variant === "areca") {
|
|
3305
|
-
elev = [1.1, 0.8, 0.5][
|
|
3306
|
-
droop = [0.45, 0.8, 1.1][
|
|
4824
|
+
elev = [1.1, 0.8, 0.5][ring2] + f.range(-0.1, 0.1);
|
|
4825
|
+
droop = [0.45, 0.8, 1.1][ring2];
|
|
3307
4826
|
} else if (variant === "fan") {
|
|
3308
|
-
elev = [1.3, 0.95, 0.6][
|
|
3309
|
-
droop = [0.25, 0.35, 0.5][
|
|
4827
|
+
elev = [1.3, 0.95, 0.6][ring2] + f.range(-0.1, 0.1);
|
|
4828
|
+
droop = [0.25, 0.35, 0.5][ring2];
|
|
3310
4829
|
} else {
|
|
3311
|
-
elev = [1.15, 0.75, 0.38][
|
|
3312
|
-
droop = [0.45, 0.78, 1.1][
|
|
4830
|
+
elev = [1.15, 0.75, 0.38][ring2] + f.range(-0.1, 0.1);
|
|
4831
|
+
droop = [0.45, 0.78, 1.1][ring2];
|
|
3313
4832
|
}
|
|
3314
4833
|
if (dead) {
|
|
3315
4834
|
elev = elev * 0.4 - 0.3;
|
|
3316
4835
|
droop *= 1.4;
|
|
3317
4836
|
}
|
|
3318
4837
|
if (dry) droop *= 1.15;
|
|
3319
|
-
const len2 = frondL * f.range(0.85, 1.1) * (
|
|
4838
|
+
const len2 = frondL * f.range(0.85, 1.1) * (ring2 === 2 ? 0.92 : 1);
|
|
3320
4839
|
const colA = dead ? mix(c0, deadC, 0.8) : c0;
|
|
3321
|
-
const colB = dead ? mix(c1, deadC, 0.7) :
|
|
4840
|
+
const colB = dead ? mix(c1, deadC, 0.7) : ring2 === 2 && dry ? mix(c1, deadC, 0.4) : c1;
|
|
3322
4841
|
if (variant === "fan") {
|
|
3323
4842
|
fanFrond(ctx, f, crownTop, az2, elev, len2, droop, colA, colB);
|
|
3324
4843
|
} else {
|
|
@@ -3429,10 +4948,10 @@ var GameUrbanThree = (() => {
|
|
|
3429
4948
|
const rows = ctx.pick(9, 6, 3);
|
|
3430
4949
|
for (let i = 0; i < nLeaves; i++) {
|
|
3431
4950
|
const f = rng.fork(i);
|
|
3432
|
-
const
|
|
4951
|
+
const ring2 = i % 3;
|
|
3433
4952
|
const az = i / nLeaves * TAU * 1 + f.range(-0.25, 0.25);
|
|
3434
|
-
const L = H * f.range(0.55, 0.72) * (
|
|
3435
|
-
const el = [1.15, 0.8, 0.45][
|
|
4953
|
+
const L = H * f.range(0.55, 0.72) * (ring2 === 2 ? 0.85 : 1);
|
|
4954
|
+
const el = [1.15, 0.8, 0.45][ring2] + f.range(-0.12, 0.12);
|
|
3436
4955
|
const horiz = dirFromAngles(az, 0);
|
|
3437
4956
|
const petL = L * 0.18;
|
|
3438
4957
|
const pEnd = [top[0] + horiz[0] * petL * Math.cos(el), top[1] + petL * Math.sin(el), top[2] + horiz[2] * petL * Math.cos(el)];
|
|
@@ -3443,7 +4962,7 @@ var GameUrbanThree = (() => {
|
|
|
3443
4962
|
const tearSeed = f.range(0, 10);
|
|
3444
4963
|
leaf(ctx.foliage, pEnd, norm([horiz[0] * Math.cos(el), Math.sin(el), horiz[2] * Math.cos(el)]), [0, 1, 0], L, L * 0.34, c0, c1, {
|
|
3445
4964
|
rows,
|
|
3446
|
-
curl: [0.55, 0.7, 0.8][
|
|
4965
|
+
curl: [0.55, 0.7, 0.8][ring2] * (dry ? 1.2 : 1),
|
|
3447
4966
|
fold: 0.35,
|
|
3448
4967
|
swayBase: 0.25,
|
|
3449
4968
|
swayTip: 1,
|
|
@@ -3543,8 +5062,8 @@ var GameUrbanThree = (() => {
|
|
|
3543
5062
|
if (hit) return hit;
|
|
3544
5063
|
const m = Math.max(Math.abs(c[0]), Math.abs(c[1]), Math.abs(c[2]));
|
|
3545
5064
|
const s = norm(c);
|
|
3546
|
-
const
|
|
3547
|
-
const q = [lerp(c[0],
|
|
5065
|
+
const sphere2 = [s[0] * m, s[1] * m, s[2] * m];
|
|
5066
|
+
const q = [lerp(c[0], sphere2[0], o.round), lerp(c[1], sphere2[1], o.round), lerp(c[2], sphere2[2], o.round)];
|
|
3548
5067
|
const base = [o.center[0] + q[0] * hx, o.center[1] + q[1] * hy, o.center[2] + q[2] * hz];
|
|
3549
5068
|
const nz = valueNoise3(base[0] * freq, base[1] * freq, base[2] * freq, seed) * 0.75 + valueNoise3(base[0] * freq * 2.3, base[1] * freq * 2.3, base[2] * freq * 2.3, seed + 7) * 0.25;
|
|
3550
5069
|
const out = norm([q[0] / Math.max(hx, 1e-6), q[1] / Math.max(hy, 1e-6), q[2] / Math.max(hz, 1e-6)]);
|
|
@@ -3750,14 +5269,14 @@ var GameUrbanThree = (() => {
|
|
|
3750
5269
|
const dark = shade(col, 0.62);
|
|
3751
5270
|
const apex = [lean * (y / H) ** 2, y + tierH * 0.55, 0];
|
|
3752
5271
|
const innerR = r * 0.32;
|
|
3753
|
-
const
|
|
5272
|
+
const ring2 = (rad, yy, k, ph) => [apex[0] + Math.cos(k / K * TAU + ph) * rad, yy, Math.sin(k / K * TAU + ph) * rad];
|
|
3754
5273
|
const tphase = i * 0.7;
|
|
3755
5274
|
for (let k = 0; k < K; k++) {
|
|
3756
5275
|
const jag = k % 2 === 0 ? 1 : 0.78;
|
|
3757
|
-
const a0 =
|
|
3758
|
-
const a1 =
|
|
3759
|
-
const o0 =
|
|
3760
|
-
const o1 =
|
|
5276
|
+
const a0 = ring2(innerR, y + tierH * 0.12, k, tphase);
|
|
5277
|
+
const a1 = ring2(innerR, y + tierH * 0.12, k + 1, tphase);
|
|
5278
|
+
const o0 = ring2(r * jag, y - tierH * 0.42, k, tphase);
|
|
5279
|
+
const o1 = ring2(r * (k % 2 === 0 ? 0.78 : 1), y - tierH * 0.42, k + 1, tphase);
|
|
3761
5280
|
const sw = 0.25 + 0.35 * (1 - f);
|
|
3762
5281
|
foliage.flat(apex, a1, a0, shade(lightTop, 0.85), 0.2, [0, 1, 0]);
|
|
3763
5282
|
foliage.flatTri(a0, a1, o1, mix(col, lightTop, 0.2), mix(col, lightTop, 0.2), lightTop, 0.2, 0.2, sw, [0, 1, 0]);
|
|
@@ -3946,12 +5465,12 @@ var GameUrbanThree = (() => {
|
|
|
3946
5465
|
const a1 = (i + 1) / sides * TAU;
|
|
3947
5466
|
ctx.wood.flatTri([0, soilY, 0], [Math.cos(a1) * rim, soilY, Math.sin(a1) * rim], [Math.cos(a0) * rim, soilY, Math.sin(a0) * rim], shade(soil, 0.9), soil, soil, 0, 0, 0, [0, 1, 0]);
|
|
3948
5467
|
}
|
|
3949
|
-
const inner =
|
|
5468
|
+
const inner = generate2(spec.plant, { seed: ctx.rng.fork("plant").seed(), lod: ctx.lod, condition: ctx.condition, season: ctx.season });
|
|
3950
5469
|
const yOff = soilY - 0.02;
|
|
3951
5470
|
const tagsOf = (part) => {
|
|
3952
|
-
const
|
|
3953
|
-
if (!
|
|
3954
|
-
const tags = new Array(
|
|
5471
|
+
const mesh2 = inner.parts[part];
|
|
5472
|
+
if (!mesh2) return void 0;
|
|
5473
|
+
const tags = new Array(mesh2.triangleCount).fill("");
|
|
3955
5474
|
for (const pc of inner.pieces) {
|
|
3956
5475
|
if (pc.part !== part) continue;
|
|
3957
5476
|
if (!ctx.pieceMeta.has(pc.id)) ctx.pieceMeta.set(pc.id, { kind: pc.kind, parent: pc.parent, pivot: [pc.pivot[0], pc.pivot[1] + yOff, pc.pivot[2]], ...pc.radius === void 0 || pc.length === void 0 || !pc.axis || !pc.base ? {} : { geom: { radius: pc.radius, length: pc.length, axis: pc.axis, base: [pc.base[0], pc.base[1] + yOff, pc.base[2]] } } });
|
|
@@ -3988,7 +5507,7 @@ var GameUrbanThree = (() => {
|
|
|
3988
5507
|
vine: generateVine,
|
|
3989
5508
|
potted: generatePotted
|
|
3990
5509
|
};
|
|
3991
|
-
function
|
|
5510
|
+
function generate2(spec, opts = {}) {
|
|
3992
5511
|
const gen = GENERATORS[spec.archetype];
|
|
3993
5512
|
if (!gen) throw new Error(`game-vegetation: unknown archetype "${spec.archetype}"`);
|
|
3994
5513
|
const seed = toSeed(opts.seed);
|
|
@@ -4197,29 +5716,29 @@ var GameUrbanThree = (() => {
|
|
|
4197
5716
|
}
|
|
4198
5717
|
const tagBox = /* @__PURE__ */ new Map();
|
|
4199
5718
|
for (const part of PART_ORDER) {
|
|
4200
|
-
const
|
|
4201
|
-
if (!
|
|
5719
|
+
const mesh2 = parts[part];
|
|
5720
|
+
if (!mesh2) continue;
|
|
4202
5721
|
for (const r of builders[part].ranges) {
|
|
4203
5722
|
const tag = r.tag || rootId;
|
|
4204
|
-
let
|
|
4205
|
-
if (!
|
|
5723
|
+
let box2 = tagBox.get(tag);
|
|
5724
|
+
if (!box2) tagBox.set(tag, box2 = { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] });
|
|
4206
5725
|
for (let i = r.indexStart; i < r.indexStart + r.indexCount; i++) {
|
|
4207
|
-
const v =
|
|
5726
|
+
const v = mesh2.indices[i] * 3;
|
|
4208
5727
|
for (let a = 0; a < 3; a++) {
|
|
4209
|
-
const c =
|
|
4210
|
-
if (c <
|
|
4211
|
-
if (c >
|
|
5728
|
+
const c = mesh2.positions[v + a];
|
|
5729
|
+
if (c < box2.min[a]) box2.min[a] = c;
|
|
5730
|
+
if (c > box2.max[a]) box2.max[a] = c;
|
|
4212
5731
|
}
|
|
4213
5732
|
}
|
|
4214
5733
|
}
|
|
4215
5734
|
}
|
|
4216
5735
|
const fruitHang = /* @__PURE__ */ new Map();
|
|
4217
5736
|
const PRIORITY = { branch: 0, pad: 0, arm: 0, stem: 1, trunk: 1, crown: 2 };
|
|
4218
|
-
for (const [tag,
|
|
5737
|
+
for (const [tag, box2] of tagBox) {
|
|
4219
5738
|
if (meta.get(tag)?.kind !== "fruit") continue;
|
|
4220
|
-
const cx = (
|
|
4221
|
-
const cy = (
|
|
4222
|
-
const cz = (
|
|
5739
|
+
const cx = (box2.min[0] + box2.max[0]) / 2;
|
|
5740
|
+
const cy = (box2.min[1] + box2.max[1]) / 2;
|
|
5741
|
+
const cz = (box2.min[2] + box2.max[2]) / 2;
|
|
4223
5742
|
let parent;
|
|
4224
5743
|
let pbox;
|
|
4225
5744
|
let bd = Infinity;
|
|
@@ -4241,24 +5760,24 @@ var GameUrbanThree = (() => {
|
|
|
4241
5760
|
pbox = pb;
|
|
4242
5761
|
}
|
|
4243
5762
|
}
|
|
4244
|
-
const pc = pbox ? [(pbox.min[0] + pbox.max[0]) / 2, (pbox.min[1] + pbox.max[1]) / 2, (pbox.min[2] + pbox.max[2]) / 2] : [cx,
|
|
4245
|
-
const pivot = [Math.min(
|
|
5763
|
+
const pc = pbox ? [(pbox.min[0] + pbox.max[0]) / 2, (pbox.min[1] + pbox.max[1]) / 2, (pbox.min[2] + pbox.max[2]) / 2] : [cx, box2.max[1], cz];
|
|
5764
|
+
const pivot = [Math.min(box2.max[0], Math.max(box2.min[0], pc[0])), Math.min(box2.max[1], Math.max(box2.min[1], pc[1])), Math.min(box2.max[2], Math.max(box2.min[2], pc[2]))];
|
|
4246
5765
|
fruitHang.set(tag, { parent, pivot });
|
|
4247
5766
|
}
|
|
4248
5767
|
const idOf = (tag, part) => primary.get(tag) === part ? tag : `${tag}:${part}`;
|
|
4249
5768
|
const out = [];
|
|
4250
5769
|
for (const part of PART_ORDER) {
|
|
4251
|
-
const
|
|
4252
|
-
if (!
|
|
5770
|
+
const mesh2 = parts[part];
|
|
5771
|
+
if (!mesh2) continue;
|
|
4253
5772
|
for (const r of builders[part].ranges) {
|
|
4254
5773
|
const tag = r.tag || rootId;
|
|
4255
5774
|
const m = meta.get(tag) ?? { kind: "stem" };
|
|
4256
5775
|
const min = [Infinity, Infinity, Infinity];
|
|
4257
5776
|
const max = [-Infinity, -Infinity, -Infinity];
|
|
4258
5777
|
for (let i = r.indexStart; i < r.indexStart + r.indexCount; i++) {
|
|
4259
|
-
const v =
|
|
5778
|
+
const v = mesh2.indices[i] * 3;
|
|
4260
5779
|
for (let a = 0; a < 3; a++) {
|
|
4261
|
-
const c =
|
|
5780
|
+
const c = mesh2.positions[v + a];
|
|
4262
5781
|
if (c < min[a]) min[a] = c;
|
|
4263
5782
|
if (c > max[a]) max[a] = c;
|
|
4264
5783
|
}
|
|
@@ -4270,7 +5789,7 @@ var GameUrbanThree = (() => {
|
|
|
4270
5789
|
else if (m.kind === "crown" || m.kind === "fruit") pivot = [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2];
|
|
4271
5790
|
else pivot = [(min[0] + max[0]) / 2, min[1], (min[2] + max[2]) / 2];
|
|
4272
5791
|
if (m.kind === "trunk" || m.kind === "stem" || m.kind === "pot" || m.kind === "soil") pivot = [0, min[1], 0];
|
|
4273
|
-
let material = materialKeyForPart(part,
|
|
5792
|
+
let material = materialKeyForPart(part, mesh2.material, condition);
|
|
4274
5793
|
if (m.kind === "pot") material = "pot";
|
|
4275
5794
|
else if (m.kind === "soil") material = "soil";
|
|
4276
5795
|
const parentTag = hang ? hang.parent : m.parent;
|
|
@@ -4295,7 +5814,7 @@ var GameUrbanThree = (() => {
|
|
|
4295
5814
|
|
|
4296
5815
|
// ../game-vegetation/dist/three.js
|
|
4297
5816
|
var MAX_ACTORS = 4;
|
|
4298
|
-
var
|
|
5817
|
+
var COMMON2 = (
|
|
4299
5818
|
/* glsl */
|
|
4300
5819
|
`
|
|
4301
5820
|
#include <common>
|
|
@@ -4356,13 +5875,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
4356
5875
|
const patch = (m) => {
|
|
4357
5876
|
m.onBeforeCompile = (shader) => {
|
|
4358
5877
|
Object.assign(shader.uniforms, uniforms);
|
|
4359
|
-
shader.vertexShader = shader.vertexShader.replace("#include <common>",
|
|
5878
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", COMMON2).replace("#include <project_vertex>", PROJECT);
|
|
4360
5879
|
};
|
|
4361
5880
|
m.customProgramCacheKey = () => "vegetation-sway-v1";
|
|
4362
5881
|
};
|
|
4363
5882
|
const flat = opts.flatShading !== false;
|
|
4364
5883
|
const rough = typeof opts.roughness === "number" ? opts.roughness : 0.85;
|
|
4365
|
-
const
|
|
5884
|
+
const make2 = (flatShading, double, roughness) => {
|
|
4366
5885
|
const m = new THREE.MeshStandardMaterial({
|
|
4367
5886
|
vertexColors: true,
|
|
4368
5887
|
flatShading,
|
|
@@ -4373,11 +5892,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
4373
5892
|
patch(m);
|
|
4374
5893
|
return m;
|
|
4375
5894
|
};
|
|
4376
|
-
const wood =
|
|
4377
|
-
const foliage =
|
|
4378
|
-
const flowers =
|
|
4379
|
-
const fruit =
|
|
4380
|
-
const spines =
|
|
5895
|
+
const wood = make2(false, false, 0.95);
|
|
5896
|
+
const foliage = make2(flat, true, rough);
|
|
5897
|
+
const flowers = make2(flat, true, 0.7);
|
|
5898
|
+
const fruit = make2(flat, false, 0.55);
|
|
5899
|
+
const spines = make2(true, true, 0.8);
|
|
4381
5900
|
const dir = uniforms.uWindDir.value;
|
|
4382
5901
|
return {
|
|
4383
5902
|
materialIds: resolveMaterialIds(opts.materialIds),
|
|
@@ -4415,13 +5934,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
4415
5934
|
};
|
|
4416
5935
|
}
|
|
4417
5936
|
var PART_ORDER2 = ["wood", "foliage", "flowers", "fruit", "spines"];
|
|
4418
|
-
function toBufferGeometry(THREE,
|
|
5937
|
+
function toBufferGeometry(THREE, mesh2) {
|
|
4419
5938
|
const g = new THREE.BufferGeometry();
|
|
4420
|
-
g.setAttribute("position", new THREE.BufferAttribute(
|
|
4421
|
-
g.setAttribute("normal", new THREE.BufferAttribute(
|
|
4422
|
-
g.setAttribute("color", new THREE.BufferAttribute(
|
|
4423
|
-
g.setAttribute("sway", new THREE.BufferAttribute(
|
|
4424
|
-
g.setIndex(new THREE.BufferAttribute(
|
|
5939
|
+
g.setAttribute("position", new THREE.BufferAttribute(mesh2.positions, 3));
|
|
5940
|
+
g.setAttribute("normal", new THREE.BufferAttribute(mesh2.normals, 3));
|
|
5941
|
+
g.setAttribute("color", new THREE.BufferAttribute(mesh2.colors, 3));
|
|
5942
|
+
g.setAttribute("sway", new THREE.BufferAttribute(mesh2.sway, 1));
|
|
5943
|
+
g.setIndex(new THREE.BufferAttribute(mesh2.indices, 1));
|
|
4425
5944
|
g.computeBoundingSphere();
|
|
4426
5945
|
g.computeBoundingBox();
|
|
4427
5946
|
return g;
|
|
@@ -4435,18 +5954,18 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
4435
5954
|
function meshFor(THREE, data, material, ids, condition) {
|
|
4436
5955
|
const geo = toBufferGeometry(THREE, data);
|
|
4437
5956
|
inflate(geo, 0.4);
|
|
4438
|
-
const
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
if (ids)
|
|
4442
|
-
return
|
|
5957
|
+
const mesh2 = new THREE.Mesh(geo, material);
|
|
5958
|
+
mesh2.name = data.kind;
|
|
5959
|
+
mesh2.userData.damageable = { material: data.material };
|
|
5960
|
+
if (ids) mesh2.userData.destructionMaterial = ids[materialKeyForPart(data.kind, data.material, condition)];
|
|
5961
|
+
return mesh2;
|
|
4443
5962
|
}
|
|
4444
|
-
function slicePiece(
|
|
5963
|
+
function slicePiece(mesh2, indexStart, indexCount, origin) {
|
|
4445
5964
|
const remap = /* @__PURE__ */ new Map();
|
|
4446
5965
|
const order = [];
|
|
4447
5966
|
const indices = new Uint32Array(indexCount);
|
|
4448
5967
|
for (let i = 0; i < indexCount; i++) {
|
|
4449
|
-
const v =
|
|
5968
|
+
const v = mesh2.indices[indexStart + i];
|
|
4450
5969
|
let n = remap.get(v);
|
|
4451
5970
|
if (n === void 0) {
|
|
4452
5971
|
n = order.length;
|
|
@@ -4461,14 +5980,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
4461
5980
|
const sway = new Float32Array(order.length);
|
|
4462
5981
|
for (let i = 0; i < order.length; i++) {
|
|
4463
5982
|
const v = order[i];
|
|
4464
|
-
positions[i * 3] =
|
|
4465
|
-
positions[i * 3 + 1] =
|
|
4466
|
-
positions[i * 3 + 2] =
|
|
4467
|
-
normals.set(
|
|
4468
|
-
colors2.set(
|
|
4469
|
-
sway[i] =
|
|
4470
|
-
}
|
|
4471
|
-
return { positions, normals, colors: colors2, sway, indices, triangleCount: indexCount / 3, kind:
|
|
5983
|
+
positions[i * 3] = mesh2.positions[v * 3] - origin[0];
|
|
5984
|
+
positions[i * 3 + 1] = mesh2.positions[v * 3 + 1] - origin[1];
|
|
5985
|
+
positions[i * 3 + 2] = mesh2.positions[v * 3 + 2] - origin[2];
|
|
5986
|
+
normals.set(mesh2.normals.subarray(v * 3, v * 3 + 3), i * 3);
|
|
5987
|
+
colors2.set(mesh2.colors.subarray(v * 3, v * 3 + 3), i * 3);
|
|
5988
|
+
sway[i] = mesh2.sway[v];
|
|
5989
|
+
}
|
|
5990
|
+
return { positions, normals, colors: colors2, sway, indices, triangleCount: indexCount / 3, kind: mesh2.kind, material: mesh2.material };
|
|
4472
5991
|
}
|
|
4473
5992
|
function createPlantObject(THREE, plant, materials, options = {}) {
|
|
4474
5993
|
const mats = materials ?? createVegetationMaterials(THREE);
|
|
@@ -4480,13 +5999,13 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
4480
5999
|
const src = plant.parts[pc.part];
|
|
4481
6000
|
if (!src) continue;
|
|
4482
6001
|
const data = slicePiece(src, pc.indexStart, pc.indexCount, pc.pivot);
|
|
4483
|
-
const
|
|
4484
|
-
|
|
4485
|
-
|
|
6002
|
+
const mesh2 = meshFor(THREE, data, mats[pc.part]);
|
|
6003
|
+
mesh2.name = pc.id;
|
|
6004
|
+
mesh2.position.set(pc.pivot[0], pc.pivot[1], pc.pivot[2]);
|
|
4486
6005
|
const id = ids[pc.material];
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
g.add(
|
|
6006
|
+
mesh2.userData.destructionMaterial = id;
|
|
6007
|
+
mesh2.userData.destructionPart = { id: pc.id, kind: pc.kind, parent: pc.parent, material: id, materialKey: pc.material, ...pc.radius === void 0 ? {} : { radius: pc.radius, length: pc.length, axis: pc.axis, base: pc.base } };
|
|
6008
|
+
g.add(mesh2);
|
|
4490
6009
|
}
|
|
4491
6010
|
g.userData = { collider: plant.collider, bendable: plant.bendable, thorny: plant.thorny, species: plant.species, bounds: plant.bounds, pieces: plant.pieces, separable: true };
|
|
4492
6011
|
return g;
|
|
@@ -7066,16 +8585,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7066
8585
|
};
|
|
7067
8586
|
}
|
|
7068
8587
|
function pickForContext(region, context, rng) {
|
|
7069
|
-
const
|
|
7070
|
-
if (!
|
|
8588
|
+
const list2 = region.contexts[context];
|
|
8589
|
+
if (!list2 || list2.length === 0) throw new Error(`game-vegetation-species: region "${region.id}" has no context "${context}"`);
|
|
7071
8590
|
const next = toNext(rng);
|
|
7072
|
-
const total =
|
|
8591
|
+
const total = list2.reduce((a, b) => a + b.weight, 0);
|
|
7073
8592
|
let r = next() * total;
|
|
7074
|
-
for (const it of
|
|
8593
|
+
for (const it of list2) {
|
|
7075
8594
|
r -= it.weight;
|
|
7076
8595
|
if (r <= 0) return it.species;
|
|
7077
8596
|
}
|
|
7078
|
-
return
|
|
8597
|
+
return list2[list2.length - 1].species;
|
|
7079
8598
|
}
|
|
7080
8599
|
|
|
7081
8600
|
// src/resolvers.ts
|
|
@@ -7107,8 +8626,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7107
8626
|
const key = `${part.id}|${part.seed}`;
|
|
7108
8627
|
if (!made.has(key)) {
|
|
7109
8628
|
try {
|
|
7110
|
-
const
|
|
7111
|
-
made.set(key, createPlantObject(THREE,
|
|
8629
|
+
const mesh2 = generate2(toSpec(part.id), { seed: part.seed, lod: options.lod ?? 1, condition: options.condition ?? "healthy", season: options.season ?? "spring", species: part.id });
|
|
8630
|
+
made.set(key, createPlantObject(THREE, mesh2, materials));
|
|
7112
8631
|
} catch {
|
|
7113
8632
|
made.set(key, null);
|
|
7114
8633
|
}
|
|
@@ -7116,8 +8635,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7116
8635
|
return made.get(key)?.clone() ?? null;
|
|
7117
8636
|
};
|
|
7118
8637
|
resolve.dispose = () => {
|
|
7119
|
-
for (const
|
|
7120
|
-
|
|
8638
|
+
for (const object2 of made.values()) {
|
|
8639
|
+
object2?.traverse((o) => o.geometry?.dispose?.());
|
|
7121
8640
|
}
|
|
7122
8641
|
made.clear();
|
|
7123
8642
|
};
|
|
@@ -7134,8 +8653,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7134
8653
|
CATALOG[def.type] = item;
|
|
7135
8654
|
return item;
|
|
7136
8655
|
}
|
|
7137
|
-
function registerItems(
|
|
7138
|
-
for (const d of
|
|
8656
|
+
function registerItems(list2) {
|
|
8657
|
+
for (const d of list2) registerItem(d);
|
|
7139
8658
|
}
|
|
7140
8659
|
var locales = {};
|
|
7141
8660
|
function registerLocale(code, table) {
|
|
@@ -7371,34 +8890,34 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7371
8890
|
function createSilhouetteBuilder(random) {
|
|
7372
8891
|
const parts = [];
|
|
7373
8892
|
const windows = [];
|
|
7374
|
-
const pick = (
|
|
8893
|
+
const pick = (list2) => list2[Math.floor(random() * list2.length)];
|
|
7375
8894
|
const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
7376
|
-
const
|
|
7377
|
-
parts.push({ shape: "box", parent: o.parent ?? ROOT, x, y: y + height / 2, z, width, height, depth, color, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) });
|
|
8895
|
+
const box2 = (x, y, z, width, height, depth, color2, o = {}) => {
|
|
8896
|
+
parts.push({ shape: "box", parent: o.parent ?? ROOT, x, y: y + height / 2, z, width, height, depth, color: color2, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) });
|
|
7378
8897
|
};
|
|
7379
|
-
const prism = (x, y, z, width, height, depth,
|
|
7380
|
-
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry: 0 });
|
|
8898
|
+
const prism = (x, y, z, width, height, depth, color2, parent = ROOT) => {
|
|
8899
|
+
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color: color2, ry: 0 });
|
|
7381
8900
|
};
|
|
7382
|
-
const
|
|
7383
|
-
parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments });
|
|
8901
|
+
const cylinder2 = (x, y, z, radiusTop, radiusBottom, height, color2, parent = ROOT, segments = 14) => {
|
|
8902
|
+
parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color: color2, segments });
|
|
7384
8903
|
};
|
|
7385
|
-
const line = (points,
|
|
7386
|
-
parts.push({ shape: "line", parent, points, color });
|
|
8904
|
+
const line = (points, color2, parent = ROOT) => {
|
|
8905
|
+
parts.push({ shape: "line", parent, points, color: color2 });
|
|
7387
8906
|
};
|
|
7388
|
-
const
|
|
7389
|
-
parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors2 });
|
|
8907
|
+
const sign2 = (text2, x, y, z, width, height, ry, colors2, parent = ROOT) => {
|
|
8908
|
+
parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text: text2, ...colors2 });
|
|
7390
8909
|
};
|
|
7391
|
-
const fence = (x0, z0, x1, z1, height,
|
|
8910
|
+
const fence = (x0, z0, x1, z1, height, color2, parent = ROOT) => {
|
|
7392
8911
|
const length = Math.hypot(x1 - x0, z1 - z0);
|
|
7393
8912
|
const count = Math.ceil(length / 3);
|
|
7394
8913
|
for (let index = 0; index <= count; index++) {
|
|
7395
8914
|
const t = index / count;
|
|
7396
|
-
|
|
8915
|
+
cylinder2(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color2, parent, 5);
|
|
7397
8916
|
}
|
|
7398
8917
|
for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], 9079950, parent);
|
|
7399
8918
|
};
|
|
7400
8919
|
const finish = (footprint) => ({ parts, windows, footprint });
|
|
7401
|
-
return { random, pick, group, box, prism, cylinder, line, sign, fence, windows, finish };
|
|
8920
|
+
return { random, pick, group, box: box2, prism, cylinder: cylinder2, line, sign: sign2, fence, windows, finish };
|
|
7402
8921
|
}
|
|
7403
8922
|
var STOREY_HEIGHT = 3;
|
|
7404
8923
|
var SHOP_AWNING_HEIGHT = 4.5;
|
|
@@ -7419,8 +8938,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7419
8938
|
const zFace = z + face * (d / 2);
|
|
7420
8939
|
b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
|
|
7421
8940
|
b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
|
|
7422
|
-
const
|
|
7423
|
-
b.sign(
|
|
8941
|
+
const text2 = b.pick(SHOP_SIGNS);
|
|
8942
|
+
b.sign(text2, x, 5.3, zFace + face * 0.12, Math.min(w * 0.7, 8), 1, face > 0 ? 0 : Math.PI, { background: "#1a1a1c", foreground: b.pick(SIGN_COLORS) });
|
|
7424
8943
|
}
|
|
7425
8944
|
}
|
|
7426
8945
|
addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
|
|
@@ -7455,8 +8974,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7455
8974
|
b.box(0, 3.1, d / 2 + 0.5, w * 0.6, 0.15, 1, 6974054, { parent: g });
|
|
7456
8975
|
for (const floor of [0, 1]) {
|
|
7457
8976
|
for (const side of [-1, 1]) {
|
|
7458
|
-
const { color, emissive } = pane(b.random() < 0.12);
|
|
7459
|
-
b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04,
|
|
8977
|
+
const { color: color2, emissive } = pane(b.random() < 0.12);
|
|
8978
|
+
b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04, color2, { parent: g, emissive });
|
|
7460
8979
|
}
|
|
7461
8980
|
}
|
|
7462
8981
|
b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
|
|
@@ -7469,8 +8988,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7469
8988
|
for (let floor = 0; floor < floors; floor++) {
|
|
7470
8989
|
const isTop = floor === floors - 1;
|
|
7471
8990
|
b.box(0, floor * 2.9, 0, w, 2.9, d, isTop && b.random() < 0.5 ? 10119760 : p.color, { parent: g });
|
|
7472
|
-
const { color, emissive } = pane(b.random() < 0.1);
|
|
7473
|
-
b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04,
|
|
8991
|
+
const { color: color2, emissive } = pane(b.random() < 0.1);
|
|
8992
|
+
b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04, color2, { parent: g, emissive });
|
|
7474
8993
|
}
|
|
7475
8994
|
b.cylinder(w * 0.25, floors * 2.9, -d * 0.2, 0.55, 0.55, 0.9, 2775712, g, 10);
|
|
7476
8995
|
for (let level = 0; level < floors; level++) b.box(w / 2 + 0.4, level * 2.9, 0, 0.8, 0.18, 1.2, 8026740, { parent: g });
|
|
@@ -7489,8 +9008,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7489
9008
|
const u = (index - (count - 1) / 2) * 2.2;
|
|
7490
9009
|
const isDoor = index === 0 && p.hasDoor;
|
|
7491
9010
|
const lit = isDoor ? false : b.random() < 0.15;
|
|
7492
|
-
const
|
|
7493
|
-
b.box(u, 1, 5.02, 0.9, 2, 0.05,
|
|
9011
|
+
const color2 = isDoor ? 3810326 : lit ? LIT_WINDOW2 : 2767434;
|
|
9012
|
+
b.box(u, 1, 5.02, 0.9, 2, 0.05, color2, { parent: g, emissive: lit });
|
|
7494
9013
|
}
|
|
7495
9014
|
return b.finish({ x: p.x, z: p.z, width: w, depth: 10, height: 6 });
|
|
7496
9015
|
}
|
|
@@ -7501,8 +9020,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7501
9020
|
b.box(0, 0.8, 0, 7, 4.2, 9, p.color ?? 7031344, { parent: g });
|
|
7502
9021
|
b.prism(0, 5, 0, 7.6, 4, 9.6, 2894376, g);
|
|
7503
9022
|
b.box(2.2, 5, -1, 0.8, 4.2, 0.8, 5920336, { parent: g });
|
|
7504
|
-
const { color, emissive } = pane(b.random() < 0.3);
|
|
7505
|
-
b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04,
|
|
9023
|
+
const { color: color2, emissive } = pane(b.random() < 0.3);
|
|
9024
|
+
b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04, color2, { parent: g, emissive });
|
|
7506
9025
|
b.box(0, 6.2, 4.6, 1, 1, 0.04, DARK_WINDOW2, { parent: g });
|
|
7507
9026
|
return b.finish({ x: p.x, z: p.z, width: 7.6, depth: 9.6, height: 9 });
|
|
7508
9027
|
}
|
|
@@ -7544,8 +9063,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
7544
9063
|
b.prism(0, 3, 0, w + 0.5, 1.6, d + 0.5, p.roofColor ?? DEFAULT_ROOF, g);
|
|
7545
9064
|
b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
|
|
7546
9065
|
for (const side of [-1, 1]) {
|
|
7547
|
-
const { color, emissive } = pane(b.random() < 0.15);
|
|
7548
|
-
b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04,
|
|
9066
|
+
const { color: color2, emissive } = pane(b.random() < 0.15);
|
|
9067
|
+
b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04, color2, { parent: g, emissive });
|
|
7549
9068
|
if (p.isBoarded || b.random() < 0.3) for (const k of [-1, 1]) b.box(side * w * 0.28, 1.5 + k * 0.25, d / 2 + 0.08, 1.3, 0.14, 0.04, 7031338, { parent: g, rz: k * 0.15 });
|
|
7550
9069
|
}
|
|
7551
9070
|
return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
|
|
@@ -12691,8 +14210,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
12691
14210
|
});
|
|
12692
14211
|
return GRM[kind];
|
|
12693
14212
|
}
|
|
12694
|
-
function mkSurf(
|
|
12695
|
-
const geo =
|
|
14213
|
+
function mkSurf(mesh2, W) {
|
|
14214
|
+
const geo = mesh2.geometry, pos = geo.attributes.position, ix = geo.index, n = ix ? ix.count : pos.count;
|
|
12696
14215
|
geo.computeBoundingBox();
|
|
12697
14216
|
const bb = geo.boundingBox.clone();
|
|
12698
14217
|
const T = [], BY = {}, BX = {}, Q = 0.02;
|
|
@@ -12709,11 +14228,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
12709
14228
|
S.hit = function(o, dd) {
|
|
12710
14229
|
const d = dd.clone().normalize();
|
|
12711
14230
|
ray.set(o, d);
|
|
12712
|
-
const
|
|
12713
|
-
if (!
|
|
14231
|
+
const list2 = Math.abs(d.y) < 0.5 ? BY[Math.floor(o.y / Q)] : BX[Math.floor(o.x / Q)];
|
|
14232
|
+
if (!list2) return null;
|
|
12714
14233
|
let best = 1e9, bt = null;
|
|
12715
|
-
for (let k = 0; k <
|
|
12716
|
-
const t = T[
|
|
14234
|
+
for (let k = 0; k < list2.length; k++) {
|
|
14235
|
+
const t = T[list2[k]];
|
|
12717
14236
|
if (!ray.intersectTriangle(t[0], t[1], t[2], false, tmp)) continue;
|
|
12718
14237
|
const dist2 = tmp.distanceTo(o);
|
|
12719
14238
|
if (dist2 < best) {
|
|
@@ -12888,8 +14407,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
12888
14407
|
t.castShadow = false;
|
|
12889
14408
|
return t;
|
|
12890
14409
|
}
|
|
12891
|
-
function lineOn(c, Gd,
|
|
12892
|
-
const pts =
|
|
14410
|
+
function lineOn(c, Gd, list2, r, mat, off) {
|
|
14411
|
+
const pts = list2.map(function(q) {
|
|
12893
14412
|
const s = Gd.at(q[0], q[1]);
|
|
12894
14413
|
return s.p.addScaledVector(s.n, off || 3e-3);
|
|
12895
14414
|
});
|
|
@@ -12960,8 +14479,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
12960
14479
|
const r = sh(q[0]), y = (r[0] + r[1]) / 2 + (q[1] || 0), s = Gd.at(q[0], y), rad = q[2] || Math.min(0.034, (r[1] - r[0]) * 0.4);
|
|
12961
14480
|
const cup = onSurf(c.cylinder(rad * 1.35, rad * 1.2, 0.012, LH, 0, 0, 0), s, 8e-3);
|
|
12962
14481
|
cup.castShadow = false;
|
|
12963
|
-
const
|
|
12964
|
-
|
|
14482
|
+
const ring2 = onSurf(c.add(new THREE.TorusGeometry(rad * 1.05, 4e-3, 6, 22), MT.chrome), s, 0.012, new V3(0, 0, 1));
|
|
14483
|
+
ring2.castShadow = false;
|
|
12965
14484
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
12966
14485
|
b.castShadow = false;
|
|
12967
14486
|
inner.push(b);
|
|
@@ -13578,9 +15097,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
13578
15097
|
});
|
|
13579
15098
|
})();
|
|
13580
15099
|
const REFLB = CM("#d3d7da", { metalness: 0.35, roughness: 0.28 });
|
|
13581
|
-
function dlo(
|
|
15100
|
+
function dlo(list2) {
|
|
13582
15101
|
return function(def) {
|
|
13583
|
-
return
|
|
15102
|
+
return list2.concat([def[def.length - 1]]);
|
|
13584
15103
|
};
|
|
13585
15104
|
}
|
|
13586
15105
|
function cabTaper(belt, top, x0, L, a) {
|
|
@@ -15938,7 +17457,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
15938
17457
|
guards: [-1.1, 2],
|
|
15939
17458
|
lightsY: 0.62
|
|
15940
17459
|
});
|
|
15941
|
-
const
|
|
17460
|
+
const box2 = boxBody(c, { x0: f - len2 - 0.1, x1: -3.72, y0: 0.98, y1: 3.38, W: 2.42, col: "#f1f1ec" });
|
|
15942
17461
|
[1, -1].forEach(function(s) {
|
|
15943
17462
|
c.roundBox(4.6, 0.32, 0.01, 0.01, CM("#1f4fa3", { roughness: 0.5 }), -1, 1.35, s * 1.217);
|
|
15944
17463
|
c.roundBox(4.6, 0.06, 0.01, 0.01, CM("#e0a21c", { roughness: 0.5 }), -1, 1.56, s * 1.217);
|
|
@@ -15969,7 +17488,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
15969
17488
|
c2.rod(new V3(-3.86, 0.6, z), new V3(-3.86, 3.5, z), 0.022, kit.steelMaterial);
|
|
15970
17489
|
});
|
|
15971
17490
|
});
|
|
15972
|
-
void
|
|
17491
|
+
void box2;
|
|
15973
17492
|
}
|
|
15974
17493
|
);
|
|
15975
17494
|
fx.register(
|
|
@@ -17146,9 +18665,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
17146
18665
|
return m;
|
|
17147
18666
|
}
|
|
17148
18667
|
function fenestron(c, x, y, R, w, n, mat, bladeMat) {
|
|
17149
|
-
const
|
|
17150
|
-
|
|
17151
|
-
c.deform.push(
|
|
18668
|
+
const ring2 = c.add(new THREE.TorusGeometry(R + 0.06, 0.07, 10, 36), mat, x, y, 0);
|
|
18669
|
+
ring2.scale.z = w / 0.14;
|
|
18670
|
+
c.deform.push(ring2);
|
|
17152
18671
|
const duct = c.cylinder(R + 0.01, R + 0.01, w, MT.dark, x, y, 0, "z", { seg: 36, open: true });
|
|
17153
18672
|
duct.material = CM("#141516", { roughness: 0.8, side: THREE.DoubleSide });
|
|
17154
18673
|
const g = new THREE.Group();
|
|
@@ -17176,7 +18695,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
17176
18695
|
);
|
|
17177
18696
|
}
|
|
17178
18697
|
c.spin.push({ o: g, axis: "z", v: 22 });
|
|
17179
|
-
return { ring, fan: g, duct };
|
|
18698
|
+
return { ring: ring2, fan: g, duct };
|
|
17180
18699
|
}
|
|
17181
18700
|
function xRotor(c, x, y, z, R, ch, mat, tip) {
|
|
17182
18701
|
const g = new THREE.Group();
|
|
@@ -18893,16 +20412,16 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
18893
20412
|
function settle(c, tilt, riseOnly) {
|
|
18894
20413
|
if (tilt) c.vehicle.rotation.z += tilt;
|
|
18895
20414
|
c.root.updateMatrixWorld(true);
|
|
18896
|
-
const inv = new THREE.Matrix4().copy(c.root.matrixWorld).invert(),
|
|
20415
|
+
const inv = new THREE.Matrix4().copy(c.root.matrixWorld).invert(), box2 = new THREE.Box3(), tmp = new THREE.Box3();
|
|
18897
20416
|
c.vehicle.traverse(function(ob) {
|
|
18898
20417
|
if (!ob.isMesh) return;
|
|
18899
20418
|
for (let q = ob; q && q !== c.vehicle; q = q.parent)
|
|
18900
20419
|
if (!q.visible || q.userData.decal || q.userData.ghost || q.userData.antena) return;
|
|
18901
20420
|
if (!ob.geometry.boundingBox) ob.geometry.computeBoundingBox();
|
|
18902
20421
|
tmp.copy(ob.geometry.boundingBox).applyMatrix4(ob.matrixWorld).applyMatrix4(inv);
|
|
18903
|
-
|
|
20422
|
+
box2.union(tmp);
|
|
18904
20423
|
});
|
|
18905
|
-
if (!
|
|
20424
|
+
if (!box2.isEmpty() && (!riseOnly || box2.min.y < -5e-3)) c.vehicle.position.y -= box2.min.y;
|
|
18906
20425
|
}
|
|
18907
20426
|
function rollover(c, o) {
|
|
18908
20427
|
o = o || {};
|
|
@@ -19179,10 +20698,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
19179
20698
|
function periscope(c, x, y, z, w, label, o) {
|
|
19180
20699
|
o = o || {};
|
|
19181
20700
|
const rot = o.rot || 0;
|
|
19182
|
-
const
|
|
20701
|
+
const box2 = c.roundBox(0.14, 0.12, w + 0.06, 0.02, o.mat || OLIVE_STEEL, x, y, z, { rot: [0, rot, 0] });
|
|
19183
20702
|
const gl = c.roundBox(0.02, 0.07, w, 0.01, MT.armorGlass, x + Math.cos(rot) * 0.07, y, z - Math.sin(rot) * 0.07, { rot: [0, rot, 0] });
|
|
19184
20703
|
if (label) kit.asGlass(c, gl, label, "laminated");
|
|
19185
|
-
return
|
|
20704
|
+
return box2;
|
|
19186
20705
|
}
|
|
19187
20706
|
function sideSeat(c, x0, x1, fl, z, dir, o) {
|
|
19188
20707
|
o = o || {};
|
|
@@ -21510,7 +23029,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21510
23029
|
o = o || {};
|
|
21511
23030
|
if (kit.lod.on) o = Object.assign({}, o, { seg: Math.max(kit.lod.minLoftSegments, Math.round((o.seg || 24) * kit.lod.loftSegmentFraction)) });
|
|
21512
23031
|
if (o.smooth) st = resample(st, o.smooth);
|
|
21513
|
-
const seg = o.seg || 24, pos = [], idx = [],
|
|
23032
|
+
const seg = o.seg || 24, pos = [], idx = [], ring2 = seg + 1, axisY = o.axis === "y";
|
|
21514
23033
|
st.forEach(function(s) {
|
|
21515
23034
|
const a0 = s[1], b0 = s[2], cc = (a0 + b0) / 2, hh = Math.abs(a0 - b0) / 2, w = s[3] / 2, e = 2 / (s[4] || o.p || 2.4), zc = s[5] || 0;
|
|
21516
23035
|
for (let j = 0; j <= seg; j++) {
|
|
@@ -21522,7 +23041,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21522
23041
|
});
|
|
21523
23042
|
for (let i = 0; i < st.length - 1; i++)
|
|
21524
23043
|
for (let k = 0; k < seg; k++) {
|
|
21525
|
-
const A = i *
|
|
23044
|
+
const A = i * ring2 + k, B = A + ring2;
|
|
21526
23045
|
if (axisY) idx.push(A, B, A + 1, A + 1, B, B + 1);
|
|
21527
23046
|
else idx.push(A, A + 1, B, A + 1, B + 1, B);
|
|
21528
23047
|
}
|
|
@@ -21534,7 +23053,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21534
23053
|
if (axisY) pos.push(cc, s[0], s[5] || 0);
|
|
21535
23054
|
else pos.push(s[0], cc, s[5] || 0);
|
|
21536
23055
|
for (let k = 0; k < seg; k++) {
|
|
21537
|
-
const a = si *
|
|
23056
|
+
const a = si * ring2 + k, flip = e === 0 !== axisY;
|
|
21538
23057
|
if (flip) idx.push(ci, a + 1, a);
|
|
21539
23058
|
else idx.push(ci, a, a + 1);
|
|
21540
23059
|
}
|
|
@@ -21721,8 +23240,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21721
23240
|
});
|
|
21722
23241
|
return GRM[kind];
|
|
21723
23242
|
}
|
|
21724
|
-
function mkSurf(
|
|
21725
|
-
const geo =
|
|
23243
|
+
function mkSurf(mesh2, W) {
|
|
23244
|
+
const geo = mesh2.geometry, pos = geo.attributes.position, ix = geo.index, n = ix ? ix.count : pos.count;
|
|
21726
23245
|
geo.computeBoundingBox();
|
|
21727
23246
|
const bb = geo.boundingBox.clone();
|
|
21728
23247
|
const T = [], BY = {}, BX = {}, Q = 0.02;
|
|
@@ -21739,11 +23258,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21739
23258
|
S.hit = function(o, dd) {
|
|
21740
23259
|
const d = dd.clone().normalize();
|
|
21741
23260
|
ray.set(o, d);
|
|
21742
|
-
const
|
|
21743
|
-
if (!
|
|
23261
|
+
const list2 = Math.abs(d.y) < 0.5 ? BY[Math.floor(o.y / Q)] : BX[Math.floor(o.x / Q)];
|
|
23262
|
+
if (!list2) return null;
|
|
21744
23263
|
let best = 1e9, bt = null;
|
|
21745
|
-
for (let k = 0; k <
|
|
21746
|
-
const t = T[
|
|
23264
|
+
for (let k = 0; k < list2.length; k++) {
|
|
23265
|
+
const t = T[list2[k]];
|
|
21747
23266
|
if (!ray.intersectTriangle(t[0], t[1], t[2], false, tmp)) continue;
|
|
21748
23267
|
const dist2 = tmp.distanceTo(o);
|
|
21749
23268
|
if (dist2 < best) {
|
|
@@ -21918,8 +23437,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21918
23437
|
t.castShadow = false;
|
|
21919
23438
|
return t;
|
|
21920
23439
|
}
|
|
21921
|
-
function lineOn(c, Gd,
|
|
21922
|
-
const pts =
|
|
23440
|
+
function lineOn(c, Gd, list2, r, mat, off) {
|
|
23441
|
+
const pts = list2.map(function(q) {
|
|
21923
23442
|
const s = Gd.at(q[0], q[1]);
|
|
21924
23443
|
return s.p.addScaledVector(s.n, off || 3e-3);
|
|
21925
23444
|
});
|
|
@@ -21990,8 +23509,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
21990
23509
|
const r = sh(q[0]), y = (r[0] + r[1]) / 2 + (q[1] || 0), s = Gd.at(q[0], y), rad = q[2] || Math.min(0.034, (r[1] - r[0]) * 0.4);
|
|
21991
23510
|
const cup = onSurf(c.cylinder(rad * 1.35, rad * 1.2, 0.012, LH, 0, 0, 0), s, 8e-3);
|
|
21992
23511
|
cup.castShadow = false;
|
|
21993
|
-
const
|
|
21994
|
-
|
|
23512
|
+
const ring2 = onSurf(c.add(new THREE.TorusGeometry(rad * 1.05, 4e-3, 6, 22), MT.chrome), s, 0.012, new V3(0, 0, 1));
|
|
23513
|
+
ring2.castShadow = false;
|
|
21995
23514
|
const b = onSurf(c.sphere(rad, MT.head, 0, 0, 0, 1, 0.55, 1), s, 0.01);
|
|
21996
23515
|
b.castShadow = false;
|
|
21997
23516
|
inner.push(b);
|
|
@@ -24068,9 +25587,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
24068
25587
|
L.inner.push(m);
|
|
24069
25588
|
return m;
|
|
24070
25589
|
}
|
|
24071
|
-
function bendSet(
|
|
25590
|
+
function bendSet(list2, piv, axis, ang) {
|
|
24072
25591
|
const q = new THREE.Quaternion().setFromAxisAngle(axis.clone().normalize(), ang);
|
|
24073
|
-
|
|
25592
|
+
list2.forEach(function(m) {
|
|
24074
25593
|
if (!m.parent) return;
|
|
24075
25594
|
m.position.sub(piv).applyQuaternion(q).add(piv);
|
|
24076
25595
|
m.quaternion.premultiply(q);
|
|
@@ -25117,7 +26636,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
25117
26636
|
);
|
|
25118
26637
|
[1, -1].forEach(function(s) {
|
|
25119
26638
|
const z = s * (s > 0 ? 0.44 : 0.4);
|
|
25120
|
-
const
|
|
26639
|
+
const box2 = c.roundBox(0.5, 0.38, 0.2, 0.03, aluBox, -0.7, 0.8, z, { def: true }), luggageCase = [box2, c.roundBox(0.52, 0.04, 0.21, 0.015, blk, -0.7, 0.98, z)];
|
|
25121
26640
|
[-0.88, -0.52].forEach(function(x) {
|
|
25122
26641
|
luggageCase.push(c.roundBox(0.04, 0.06, 0.03, 0.01, blk, x, 0.95, z + s * 0.1));
|
|
25123
26642
|
});
|
|
@@ -25131,7 +26650,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
25131
26650
|
blk,
|
|
25132
26651
|
{ seg: 12 }
|
|
25133
26652
|
);
|
|
25134
|
-
c.part("cargo", s > 0 ? trl("labels.right_pannier") : trl("labels.left_pannier"),
|
|
26653
|
+
c.part("cargo", s > 0 ? trl("labels.right_pannier") : trl("labels.left_pannier"), box2, new V3(-0.7, 0.8, z), 0.6);
|
|
25135
26654
|
c.zone(
|
|
25136
26655
|
s > 0 ? "case_right" : "case_left",
|
|
25137
26656
|
s > 0 ? trl("labels.right_pannier") : trl("labels.left_pannier"),
|
|
@@ -25779,7 +27298,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
25779
27298
|
function sloft(st, o) {
|
|
25780
27299
|
o = o || {};
|
|
25781
27300
|
if (kit.lod.on) o = Object.assign({}, o, { seg: Math.max(kit.lod.minLoftSegments, Math.round((o.seg || 24) * kit.lod.loftSegmentFraction)) });
|
|
25782
|
-
const seg = o.seg || 24, pos = [], idx = [],
|
|
27301
|
+
const seg = o.seg || 24, pos = [], idx = [], ring2 = seg + 1, axisY = o.axis === "y";
|
|
25783
27302
|
st.forEach(function(s) {
|
|
25784
27303
|
const a0 = s[1], b0 = s[2], cc = (a0 + b0) / 2, hh = Math.abs(a0 - b0) / 2, w = s[3] / 2, e = 2 / (s[4] || o.p || 2.4), zc = s[5] || 0;
|
|
25785
27304
|
for (let j = 0; j <= seg; j++) {
|
|
@@ -25791,7 +27310,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
25791
27310
|
});
|
|
25792
27311
|
for (let i = 0; i < st.length - 1; i++)
|
|
25793
27312
|
for (let k = 0; k < seg; k++) {
|
|
25794
|
-
const A = i *
|
|
27313
|
+
const A = i * ring2 + k, B = A + ring2;
|
|
25795
27314
|
if (axisY) idx.push(A, B, A + 1, A + 1, B, B + 1);
|
|
25796
27315
|
else idx.push(A, A + 1, B, A + 1, B + 1, B);
|
|
25797
27316
|
}
|
|
@@ -25803,7 +27322,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
25803
27322
|
if (axisY) pos.push(cc, s[0], s[5] || 0);
|
|
25804
27323
|
else pos.push(s[0], cc, s[5] || 0);
|
|
25805
27324
|
for (let k = 0; k < seg; k++) {
|
|
25806
|
-
const a = si *
|
|
27325
|
+
const a = si * ring2 + k, flip = e === 0 !== axisY;
|
|
25807
27326
|
if (flip) idx.push(ci, a + 1, a);
|
|
25808
27327
|
else idx.push(ci, a, a + 1);
|
|
25809
27328
|
}
|
|
@@ -27034,12 +28553,12 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
27034
28553
|
});
|
|
27035
28554
|
Z.fairing = grab(c, n0).concat([fendF]);
|
|
27036
28555
|
const HL = lamp(c, { style: "round", x: 0.72, y: 0.84, z: 0, w: 0.19, h: 0.19, d: 0.1, side: 1, label: trl("labels.headlight") });
|
|
27037
|
-
const
|
|
27038
|
-
|
|
27039
|
-
|
|
27040
|
-
|
|
27041
|
-
HL.group.add(
|
|
27042
|
-
HL.inner.push(
|
|
28556
|
+
const ring2 = new THREE.Mesh(new THREE.TorusGeometry(0.075, 7e-3, 6, 32), DRL);
|
|
28557
|
+
ring2.rotation.y = PI / 2;
|
|
28558
|
+
ring2.position.set(0.052, 0, 0);
|
|
28559
|
+
ring2.castShadow = false;
|
|
28560
|
+
HL.group.add(ring2);
|
|
28561
|
+
HL.inner.push(ring2);
|
|
27043
28562
|
n0 = c.vehicle.children.length;
|
|
27044
28563
|
const TK = [
|
|
27045
28564
|
[0.46, 0.89, 0.76, 0.18, 2.2],
|
|
@@ -30088,7 +31607,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
30088
31607
|
});
|
|
30089
31608
|
}
|
|
30090
31609
|
function roofLining(c, extra) {
|
|
30091
|
-
const m = c.meta, yb = (m.glasshouse ? m.glasshouse.belt : m.belt) + 0.25, bb = new THREE.Box3(), sz = new V3(), ce = new V3(), gl = new Set(c.glass),
|
|
31610
|
+
const m = c.meta, yb = (m.glasshouse ? m.glasshouse.belt : m.belt) + 0.25, bb = new THREE.Box3(), sz = new V3(), ce = new V3(), gl = new Set(c.glass), list2 = [];
|
|
30092
31611
|
const cols = [m.color, m.roofCol].filter(Boolean).map(function(h) {
|
|
30093
31612
|
return new THREE.Color(h).getHex();
|
|
30094
31613
|
});
|
|
@@ -30100,14 +31619,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
30100
31619
|
bb.copy(o.geometry.boundingBox).applyMatrix4(o.matrix);
|
|
30101
31620
|
bb.getSize(sz);
|
|
30102
31621
|
bb.getCenter(ce);
|
|
30103
|
-
if (sz.x > 0.5 && sz.z > 0.6 && sz.y < 0.08 && ce.y > yb)
|
|
31622
|
+
if (sz.x > 0.5 && sz.z > 0.6 && sz.y < 0.08 && ce.y > yb) list2.push(o);
|
|
30104
31623
|
});
|
|
30105
31624
|
extra = extra || [];
|
|
30106
|
-
if (
|
|
31625
|
+
if (list2.length || extra.length)
|
|
30107
31626
|
c.zone("roof", null, null, null, {
|
|
30108
31627
|
onBand: function(c2, b) {
|
|
30109
31628
|
if (b >= 4)
|
|
30110
|
-
|
|
31629
|
+
list2.concat(extra).forEach(function(o) {
|
|
30111
31630
|
o.visible = false;
|
|
30112
31631
|
});
|
|
30113
31632
|
}
|
|
@@ -31892,7 +33411,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31892
33411
|
}
|
|
31893
33412
|
return pts;
|
|
31894
33413
|
}
|
|
31895
|
-
const
|
|
33414
|
+
const ring2 = 2 * (m1 + m2) + 1;
|
|
31896
33415
|
for (let i = 0; i <= n; i++) {
|
|
31897
33416
|
const t = i / n, x = o.x0 + t * o.L, P = sec(t), row = [];
|
|
31898
33417
|
for (let k = P.length - 1; k >= 1; k--) row.push([-P[k][0], P[k][1]]);
|
|
@@ -31908,8 +33427,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31908
33427
|
});
|
|
31909
33428
|
}
|
|
31910
33429
|
for (let a = 0; a < n; a++)
|
|
31911
|
-
for (let k2 = 0; k2 <
|
|
31912
|
-
const p0 = a *
|
|
33430
|
+
for (let k2 = 0; k2 < ring2 - 1; k2++) {
|
|
33431
|
+
const p0 = a * ring2 + k2, p1 = p0 + ring2;
|
|
31913
33432
|
idx.push(p0, p0 + 1, p1, p0 + 1, p1 + 1, p1);
|
|
31914
33433
|
}
|
|
31915
33434
|
const ci = pos.length / 3, sh0 = o.sheer(0), ky0 = o.keel(0);
|
|
@@ -31918,7 +33437,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31918
33437
|
const c0 = o.col(o.x0, (sh0[1] + ky0) / 2, 0);
|
|
31919
33438
|
col.push(c0.r, c0.g, c0.b);
|
|
31920
33439
|
}
|
|
31921
|
-
for (let r = 0; r <
|
|
33440
|
+
for (let r = 0; r < ring2 - 1; r++) idx.push(ci, r + 1, r);
|
|
31922
33441
|
let g = new THREE.BufferGeometry();
|
|
31923
33442
|
g.setAttribute("position", new THREE.Float32BufferAttribute(pos, 3));
|
|
31924
33443
|
if (o.col) g.setAttribute("color", new THREE.Float32BufferAttribute(col, 3));
|
|
@@ -31991,7 +33510,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
31991
33510
|
function foilGeo(sts, o) {
|
|
31992
33511
|
o = o || {};
|
|
31993
33512
|
const n = o.n || 12, up = o.up || "y", pos = [], idx = [];
|
|
31994
|
-
let
|
|
33513
|
+
let ring2;
|
|
31995
33514
|
const cnt = 0;
|
|
31996
33515
|
function prof(st) {
|
|
31997
33516
|
const out = [], cam = st.cam || 0;
|
|
@@ -32011,14 +33530,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32011
33530
|
}
|
|
32012
33531
|
sts.forEach(function(st) {
|
|
32013
33532
|
const P = prof(st);
|
|
32014
|
-
|
|
33533
|
+
ring2 = P.length;
|
|
32015
33534
|
P.forEach(function(p) {
|
|
32016
33535
|
pos.push(p[0], p[1], p[2]);
|
|
32017
33536
|
});
|
|
32018
33537
|
});
|
|
32019
33538
|
for (let a = 0; a < sts.length - 1; a++)
|
|
32020
|
-
for (let k = 0; k <
|
|
32021
|
-
const k1 = (k + 1) %
|
|
33539
|
+
for (let k = 0; k < ring2; k++) {
|
|
33540
|
+
const k1 = (k + 1) % ring2, p0 = a * ring2 + k, p1 = (a + 1) * ring2 + k, q0 = a * ring2 + k1, q1 = (a + 1) * ring2 + k1;
|
|
32022
33541
|
idx.push(p0, q0, p1, q0, q1, p1);
|
|
32023
33542
|
}
|
|
32024
33543
|
;
|
|
@@ -32026,8 +33545,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32026
33545
|
if (sts[si].noCap) return;
|
|
32027
33546
|
const ci = pos.length / 3, st = sts[si];
|
|
32028
33547
|
pos.push(st.p[0] - st.c * 0.35, st.p[1], st.p[2]);
|
|
32029
|
-
for (let k = 0; k <
|
|
32030
|
-
const a0 = si *
|
|
33548
|
+
for (let k = 0; k < ring2; k++) {
|
|
33549
|
+
const a0 = si * ring2 + k, a1 = si * ring2 + (k + 1) % ring2;
|
|
32031
33550
|
if (e === 0) idx.push(ci, a1, a0);
|
|
32032
33551
|
else idx.push(ci, a0, a1);
|
|
32033
33552
|
}
|
|
@@ -32255,7 +33774,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32255
33774
|
blue: CM("#1d4f9a", { emissive: new THREE.Color("#3a7cff"), emissiveIntensity: 1.4, roughness: 0.3 })
|
|
32256
33775
|
};
|
|
32257
33776
|
const HALO = { red: "#ff3a2a", green: "#3aff7a", white: "#fff1d0", amber: "#ffb040", blue: "#4a8cff" };
|
|
32258
|
-
function navLight(c, x, y, z,
|
|
33777
|
+
function navLight(c, x, y, z, color2, label, o) {
|
|
32259
33778
|
o = o || {};
|
|
32260
33779
|
const r = o.r || 0.045, g = new THREE.Group();
|
|
32261
33780
|
g.position.set(x, y, z);
|
|
@@ -32265,11 +33784,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32265
33784
|
base.position.y = -r * 0.4;
|
|
32266
33785
|
if (o.rot) base.rotation.set(o.rot[0], o.rot[1], o.rot[2]);
|
|
32267
33786
|
g.add(base);
|
|
32268
|
-
const bulb = new THREE.Mesh(new THREE.SphereGeometry(r * 0.55, 10, 8), NAV[
|
|
33787
|
+
const bulb = new THREE.Mesh(new THREE.SphereGeometry(r * 0.55, 10, 8), NAV[color2] || NAV.white);
|
|
32269
33788
|
g.add(bulb);
|
|
32270
33789
|
const lens = new THREE.Mesh(
|
|
32271
33790
|
new THREE.SphereGeometry(r, 12, 8),
|
|
32272
|
-
|
|
33791
|
+
color2 === "red" ? MT.lensRed : color2 === "amber" ? MT.lensAmber : MT.lens
|
|
32273
33792
|
);
|
|
32274
33793
|
lens.renderOrder = 3;
|
|
32275
33794
|
g.add(lens);
|
|
@@ -32278,10 +33797,10 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32278
33797
|
inner: [bulb],
|
|
32279
33798
|
lens: [lens],
|
|
32280
33799
|
kind: o.front ? "headlight" : "taillight",
|
|
32281
|
-
tint:
|
|
33800
|
+
tint: color2 === "red" ? "red" : "clear",
|
|
32282
33801
|
broken: false
|
|
32283
33802
|
};
|
|
32284
|
-
if (o.halo !== false) L.halo = [kit.halo(g, 0, 0, 0, o.hs || 0.35, HALO[
|
|
33803
|
+
if (o.halo !== false) L.halo = [kit.halo(g, 0, 0, 0, o.hs || 0.35, HALO[color2] || "#ffffff", 0.5)];
|
|
32285
33804
|
c.lights.push(L);
|
|
32286
33805
|
const pt = c.part(o.front ? "headlight" : "taillight", label, g, new V3(x, y, z), o.reach || 0.5);
|
|
32287
33806
|
pt.light = L;
|
|
@@ -32403,15 +33922,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
32403
33922
|
c.roundBox(0.16 * r, 0.03, 0.04, 0.012, MT.chrome, x, y + 0.04, z);
|
|
32404
33923
|
c.roundBox(0.04, 0.04, 0.03, 0.01, MT.chrome, x, y + 0.02, z);
|
|
32405
33924
|
}
|
|
32406
|
-
function skinOf(c,
|
|
33925
|
+
function skinOf(c, mesh2, keep, mat, o) {
|
|
32407
33926
|
o = o || {};
|
|
32408
|
-
const g =
|
|
33927
|
+
const g = mesh2.geometry.clone(), p = g.attributes.position, sz = o.sz || 1.006, dy = o.dy || 0;
|
|
32409
33928
|
for (let i = 0; i < p.count; i++) {
|
|
32410
33929
|
p.setZ(i, p.getZ(i) * sz);
|
|
32411
33930
|
p.setY(i, p.getY(i) + dy * Math.sign(p.getY(i)));
|
|
32412
33931
|
}
|
|
32413
33932
|
if (g.attributes.color) g.deleteAttribute("color");
|
|
32414
|
-
const m = c.add(g, mat,
|
|
33933
|
+
const m = c.add(g, mat, mesh2.position.x, mesh2.position.y, mesh2.position.z);
|
|
32415
33934
|
m.castShadow = false;
|
|
32416
33935
|
kit.cutMesh(m, [keep], 40, o.sub ? { sub: o.sub } : void 0);
|
|
32417
33936
|
c.deform.push(m);
|
|
@@ -34796,26 +36315,26 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34796
36315
|
const items = [];
|
|
34797
36316
|
const windows = { lit: [], dark: [] };
|
|
34798
36317
|
const lights = options.budget ?? createLightBudget(Number.POSITIVE_INFINITY);
|
|
34799
|
-
const pick = (
|
|
36318
|
+
const pick = (list2) => list2[Math.floor(random() * list2.length)];
|
|
34800
36319
|
const group = (x, y, z, ry = 0, parent = ROOT2) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
|
|
34801
|
-
const
|
|
34802
|
-
const part = { shape: "box", parent: o.parent ?? ROOT2, x, y: y + height / 2, z, width, height, depth, color, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) };
|
|
36320
|
+
const box2 = (x, y, z, width, height, depth, color2, o = {}) => {
|
|
36321
|
+
const part = { shape: "box", parent: o.parent ?? ROOT2, x, y: y + height / 2, z, width, height, depth, color: color2, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) };
|
|
34803
36322
|
if (o.style) part.style = o.style;
|
|
34804
36323
|
parts.push(part);
|
|
34805
36324
|
};
|
|
34806
|
-
const
|
|
34807
|
-
const part = { shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments };
|
|
36325
|
+
const cylinder2 = (x, y, z, radiusTop, radiusBottom, height, color2, parent = ROOT2, segments = DEFAULT_CYLINDER_SEGMENTS, style) => {
|
|
36326
|
+
const part = { shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color: color2, segments };
|
|
34808
36327
|
if (style) part.style = style;
|
|
34809
36328
|
parts.push(part);
|
|
34810
36329
|
};
|
|
34811
|
-
const
|
|
34812
|
-
parts.push({ shape: "cone", parent, x, y: y + height / 2, z, radius, height, color, segments });
|
|
36330
|
+
const cone2 = (x, y, z, radius, height, color2, parent = ROOT2, segments = DEFAULT_CONE_SEGMENTS) => {
|
|
36331
|
+
parts.push({ shape: "cone", parent, x, y: y + height / 2, z, radius, height, color: color2, segments });
|
|
34813
36332
|
};
|
|
34814
|
-
const prism = (x, y, z, width, height, depth,
|
|
34815
|
-
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry });
|
|
36333
|
+
const prism = (x, y, z, width, height, depth, color2, parent = ROOT2, ry = 0) => {
|
|
36334
|
+
parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color: color2, ry });
|
|
34816
36335
|
};
|
|
34817
|
-
const
|
|
34818
|
-
const part = { shape: "plane", parent, x, y, z, width, depth, color };
|
|
36336
|
+
const plane2 = (x, y, z, width, depth, color2, style, parent = ROOT2) => {
|
|
36337
|
+
const part = { shape: "plane", parent, x, y, z, width, depth, color: color2 };
|
|
34819
36338
|
if (style) part.style = style;
|
|
34820
36339
|
parts.push(part);
|
|
34821
36340
|
};
|
|
@@ -34825,11 +36344,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34825
36344
|
const terrain = (grid, parent = ROOT2) => {
|
|
34826
36345
|
parts.push({ shape: "terrain", parent, grid });
|
|
34827
36346
|
};
|
|
34828
|
-
const crown = (x, y, z, radius,
|
|
34829
|
-
parts.push({ shape: "crown", parent, x, y, z, radius, color });
|
|
36347
|
+
const crown = (x, y, z, radius, color2, parent = ROOT2) => {
|
|
36348
|
+
parts.push({ shape: "crown", parent, x, y, z, radius, color: color2 });
|
|
34830
36349
|
};
|
|
34831
|
-
const line = (points,
|
|
34832
|
-
parts.push({ shape: "line", parent, points, color });
|
|
36350
|
+
const line = (points, color2, parent = ROOT2) => {
|
|
36351
|
+
parts.push({ shape: "line", parent, points, color: color2 });
|
|
34833
36352
|
};
|
|
34834
36353
|
const label = (lines, x, y, z, width, height, ry, o, parent = ROOT2) => {
|
|
34835
36354
|
parts.push({ shape: "label", parent, x, y, z, width, height, ry, lines, background: o.background, foreground: o.foreground, font: o.font ?? DEFAULT_LABEL_FONT, canvas: o.canvas ?? { width: 512, height: 128 } });
|
|
@@ -34845,14 +36364,14 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34845
36364
|
if (o.color !== void 0) part.color = o.color;
|
|
34846
36365
|
parts.push(part);
|
|
34847
36366
|
};
|
|
34848
|
-
const item = (x, y, z, width, height, depth,
|
|
34849
|
-
items.push({ x, y, z, width, height, depth, color, rx, ry, rz });
|
|
36367
|
+
const item = (x, y, z, width, height, depth, color2, ry = 0, rx = 0, rz = 0) => {
|
|
36368
|
+
items.push({ x, y, z, width, height, depth, color: color2, rx, ry, rz });
|
|
34850
36369
|
};
|
|
34851
|
-
const fence = (x0, z0, x1, z1, height,
|
|
36370
|
+
const fence = (x0, z0, x1, z1, height, color2 = DEFAULT_FENCE_POST) => {
|
|
34852
36371
|
const count = Math.ceil(Math.hypot(x1 - x0, z1 - z0) / 3);
|
|
34853
36372
|
for (let index = 0; index <= count; index++) {
|
|
34854
36373
|
const t = index / count;
|
|
34855
|
-
|
|
36374
|
+
cylinder2(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color2, ROOT2, 5);
|
|
34856
36375
|
}
|
|
34857
36376
|
for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], DEFAULT_FENCE_WIRE);
|
|
34858
36377
|
};
|
|
@@ -34862,7 +36381,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34862
36381
|
for (const window of silhouette.windows) (window.isLit ? windows.lit : windows.dark).push(window);
|
|
34863
36382
|
};
|
|
34864
36383
|
const scene = () => ({ parts, items, windows });
|
|
34865
|
-
return { random, pick, lights, parts, items, windows, group, box, cylinder, cone, prism, plane, vault, terrain, crown, line, label, light, spot, place, item, fence, add: add2, scene };
|
|
36384
|
+
return { random, pick, lights, parts, items, windows, group, box: box2, cylinder: cylinder2, cone: cone2, prism, plane: plane2, vault, terrain, crown, line, label, light, spot, place, item, fence, add: add2, scene };
|
|
34866
36385
|
}
|
|
34867
36386
|
var on = (o) => o.on ?? true;
|
|
34868
36387
|
function drawMast(b, mast, x, z, parent = -1) {
|
|
@@ -34972,8 +36491,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34972
36491
|
const style = walkStyle(options);
|
|
34973
36492
|
b.box(x, 0, z, w, style.height, d, style.color, { style: { castShadow: style.castShadow } });
|
|
34974
36493
|
}
|
|
34975
|
-
function drawGround(b,
|
|
34976
|
-
b.plane(0, 0, 0, size, size,
|
|
36494
|
+
function drawGround(b, color2 = DEFAULT_GROUND_COLOR, size = DEFAULT_GROUND_SIZE) {
|
|
36495
|
+
b.plane(0, 0, 0, size, size, color2);
|
|
34977
36496
|
}
|
|
34978
36497
|
function drawStreetGrid(b, block, count, width, options) {
|
|
34979
36498
|
for (let k = -count; k <= count; k++) {
|
|
@@ -34982,6 +36501,85 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34982
36501
|
}
|
|
34983
36502
|
for (let i = -count; i < count; i++) for (let j = -count; j < count; j++) drawWalk(b, i * block + block / 2, j * block + block / 2, block - width, block - width, options);
|
|
34984
36503
|
}
|
|
36504
|
+
var DEFAULT_TOWER_PRESETS = [
|
|
36505
|
+
{ id: "office", weight: 0.6, storeyHeight: 4, mechanicalEvery: 20, glass: true, color: 3955315 },
|
|
36506
|
+
{ id: "residential", weight: 0.4, storeyHeight: 3.1, mechanicalEvery: 25, glass: false }
|
|
36507
|
+
];
|
|
36508
|
+
var TOWER_DEFAULTS = { minHeight: 60, maxStoreys: 60, mast: 0.06, lit: 0.12 };
|
|
36509
|
+
var PODIUM = 1.5;
|
|
36510
|
+
var PLANT = 2895667;
|
|
36511
|
+
var PODIUM_COLOR = 2764081;
|
|
36512
|
+
var PARAPET = 2763308;
|
|
36513
|
+
var MAST = 10133670;
|
|
36514
|
+
var MAX_MAST = 25;
|
|
36515
|
+
var GLASS_COLUMN = 1.6;
|
|
36516
|
+
var MASONRY_COLUMN = 3;
|
|
36517
|
+
function pickTowerPreset(b, presets) {
|
|
36518
|
+
const total = presets.reduce((sum2, p) => sum2 + p.weight, 0);
|
|
36519
|
+
let r = b.random() * total;
|
|
36520
|
+
for (const preset of presets) {
|
|
36521
|
+
r -= preset.weight;
|
|
36522
|
+
if (r < 0) return preset;
|
|
36523
|
+
}
|
|
36524
|
+
return presets[presets.length - 1];
|
|
36525
|
+
}
|
|
36526
|
+
var towerStoreys = (height, preset) => Math.max(3, Math.round(height / preset.storeyHeight));
|
|
36527
|
+
function drawTower(b, request, options = {}) {
|
|
36528
|
+
const { x, z, width: w, depth: d, preset } = request;
|
|
36529
|
+
const storeys = towerStoreys(request.height, preset);
|
|
36530
|
+
const h = preset.storeyHeight;
|
|
36531
|
+
const podium = h * PODIUM;
|
|
36532
|
+
const skin = preset.color ?? request.color;
|
|
36533
|
+
const mechanical = /* @__PURE__ */ new Set();
|
|
36534
|
+
if (preset.mechanicalEvery) {
|
|
36535
|
+
for (let k = preset.mechanicalEvery; k < storeys - 1; k += preset.mechanicalEvery) if (storeys - 1 - k >= preset.mechanicalEvery / 2) mechanical.add(k);
|
|
36536
|
+
}
|
|
36537
|
+
mechanical.add(storeys - 1);
|
|
36538
|
+
b.box(x, 0, z, w + 0.6, podium, d + 0.6, PODIUM_COLOR);
|
|
36539
|
+
const shaftTop = (storeys - 1) * h;
|
|
36540
|
+
b.box(x, podium, z, w, shaftTop - podium + (podium - h), d, skin);
|
|
36541
|
+
for (const k of mechanical) {
|
|
36542
|
+
if (k === storeys - 1) b.box(x, k * h + (podium - h), z, w * 0.82, h, d * 0.82, PLANT);
|
|
36543
|
+
else b.box(x, k * h + (podium - h), z, w + 0.4, h, d + 0.4, PLANT);
|
|
36544
|
+
}
|
|
36545
|
+
const top = storeys * h + (podium - h);
|
|
36546
|
+
b.box(x, top, z, w * 0.82 + 0.3, 0.35, d * 0.82 + 0.3, PARAPET);
|
|
36547
|
+
const mast = Math.min(MAX_MAST, top * (options.mast ?? TOWER_DEFAULTS.mast));
|
|
36548
|
+
if (mast > 0.5) b.cylinder(x, top + 0.35, z, 0.12, 0.2, mast, MAST, -1, 6);
|
|
36549
|
+
const column = preset.glass ? GLASS_COLUMN : MASONRY_COLUMN;
|
|
36550
|
+
const winHeight = preset.glass ? h * 0.62 : 1.5;
|
|
36551
|
+
const winWidth = preset.glass ? column * 0.86 : 1.3;
|
|
36552
|
+
const faces = [[0, d / 2 + 0.03, w, 0], [0, -d / 2 - 0.03, w, Math.PI], [w / 2 + 0.03, 0, d, Math.PI / 2], [-w / 2 - 0.03, 0, d, -Math.PI / 2]];
|
|
36553
|
+
for (const [ox, oz, faceWidth, ry] of faces) {
|
|
36554
|
+
const columns = Math.max(1, Math.floor(faceWidth / column));
|
|
36555
|
+
for (let storey = 1; storey < storeys; storey++) {
|
|
36556
|
+
if (mechanical.has(storey)) continue;
|
|
36557
|
+
const y = storey * h + (podium - h) + h * (preset.glass ? 0.2 : 0.35);
|
|
36558
|
+
for (let c = 0; c < columns; c++) {
|
|
36559
|
+
const u = (c - (columns - 1) / 2) * column;
|
|
36560
|
+
const lit = b.random() < request.lit;
|
|
36561
|
+
(lit ? b.windows.lit : b.windows.dark).push({ x: ox ? x + ox : x + u, y, z: ox ? z + u : z + oz, ry, width: winWidth, height: winHeight, isLit: lit });
|
|
36562
|
+
}
|
|
36563
|
+
}
|
|
36564
|
+
}
|
|
36565
|
+
return { x, z, width: w, depth: d, height: top + 0.35 + (mast > 0.5 ? mast : 0), color: request.color };
|
|
36566
|
+
}
|
|
36567
|
+
function createTowerDrawer(towers, plain) {
|
|
36568
|
+
const minHeight = towers.minHeight ?? TOWER_DEFAULTS.minHeight;
|
|
36569
|
+
const presets = towers.presets ?? DEFAULT_TOWER_PRESETS;
|
|
36570
|
+
return (b, x, z, width, depth, height, color2, o = {}) => {
|
|
36571
|
+
if (height < minHeight || presets.length === 0) return plain(b, x, z, width, depth, height, color2, o);
|
|
36572
|
+
const preset = (o.preset ? presets.find((p) => p.id === o.preset) : void 0) ?? pickTowerPreset(b, presets);
|
|
36573
|
+
const top = (preset.maxStoreys ?? towers.maxStoreys ?? TOWER_DEFAULTS.maxStoreys) * preset.storeyHeight;
|
|
36574
|
+
const bottom = (preset.minStoreys ?? 0) * preset.storeyHeight;
|
|
36575
|
+
const request = { x, z, width, depth, height: Math.min(Math.max(height, bottom), top), color: color2, preset, lit: o.lit ?? towers.lit ?? TOWER_DEFAULTS.lit, ...o.use ? { use: o.use } : {}, ...o.storeys ? { storeys: o.storeys } : {} };
|
|
36576
|
+
if (towers.provider) {
|
|
36577
|
+
towers.provider(request, b);
|
|
36578
|
+
return { x, z, width, depth, height: request.height, color: color2 };
|
|
36579
|
+
}
|
|
36580
|
+
return drawTower(b, request, towers);
|
|
36581
|
+
};
|
|
36582
|
+
}
|
|
34985
36583
|
var DEFAULT_FACADES = [3882821, 4866104, 3489853, 4602426, 4143924, 3093561, 5327167, 5920334];
|
|
34986
36584
|
var DEFAULT_PARTITIONS = 2;
|
|
34987
36585
|
var DEFAULT_GAP = 0.08;
|
|
@@ -34989,9 +36587,21 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
34989
36587
|
var FOOTPRINT_TRIM = 1.2;
|
|
34990
36588
|
var FOOTPRINT_JITTER = 1.5;
|
|
34991
36589
|
var HEIGHT_CURVE = 1.6;
|
|
34992
|
-
|
|
34993
|
-
|
|
34994
|
-
|
|
36590
|
+
var DEFAULT_STOREY_PRESETS = { commercial: "office", residential: "residential" };
|
|
36591
|
+
function drawBuilding(b, x, z, width, depth, height, color2, options = {}) {
|
|
36592
|
+
b.add(createCityBuilding({ x, z, width, depth, height, color: color2, random: b.random, shopChance: options.shop, litChance: options.lit }));
|
|
36593
|
+
return { x, z, width, depth, height, color: color2 };
|
|
36594
|
+
}
|
|
36595
|
+
function pickUse(b, uses) {
|
|
36596
|
+
const entries = Object.entries(uses).filter(([, w]) => w > 0);
|
|
36597
|
+
const total = entries.reduce((sum2, [, w]) => sum2 + w, 0);
|
|
36598
|
+
if (total <= 0) return void 0;
|
|
36599
|
+
let r = b.random() * total;
|
|
36600
|
+
for (const [use, w] of entries) {
|
|
36601
|
+
r -= w;
|
|
36602
|
+
if (r < 0) return use;
|
|
36603
|
+
}
|
|
36604
|
+
return entries[entries.length - 1][0];
|
|
34995
36605
|
}
|
|
34996
36606
|
var lerp2 = (a, b, t) => a + (b - a) * t;
|
|
34997
36607
|
function fillBlock(b, cx, cz, size, minHeight, maxHeight, options = {}) {
|
|
@@ -35005,11 +36615,25 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35005
36615
|
if (b.random() < (options.gap || DEFAULT_GAP)) continue;
|
|
35006
36616
|
const width = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
35007
36617
|
const depth = lot - FOOTPRINT_TRIM - b.random() * FOOTPRINT_JITTER;
|
|
35008
|
-
const
|
|
36618
|
+
const t = Math.pow(b.random(), HEIGHT_CURVE);
|
|
36619
|
+
let height = lerp2(minHeight, maxHeight, t);
|
|
36620
|
+
let extra = null;
|
|
36621
|
+
if (options.floors !== void 0 || options.uses) {
|
|
36622
|
+
const use = options.uses ? pickUse(b, options.uses) : void 0;
|
|
36623
|
+
const presetId = use ? (options.storeyPresets ?? DEFAULT_STOREY_PRESETS)[use] : void 0;
|
|
36624
|
+
const preset = presetId ? (options.presets ?? DEFAULT_TOWER_PRESETS).find((p) => p.id === presetId) : void 0;
|
|
36625
|
+
extra = { use, preset: preset?.id };
|
|
36626
|
+
if (options.floors !== void 0) {
|
|
36627
|
+
const [lo, hi] = typeof options.floors === "number" ? [options.floors, options.floors] : options.floors;
|
|
36628
|
+
const storeys = Math.max(1, Math.round(lerp2(lo, hi, t)));
|
|
36629
|
+
height = storeys * (preset?.storeyHeight ?? options.storeyHeight ?? DEFAULT_TOWER_PRESETS[DEFAULT_TOWER_PRESETS.length - 1].storeyHeight);
|
|
36630
|
+
extra.storeys = storeys;
|
|
36631
|
+
}
|
|
36632
|
+
}
|
|
35009
36633
|
const x = cx - half + lot * (i + 0.5);
|
|
35010
36634
|
const z = cz - half + lot * (j + 0.5);
|
|
35011
|
-
const
|
|
35012
|
-
placed.push(drawBuilding(b, x, z, width, depth, height,
|
|
36635
|
+
const color2 = b.pick(palette);
|
|
36636
|
+
placed.push((options.draw ?? drawBuilding)(b, x, z, width, depth, height, color2, extra ? { ...options, ...extra } : options));
|
|
35013
36637
|
}
|
|
35014
36638
|
}
|
|
35015
36639
|
return placed;
|
|
@@ -35029,11 +36653,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35029
36653
|
var LABEL_CANVAS = { width: 1024, height: 200 };
|
|
35030
36654
|
var SANDBAG_LENGTH = 0.56;
|
|
35031
36655
|
var colors = (c) => ({ ...DEFAULT_PROP_COLORS, ...c });
|
|
35032
|
-
function drawFence(b, x0, z0, x1, z1, height,
|
|
35033
|
-
b.fence(x0, z0, x1, z1, height,
|
|
36656
|
+
function drawFence(b, x0, z0, x1, z1, height, color2) {
|
|
36657
|
+
b.fence(x0, z0, x1, z1, height, color2);
|
|
35034
36658
|
}
|
|
35035
|
-
function drawTent(b, x, z, ry, w, d,
|
|
35036
|
-
b.prism(x, 0, z, w, w * 0.7, d,
|
|
36659
|
+
function drawTent(b, x, z, ry, w, d, color2, c) {
|
|
36660
|
+
b.prism(x, 0, z, w, w * 0.7, d, color2 ?? colors(c).tent, -1, ry);
|
|
35037
36661
|
}
|
|
35038
36662
|
function drawPine(b, x, z, scale = 1, y = 0, c) {
|
|
35039
36663
|
const palette = colors(c);
|
|
@@ -35047,9 +36671,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35047
36671
|
b.cylinder(x, 0, z, 0.12 * scale, 0.18 * scale, 1.8 * scale, palette.trunk);
|
|
35048
36672
|
b.crown(x, 2.6 * scale, z, 1.3 * scale, b.pick(palette.crowns));
|
|
35049
36673
|
}
|
|
35050
|
-
function drawContainer(b, x, y, z, ry,
|
|
36674
|
+
function drawContainer(b, x, y, z, ry, color2) {
|
|
35051
36675
|
const group = b.group(x, y, z, ry);
|
|
35052
|
-
b.box(0, 0, 0, 2.44, 2.6, 6.06,
|
|
36676
|
+
b.box(0, 0, 0, 2.44, 2.6, 6.06, color2, { parent: group, style: { roughness: 0.7, metalness: 0.3 } });
|
|
35053
36677
|
for (let k = -2; k <= 2; k++) b.box(k * 0.5, 0, 3.04, 0.06, 2.6, 0.04, 1710618, { parent: group });
|
|
35054
36678
|
}
|
|
35055
36679
|
function drawWatchtower(b, x, z, searchlight = false, c) {
|
|
@@ -35061,11 +36685,11 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35061
36685
|
if (searchlight) b.spot({ x, y: 9, z, color: 15134463, intensity: 2.4, distance: 70, decay: 1.2, angle: 0.35, penumbra: 0.5, target: { x: x + 20, y: 0, z: z + 20 }, sweep: { speed: 0.5, radius: 25 } });
|
|
35062
36686
|
}
|
|
35063
36687
|
function drawSandbags(b, x, z, length, along, rows = 3, c) {
|
|
35064
|
-
const
|
|
36688
|
+
const color2 = colors(c).sandbags;
|
|
35065
36689
|
for (let r = 0; r < rows; r++) {
|
|
35066
36690
|
for (let i = 0; i < length / SANDBAG_LENGTH; i++) {
|
|
35067
36691
|
const u = -length / 2 + 0.28 + i * SANDBAG_LENGTH + r % 2 * 0.28;
|
|
35068
|
-
b.item(along === "x" ? x + u : x, r * 0.21, along === "x" ? z : z + u, along === "x" ? 0.55 : 0.32, 0.22, along === "x" ? 0.32 : 0.55,
|
|
36692
|
+
b.item(along === "x" ? x + u : x, r * 0.21, along === "x" ? z : z + u, along === "x" ? 0.55 : 0.32, 0.22, along === "x" ? 0.32 : 0.55, color2);
|
|
35069
36693
|
}
|
|
35070
36694
|
}
|
|
35071
36695
|
}
|
|
@@ -35073,8 +36697,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35073
36697
|
const { background, foreground } = colors(c).label;
|
|
35074
36698
|
b.label([title, subtitle], x, 2.4, z, 22, 4.4, 0, { background, foreground, font: LABEL_FONT, canvas: LABEL_CANVAS });
|
|
35075
36699
|
}
|
|
35076
|
-
function drawWires(b, points, y,
|
|
35077
|
-
b.line(points.map(([px, pz]) => [px, y, pz]),
|
|
36700
|
+
function drawWires(b, points, y, color2, c) {
|
|
36701
|
+
b.line(points.map(([px, pz]) => [px, y, pz]), color2 ?? colors(c).wire);
|
|
35078
36702
|
}
|
|
35079
36703
|
var DEFAULT_WIDTH = 8;
|
|
35080
36704
|
var DEFAULT_DEPTH = 10;
|
|
@@ -35136,18 +36760,18 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35136
36760
|
else kit.props.tree(b, request.x, request.z, request.scale);
|
|
35137
36761
|
plan.plants.push(request);
|
|
35138
36762
|
},
|
|
35139
|
-
building: (x, z, width, depth, height,
|
|
35140
|
-
plan.buildings.push(kit.block.building(b, x, z, width, depth, height,
|
|
36763
|
+
building: (x, z, width, depth, height, color2, o) => {
|
|
36764
|
+
plan.buildings.push(kit.block.building(b, x, z, width, depth, height, color2, o));
|
|
35141
36765
|
}
|
|
35142
36766
|
};
|
|
35143
36767
|
composer(context);
|
|
35144
|
-
const
|
|
35145
|
-
kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z,
|
|
36768
|
+
const text2 = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
|
|
36769
|
+
kit.props.label(b, origin.x, origin.z + LABEL_OFFSET_Z, text2.title, text2.caption);
|
|
35146
36770
|
return plan;
|
|
35147
36771
|
}
|
|
35148
36772
|
function patternMarker(pattern, origin, locale = DEFAULT_LOCALE) {
|
|
35149
|
-
const
|
|
35150
|
-
return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label:
|
|
36773
|
+
const text2 = pattern.text[locale] ?? pattern.text[DEFAULT_LOCALE];
|
|
36774
|
+
return { x: origin.x, y: pattern.marker.height, z: origin.z + (pattern.marker.dz ?? 0), label: text2.label, detail: text2.detail };
|
|
35151
36775
|
}
|
|
35152
36776
|
var composeDowntown = ({ b, kit, pattern, params, x0, z0, pole, building, vehicle }) => {
|
|
35153
36777
|
b.plane(x0, 0.02, z0, params.roadLength, pattern.streetWidth, params.roadColor);
|
|
@@ -35543,6 +37167,359 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35543
37167
|
rural: { x: 80, z: 56 },
|
|
35544
37168
|
military: { x: 160, z: 0 }
|
|
35545
37169
|
};
|
|
37170
|
+
var DEFAULT_DISTRICT_KINDS = [
|
|
37171
|
+
{ id: "downtown", color: "#3b4f7a", zone: "zt", pattern: "financial_district", floors: [20, 60], uses: { commercial: 0.7, residential: 0.3 }, block: 90, text: { "pt-BR": { name: "Centro financeiro", description: "Arranha-c\xE9us de escrit\xF3rio e torres residenciais, de 20 a 60 andares." }, en: { name: "Downtown core", description: "Office and residential towers, 20 to 60 storeys." } } },
|
|
37172
|
+
{ id: "commercial_center", color: "#5b6fa3", zone: "zc", pattern: "downtown", floors: [4, 15], uses: { commercial: 0.6, residential: 0.4 }, block: 70, text: { "pt-BR": { name: "Centro comercial", description: "Loja no t\xE9rreo e escrit\xF3rios ou apartamentos em cima, de 4 a 15 andares." }, en: { name: "Commercial centre", description: "Shops on the ground floor, offices or flats above, 4 to 15 storeys." } } },
|
|
37173
|
+
{ id: "residential_high", color: "#7a68a6", zone: "zt", pattern: "downtown", floors: [8, 25], uses: { residential: 0.9, commercial: 0.1 }, block: 80, text: { "pt-BR": { name: "Bairro de pr\xE9dios", description: "Condom\xEDnios de pr\xE9dios de 8 a 25 andares com \xE1rea de lazer." }, en: { name: "High-rise residential", description: "Apartment blocks, 8 to 25 storeys." } } },
|
|
37174
|
+
{ id: "mixed", color: "#9c7fb8", zone: "zr3_zm", pattern: "downtown", floors: [2, 8], uses: { residential: 0.6, commercial: 0.4 }, block: 60, text: { "pt-BR": { name: "Uso misto", description: "Sobrados, pr\xE9dios baixos e com\xE9rcio de rua, de 2 a 8 andares." }, en: { name: "Mixed use", description: "Townhouses, low blocks and street shops, 2 to 8 storeys." } } },
|
|
37175
|
+
{ id: "residential_mid", color: "#d9a35b", zone: "zr2", pattern: "suburb", floors: [1, 4], uses: { residential: 0.95, commercial: 0.05 }, block: 60, text: { "pt-BR": { name: "Residencial m\xE9dio", description: "Casas, sobrados e pr\xE9dios pequenos de at\xE9 4 andares." }, en: { name: "Mid-density residential", description: "Houses and small blocks up to 4 storeys." } } },
|
|
37176
|
+
{ id: "residential_low", color: "#e8c477", zone: "zr1", pattern: "suburb", floors: [1, 2], uses: { residential: 1 }, block: 70, text: { "pt-BR": { name: "Residencial de casas", description: "Casas t\xE9rreas e sobrados com quintal e recuo." }, en: { name: "Low-density residential", description: "Houses with yards and setbacks." } } },
|
|
37177
|
+
{ id: "residential_gated", color: "#c9d27a", zone: "zr_ap", pattern: "suburb", floors: [1, 3], uses: { residential: 1 }, block: 110, text: { "pt-BR": { name: "Condom\xEDnio fechado", description: "Casas grandes em lotes amplos, muro e portaria." }, en: { name: "Gated community", description: "Large houses on big lots behind a wall and gate." } } },
|
|
37178
|
+
{ id: "residential_popular", color: "#d97a5b", zone: "zr2", pattern: "working_class", floors: [1, 3], uses: { residential: 0.9, commercial: 0.1 }, block: 45, text: { "pt-BR": { name: "Bairro popular", description: "Casas geminadas de laje, sem recuo, ruas estreitas." }, en: { name: "Working-class housing", description: "Terraced self-built houses, narrow streets." } } },
|
|
37179
|
+
{ id: "historic", color: "#b5835a", zone: "zr3_zm", pattern: "historic_street", floors: [1, 3], uses: { residential: 0.6, commercial: 0.4 }, block: 50, text: { "pt-BR": { name: "Centro hist\xF3rico", description: "Casario colonial colado, rua de pedra, igreja e pra\xE7a." }, en: { name: "Historic quarter", description: "Colonial row houses, cobbled streets, church and square." } } },
|
|
37180
|
+
{ id: "institutional", color: "#5aa3a0", zone: "zc", pattern: "downtown", floors: [2, 8], uses: { institutional: 1 }, block: 120, text: { "pt-BR": { name: "Institucional", description: "Hospital, escola, campus, prefeitura, delegacia e quartel de bombeiros." }, en: { name: "Institutional", description: "Hospital, schools, campus, city hall, police and fire stations." } } },
|
|
37181
|
+
{ id: "industrial", color: "#7d7d74", zone: "zi", pattern: "industrial_port", floors: [1, 1], uses: { industrial: 1 }, block: 140, text: { "pt-BR": { name: "Distrito industrial", description: "Galp\xF5es de 8 a 12 m, p\xE1tios e chamin\xE9s." }, en: { name: "Industrial district", description: "Sheds 8 to 12 m tall, yards and stacks." } } },
|
|
37182
|
+
{ id: "logistics", color: "#96968a", zone: "zi", pattern: "industrial_port", floors: [1, 1], uses: { industrial: 0.8, commercial: 0.2 }, block: 160, text: { "pt-BR": { name: "Distrito log\xEDstico", description: "Centros de distribui\xE7\xE3o, posto de caminhoneiro, p\xE1tio de carretas." }, en: { name: "Logistics", description: "Distribution centres, truck stop, trailer yards." } } },
|
|
37183
|
+
{ id: "port", color: "#5f7f8f", zone: "zi", pattern: "industrial_port", floors: [1, 1], uses: { industrial: 1 }, block: 150, text: { "pt-BR": { name: "Porto", description: "Cais, guindastes, cont\xEAineres empilhados e armaz\xE9ns." }, en: { name: "Port", description: "Quays, cranes, stacked containers and warehouses." } } },
|
|
37184
|
+
{ id: "rail_yard", color: "#6e6158", zone: "zi", pattern: "railway_village", floors: [1, 1], uses: { industrial: 1 }, block: 150, text: { "pt-BR": { name: "P\xE1tio ferrovi\xE1rio", description: "Linhas, rotunda, oficina e vag\xF5es parados." }, en: { name: "Rail yard", description: "Tracks, roundhouse, workshop and parked wagons." } } },
|
|
37185
|
+
{ id: "company_housing", color: "#cfae86", zone: "zr1", pattern: "railway_village", floors: [1, 1], uses: { residential: 1 }, block: 50, text: { "pt-BR": { name: "Vila oper\xE1ria", description: "Casas iguais em fila, da ferrovia ou da f\xE1brica." }, en: { name: "Company housing", description: "Identical row houses of the railway or the mill." } } },
|
|
37186
|
+
{ id: "hillside_resort", color: "#a3b58a", zone: "zr1", pattern: "mountain_resort", floors: [1, 2], uses: { residential: 0.7, commercial: 0.3 }, block: 60, text: { "pt-BR": { name: "Serra e pousadas", description: "Chal\xE9s e pousadas na encosta, ruas em curva." }, en: { name: "Hillside resort", description: "Chalets and inns on the slope." } } },
|
|
37187
|
+
{ id: "stilt", color: "#7fa3b5", zone: "zr1", pattern: "stilt_village", floors: [1, 1], uses: { residential: 1 }, block: 40, text: { "pt-BR": { name: "Palafitas", description: "Casas sobre estacas na beira da \xE1gua, trapiches." }, en: { name: "Stilt village", description: "Houses on stilts by the water, jetties." } } },
|
|
37188
|
+
{ id: "park", color: "#6f9a5e", zone: null, pattern: null, floors: [0, 0], uses: {}, block: 0, text: { "pt-BR": { name: "Parque e pra\xE7a", description: "\xC1rea verde, pra\xE7a, campo de futebol." }, en: { name: "Park", description: "Green space, square, football pitch." } } },
|
|
37189
|
+
{ id: "rural", color: "#a7b86d", zone: "za", pattern: "rural", floors: [1, 2], uses: { residential: 0.8, agricultural: 0.2 }, block: 250, text: { "pt-BR": { name: "Ch\xE1caras e s\xEDtios", description: "S\xEDtios com casa sede, horta e pomar." }, en: { name: "Smallholdings", description: "Smallholdings with a farmhouse, garden and orchard." } } },
|
|
37190
|
+
{ id: "farmland", color: "#c8c67e", zone: "za", pattern: "rural", floors: [0, 1], uses: { agricultural: 1 }, block: 400, text: { "pt-BR": { name: "Lavoura", description: "Planta\xE7\xE3o, silos e estradas de terra." }, en: { name: "Farmland", description: "Crops, silos and dirt roads." } } },
|
|
37191
|
+
{ id: "airfield", color: "#b8b8a8", zone: "za", pattern: "rural", floors: [1, 1], uses: { industrial: 1 }, block: 300, text: { "pt-BR": { name: "Aer\xF3dromo", description: "Pista, hangares e torre." }, en: { name: "Airfield", description: "Runway, hangars and tower." } } },
|
|
37192
|
+
{ id: "wild", color: "#4f7a4a", zone: null, pattern: null, floors: [0, 0], uses: {}, block: 0, text: { "pt-BR": { name: "Mata e morro", description: "Mata, morro, \xE1rea sem constru\xE7\xE3o." }, en: { name: "Woods and hills", description: "Woods and hills, unbuilt." } } }
|
|
37193
|
+
];
|
|
37194
|
+
var DEFAULT_CITY_PLANS = [
|
|
37195
|
+
{
|
|
37196
|
+
id: "metropolis",
|
|
37197
|
+
scale: "metropolis",
|
|
37198
|
+
size: [6e3, 6e3],
|
|
37199
|
+
text: { "pt-BR": { name: "Metr\xF3pole", description: "Centro financeiro de arranha-c\xE9us cercado de com\xE9rcio, bairros de pr\xE9dios, casas, periferia e ind\xFAstria na ferrovia." }, en: { name: "Metropolis", description: "Tower downtown ringed by commerce, high-rise and low-rise neighbourhoods, outskirts and industry on the railway." } },
|
|
37200
|
+
districts: [
|
|
37201
|
+
{ id: "outskirts", kind: "rural", rect: [-3e3, -3e3, 3e3, 3e3] },
|
|
37202
|
+
{ id: "suburbs_w", kind: "residential_low", rect: [-2600, -2600, -600, 2600] },
|
|
37203
|
+
{ id: "suburbs_e", kind: "residential_mid", rect: [600, -2600, 2600, 1e3] },
|
|
37204
|
+
{ id: "periphery", kind: "residential_popular", rect: [-3e3, 1200, -800, 3e3] },
|
|
37205
|
+
{ id: "gated", kind: "residential_gated", rect: [1400, -3e3, 3e3, -1800] },
|
|
37206
|
+
{ id: "industry", kind: "industrial", rect: [600, 1600, 3e3, 3e3] },
|
|
37207
|
+
{ id: "logistics", kind: "logistics", rect: [2200, 400, 3e3, 1600] },
|
|
37208
|
+
{ id: "mixed", kind: "mixed", rect: [-1600, -1600, 1600, 1400] },
|
|
37209
|
+
{ id: "high", kind: "residential_high", rect: [-1400, -1500, -300, -300] },
|
|
37210
|
+
{ id: "high2", kind: "residential_high", rect: [300, -1500, 1400, -600] },
|
|
37211
|
+
{ id: "center", kind: "commercial_center", rect: [-900, -700, 1100, 800] },
|
|
37212
|
+
{ id: "downtown", kind: "downtown", rect: [-400, -400, 600, 400] },
|
|
37213
|
+
{ id: "hospital", kind: "institutional", rect: [-1600, 500, -1e3, 1100] },
|
|
37214
|
+
{ id: "campus", kind: "institutional", rect: [1300, -500, 2e3, 300] },
|
|
37215
|
+
{ id: "park", kind: "park", rect: [-300, 500, 500, 1100] },
|
|
37216
|
+
{ id: "hills", kind: "wild", rect: [-3e3, -3e3, -1800, -1800] }
|
|
37217
|
+
],
|
|
37218
|
+
lines: [
|
|
37219
|
+
{ id: "ring", kind: "avenue", width: 30, points: [[-1700, -1700], [1700, -1700], [1700, 1500], [-1700, 1500], [-1700, -1700]] },
|
|
37220
|
+
{ id: "main_ns", kind: "avenue", width: 30, points: [[100, -3e3], [100, 3e3]] },
|
|
37221
|
+
{ id: "main_ew", kind: "avenue", width: 30, points: [[-3e3, 0], [3e3, 0]] },
|
|
37222
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-3e3, 1500], [0, 1500], [3e3, 1300]] },
|
|
37223
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-3e3, -2200], [3e3, -2400]] }
|
|
37224
|
+
],
|
|
37225
|
+
landmarks: [
|
|
37226
|
+
{ kind: "station", at: [0, 1500] },
|
|
37227
|
+
{ kind: "hospital", at: [-1300, 800] },
|
|
37228
|
+
{ kind: "stadium", at: [1700, -100] },
|
|
37229
|
+
{ kind: "cathedral", at: [100, 100] },
|
|
37230
|
+
{ kind: "mall", at: [800, 600] },
|
|
37231
|
+
{ kind: "police", at: [300, -300] },
|
|
37232
|
+
{ kind: "fire", at: [-500, 600] },
|
|
37233
|
+
{ kind: "gas_station", at: [2400, -2300] }
|
|
37234
|
+
]
|
|
37235
|
+
},
|
|
37236
|
+
{
|
|
37237
|
+
id: "regional_city",
|
|
37238
|
+
scale: "city",
|
|
37239
|
+
size: [4e3, 4e3],
|
|
37240
|
+
text: { "pt-BR": { name: "Cidade m\xE9dia", description: "Centro comercial com poucos pr\xE9dios altos, bairros de casas e um distrito industrial." }, en: { name: "Regional city", description: "Commercial centre with a few tall buildings, house neighbourhoods and an industrial district." } },
|
|
37241
|
+
districts: [
|
|
37242
|
+
{ id: "outskirts", kind: "rural", rect: [-2e3, -2e3, 2e3, 2e3] },
|
|
37243
|
+
{ id: "res_w", kind: "residential_low", rect: [-1800, -1600, -300, 1600] },
|
|
37244
|
+
{ id: "res_e", kind: "residential_mid", rect: [300, -1600, 1800, 600] },
|
|
37245
|
+
{ id: "popular", kind: "residential_popular", rect: [-1800, 800, -500, 1800] },
|
|
37246
|
+
{ id: "industry", kind: "industrial", rect: [500, 900, 2e3, 2e3] },
|
|
37247
|
+
{ id: "mixed", kind: "mixed", rect: [-800, -800, 900, 700] },
|
|
37248
|
+
{ id: "center", kind: "commercial_center", rect: [-400, -400, 500, 350] },
|
|
37249
|
+
{ id: "square", kind: "park", rect: [-100, -100, 150, 100] },
|
|
37250
|
+
{ id: "hospital", kind: "institutional", rect: [600, -900, 1e3, -500] }
|
|
37251
|
+
],
|
|
37252
|
+
lines: [
|
|
37253
|
+
{ id: "main_ns", kind: "avenue", width: 24, points: [[0, -2e3], [0, 2e3]] },
|
|
37254
|
+
{ id: "main_ew", kind: "avenue", width: 24, points: [[-2e3, 0], [2e3, 0]] },
|
|
37255
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-2e3, 1600], [2e3, 1500]] }
|
|
37256
|
+
],
|
|
37257
|
+
landmarks: [
|
|
37258
|
+
{ kind: "church", at: [0, 0] },
|
|
37259
|
+
{ kind: "hospital", at: [800, -700] },
|
|
37260
|
+
{ kind: "school", at: [-600, 300] },
|
|
37261
|
+
{ kind: "police", at: [200, -200] },
|
|
37262
|
+
{ kind: "market", at: [300, 200] },
|
|
37263
|
+
{ kind: "gas_station", at: [1500, 1550] }
|
|
37264
|
+
]
|
|
37265
|
+
},
|
|
37266
|
+
{
|
|
37267
|
+
id: "port_city",
|
|
37268
|
+
scale: "city",
|
|
37269
|
+
size: [4e3, 3e3],
|
|
37270
|
+
text: { "pt-BR": { name: "Cidade portu\xE1ria", description: "Cais e armaz\xE9ns na margem, centro hist\xF3rico atr\xE1s do porto e bairros subindo o morro." }, en: { name: "Port city", description: "Quays and warehouses on the shore, old town behind the port and neighbourhoods up the hill." } },
|
|
37271
|
+
districts: [
|
|
37272
|
+
{ id: "outskirts", kind: "rural", rect: [-2e3, -1500, 2e3, 1500] },
|
|
37273
|
+
{ id: "hill", kind: "residential_low", rect: [500, -1500, 2e3, -200] },
|
|
37274
|
+
{ id: "res", kind: "residential_mid", rect: [-300, -500, 1300, 900] },
|
|
37275
|
+
{ id: "industry", kind: "industrial", rect: [800, 500, 2e3, 1500] },
|
|
37276
|
+
{ id: "port", kind: "port", rect: [-2e3, -1300, -900, 1100] },
|
|
37277
|
+
{ id: "old_town", kind: "historic", rect: [-900, -800, 200, 400] },
|
|
37278
|
+
{ id: "center", kind: "commercial_center", rect: [-500, -400, 200, 100] },
|
|
37279
|
+
{ id: "stilts", kind: "stilt", rect: [-2e3, 1e3, -1200, 1500] }
|
|
37280
|
+
],
|
|
37281
|
+
lines: [
|
|
37282
|
+
{ id: "shore", kind: "river", width: 60, points: [[-2e3, -1500], [-1950, 0], [-1900, 1500]] },
|
|
37283
|
+
{ id: "main", kind: "avenue", width: 20, points: [[-900, 0], [2e3, 0]] },
|
|
37284
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-1200, -1500], [-1e3, -400]] }
|
|
37285
|
+
],
|
|
37286
|
+
landmarks: [
|
|
37287
|
+
{ kind: "harbour_office", at: [-1200, 0] },
|
|
37288
|
+
{ kind: "church", at: [-300, -200] },
|
|
37289
|
+
{ kind: "market", at: [-600, 200] },
|
|
37290
|
+
{ kind: "lighthouse", at: [1600, -1300] },
|
|
37291
|
+
{ kind: "hospital", at: [700, 200] }
|
|
37292
|
+
]
|
|
37293
|
+
},
|
|
37294
|
+
{
|
|
37295
|
+
id: "rail_town",
|
|
37296
|
+
scale: "town",
|
|
37297
|
+
size: [3e3, 3e3],
|
|
37298
|
+
text: { "pt-BR": { name: "Cidade ferrovi\xE1ria", description: "Cidade que cresceu em volta da esta\xE7\xE3o: p\xE1tio, centro velho, vila da companhia." }, en: { name: "Rail town", description: "A town grown around its station: rail yard, old centre, company village." } },
|
|
37299
|
+
districts: [
|
|
37300
|
+
{ id: "outskirts", kind: "rural", rect: [-1500, -1500, 1500, 1500] },
|
|
37301
|
+
{ id: "res", kind: "residential_low", rect: [500, 500, 1500, 1500] },
|
|
37302
|
+
{ id: "logistics", kind: "logistics", rect: [500, -700, 1500, 500] },
|
|
37303
|
+
{ id: "popular", kind: "residential_popular", rect: [-1500, 600, -300, 1500] },
|
|
37304
|
+
{ id: "company", kind: "company_housing", rect: [-1500, -800, -700, 500] },
|
|
37305
|
+
{ id: "yard", kind: "rail_yard", rect: [-700, 0, 500, 500] },
|
|
37306
|
+
{ id: "old_center", kind: "historic", rect: [-500, -700, 500, 0] }
|
|
37307
|
+
],
|
|
37308
|
+
lines: [
|
|
37309
|
+
{ id: "rail", kind: "rail", width: 12, points: [[-1500, 250], [1500, 250]] },
|
|
37310
|
+
{ id: "highway", kind: "highway", width: 24, points: [[-1500, -100], [1500, -200]] }
|
|
37311
|
+
],
|
|
37312
|
+
landmarks: [
|
|
37313
|
+
{ kind: "station", at: [0, 100] },
|
|
37314
|
+
{ kind: "church", at: [0, -400] },
|
|
37315
|
+
{ kind: "gas_station", at: [1e3, -150] }
|
|
37316
|
+
]
|
|
37317
|
+
},
|
|
37318
|
+
{
|
|
37319
|
+
id: "country_town",
|
|
37320
|
+
scale: "town",
|
|
37321
|
+
size: [2400, 2400],
|
|
37322
|
+
text: { "pt-BR": { name: "Cidade do interior", description: "Pra\xE7a da matriz no meio, com\xE9rcio t\xE9rreo em volta, casas, cooperativa e s\xEDtios em volta." }, en: { name: "Country town", description: "Main square with the church, ground-floor shops around, houses, a co-op and smallholdings." } },
|
|
37323
|
+
districts: [
|
|
37324
|
+
{ id: "farms", kind: "farmland", rect: [-1200, -1200, 1200, 1200] },
|
|
37325
|
+
{ id: "chacaras", kind: "rural", rect: [-900, -900, 900, 900] },
|
|
37326
|
+
{ id: "houses", kind: "residential_low", rect: [-600, -600, 600, 500] },
|
|
37327
|
+
{ id: "company", kind: "company_housing", rect: [-700, 350, -250, 700] },
|
|
37328
|
+
{ id: "coop", kind: "farmland", rect: [350, 500, 900, 900], floors: [1, 1], uses: { agricultural: 0.5, industrial: 0.5 } },
|
|
37329
|
+
{ id: "center", kind: "historic", rect: [-250, -250, 250, 250] },
|
|
37330
|
+
{ id: "square", kind: "park", rect: [-60, -60, 60, 60] }
|
|
37331
|
+
],
|
|
37332
|
+
lines: [
|
|
37333
|
+
{ id: "main", kind: "street", width: 12, points: [[-1200, 0], [1200, 0]] },
|
|
37334
|
+
{ id: "road", kind: "street", width: 8, points: [[0, -1200], [0, 1200]] }
|
|
37335
|
+
],
|
|
37336
|
+
landmarks: [
|
|
37337
|
+
{ kind: "church", at: [0, -70] },
|
|
37338
|
+
{ kind: "market", at: [120, 80] },
|
|
37339
|
+
{ kind: "school", at: [-300, -200] },
|
|
37340
|
+
{ kind: "gas_station", at: [700, 0] },
|
|
37341
|
+
{ kind: "silo", at: [600, 700] }
|
|
37342
|
+
]
|
|
37343
|
+
},
|
|
37344
|
+
{
|
|
37345
|
+
id: "mountain_village",
|
|
37346
|
+
scale: "village",
|
|
37347
|
+
size: [1600, 1200],
|
|
37348
|
+
text: { "pt-BR": { name: "Vila serrana", description: "Largo da igreja, rua das pousadas subindo a serra e chal\xE9s espalhados na encosta." }, en: { name: "Mountain village", description: "Church square, a street of inns up the slope and chalets scattered on the hill." } },
|
|
37349
|
+
districts: [
|
|
37350
|
+
{ id: "wild", kind: "wild", rect: [-800, -600, 800, 600] },
|
|
37351
|
+
{ id: "slope", kind: "rural", rect: [-700, -500, 700, 500] },
|
|
37352
|
+
{ id: "resort", kind: "hillside_resort", rect: [-400, -450, 500, 0] },
|
|
37353
|
+
{ id: "square", kind: "historic", rect: [-250, 0, 250, 300] },
|
|
37354
|
+
{ id: "station", kind: "rail_yard", rect: [300, 150, 700, 450] }
|
|
37355
|
+
],
|
|
37356
|
+
lines: [
|
|
37357
|
+
{ id: "road", kind: "street", width: 7, points: [[-800, 200], [0, 150], [800, 300]] }
|
|
37358
|
+
],
|
|
37359
|
+
landmarks: [
|
|
37360
|
+
{ kind: "church", at: [0, 150] },
|
|
37361
|
+
{ kind: "inn", at: [100, -200] },
|
|
37362
|
+
{ kind: "station", at: [500, 300] }
|
|
37363
|
+
]
|
|
37364
|
+
}
|
|
37365
|
+
];
|
|
37366
|
+
var DEFAULT_MAX_FLOORS = 60;
|
|
37367
|
+
var DEFAULT_STREET = 10;
|
|
37368
|
+
var DEFAULT_LOCALE2 = "pt-BR";
|
|
37369
|
+
var DEFAULT_STOREY_PRESETS2 = { commercial: "office", residential: "residential" };
|
|
37370
|
+
var text = (by, locale) => by[locale] ?? by[DEFAULT_LOCALE2];
|
|
37371
|
+
var named = (name, locale) => typeof name === "string" || name === void 0 ? name : name[locale] ?? name[DEFAULT_LOCALE2];
|
|
37372
|
+
var mergeLists = (parent, own) => {
|
|
37373
|
+
const list2 = [...parent];
|
|
37374
|
+
for (const item of own) {
|
|
37375
|
+
const at = list2.findIndex((p) => p.id === item.id);
|
|
37376
|
+
if (at >= 0) list2[at] = item;
|
|
37377
|
+
else list2.push(item);
|
|
37378
|
+
}
|
|
37379
|
+
return list2;
|
|
37380
|
+
};
|
|
37381
|
+
function createCities(o) {
|
|
37382
|
+
const maxFloors = o.maxFloors ?? DEFAULT_MAX_FLOORS;
|
|
37383
|
+
const kindEntries = mergeById(DEFAULT_DISTRICT_KINDS, o.packs?.kinds);
|
|
37384
|
+
applyPatches(kindEntries, o.patch?.kinds);
|
|
37385
|
+
removeIds(kindEntries, o.remove?.kinds);
|
|
37386
|
+
const planEntries = mergeById(DEFAULT_CITY_PLANS, o.packs?.plans);
|
|
37387
|
+
applyPatches(planEntries, o.patch?.plans);
|
|
37388
|
+
removeIds(planEntries, o.remove?.plans);
|
|
37389
|
+
const registry = (entries, label) => ({
|
|
37390
|
+
ids: () => [...entries.keys()],
|
|
37391
|
+
list: () => [...entries.values()],
|
|
37392
|
+
has: (id) => entries.has(id),
|
|
37393
|
+
get(id) {
|
|
37394
|
+
const entry = entries.get(id);
|
|
37395
|
+
if (!entry) throw new Error(`Unknown ${label} "${id}"`);
|
|
37396
|
+
return entry;
|
|
37397
|
+
}
|
|
37398
|
+
});
|
|
37399
|
+
const kinds = registry(kindEntries, "district kind");
|
|
37400
|
+
const rawPlans = registry(planEntries, "city plan");
|
|
37401
|
+
const resolvePlan = (plan, seen = []) => {
|
|
37402
|
+
if (seen.includes(plan.id)) throw new Error(`City plan "${plan.id}" extends itself (${[...seen, plan.id].join(" > ")})`);
|
|
37403
|
+
const parent = plan.extends ? resolvePlan(rawPlans.get(plan.extends), [...seen, plan.id]) : void 0;
|
|
37404
|
+
const scale = plan.scale ?? parent?.scale;
|
|
37405
|
+
const size = plan.size ?? parent?.size;
|
|
37406
|
+
const planText = plan.text ?? parent?.text;
|
|
37407
|
+
if (!scale || !size || !planText) throw new Error(`City plan "${plan.id}" has no ${!scale ? "scale" : !size ? "size" : "text"}`);
|
|
37408
|
+
return {
|
|
37409
|
+
id: plan.id,
|
|
37410
|
+
scale,
|
|
37411
|
+
size,
|
|
37412
|
+
text: planText,
|
|
37413
|
+
extends: plan.extends,
|
|
37414
|
+
districts: mergeLists(parent?.districts ?? [], plan.districts ?? []),
|
|
37415
|
+
lines: mergeLists(parent?.lines ?? [], plan.lines ?? []),
|
|
37416
|
+
landmarks: [...parent?.landmarks ?? [], ...plan.landmarks ?? []]
|
|
37417
|
+
};
|
|
37418
|
+
};
|
|
37419
|
+
const plans = {
|
|
37420
|
+
...rawPlans,
|
|
37421
|
+
/** The plan with everything inherited from the plans it extends. */
|
|
37422
|
+
resolve: (id) => resolvePlan(rawPlans.get(id))
|
|
37423
|
+
};
|
|
37424
|
+
const layout = (planOrId, options = {}) => {
|
|
37425
|
+
const plan = typeof planOrId === "string" ? plans.resolve(planOrId) : resolvePlan(planOrId);
|
|
37426
|
+
const locale = options.locale ?? DEFAULT_LOCALE2;
|
|
37427
|
+
const cap = Math.max(0, options.maxFloors ?? maxFloors);
|
|
37428
|
+
const clamp22 = (n) => Math.min(cap, Math.max(0, n));
|
|
37429
|
+
const districts = plan.districts.map((d) => {
|
|
37430
|
+
const kind = kinds.get(d.kind);
|
|
37431
|
+
const kindText = text(kind.text, locale);
|
|
37432
|
+
const [lo, hi] = d.floors ?? kind.floors;
|
|
37433
|
+
return {
|
|
37434
|
+
id: d.id,
|
|
37435
|
+
kind: kind.id,
|
|
37436
|
+
rect: d.rect,
|
|
37437
|
+
name: named(d.name, locale) ?? kindText.name,
|
|
37438
|
+
description: kindText.description,
|
|
37439
|
+
color: kind.color,
|
|
37440
|
+
zone: kind.zone,
|
|
37441
|
+
pattern: kind.pattern,
|
|
37442
|
+
floors: [clamp22(lo), clamp22(hi)],
|
|
37443
|
+
uses: d.uses ?? kind.uses,
|
|
37444
|
+
block: kind.block
|
|
37445
|
+
};
|
|
37446
|
+
});
|
|
37447
|
+
const inside = (d, x, z) => x >= d.rect[0] && x <= d.rect[2] && z >= d.rect[1] && z <= d.rect[3];
|
|
37448
|
+
const districtAt = (x, z) => {
|
|
37449
|
+
for (let i = districts.length - 1; i >= 0; i--) if (inside(districts[i], x, z)) return districts[i];
|
|
37450
|
+
return void 0;
|
|
37451
|
+
};
|
|
37452
|
+
const blocks = (districtId, bo = {}) => {
|
|
37453
|
+
const d = districts.find((x) => x.id === districtId);
|
|
37454
|
+
if (!d) throw new Error(`City plan "${plan.id}" has no district "${districtId}"`);
|
|
37455
|
+
const street = bo.street ?? DEFAULT_STREET;
|
|
37456
|
+
const cell = d.block;
|
|
37457
|
+
const side = cell - street;
|
|
37458
|
+
if (cell <= 0 || side <= 0) return [];
|
|
37459
|
+
const [x0, z0, x1, z1] = d.rect;
|
|
37460
|
+
const cols = Math.floor((x1 - x0) / cell);
|
|
37461
|
+
const rows = Math.floor((z1 - z0) / cell);
|
|
37462
|
+
const ox = x0 + (x1 - x0 - cols * cell) / 2;
|
|
37463
|
+
const oz = z0 + (z1 - z0 - rows * cell) / 2;
|
|
37464
|
+
const found = [];
|
|
37465
|
+
for (let i = 0; i < cols; i++) {
|
|
37466
|
+
for (let j = 0; j < rows; j++) {
|
|
37467
|
+
const x = ox + cell * (i + 0.5);
|
|
37468
|
+
const z = oz + cell * (j + 0.5);
|
|
37469
|
+
if (districtAt(x, z)?.id !== d.id) continue;
|
|
37470
|
+
found.push({ x, z, size: [side, side], district: d.id, building: { floors: d.floors, uses: d.uses, zone: d.zone, pattern: d.pattern } });
|
|
37471
|
+
}
|
|
37472
|
+
}
|
|
37473
|
+
return found;
|
|
37474
|
+
};
|
|
37475
|
+
const landmarks = plan.landmarks.map((l) => {
|
|
37476
|
+
const { name, ...rest } = l;
|
|
37477
|
+
const label = named(name, locale);
|
|
37478
|
+
return label === void 0 ? rest : { ...rest, name: label };
|
|
37479
|
+
});
|
|
37480
|
+
const planText = text(plan.text, locale);
|
|
37481
|
+
return { id: plan.id, scale: plan.scale, size: plan.size, name: planText.name, description: planText.description, districts, lines: [...plan.lines], landmarks, districtAt, blocks };
|
|
37482
|
+
};
|
|
37483
|
+
const stats2 = (l) => {
|
|
37484
|
+
const xs = [...new Set(l.districts.flatMap((d) => [d.rect[0], d.rect[2]]))].sort((a, b) => a - b);
|
|
37485
|
+
const zs = [...new Set(l.districts.flatMap((d) => [d.rect[1], d.rect[3]]))].sort((a, b) => a - b);
|
|
37486
|
+
const area3 = /* @__PURE__ */ new Map();
|
|
37487
|
+
let total = 0;
|
|
37488
|
+
for (let i = 0; i + 1 < xs.length; i++) {
|
|
37489
|
+
for (let j = 0; j + 1 < zs.length; j++) {
|
|
37490
|
+
const x0 = xs[i];
|
|
37491
|
+
const z0 = zs[j];
|
|
37492
|
+
const w = xs[i + 1] - x0;
|
|
37493
|
+
const d = zs[j + 1] - z0;
|
|
37494
|
+
const owner = l.districtAt(x0 + w / 2, z0 + d / 2);
|
|
37495
|
+
if (!owner) continue;
|
|
37496
|
+
area3.set(owner.kind, (area3.get(owner.kind) ?? 0) + w * d);
|
|
37497
|
+
total += w * d;
|
|
37498
|
+
}
|
|
37499
|
+
}
|
|
37500
|
+
const count = /* @__PURE__ */ new Map();
|
|
37501
|
+
for (const d of l.districts) count.set(d.kind, (count.get(d.kind) ?? 0) + 1);
|
|
37502
|
+
return [...count.keys()].map((kind) => ({ kind, area: area3.get(kind) ?? 0, share: total > 0 ? (area3.get(kind) ?? 0) / total : 0, districts: count.get(kind) ?? 0 })).sort((a, b) => b.area - a.area);
|
|
37503
|
+
};
|
|
37504
|
+
const fillBlock2 = (b, block, options = {}) => {
|
|
37505
|
+
const { floors, uses, pattern } = block.building;
|
|
37506
|
+
if (floors[1] <= 0 || pattern === null) return [];
|
|
37507
|
+
const { shop, ...rest } = options;
|
|
37508
|
+
const storey = options.storeyHeight ?? o.presets.find((p) => p.id === DEFAULT_STOREY_PRESETS2.residential)?.storeyHeight ?? o.presets[0]?.storeyHeight;
|
|
37509
|
+
return o.fill(b, {
|
|
37510
|
+
cx: block.x,
|
|
37511
|
+
cz: block.z,
|
|
37512
|
+
size: Math.min(block.size[0], block.size[1]),
|
|
37513
|
+
minHeight: floors[0] * storey,
|
|
37514
|
+
maxHeight: floors[1] * storey,
|
|
37515
|
+
floors: [floors[0], floors[1]],
|
|
37516
|
+
uses,
|
|
37517
|
+
shop: shop ?? uses.commercial ?? 0,
|
|
37518
|
+
...rest
|
|
37519
|
+
});
|
|
37520
|
+
};
|
|
37521
|
+
return { maxFloors, kinds, plans, layout, stats: stats2, fillBlock: fillBlock2 };
|
|
37522
|
+
}
|
|
35546
37523
|
var clamp3 = (v, a, b) => v < a ? a : v > b ? b : v;
|
|
35547
37524
|
var lerp22 = (a, b, t) => a + (b - a) * t;
|
|
35548
37525
|
var smoothstep = (a, b, x) => {
|
|
@@ -35679,6 +37656,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35679
37656
|
const lightBudget = options.lights?.budget ?? DEFAULT_LIGHT_BUDGET;
|
|
35680
37657
|
const facades = options.facades ?? DEFAULT_FACADES;
|
|
35681
37658
|
const propColors = { ...DEFAULT_PROP_COLORS, ...options.props };
|
|
37659
|
+
const towers = options.towers ? options.towers === true ? {} : options.towers : null;
|
|
37660
|
+
const building = (b, x, z, width, depth, height, color2, o) => drawBuilding(b, x, z, width, depth, height, color2, o);
|
|
37661
|
+
const drawBlockBuilding = towers ? createTowerDrawer(towers, building) : building;
|
|
35682
37662
|
const poles = {
|
|
35683
37663
|
ids: () => [...entries.keys()],
|
|
35684
37664
|
list: () => [...entries.values()],
|
|
@@ -35700,19 +37680,19 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35700
37680
|
},
|
|
35701
37681
|
block: {
|
|
35702
37682
|
facades,
|
|
35703
|
-
building:
|
|
35704
|
-
fill: (b, g) => fillBlock(b, g.cx, g.cz, g.size, g.minHeight, g.maxHeight, { palette: facades, ...g })
|
|
37683
|
+
building: drawBlockBuilding,
|
|
37684
|
+
fill: (b, g) => fillBlock(b, g.cx, g.cz, g.size, g.minHeight, g.maxHeight, { palette: facades, draw: drawBlockBuilding, ...towers?.presets ? { presets: towers.presets } : {}, ...g })
|
|
35705
37685
|
},
|
|
35706
37686
|
props: {
|
|
35707
37687
|
fence: drawFence,
|
|
35708
|
-
tent: (b, x, z, ry, w, d,
|
|
37688
|
+
tent: (b, x, z, ry, w, d, color2) => drawTent(b, x, z, ry, w, d, color2, propColors),
|
|
35709
37689
|
pine: (b, x, z, scale, y) => drawPine(b, x, z, scale, y, propColors),
|
|
35710
37690
|
tree: (b, x, z, scale) => drawTree(b, x, z, scale, propColors),
|
|
35711
37691
|
container: drawContainer,
|
|
35712
37692
|
watchtower: (b, x, z, searchlight) => drawWatchtower(b, x, z, searchlight, propColors),
|
|
35713
37693
|
sandbags: (b, x, z, length, along, rows) => drawSandbags(b, x, z, length, along, rows, propColors),
|
|
35714
37694
|
label: (b, x, z, title, subtitle) => drawStripLabel(b, x, z, title, subtitle, propColors),
|
|
35715
|
-
wires: (b, points, y,
|
|
37695
|
+
wires: (b, points, y, color2) => drawWires(b, points, y, color2, propColors)
|
|
35716
37696
|
}
|
|
35717
37697
|
};
|
|
35718
37698
|
const builder = (o = {}) => createUrbanBuilder(o.random ?? createSilhouetteRandom(o.seed ?? DEFAULT_URBAN_SEED), { budget: createLightBudget(o.budget ?? lightBudget) });
|
|
@@ -35744,7 +37724,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35744
37724
|
}
|
|
35745
37725
|
};
|
|
35746
37726
|
const terrain = (o) => createTerrain(o);
|
|
35747
|
-
|
|
37727
|
+
const cities = createCities({
|
|
37728
|
+
maxFloors: options.cities?.maxFloors,
|
|
37729
|
+
packs: { kinds: options.packs?.districtKinds, plans: options.packs?.cityPlans },
|
|
37730
|
+
patch: { kinds: options.patch?.districtKinds, plans: options.patch?.cityPlans },
|
|
37731
|
+
remove: { kinds: options.remove?.districtKinds, plans: options.remove?.cityPlans },
|
|
37732
|
+
presets: towers?.presets ?? DEFAULT_TOWER_PRESETS,
|
|
37733
|
+
fill: core.block.fill
|
|
37734
|
+
});
|
|
37735
|
+
return { ...core, poles, cities, builder, neighbourhoods, terrain, drawTerrain, towers: { enabled: towers !== null, options: towers } };
|
|
35748
37736
|
}
|
|
35749
37737
|
var PARKED_VEHICLE_IDS = [...cars, ...suvs, ...pickups.filter((id) => id !== "monster_truck"), ...vans];
|
|
35750
37738
|
var hashSpot = (x, z, ry) => {
|
|
@@ -35762,8 +37750,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35762
37750
|
if (ids.length === 0) throw new Error("The vehicle provider needs at least one vehicle id");
|
|
35763
37751
|
return (request, b) => {
|
|
35764
37752
|
const id = options.pick ? options.pick(request, ids) : ids[hashSpot(request.x, request.z, request.ry) % ids.length];
|
|
35765
|
-
const
|
|
35766
|
-
b.place("vehicle", id, request.x, request.z, { ry: request.ry, ...
|
|
37753
|
+
const color2 = options.paint ? options.paint(request) : request.color;
|
|
37754
|
+
b.place("vehicle", id, request.x, request.z, { ry: request.ry, ...color2 === void 0 ? {} : { color: color2 }, seed: `${id}:${request.x}:${request.z}` });
|
|
35767
37755
|
};
|
|
35768
37756
|
}
|
|
35769
37757
|
var DEFAULT_PLANT_CONTEXTS = { tree: "sidewalk_tree", pine: "square" };
|
|
@@ -35923,9 +37911,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
35923
37911
|
var placements = (parts, kind) => parts.filter((part) => part.shape === "placement" && part.kind === kind);
|
|
35924
37912
|
var round = (value, digits = 3) => Math.round(value * 10 ** digits) / 10 ** digits;
|
|
35925
37913
|
var createScenarios2 = (deps) => {
|
|
35926
|
-
const { text } = deps;
|
|
35927
|
-
const say = (id, ...values) => fill(
|
|
35928
|
-
const scenario = (id, run) => ({ id, name:
|
|
37914
|
+
const { text: text2 } = deps;
|
|
37915
|
+
const say = (id, ...values) => fill(text2.checks[id], ...values);
|
|
37916
|
+
const scenario = (id, run) => ({ id, name: text2.scenarios[id] ?? id, run });
|
|
35929
37917
|
const kit = () => createUrban(deps.urban);
|
|
35930
37918
|
return [
|
|
35931
37919
|
scenario("urban_pole_heights", ({ expect }) => {
|
|
@@ -36047,7 +38035,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36047
38035
|
var LAMP_SHARES = { pattern: void 0, all: 1, half: 0.5, none: 0 };
|
|
36048
38036
|
function createUrbanTestModule(options = {}) {
|
|
36049
38037
|
const locale = (options.locale ?? "pt-BR").toLowerCase().startsWith("en") ? "en" : "pt-BR";
|
|
36050
|
-
const
|
|
38038
|
+
const text2 = TEXT[locale];
|
|
36051
38039
|
const terrain = options.terrain ?? DEFAULT_TEST_TERRAIN;
|
|
36052
38040
|
const seed = options.seed ?? 7;
|
|
36053
38041
|
const benchOrigin = options.benchOrigin ?? [0, -60];
|
|
@@ -36105,7 +38093,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36105
38093
|
};
|
|
36106
38094
|
const item = (id) => ({
|
|
36107
38095
|
id,
|
|
36108
|
-
name:
|
|
38096
|
+
name: text2.items[id] ?? id,
|
|
36109
38097
|
movable: false,
|
|
36110
38098
|
build: (THREE) => {
|
|
36111
38099
|
three = THREE;
|
|
@@ -36116,9 +38104,9 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36116
38104
|
},
|
|
36117
38105
|
spec: (root) => {
|
|
36118
38106
|
const THREE_ = three;
|
|
36119
|
-
const
|
|
36120
|
-
const size =
|
|
36121
|
-
const centre =
|
|
38107
|
+
const box2 = new THREE_.Box3().setFromObject(root);
|
|
38108
|
+
const size = box2.getSize(new THREE_.Vector3());
|
|
38109
|
+
const centre = box2.getCenter(new THREE_.Vector3());
|
|
36122
38110
|
return { name: id, tags: ["structure"], parts: [{ id: "ground", material: "concrete", shape: { kind: "box", size: [Math.max(size.x, 0.1), Math.max(size.y, 0.1), Math.max(size.z, 0.1)] }, position: [centre.x, centre.y, centre.z], anchored: true }] };
|
|
36123
38111
|
}
|
|
36124
38112
|
});
|
|
@@ -36136,7 +38124,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36136
38124
|
if (!host || !three) return;
|
|
36137
38125
|
clearBench();
|
|
36138
38126
|
const kit = kitWith({ lamps: settings.lamps, budget: settings.budget });
|
|
36139
|
-
if ((settings.providers === "vehicles" || settings.providers === "both") && !options.vehicles) host.log(
|
|
38127
|
+
if ((settings.providers === "vehicles" || settings.providers === "both") && !options.vehicles) host.log(text2.log.no_vehicles);
|
|
36140
38128
|
const b = kit.builder({ seed: settings.seed });
|
|
36141
38129
|
const made = kit.neighbourhoods.get(settings.pattern).compose(b, { x: 0, z: 0 }, { locale, providers: providersOf(settings.providers) });
|
|
36142
38130
|
const document = options.document ?? globalThis.document;
|
|
@@ -36145,7 +38133,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36145
38133
|
host.scene.add(drawn.root);
|
|
36146
38134
|
bench = { root: drawn.root, drawn, plan: made };
|
|
36147
38135
|
lightsLeft = b.lights.left;
|
|
36148
|
-
host.log(fill(
|
|
38136
|
+
host.log(fill(text2.log.composed, settings.pattern, settings.seed, countObjects(drawn.root), drawn.lights.length), { pattern: settings.pattern, seed: settings.seed });
|
|
36149
38137
|
helpersShown = "";
|
|
36150
38138
|
};
|
|
36151
38139
|
let lightsLeft = settings.budget;
|
|
@@ -36155,7 +38143,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36155
38143
|
if (change.id === "compose") composeBench();
|
|
36156
38144
|
else if (change.id === "clear") {
|
|
36157
38145
|
clearBench();
|
|
36158
|
-
host?.log(
|
|
38146
|
+
host?.log(text2.log.cleared);
|
|
36159
38147
|
}
|
|
36160
38148
|
return;
|
|
36161
38149
|
}
|
|
@@ -36203,15 +38191,15 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36203
38191
|
if (!entry.root.parent) continue;
|
|
36204
38192
|
entry.root.updateMatrixWorld(true);
|
|
36205
38193
|
if (host.bench.layer("bounds")) {
|
|
36206
|
-
const
|
|
38194
|
+
const box2 = new three.Box3().setFromObject(entry.root);
|
|
36207
38195
|
const inverse = new three.Matrix4().copy(entry.root.matrixWorld).invert();
|
|
36208
|
-
|
|
36209
|
-
mark(entry.root, new three.Box3Helper(
|
|
38196
|
+
box2.applyMatrix4(inverse);
|
|
38197
|
+
mark(entry.root, new three.Box3Helper(box2, 6737151));
|
|
36210
38198
|
}
|
|
36211
38199
|
if (host.bench.layer("light_radius")) {
|
|
36212
38200
|
for (const light of entry.drawn.lights) {
|
|
36213
38201
|
if (!light.isPointLight && !light.isSpotLight) continue;
|
|
36214
|
-
const reach = new three.Mesh(new three.SphereGeometry(Math.min(light.distance || 10, 60), 14, 8), new three.MeshBasicMaterial({ color: light.color ?? 16764006, wireframe: true, transparent: true, opacity: 0.25 }));
|
|
38202
|
+
const reach = new three.Mesh(new (shapeGeometries(three)).SphereGeometry(Math.min(light.distance || 10, 60), 14, 8), new three.MeshBasicMaterial({ color: light.color ?? 16764006, wireframe: true, transparent: true, opacity: 0.25 }));
|
|
36215
38203
|
reach.position.copy(light.position);
|
|
36216
38204
|
mark(light.parent ?? entry.root, reach);
|
|
36217
38205
|
}
|
|
@@ -36244,7 +38232,7 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36244
38232
|
}
|
|
36245
38233
|
return { drawCalls, triangles: Math.round(triangles), lights, instances };
|
|
36246
38234
|
};
|
|
36247
|
-
const
|
|
38235
|
+
const area3 = () => {
|
|
36248
38236
|
const placements2 = [];
|
|
36249
38237
|
poleIds.forEach((id, index) => placements2.push({ item: `pole_${id}`, position: [(index - (poleIds.length - 1) / 2) * POLE_GAP, 0, POLE_ROW_Z], rotationY: 0 }));
|
|
36250
38238
|
placements2.push({ item: "street_grid", position: [-230, 0, -200], rotationY: 0 });
|
|
@@ -36259,8 +38247,8 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36259
38247
|
};
|
|
36260
38248
|
return {
|
|
36261
38249
|
id: URBAN_AREA_ID,
|
|
36262
|
-
name:
|
|
36263
|
-
group:
|
|
38250
|
+
name: text2.name,
|
|
38251
|
+
group: text2.group,
|
|
36264
38252
|
get placed() {
|
|
36265
38253
|
return placed;
|
|
36266
38254
|
},
|
|
@@ -36270,25 +38258,25 @@ gl_Position = projectionMatrix * mvPosition;
|
|
|
36270
38258
|
},
|
|
36271
38259
|
items: () => itemIds.map(item),
|
|
36272
38260
|
scenarios: () => createScenarios2({ text: TEXT[locale], urban: options.urban, terrain }),
|
|
36273
|
-
area,
|
|
38261
|
+
area: area3,
|
|
36274
38262
|
controls: () => [
|
|
36275
|
-
{ kind: "choice", id: "pattern", name:
|
|
36276
|
-
{ kind: "number", id: "seed", name:
|
|
36277
|
-
{ kind: "choice", id: "lamps", name:
|
|
36278
|
-
{ kind: "number", id: "budget", name:
|
|
36279
|
-
{ kind: "choice", id: "providers", name:
|
|
36280
|
-
{ kind: "action", id: "compose", name:
|
|
36281
|
-
{ kind: "action", id: "clear", name:
|
|
38263
|
+
{ kind: "choice", id: "pattern", name: text2.controls.pattern, hint: text2.hints.pattern, options: patternIds.map((id) => ({ id, name: text2.items[id] ?? id })), default: patternIds.includes("downtown") ? "downtown" : patternIds[0] },
|
|
38264
|
+
{ kind: "number", id: "seed", name: text2.controls.seed, hint: text2.hints.seed, min: 0, max: 99999, step: 1, default: seed },
|
|
38265
|
+
{ kind: "choice", id: "lamps", name: text2.controls.lamps, hint: text2.hints.lamps, options: Object.keys(LAMP_SHARES).map((id) => ({ id, name: text2.options.lamps[id] })), default: "pattern" },
|
|
38266
|
+
{ kind: "number", id: "budget", name: text2.controls.budget, hint: text2.hints.budget, min: 0, max: 64, step: 1, default: 16 },
|
|
38267
|
+
{ kind: "choice", id: "providers", name: text2.controls.providers, hint: text2.hints.providers, options: ["none", "plants", "vehicles", "both"].map((id) => ({ id, name: text2.options.providers[id] })), default: "none" },
|
|
38268
|
+
{ kind: "action", id: "compose", name: text2.actions.compose },
|
|
38269
|
+
{ kind: "action", id: "clear", name: text2.actions.clear }
|
|
36282
38270
|
],
|
|
36283
|
-
layers: () => ["light_radius", "bounds"].map((id) => ({ id, name:
|
|
38271
|
+
layers: () => ["light_radius", "bounds"].map((id) => ({ id, name: text2.layers[id], default: false })),
|
|
36284
38272
|
metrics: () => [
|
|
36285
|
-
{ id: "samples", name:
|
|
36286
|
-
{ id: "draw_calls", name:
|
|
36287
|
-
{ id: "triangles", name:
|
|
36288
|
-
{ id: "real_lights", name:
|
|
36289
|
-
{ id: "instances", name:
|
|
36290
|
-
{ id: "bench_objects", name:
|
|
36291
|
-
{ id: "light_budget_left", name:
|
|
38273
|
+
{ id: "samples", name: text2.metrics.samples, read: () => [...placed.values()].filter((entry) => entry.root.parent).length },
|
|
38274
|
+
{ id: "draw_calls", name: text2.metrics.draw_calls, read: () => count().drawCalls },
|
|
38275
|
+
{ id: "triangles", name: text2.metrics.triangles, read: () => count().triangles },
|
|
38276
|
+
{ id: "real_lights", name: text2.metrics.real_lights, read: () => count().lights },
|
|
38277
|
+
{ id: "instances", name: text2.metrics.instances, read: () => count().instances },
|
|
38278
|
+
{ id: "bench_objects", name: text2.metrics.bench_objects, read: () => bench ? countObjects(bench.root) : 0 },
|
|
38279
|
+
{ id: "light_budget_left", name: text2.metrics.light_budget_left, read: () => bench ? lightsLeft : settings.budget }
|
|
36292
38280
|
],
|
|
36293
38281
|
attach(lent) {
|
|
36294
38282
|
host = lent;
|