@agent-os-lab/agent-game-sdk 0.1.9 → 0.1.10
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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/office/core/types.ts +18 -0
- package/src/office/layout/config.ts +234 -25
- package/src/office/layout/index.ts +1 -0
- package/src/office/layout/navigation.ts +168 -0
- package/src/office/layout/resolver.ts +644 -13
- package/src/office/mount.ts +12 -0
- package/src/office/react/AgentGameOfficeView.ts +20 -4
- package/src/office/renderers/three/agent-body-instancing.ts +16 -8
- package/src/office/renderers/three/agent-effect-instancing.ts +26 -12
- package/src/office/renderers/three/agent-route.ts +220 -0
- package/src/office/renderers/three/mount.ts +108 -21
- package/src/office/renderers/three/scene.ts +652 -18
- package/src/runtime-agent-list.ts +15 -0
|
@@ -10,6 +10,13 @@ const OFFICE_FLOOR_CENTER_X = 16;
|
|
|
10
10
|
const OFFICE_FLOOR_WIDTH = 68 * OFFICE_LAYOUT_SCALE;
|
|
11
11
|
const OFFICE_FLOOR_DEPTH = 24 * OFFICE_LAYOUT_SCALE;
|
|
12
12
|
const OFFICE_FLOOR_TILE_SIZE = 3;
|
|
13
|
+
const GLASS_FACADE_DEFAULT_HEIGHT = 17.2;
|
|
14
|
+
const GLASS_FACADE_OUTWARD_OFFSET = 0.42;
|
|
15
|
+
const GLASS_FACADE_PANEL_THICKNESS = 0.08;
|
|
16
|
+
const GLASS_FACADE_VERTICAL_OVERLAP = 0.35;
|
|
17
|
+
const GLASS_ROOF_THICKNESS = 0.08;
|
|
18
|
+
const GLASS_FACADE_DETAIL_OFFSET = 0.07;
|
|
19
|
+
const GLASS_FACADE_FRAME_THICKNESS = 0.12;
|
|
13
20
|
|
|
14
21
|
export function scaleOfficeX(x: number): number {
|
|
15
22
|
return OFFICE_FLOOR_CENTER_X + (x - OFFICE_FLOOR_CENTER_X) * OFFICE_LAYOUT_SCALE;
|
|
@@ -162,20 +169,272 @@ export function addLights(scene: THREE.Scene): void {
|
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
export function buildOfficeScene(scene: THREE.Scene, layout: ResolvedOfficeLayout): void {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
buildOfficeGround(scene, layout);
|
|
173
|
+
buildBuildingBase(scene, layout);
|
|
174
|
+
buildBuildingStructuralColumns(scene, layout);
|
|
175
|
+
buildBuildingConnectors(scene, layout);
|
|
176
|
+
const floorElevations = layout.building.floors.map((floor) => floor.elevation);
|
|
177
|
+
const firstFloorElevation = Math.min(...floorElevations);
|
|
178
|
+
const topFloorElevation = Math.max(...floorElevations);
|
|
179
|
+
|
|
180
|
+
for (const floor of layout.building.floors) {
|
|
181
|
+
const floorGroup = new THREE.Group();
|
|
182
|
+
floorGroup.name = `officeFloor:${floor.id}`;
|
|
183
|
+
floorGroup.position.y = floor.elevation;
|
|
184
|
+
const floorTarget = floorGroup as unknown as THREE.Scene;
|
|
185
|
+
buildOfficeBaseFloors(floorTarget, floor.floorTiles);
|
|
186
|
+
buildOfficeWalls(floorTarget, floor.walls);
|
|
187
|
+
const higherFloorElevations = floorElevations.filter((elevation) => elevation > floor.elevation);
|
|
188
|
+
const glassHeight = higherFloorElevations.length > 0
|
|
189
|
+
? Math.min(...higherFloorElevations) - floor.elevation + GLASS_FACADE_VERTICAL_OVERLAP
|
|
190
|
+
: GLASS_FACADE_DEFAULT_HEIGHT;
|
|
191
|
+
buildOfficeGlassFacade(floorTarget, floor.floorTiles, glassHeight, {
|
|
192
|
+
detail: floor.elevation === firstFloorElevation ? "entrance" : undefined,
|
|
193
|
+
includeRoof: floor.elevation === topFloorElevation,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const batcher = new BoxInstanceBatcher(floorTarget);
|
|
197
|
+
const transparentCache = new TransparentBoxCache();
|
|
198
|
+
activeBoxBatcher = batcher;
|
|
199
|
+
activeTransparentBoxCache = transparentCache;
|
|
200
|
+
try {
|
|
201
|
+
floor.components.forEach((component) => renderOfficeComponent(floorTarget, component));
|
|
202
|
+
} finally {
|
|
203
|
+
activeBoxBatcher = undefined;
|
|
204
|
+
activeTransparentBoxCache = undefined;
|
|
205
|
+
}
|
|
206
|
+
batcher.flush();
|
|
207
|
+
scene.add(floorGroup);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function buildBuildingBase(scene: THREE.Scene, layout: ResolvedOfficeLayout): void {
|
|
212
|
+
const floors = [...layout.building.floors].sort((a, b) => a.elevation - b.elevation);
|
|
213
|
+
const groundFloor = floors[0];
|
|
214
|
+
if (!groundFloor) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const bounds = getFloorTileEdges(groundFloor.floorTiles);
|
|
218
|
+
if (!bounds) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const group = new THREE.Group();
|
|
223
|
+
group.name = "officeBuildingBase";
|
|
224
|
+
const batcher = new ColoredBoxBatcher(group, "officeBuildingBaseMesh");
|
|
225
|
+
const width = bounds.maxX - bounds.minX;
|
|
226
|
+
const depth = bounds.maxZ - bounds.minZ;
|
|
227
|
+
const x = (bounds.minX + bounds.maxX) / 2;
|
|
228
|
+
const z = (bounds.minZ + bounds.maxZ) / 2;
|
|
229
|
+
|
|
230
|
+
addBaseRing(batcher, 0x9f9a8b, width, depth, x, z, 1.6, 0.48, -0.24);
|
|
231
|
+
addBaseRing(batcher, 0x858173, width + 1.6, depth + 1.6, x, z, 1.2, 0.16, -0.56);
|
|
232
|
+
batcher.flush();
|
|
233
|
+
scene.add(group);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function addBaseRing(
|
|
237
|
+
batcher: ColoredBoxBatcher,
|
|
238
|
+
color: number,
|
|
239
|
+
innerWidth: number,
|
|
240
|
+
innerDepth: number,
|
|
241
|
+
x: number,
|
|
242
|
+
z: number,
|
|
243
|
+
thickness: number,
|
|
244
|
+
height: number,
|
|
245
|
+
y: number,
|
|
246
|
+
): void {
|
|
247
|
+
batcher.addBox(color, innerWidth + thickness * 2, height, thickness, x, y, z - innerDepth / 2 - thickness / 2, 0);
|
|
248
|
+
batcher.addBox(color, innerWidth + thickness * 2, height, thickness, x, y, z + innerDepth / 2 + thickness / 2, 0);
|
|
249
|
+
batcher.addBox(color, thickness, height, innerDepth, x - innerWidth / 2 - thickness / 2, y, z, 0);
|
|
250
|
+
batcher.addBox(color, thickness, height, innerDepth, x + innerWidth / 2 + thickness / 2, y, z, 0);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function buildBuildingStructuralColumns(scene: THREE.Scene, layout: ResolvedOfficeLayout): void {
|
|
254
|
+
const floors = [...layout.building.floors].sort((a, b) => a.elevation - b.elevation);
|
|
255
|
+
if (floors.length < 2) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const group = new THREE.Group();
|
|
260
|
+
group.name = "officeStructuralColumns";
|
|
261
|
+
const batcher = new BoxInstanceBatcher(group);
|
|
262
|
+
|
|
263
|
+
for (let floorIndex = 1; floorIndex < floors.length; floorIndex += 1) {
|
|
264
|
+
const lowerFloor = floors[floorIndex - 1]!;
|
|
265
|
+
const upperFloor = floors[floorIndex]!;
|
|
266
|
+
const bounds = getFloorTileEdges(upperFloor.floorTiles);
|
|
267
|
+
if (!bounds) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const columnHeight = Math.max(0.1, upperFloor.elevation - lowerFloor.elevation - 0.2);
|
|
272
|
+
const columnY = lowerFloor.elevation + 0.1 + columnHeight / 2;
|
|
273
|
+
getStructuralColumnPositions(bounds).forEach((position) => {
|
|
274
|
+
batcher.addBox(0x9a9688, 0.58, columnHeight, 0.58, position.x, columnY, position.z, 0);
|
|
275
|
+
});
|
|
177
276
|
}
|
|
277
|
+
|
|
178
278
|
batcher.flush();
|
|
279
|
+
scene.add(group);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getFloorTileEdges(floorTiles: ResolvedOfficeLayout["scene"]["floorTiles"]) {
|
|
283
|
+
if (floorTiles.length === 0) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return floorTiles.reduce(
|
|
287
|
+
(bounds, tile) => ({
|
|
288
|
+
minX: Math.min(bounds.minX, tile.x - tile.width / 2),
|
|
289
|
+
maxX: Math.max(bounds.maxX, tile.x + tile.width / 2),
|
|
290
|
+
minZ: Math.min(bounds.minZ, tile.z - tile.depth / 2),
|
|
291
|
+
maxZ: Math.max(bounds.maxZ, tile.z + tile.depth / 2),
|
|
292
|
+
}),
|
|
293
|
+
{
|
|
294
|
+
minX: Number.POSITIVE_INFINITY,
|
|
295
|
+
maxX: Number.NEGATIVE_INFINITY,
|
|
296
|
+
minZ: Number.POSITIVE_INFINITY,
|
|
297
|
+
maxZ: Number.NEGATIVE_INFINITY,
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getStructuralColumnPositions(bounds: { minX: number; maxX: number; minZ: number; maxZ: number }) {
|
|
303
|
+
const inset = 1.25;
|
|
304
|
+
const minX = bounds.minX + inset;
|
|
305
|
+
const maxX = bounds.maxX - inset;
|
|
306
|
+
const minZ = bounds.minZ + inset;
|
|
307
|
+
const maxZ = bounds.maxZ - inset;
|
|
308
|
+
return [
|
|
309
|
+
{ x: minX, z: minZ },
|
|
310
|
+
{ x: maxX, z: minZ },
|
|
311
|
+
{ x: minX, z: maxZ },
|
|
312
|
+
{ x: maxX, z: maxZ },
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function buildOfficeGround(scene: THREE.Scene, layout: ResolvedOfficeLayout): void {
|
|
317
|
+
const group = new THREE.Group();
|
|
318
|
+
group.name = "officeGroundEnvironment";
|
|
319
|
+
|
|
320
|
+
const campusWidth = layout.scene.width + 110;
|
|
321
|
+
const campusDepth = layout.scene.depth + 92;
|
|
322
|
+
const centerX = layout.scene.center.x;
|
|
323
|
+
const centerZ = layout.scene.center.z;
|
|
324
|
+
const westX = centerX - layout.scene.width / 2 - 18;
|
|
325
|
+
const southZ = centerZ + layout.scene.depth / 2 + 18;
|
|
326
|
+
const parkingX = westX - 12;
|
|
327
|
+
const parkingZ = southZ - 14;
|
|
328
|
+
const treeGroup = new THREE.Group();
|
|
329
|
+
treeGroup.name = "officeTrees";
|
|
330
|
+
const vehicleGroup = new THREE.Group();
|
|
331
|
+
vehicleGroup.name = "officeVehicles";
|
|
332
|
+
const batcher = new ColoredBoxBatcher(group, "officeGroundEnvironmentMesh");
|
|
333
|
+
|
|
334
|
+
addEnvironmentMarker(group, "officeGroundPlane");
|
|
335
|
+
addEnvironmentMarker(group, "officeRoad:main");
|
|
336
|
+
addEnvironmentMarker(group, "officeRoad:service");
|
|
337
|
+
addEnvironmentMarker(group, "officeSidewalk:south");
|
|
338
|
+
addEnvironmentMarker(group, "officeSidewalk:west");
|
|
339
|
+
addEnvironmentMarker(group, "officeParking:visitor");
|
|
340
|
+
batcher.addBox(0xb9c0b7, campusWidth, 0.08, campusDepth, centerX, -0.16, centerZ, 0);
|
|
341
|
+
batcher.addBox(0x4b5563, campusWidth - 20, 0.04, 9, centerX, -0.09, southZ, 0);
|
|
342
|
+
batcher.addBox(0x4b5563, 8, 0.04, campusDepth - 18, westX, -0.09, centerZ, 0);
|
|
343
|
+
batcher.addBox(0xd7d3c8, campusWidth - 28, 0.04, 3.2, centerX, -0.06, southZ - 6.2, 0);
|
|
344
|
+
batcher.addBox(0xd7d3c8, 3.2, 0.04, campusDepth - 26, westX + 5.8, -0.06, centerZ, 0);
|
|
345
|
+
batcher.addBox(0x8d948b, 14, 0.035, 10, parkingX, -0.055, parkingZ, 0);
|
|
346
|
+
|
|
347
|
+
for (const xOffset of [-42, -30, -18, -6, 8, 20, 32, 44]) {
|
|
348
|
+
addTree(batcher, centerX + xOffset, southZ + 7.5);
|
|
349
|
+
}
|
|
350
|
+
for (const zOffset of [-28, -16, -4, 8]) {
|
|
351
|
+
addTree(batcher, westX - 8, centerZ + zOffset);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
addCar(batcher, centerX - 28, southZ + 0.6, 0xe11d48, 0);
|
|
355
|
+
addCar(batcher, centerX - 12, southZ - 1.2, 0x2563eb, Math.PI);
|
|
356
|
+
addCar(batcher, centerX + 18, southZ + 0.7, 0xf59e0b, 0);
|
|
357
|
+
addCar(batcher, westX, centerZ - 18, 0x14b8a6, Math.PI / 2);
|
|
358
|
+
addCar(batcher, parkingX, parkingZ, 0xf8fafc, Math.PI / 2);
|
|
359
|
+
batcher.flush();
|
|
360
|
+
group.add(treeGroup, vehicleGroup);
|
|
361
|
+
|
|
362
|
+
scene.add(group);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function addEnvironmentMarker(scene: THREE.Object3D, name: string): void {
|
|
366
|
+
const marker = new THREE.Group();
|
|
367
|
+
marker.name = name;
|
|
368
|
+
scene.add(marker);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function addTree(batcher: ColoredBoxBatcher, x: number, z: number): void {
|
|
372
|
+
batcher.addBox(0x7c4a28, 0.36, 1.4, 0.36, x, 0.56, z, 0);
|
|
373
|
+
batcher.addBox(0x2f6b3f, 1.65, 1.35, 1.65, x, 1.62, z, Math.PI / 4);
|
|
374
|
+
batcher.addBox(0x3f8550, 1.25, 1.05, 1.25, x, 2.35, z, Math.PI / 4);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function addCar(batcher: ColoredBoxBatcher, x: number, z: number, color: number, rotationY: number): void {
|
|
378
|
+
batcher.addBox(color, 3.4, 0.7, 1.55, x, 0.28, z, rotationY);
|
|
379
|
+
batcher.addBox(
|
|
380
|
+
0xc7d2fe,
|
|
381
|
+
1.45,
|
|
382
|
+
0.48,
|
|
383
|
+
1.18,
|
|
384
|
+
x + Math.cos(rotationY) * 0.28,
|
|
385
|
+
0.86,
|
|
386
|
+
z - Math.sin(rotationY) * 0.28,
|
|
387
|
+
rotationY,
|
|
388
|
+
);
|
|
389
|
+
for (const wheelX of [-1.05, 1.05]) {
|
|
390
|
+
for (const wheelZ of [-0.72, 0.72]) {
|
|
391
|
+
addLocalEnvironmentBox(batcher, 0x111827, 0.42, 0.42, 0.18, x, z, wheelX, 0.16, wheelZ, rotationY);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function addLocalEnvironmentBox(
|
|
397
|
+
batcher: ColoredBoxBatcher,
|
|
398
|
+
color: number,
|
|
399
|
+
width: number,
|
|
400
|
+
height: number,
|
|
401
|
+
depth: number,
|
|
402
|
+
originX: number,
|
|
403
|
+
originZ: number,
|
|
404
|
+
localX: number,
|
|
405
|
+
y: number,
|
|
406
|
+
localZ: number,
|
|
407
|
+
rotationY: number,
|
|
408
|
+
) {
|
|
409
|
+
const cos = Math.cos(rotationY);
|
|
410
|
+
const sin = Math.sin(rotationY);
|
|
411
|
+
batcher.addBox(
|
|
412
|
+
color,
|
|
413
|
+
width,
|
|
414
|
+
height,
|
|
415
|
+
depth,
|
|
416
|
+
originX + localX * cos + localZ * sin,
|
|
417
|
+
y,
|
|
418
|
+
originZ - localX * sin + localZ * cos,
|
|
419
|
+
rotationY,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export const OFFICE_FLOOR_FOCUS_DIM_OPACITY = 0.18;
|
|
424
|
+
|
|
425
|
+
export function applyOfficeFloorFocus(
|
|
426
|
+
scene: THREE.Scene,
|
|
427
|
+
layout: ResolvedOfficeLayout,
|
|
428
|
+
focusedFloorId: string | null,
|
|
429
|
+
): void {
|
|
430
|
+
const floorIds = new Set(layout.building.floors.map((floor) => floor.id));
|
|
431
|
+
scene.children.forEach((child) => {
|
|
432
|
+
const floorId = parseOfficeFloorGroupId(child.name);
|
|
433
|
+
if (!floorId || !floorIds.has(floorId)) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
setObjectOpacity(child, !focusedFloorId || floorId === focusedFloorId ? 1 : OFFICE_FLOOR_FOCUS_DIM_OPACITY);
|
|
437
|
+
});
|
|
179
438
|
}
|
|
180
439
|
|
|
181
440
|
export function disposeObject(object: THREE.Object3D): void {
|
|
@@ -193,13 +452,48 @@ export function disposeObject(object: THREE.Object3D): void {
|
|
|
193
452
|
});
|
|
194
453
|
}
|
|
195
454
|
|
|
455
|
+
function parseOfficeFloorGroupId(name: string): string | null {
|
|
456
|
+
return name.startsWith("officeFloor:") ? name.slice("officeFloor:".length) : null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function setObjectOpacity(object: THREE.Object3D, opacity: number): void {
|
|
460
|
+
object.traverse((child) => {
|
|
461
|
+
const mesh = child as THREE.Mesh;
|
|
462
|
+
if (!mesh.material) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (Array.isArray(mesh.material)) {
|
|
466
|
+
mesh.material = mesh.material.map((material) => prepareFocusMaterial(material, opacity));
|
|
467
|
+
} else {
|
|
468
|
+
mesh.material = prepareFocusMaterial(mesh.material, opacity);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function prepareFocusMaterial<TMaterial extends THREE.Material>(material: TMaterial, opacity: number): TMaterial {
|
|
474
|
+
const focusMaterial = (material.userData.officeFocusMaterialCloned ? material : material.clone()) as TMaterial;
|
|
475
|
+
const baseOpacity = typeof material.userData.officeBaseOpacity === "number" ? material.userData.officeBaseOpacity : material.opacity;
|
|
476
|
+
const baseTransparent = typeof material.userData.officeBaseTransparent === "boolean" ? material.userData.officeBaseTransparent : material.transparent;
|
|
477
|
+
const baseDepthWrite = typeof material.userData.officeBaseDepthWrite === "boolean" ? material.userData.officeBaseDepthWrite : material.depthWrite;
|
|
478
|
+
const resolvedOpacity = baseOpacity * opacity;
|
|
479
|
+
focusMaterial.userData.officeFocusMaterialCloned = true;
|
|
480
|
+
focusMaterial.userData.officeBaseOpacity = baseOpacity;
|
|
481
|
+
focusMaterial.userData.officeBaseTransparent = baseTransparent;
|
|
482
|
+
focusMaterial.userData.officeBaseDepthWrite = baseDepthWrite;
|
|
483
|
+
focusMaterial.transparent = baseTransparent || resolvedOpacity < 1;
|
|
484
|
+
focusMaterial.opacity = resolvedOpacity;
|
|
485
|
+
focusMaterial.depthWrite = baseDepthWrite && resolvedOpacity >= 1;
|
|
486
|
+
focusMaterial.needsUpdate = true;
|
|
487
|
+
return focusMaterial;
|
|
488
|
+
}
|
|
489
|
+
|
|
196
490
|
class BoxInstanceBatcher {
|
|
197
|
-
private readonly scene: THREE.
|
|
491
|
+
private readonly scene: THREE.Object3D;
|
|
198
492
|
private readonly geometryCache = new Map<string, THREE.BoxGeometry>();
|
|
199
493
|
private readonly materialCache = new Map<number, THREE.MeshLambertMaterial>();
|
|
200
494
|
private readonly batches = new Map<number, THREE.BufferGeometry[]>();
|
|
201
495
|
|
|
202
|
-
constructor(scene: THREE.
|
|
496
|
+
constructor(scene: THREE.Object3D) {
|
|
203
497
|
this.scene = scene;
|
|
204
498
|
}
|
|
205
499
|
|
|
@@ -257,6 +551,63 @@ class BoxInstanceBatcher {
|
|
|
257
551
|
}
|
|
258
552
|
}
|
|
259
553
|
|
|
554
|
+
class ColoredBoxBatcher {
|
|
555
|
+
private readonly scene: THREE.Object3D;
|
|
556
|
+
private readonly name: string;
|
|
557
|
+
private readonly geometries: THREE.BufferGeometry[] = [];
|
|
558
|
+
|
|
559
|
+
constructor(scene: THREE.Object3D, name: string) {
|
|
560
|
+
this.scene = scene;
|
|
561
|
+
this.name = name;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
addBox(
|
|
565
|
+
color: number,
|
|
566
|
+
width: number,
|
|
567
|
+
height: number,
|
|
568
|
+
depth: number,
|
|
569
|
+
x: number,
|
|
570
|
+
y: number,
|
|
571
|
+
z: number,
|
|
572
|
+
rotationY: number,
|
|
573
|
+
) {
|
|
574
|
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
575
|
+
matrixHelper.position.set(x, y, z);
|
|
576
|
+
matrixHelper.rotation.set(0, rotationY, 0);
|
|
577
|
+
matrixHelper.scale.set(1, 1, 1);
|
|
578
|
+
matrixHelper.updateMatrix();
|
|
579
|
+
geometry.applyMatrix4(matrixHelper.matrix);
|
|
580
|
+
|
|
581
|
+
const vertexColor = new THREE.Color(color);
|
|
582
|
+
const position = geometry.getAttribute("position");
|
|
583
|
+
const colors = new Float32Array(position.count * 3);
|
|
584
|
+
for (let index = 0; index < position.count; index += 1) {
|
|
585
|
+
colors[index * 3] = vertexColor.r;
|
|
586
|
+
colors[index * 3 + 1] = vertexColor.g;
|
|
587
|
+
colors[index * 3 + 2] = vertexColor.b;
|
|
588
|
+
}
|
|
589
|
+
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
590
|
+
this.geometries.push(geometry);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
flush() {
|
|
594
|
+
const mergedGeometry = mergeGeometries(this.geometries, false);
|
|
595
|
+
this.geometries.forEach((geometry) => geometry.dispose());
|
|
596
|
+
this.geometries.length = 0;
|
|
597
|
+
if (!mergedGeometry) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const mesh = new THREE.Mesh(
|
|
601
|
+
mergedGeometry,
|
|
602
|
+
new THREE.MeshLambertMaterial({ vertexColors: true }),
|
|
603
|
+
);
|
|
604
|
+
mesh.castShadow = false;
|
|
605
|
+
mesh.receiveShadow = true;
|
|
606
|
+
mesh.name = this.name;
|
|
607
|
+
this.scene.add(mesh);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
260
611
|
let activeBoxBatcher: BoxInstanceBatcher | undefined;
|
|
261
612
|
|
|
262
613
|
class TransparentBoxCache {
|
|
@@ -294,8 +645,8 @@ class TransparentBoxCache {
|
|
|
294
645
|
|
|
295
646
|
let activeTransparentBoxCache: TransparentBoxCache | undefined;
|
|
296
647
|
|
|
297
|
-
function buildOfficeBaseFloors(scene: THREE.Scene,
|
|
298
|
-
|
|
648
|
+
function buildOfficeBaseFloors(scene: THREE.Scene, floorTiles: ResolvedOfficeLayout["scene"]["floorTiles"]) {
|
|
649
|
+
floorTiles.forEach((tile) => {
|
|
299
650
|
addBaseFloor(scene, tile.color, tile.x, tile.z, tile.width, tile.depth);
|
|
300
651
|
});
|
|
301
652
|
}
|
|
@@ -335,9 +686,9 @@ function addGridFloorLines(scene: THREE.Scene, x: number, z: number, width: numb
|
|
|
335
686
|
scene.add(lines);
|
|
336
687
|
}
|
|
337
688
|
|
|
338
|
-
function buildOfficeWalls(scene: THREE.Scene,
|
|
689
|
+
function buildOfficeWalls(scene: THREE.Scene, walls: ResolvedOfficeLayout["scene"]["walls"]) {
|
|
339
690
|
const materialCache = new Map<number, THREE.MeshLambertMaterial>();
|
|
340
|
-
|
|
691
|
+
walls.forEach((wall) => {
|
|
341
692
|
const material = materialCache.get(wall.color) ?? (() => {
|
|
342
693
|
const created = new THREE.MeshLambertMaterial({ color: wall.color });
|
|
343
694
|
materialCache.set(wall.color, created);
|
|
@@ -356,6 +707,164 @@ function buildOfficeWalls(scene: THREE.Scene, layout: ResolvedOfficeLayout) {
|
|
|
356
707
|
});
|
|
357
708
|
}
|
|
358
709
|
|
|
710
|
+
function buildOfficeGlassFacade(
|
|
711
|
+
scene: THREE.Scene,
|
|
712
|
+
floorTiles: ResolvedOfficeLayout["scene"]["floorTiles"],
|
|
713
|
+
height = GLASS_FACADE_DEFAULT_HEIGHT,
|
|
714
|
+
options: { detail?: "entrance" | "windows"; includeRoof?: boolean } = {},
|
|
715
|
+
) {
|
|
716
|
+
const bounds = getFloorTileEdges(floorTiles);
|
|
717
|
+
if (!bounds) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const geometries: THREE.BufferGeometry[] = [];
|
|
721
|
+
const group = new THREE.Group();
|
|
722
|
+
group.name = "officeGlassFacade";
|
|
723
|
+
const width = bounds.maxX - bounds.minX;
|
|
724
|
+
const depth = bounds.maxZ - bounds.minZ;
|
|
725
|
+
const closedWidth = width + GLASS_FACADE_OUTWARD_OFFSET * 2 + GLASS_FACADE_PANEL_THICKNESS;
|
|
726
|
+
const closedDepth = depth + GLASS_FACADE_OUTWARD_OFFSET * 2 + GLASS_FACADE_PANEL_THICKNESS;
|
|
727
|
+
const centerX = (bounds.minX + bounds.maxX) / 2;
|
|
728
|
+
const centerZ = (bounds.minZ + bounds.maxZ) / 2;
|
|
729
|
+
const panelY = height / 2;
|
|
730
|
+
|
|
731
|
+
[
|
|
732
|
+
{ id: "north", width: closedWidth, depth: GLASS_FACADE_PANEL_THICKNESS, x: centerX, z: bounds.minZ - GLASS_FACADE_OUTWARD_OFFSET },
|
|
733
|
+
{ id: "south", width: closedWidth, depth: GLASS_FACADE_PANEL_THICKNESS, x: centerX, z: bounds.maxZ + GLASS_FACADE_OUTWARD_OFFSET },
|
|
734
|
+
{ id: "west", width: GLASS_FACADE_PANEL_THICKNESS, depth: closedDepth, x: bounds.minX - GLASS_FACADE_OUTWARD_OFFSET, z: centerZ },
|
|
735
|
+
{ id: "east", width: GLASS_FACADE_PANEL_THICKNESS, depth: closedDepth, x: bounds.maxX + GLASS_FACADE_OUTWARD_OFFSET, z: centerZ },
|
|
736
|
+
].forEach((panel) => {
|
|
737
|
+
const geometry = new THREE.BoxGeometry(panel.width, height, panel.depth);
|
|
738
|
+
matrixHelper.position.set(panel.x, panelY, panel.z);
|
|
739
|
+
matrixHelper.rotation.set(0, 0, 0);
|
|
740
|
+
matrixHelper.scale.set(1, 1, 1);
|
|
741
|
+
matrixHelper.updateMatrix();
|
|
742
|
+
geometry.applyMatrix4(matrixHelper.matrix);
|
|
743
|
+
geometries.push(geometry);
|
|
744
|
+
|
|
745
|
+
const marker = new THREE.Object3D();
|
|
746
|
+
marker.name = `officeGlassFacade:${panel.id}`;
|
|
747
|
+
marker.position.set(panel.x, panelY, panel.z);
|
|
748
|
+
marker.userData.officeGlassFacadePanel = { width: panel.width, height, depth: panel.depth };
|
|
749
|
+
group.add(marker);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
if (options.includeRoof) {
|
|
753
|
+
const geometry = new THREE.BoxGeometry(closedWidth, GLASS_ROOF_THICKNESS, closedDepth);
|
|
754
|
+
matrixHelper.position.set(centerX, height + GLASS_ROOF_THICKNESS / 2, centerZ);
|
|
755
|
+
matrixHelper.rotation.set(0, 0, 0);
|
|
756
|
+
matrixHelper.scale.set(1, 1, 1);
|
|
757
|
+
matrixHelper.updateMatrix();
|
|
758
|
+
geometry.applyMatrix4(matrixHelper.matrix);
|
|
759
|
+
geometries.push(geometry);
|
|
760
|
+
|
|
761
|
+
const marker = new THREE.Object3D();
|
|
762
|
+
marker.name = "officeGlassRoof";
|
|
763
|
+
marker.position.set(centerX, height, centerZ);
|
|
764
|
+
marker.userData.officeGlassRoof = { width: closedWidth, height: GLASS_ROOF_THICKNESS, depth: closedDepth };
|
|
765
|
+
group.add(marker);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const mergedGeometry = mergeGeometries(geometries, false);
|
|
769
|
+
geometries.forEach((geometry) => geometry.dispose());
|
|
770
|
+
if (!mergedGeometry) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const mesh = new THREE.Mesh(
|
|
774
|
+
mergedGeometry,
|
|
775
|
+
new THREE.MeshLambertMaterial({ color: 0x94a3b8, transparent: true, opacity: 0.18, depthWrite: false }),
|
|
776
|
+
);
|
|
777
|
+
mesh.name = "officeGlassFacadeMesh";
|
|
778
|
+
mesh.castShadow = false;
|
|
779
|
+
mesh.receiveShadow = false;
|
|
780
|
+
group.add(mesh);
|
|
781
|
+
if (options.detail === "entrance") {
|
|
782
|
+
addGlassFacadeEntrance(group, bounds.maxZ + GLASS_FACADE_OUTWARD_OFFSET + GLASS_FACADE_DETAIL_OFFSET, centerX);
|
|
783
|
+
}
|
|
784
|
+
scene.add(group);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function addGlassFacadeEntrance(group: THREE.Group, southZ: number, centerX: number) {
|
|
788
|
+
const panes: THREE.BufferGeometry[] = [];
|
|
789
|
+
const frames: THREE.BufferGeometry[] = [];
|
|
790
|
+
const handles: THREE.BufferGeometry[] = [];
|
|
791
|
+
const doorWidth = 7.2;
|
|
792
|
+
const doorHeight = 6.4;
|
|
793
|
+
const doorY = doorHeight / 2;
|
|
794
|
+
pushBoxGeometry(panes, doorWidth / 2 - 0.22, doorHeight, 0.05, centerX - doorWidth / 4, doorY, southZ);
|
|
795
|
+
pushBoxGeometry(panes, doorWidth / 2 - 0.22, doorHeight, 0.05, centerX + doorWidth / 4, doorY, southZ);
|
|
796
|
+
pushBoxGeometry(frames, GLASS_FACADE_FRAME_THICKNESS * 1.35, doorHeight + 0.45, 0.1, centerX - doorWidth / 2, doorY, southZ + 0.02);
|
|
797
|
+
pushBoxGeometry(frames, GLASS_FACADE_FRAME_THICKNESS * 1.35, doorHeight + 0.45, 0.1, centerX + doorWidth / 2, doorY, southZ + 0.02);
|
|
798
|
+
pushBoxGeometry(frames, doorWidth + 0.45, GLASS_FACADE_FRAME_THICKNESS * 1.35, 0.1, centerX, doorHeight + 0.12, southZ + 0.02);
|
|
799
|
+
pushBoxGeometry(frames, doorWidth + 1.4, 0.28, 0.9, centerX, doorHeight + 0.55, southZ + 0.35);
|
|
800
|
+
pushBoxGeometry(frames, GLASS_FACADE_FRAME_THICKNESS, doorHeight, 0.1, centerX, doorY, southZ + 0.03);
|
|
801
|
+
pushBoxGeometry(handles, 0.12, 1.2, 0.12, centerX - 0.38, 2.65, southZ + 0.12);
|
|
802
|
+
pushBoxGeometry(handles, 0.12, 1.2, 0.12, centerX + 0.38, 2.65, southZ + 0.12);
|
|
803
|
+
addMergedMesh(group, panes, new THREE.MeshLambertMaterial({ color: 0xa5f3fc, transparent: true, opacity: 0.26, depthWrite: false }), "officeGlassFacadeEntrance:panes");
|
|
804
|
+
addMergedMesh(group, frames, new THREE.MeshLambertMaterial({ color: 0x1f2937 }), "officeGlassFacadeEntrance:frame");
|
|
805
|
+
addMergedMesh(group, handles, new THREE.MeshLambertMaterial({ color: 0xe5e7eb }), "officeGlassFacadeEntrance:handles");
|
|
806
|
+
const marker = new THREE.Object3D();
|
|
807
|
+
marker.name = "officeGlassFacadeEntrance";
|
|
808
|
+
marker.position.set(centerX, doorY, southZ);
|
|
809
|
+
group.add(marker);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function pushBoxGeometry(
|
|
813
|
+
geometries: THREE.BufferGeometry[],
|
|
814
|
+
width: number,
|
|
815
|
+
height: number,
|
|
816
|
+
depth: number,
|
|
817
|
+
x: number,
|
|
818
|
+
y: number,
|
|
819
|
+
z: number,
|
|
820
|
+
) {
|
|
821
|
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
822
|
+
matrixHelper.position.set(x, y, z);
|
|
823
|
+
matrixHelper.rotation.set(0, 0, 0);
|
|
824
|
+
matrixHelper.scale.set(1, 1, 1);
|
|
825
|
+
matrixHelper.updateMatrix();
|
|
826
|
+
geometry.applyMatrix4(matrixHelper.matrix);
|
|
827
|
+
geometries.push(geometry);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function addMergedMesh(group: THREE.Group, geometries: THREE.BufferGeometry[], material: THREE.Material, name: string) {
|
|
831
|
+
const geometry = mergeGeometries(geometries, false);
|
|
832
|
+
geometries.forEach((item) => item.dispose());
|
|
833
|
+
if (!geometry) {
|
|
834
|
+
material.dispose();
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
838
|
+
mesh.name = name;
|
|
839
|
+
mesh.castShadow = false;
|
|
840
|
+
mesh.receiveShadow = false;
|
|
841
|
+
group.add(mesh);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function buildBuildingConnectors(scene: THREE.Scene, layout: ResolvedOfficeLayout) {
|
|
845
|
+
layout.building.connectors.forEach((connector) => {
|
|
846
|
+
if (connector.stops.length < 2) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const firstStop = connector.stops[0]!;
|
|
850
|
+
const minY = Math.min(...connector.stops.map((stop) => stop.cabinPosition.y));
|
|
851
|
+
const maxY = Math.max(...connector.stops.map((stop) => stop.cabinPosition.y));
|
|
852
|
+
const height = Math.max(0.1, maxY - minY + 2.8);
|
|
853
|
+
addTransparentBox(
|
|
854
|
+
scene,
|
|
855
|
+
0x94a3b8,
|
|
856
|
+
2.4,
|
|
857
|
+
height,
|
|
858
|
+
1.25,
|
|
859
|
+
firstStop.cabinPosition.x,
|
|
860
|
+
minY + height / 2 - 1.4,
|
|
861
|
+
firstStop.cabinPosition.z,
|
|
862
|
+
0.18,
|
|
863
|
+
`elevatorShaft:${connector.id}`,
|
|
864
|
+
);
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
359
868
|
function addOfficeWallBox(
|
|
360
869
|
scene: THREE.Scene,
|
|
361
870
|
width: number,
|
|
@@ -510,6 +1019,36 @@ function renderOfficeComponent(scene: THREE.Scene, component: ResolvedOfficeComp
|
|
|
510
1019
|
component.props?.depth,
|
|
511
1020
|
);
|
|
512
1021
|
return;
|
|
1022
|
+
case "diningTable":
|
|
1023
|
+
addDiningTable(scene, x, z);
|
|
1024
|
+
return;
|
|
1025
|
+
case "cafeCounter":
|
|
1026
|
+
addCafeCounter(scene, x, z);
|
|
1027
|
+
return;
|
|
1028
|
+
case "cafeTable":
|
|
1029
|
+
addCafeTable(scene, x, z);
|
|
1030
|
+
return;
|
|
1031
|
+
case "kitchenCounter":
|
|
1032
|
+
addKitchenCounter(scene, x, z);
|
|
1033
|
+
return;
|
|
1034
|
+
case "kitchenStove":
|
|
1035
|
+
addKitchenStove(scene, x, z);
|
|
1036
|
+
return;
|
|
1037
|
+
case "kitchenFridge":
|
|
1038
|
+
addKitchenFridge(scene, x, z);
|
|
1039
|
+
return;
|
|
1040
|
+
case "gardenPlanter":
|
|
1041
|
+
addGardenPlanter(scene, x, z, rotation);
|
|
1042
|
+
return;
|
|
1043
|
+
case "gardenTree":
|
|
1044
|
+
addGardenTree(scene, x, z);
|
|
1045
|
+
return;
|
|
1046
|
+
case "gardenBench":
|
|
1047
|
+
addGardenBench(scene, x, z, rotation);
|
|
1048
|
+
return;
|
|
1049
|
+
case "gardenPath":
|
|
1050
|
+
addGardenPath(scene, x, z, component.props?.width, component.props?.depth, rotation);
|
|
1051
|
+
return;
|
|
513
1052
|
case "meetingGlassRoom":
|
|
514
1053
|
addMeetingGlassRoom(
|
|
515
1054
|
scene,
|
|
@@ -581,6 +1120,15 @@ function renderOfficeComponent(scene: THREE.Scene, component: ResolvedOfficeComp
|
|
|
581
1120
|
case "gymMirror":
|
|
582
1121
|
addGymMirror(scene, x, z, rotation, component.props?.width, component.props?.faceDirection ?? 1);
|
|
583
1122
|
return;
|
|
1123
|
+
case "elevatorDoor":
|
|
1124
|
+
addElevatorDoor(scene, x, z, component.props?.width, component.props?.height);
|
|
1125
|
+
return;
|
|
1126
|
+
case "elevatorFrame":
|
|
1127
|
+
addElevatorFrame(scene, x, z, component.props?.width, component.props?.depth, component.props?.height);
|
|
1128
|
+
return;
|
|
1129
|
+
case "elevatorIndicator":
|
|
1130
|
+
addElevatorIndicator(scene, x, component.position.y ?? 2.6, z, component.props?.width, component.props?.height);
|
|
1131
|
+
return;
|
|
584
1132
|
case "glassWall":
|
|
585
1133
|
case "glassDoor":
|
|
586
1134
|
return;
|
|
@@ -643,6 +1191,76 @@ function addMeetingTable(
|
|
|
643
1191
|
addBox(scene, legColor, 0.2, 1.5, 0.2, x + 2.2, 0.75, z + legZ);
|
|
644
1192
|
}
|
|
645
1193
|
|
|
1194
|
+
function addDiningTable(scene: THREE.Scene, x: number, z: number) {
|
|
1195
|
+
addBox(scene, 0xc98b32, 2.6, 0.14, 1.8, x, 1.05, z);
|
|
1196
|
+
addBox(scene, 0x7c3f12, 0.14, 1.05, 0.14, x - 1.08, 0.52, z - 0.68);
|
|
1197
|
+
addBox(scene, 0x7c3f12, 0.14, 1.05, 0.14, x + 1.08, 0.52, z - 0.68);
|
|
1198
|
+
addBox(scene, 0x7c3f12, 0.14, 1.05, 0.14, x - 1.08, 0.52, z + 0.68);
|
|
1199
|
+
addBox(scene, 0x7c3f12, 0.14, 1.05, 0.14, x + 1.08, 0.52, z + 0.68);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function addCafeCounter(scene: THREE.Scene, x: number, z: number) {
|
|
1203
|
+
addBox(scene, 0x7c2d12, 4.2, 0.78, 0.72, x, 0.39, z);
|
|
1204
|
+
addBox(scene, 0xd6c4a8, 4.38, 0.12, 0.9, x, 0.84, z);
|
|
1205
|
+
addBox(scene, 0x1f2937, 0.72, 0.42, 0.34, x - 1.15, 1.12, z - 0.18);
|
|
1206
|
+
addBox(scene, 0x94a3b8, 0.62, 0.06, 0.26, x - 1.15, 1.36, z - 0.34);
|
|
1207
|
+
addBox(scene, 0x334155, 0.34, 0.36, 0.28, x - 0.38, 1.05, z + 0.08);
|
|
1208
|
+
addBox(scene, 0xf8fafc, 0.18, 0.2, 0.18, x + 0.45, 1.03, z - 0.12);
|
|
1209
|
+
addBox(scene, 0xf8fafc, 0.18, 0.2, 0.18, x + 0.78, 1.03, z - 0.12);
|
|
1210
|
+
addBox(scene, 0xf59e0b, 0.5, 0.18, 0.36, x + 1.35, 1.02, z + 0.1);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function addCafeTable(scene: THREE.Scene, x: number, z: number) {
|
|
1214
|
+
addBox(scene, 0xb7791f, 1.65, 0.12, 1.65, x, 0.9, z);
|
|
1215
|
+
addBox(scene, 0x7c3f12, 0.18, 0.9, 0.18, x, 0.45, z);
|
|
1216
|
+
addBox(scene, 0x7c3f12, 0.9, 0.08, 0.9, x, 0.08, z);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function addKitchenCounter(scene: THREE.Scene, x: number, z: number) {
|
|
1220
|
+
addBox(scene, 0x92400e, 4.8, 0.8, 0.72, x, 0.4, z);
|
|
1221
|
+
addBox(scene, 0xe5e7eb, 4.95, 0.12, 0.9, x, 0.86, z);
|
|
1222
|
+
addBox(scene, 0x64748b, 0.84, 0.08, 0.5, x - 1.35, 0.95, z - 0.02);
|
|
1223
|
+
addBox(scene, 0x94a3b8, 0.52, 0.05, 0.32, x + 1.35, 0.96, z - 0.04);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function addKitchenStove(scene: THREE.Scene, x: number, z: number) {
|
|
1227
|
+
addBox(scene, 0x334155, 1.5, 0.82, 0.82, x, 0.42, z);
|
|
1228
|
+
addBox(scene, 0x111827, 1.2, 0.08, 0.62, x, 0.88, z);
|
|
1229
|
+
addBox(scene, 0xf97316, 0.22, 0.04, 0.22, x - 0.32, 0.94, z - 0.14);
|
|
1230
|
+
addBox(scene, 0xf97316, 0.22, 0.04, 0.22, x + 0.32, 0.94, z + 0.14);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function addKitchenFridge(scene: THREE.Scene, x: number, z: number) {
|
|
1234
|
+
addBox(scene, 0xe2e8f0, 1.1, 2.0, 0.78, x, 1.0, z);
|
|
1235
|
+
addBox(scene, 0xcbd5e1, 1.02, 0.05, 0.8, x, 1.42, z + 0.02);
|
|
1236
|
+
addBox(scene, 0x64748b, 0.06, 0.58, 0.06, x + 0.42, 1.1, z + 0.42);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function addGardenPath(scene: THREE.Scene, x: number, z: number, width = 4, depth = 1, rotation = 0) {
|
|
1240
|
+
addLocalBox(scene, 0xb9b39f, width, 0.05, depth, x, z, 0, 0.05, 0, rotation);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function addGardenPlanter(scene: THREE.Scene, x: number, z: number, rotation = 0) {
|
|
1244
|
+
addLocalBox(scene, 0x8b5e3c, 4.4, 0.5, 1.15, x, z, 0, 0.25, 0, rotation);
|
|
1245
|
+
addLocalBox(scene, 0x4d7c3f, 4.05, 0.18, 0.84, x, z, 0, 0.58, 0, rotation);
|
|
1246
|
+
for (const offset of [-1.35, -0.45, 0.45, 1.35]) {
|
|
1247
|
+
addLocalBox(scene, 0x2f6b3f, 0.52, 0.46, 0.52, x, z, offset, 0.84, 0, rotation + Math.PI / 4);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function addGardenTree(scene: THREE.Scene, x: number, z: number) {
|
|
1252
|
+
addBox(scene, 0x7c4a28, 0.34, 1.15, 0.34, x, 0.58, z);
|
|
1253
|
+
addBox(scene, 0x2f6b3f, 1.45, 1.15, 1.45, x, 1.35, z, Math.PI / 4);
|
|
1254
|
+
addBox(scene, 0x3f8550, 1.05, 0.88, 1.05, x, 2.02, z, Math.PI / 4);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function addGardenBench(scene: THREE.Scene, x: number, z: number, rotation = 0) {
|
|
1258
|
+
addLocalBox(scene, 0x8b5e3c, 3.1, 0.2, 0.58, x, z, 0, 0.58, 0, rotation);
|
|
1259
|
+
addLocalBox(scene, 0x6b4428, 3.1, 0.72, 0.18, x, z, 0, 0.9, 0.28, rotation);
|
|
1260
|
+
addLocalBox(scene, 0x475569, 0.12, 0.55, 0.12, x, z, -1.25, 0.28, -0.18, rotation);
|
|
1261
|
+
addLocalBox(scene, 0x475569, 0.12, 0.55, 0.12, x, z, 1.25, 0.28, -0.18, rotation);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
646
1264
|
function addMeetingGlassRoom(
|
|
647
1265
|
scene: THREE.Scene,
|
|
648
1266
|
roomIndex: number,
|
|
@@ -876,6 +1494,22 @@ function addGymMirror(scene: THREE.Scene, x: number, z: number, rotation = 0, wi
|
|
|
876
1494
|
addLocalBox(scene, 0xe0f2fe, 0.08, 1.42, 0.05, x, z, width * 0.18, 1.45, 0.14 * faceDirection, rotation);
|
|
877
1495
|
}
|
|
878
1496
|
|
|
1497
|
+
function addElevatorDoor(scene: THREE.Scene, x: number, z: number, width = 1.5, height = 2.4) {
|
|
1498
|
+
addBox(scene, 0x475569, width, height, 0.08, x, height / 2, z);
|
|
1499
|
+
addBox(scene, 0xcbd5e1, 0.04, height * 0.84, 0.1, x, height / 2, z + 0.05);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function addElevatorFrame(scene: THREE.Scene, x: number, z: number, width = 2.2, depth = 1.1, height = 2.8) {
|
|
1503
|
+
addBox(scene, 0x334155, width, 0.16, depth, x, height, z);
|
|
1504
|
+
addBox(scene, 0x334155, 0.16, height, depth, x - width / 2, height / 2, z);
|
|
1505
|
+
addBox(scene, 0x334155, 0.16, height, depth, x + width / 2, height / 2, z);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function addElevatorIndicator(scene: THREE.Scene, x: number, y: number, z: number, width = 0.45, height = 0.3) {
|
|
1509
|
+
addBox(scene, 0x0f172a, width, height, 0.08, x, y, z + 0.08);
|
|
1510
|
+
addBox(scene, 0x38bdf8, width * 0.62, height * 0.46, 0.09, x, y, z + 0.13);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
879
1513
|
function addBox(
|
|
880
1514
|
scene: THREE.Scene,
|
|
881
1515
|
color: number,
|