Download src/roomLayout.ts from gpue/flyyt: direct link, hf CLI and curl.
- Browser
- Download file 9.7 kB
-
https://huggingface.co/spaces/gpue/flyyt/resolve/main/src/roomLayout.ts
- Command line
-
hf download hf://spaces/gpue/flyyt/src/roomLayout.ts
-
curl -L -o roomLayout.ts https://huggingface.co/spaces/gpue/flyyt/resolve/main/src/roomLayout.ts
9.7 kB
| import { FLY_AREA_CONFIG } from "./env"; | |
| /** | |
| * Axis-aligned bounding box in mm, world (three.js) XZ coordinates. | |
| * `topY` is optional and doubles this same shape as "floor height" data: | |
| * undefined means flies never stand on it (e.g. a wall), a number means a | |
| * walkable/landable surface at that height (e.g. a tabletop). | |
| */ | |
| export interface AabbMm { | |
| minX: number; | |
| maxX: number; | |
| minZ: number; | |
| maxZ: number; | |
| topY?: number; | |
| } | |
| export interface FurniturePlacement { | |
| id: string; | |
| /** path under public/, served by Vite at the same path. */ | |
| modelUrl: string; | |
| position: [number, number, number]; | |
| rotationY: number; | |
| /** uniform scale converting the source glTF's meter units to this app's mm world. */ | |
| scale: number; | |
| /** collision footprint(s) this placement contributes -- empty for purely decorative props. */ | |
| colliders: AabbMm[]; | |
| } | |
| // Room interior derives from FLY_AREA_CONFIG (not its own copy) so the two | |
| // can never drift apart -- FLY_AREA_CONFIG already drives camera/grid | |
| // framing math elsewhere, this just reuses it for wall placement. | |
| // wallHeightMm/wallThicknessMm/TILE_MM match the authored size of | |
| // public/models/room/wall.glb and floor.glb (Kenney "Furniture Kit", CC0): | |
| // each is a 1m x ~1.29m x 0.05m segment meant to be tiled edge-to-edge. | |
| export const ROOM_CONFIG = { | |
| widthMm: FLY_AREA_CONFIG.widthMm, | |
| depthMm: FLY_AREA_CONFIG.depthMm, | |
| wallHeightMm: 1289.5, | |
| wallThicknessMm: 50, | |
| }; | |
| export const TILE_MM = 1000; | |
| // CC0 tileable textures (Poly Haven), applied as diffuse maps onto the | |
| // floor/wall meshes in RoomScene.tsx -- see CREDITS.md. | |
| export const FLOOR_TEXTURE_URL = "/textures/floor_diff.jpg"; | |
| export const WALL_TEXTURE_URL = "/textures/wall_diff.jpg"; | |
| const HALF_WIDTH = ROOM_CONFIG.widthMm / 2; | |
| const HALF_DEPTH = ROOM_CONFIG.depthMm / 2; | |
| // Four thin boxes ringing the interior, computed from ROOM_CONFIG rather | |
| // than hand-authored, since wall placement is mechanical. | |
| export const WALL_COLLIDERS: AabbMm[] = [ | |
| { minX: -HALF_WIDTH, maxX: HALF_WIDTH, minZ: -HALF_DEPTH, maxZ: -HALF_DEPTH + ROOM_CONFIG.wallThicknessMm }, // south | |
| { minX: -HALF_WIDTH, maxX: HALF_WIDTH, minZ: HALF_DEPTH - ROOM_CONFIG.wallThicknessMm, maxZ: HALF_DEPTH }, // north | |
| { minX: -HALF_WIDTH, maxX: -HALF_WIDTH + ROOM_CONFIG.wallThicknessMm, minZ: -HALF_DEPTH, maxZ: HALF_DEPTH }, // west | |
| { minX: HALF_WIDTH - ROOM_CONFIG.wallThicknessMm, maxX: HALF_WIDTH, minZ: -HALF_DEPTH, maxZ: HALF_DEPTH }, // east | |
| ]; | |
| // Furniture layout -- positions/colliders measured directly from each | |
| // downloaded glTF's accessor bounding box (see tools/inspect done during | |
| // execution), not guessed: every piece's local origin sits at a known | |
| // corner or center, and `position` is that corner/center's world-space | |
| // placement minus the piece's own local min/center, so these numbers | |
| // already account for each model's authored origin offset. | |
| // | |
| // Sources (all CC0, see CREDITS.md): | |
| // - table/chair/sofa/plant/rug/floor/wall: Kenney "Furniture Kit" | |
| // - bowl/apple/lemon: Poly Haven "Food & Kitchen" collection | |
| export const FURNITURE: FurniturePlacement[] = [ | |
| // tableCross.glb: local bbox X[0,852.1] Z[-447.4,0] Y[0,347.4] (mm) -- | |
| // verified with `gltf-transform inspect` (scene-graph-aware; an earlier | |
| // raw-accessor read of this specific file ignored a node-level scale and | |
| // was ~2.4x too large, which is why the fruit/chairs looked stranded off | |
| // the table before). Placed against the north wall. | |
| { | |
| id: "table", | |
| modelUrl: "/models/room/table.glb", | |
| position: [-426.06, 0, 1400], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [{ minX: -426.06, maxX: 426.06, minZ: 952.63, maxZ: 1400, topY: 347.41 }], | |
| }, | |
| // chair.glb: local bbox X[0,200] Z[-200,0] Y[0,470] (mm), pulled up snug | |
| // against the table's near edge (952.63mm, ~22.6mm clearance). topY makes | |
| // the seat/back landable, same as the table (see resolvePosition's | |
| // altitude-aware obstacle check in collision.ts). | |
| { | |
| id: "chair-1", | |
| modelUrl: "/models/room/chair.glb", | |
| position: [-350, 0, 930], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [{ minX: -350, maxX: -150, minZ: 730, maxZ: 930, topY: 470 }], | |
| }, | |
| { | |
| id: "chair-2", | |
| modelUrl: "/models/room/chair.glb", | |
| position: [150, 0, 930], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [{ minX: 150, maxX: 350, minZ: 730, maxZ: 930, topY: 470 }], | |
| }, | |
| // loungeSofa.glb: local bbox X[0,980] Z[-410,0] Y[0,460] (mm), placed | |
| // along the west wall. Landable (topY), same reasoning as the chairs. | |
| { | |
| id: "sofa", | |
| modelUrl: "/models/room/sofa.glb", | |
| position: [-1900, 0, -190], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [{ minX: -1900, maxX: -920, minZ: -600, maxZ: -190, topY: 460 }], | |
| }, | |
| // pottedPlant.glb: local bbox X[-106.03,106.03] Z[-120.73,120.73] | |
| // Y[0,654.02] (mm), origin centered in X/Z (verified with `gltf-transform | |
| // inspect`) -- tucked in the corner opposite the sofa. Deliberately no | |
| // topY: a potted plant's foliage isn't a landable surface, so it stays a | |
| // full-height obstacle at every altitude, unlike the table/chairs/sofa/shelf. | |
| { | |
| id: "plant", | |
| modelUrl: "/models/room/plant.glb", | |
| position: [1800, 0, -1300], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [{ minX: 1693.97, maxX: 1906.03, minZ: -1420.73, maxZ: -1179.27 }], | |
| }, | |
| // rugRectangle.glb: purely decorative (no collider) -- laid under the | |
| // free-floor area so the flies have somewhere visually distinct to walk. | |
| { | |
| id: "rug", | |
| modelUrl: "/models/room/rug.glb", | |
| position: [-160, 0.5, -265], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [], | |
| }, | |
| // Fruit bowl + fruit, sitting on the table's topY (347.41mm, centered | |
| // over its Z[952.63,1400] footprint) -- colliders stay [] (a grounded | |
| // fly's XZ collision already keeps it out of the table's whole footprint | |
| // via ALL_OBSTACLES, so nothing here needs to independently block | |
| // horizontal movement), but each still gets its own small landable | |
| // footprint in FRUIT_FLOOR_SURFACES below so a fly can land specifically | |
| // on top of a piece of fruit, not just anywhere on the table. bowl/apple | |
| // have bottom-centered local origins (position.y == topY places them | |
| // flush); lemon's origin sits mid-body, so its position.y adds back its | |
| // local min-Y offset (52.29mm) to rest flush. | |
| { | |
| id: "bowl", | |
| modelUrl: "/models/room/bowl.glb", | |
| position: [0, 347.41, 1176.3], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [], | |
| }, | |
| { | |
| id: "apple", | |
| modelUrl: "/models/room/apple.glb", | |
| position: [-120, 347.41, 1176.3], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [], | |
| }, | |
| { | |
| id: "lemon", | |
| modelUrl: "/models/room/lemon.glb", | |
| position: [100, 399.7, 1186.3], | |
| rotationY: 0, | |
| scale: 1000, | |
| colliders: [], | |
| }, | |
| // bookcaseOpen.glb: local bbox X[0,400] Z[-250,0] Y[0,880] (mm), rotated | |
| // -90deg so its depth (250mm) faces the east wall and stands flush | |
| // against it, in the room's north half (clear of the plant/sofa corners). | |
| // topY makes its top landable (reachable around altitude level 8+). | |
| { | |
| id: "shelf", | |
| modelUrl: "/models/room/shelf.glb", | |
| position: [1700, 0, 700], | |
| rotationY: -Math.PI / 2, | |
| scale: 1000, | |
| colliders: [{ minX: 1700, maxX: 1950, minZ: 700, maxZ: 1100, topY: 880 }], | |
| }, | |
| ]; | |
| /** Everything collision.ts checks fly positions against (horizontal blocking). */ | |
| export const ALL_OBSTACLES: AabbMm[] = [...WALL_COLLIDERS, ...FURNITURE.flatMap((f) => f.colliders)]; | |
| /** | |
| * Raised walkable surfaces for floorHeightAt's height lookup only -- NOT | |
| * horizontal obstacles (a fly can freely walk onto these footprints, it | |
| * just stands slightly higher while over them). Includes every | |
| * topY-bearing FURNITURE collider (e.g. the table) plus the rug, which | |
| * isn't a FURNITURE collider at all (flies must be able to walk across it, | |
| * unlike the table) but still has real thickness -- rugRectangle.glb: local | |
| * bbox X[0,1570] Z[-920,0] Y[0,10] (mm), at its placed position or a | |
| * grounded fly would render with its body clipped partway into the rug | |
| * mesh instead of standing on top of it. | |
| */ | |
| // Each fruit prop's own small landable surface, ON TOP of the table | |
| // (topY = table's 347.41 + this prop's own authored height) -- kept | |
| // separate from FURNITURE's colliders (which stay [] so these never | |
| // independently block horizontal movement, same reasoning as the rug | |
| // below). bowl: 130.7x73.1x124.5mm @ (0,347.41,1176.3), bottom-centered. | |
| // apple: 97.6x85.4x95.9mm @ (-120,347.41,1176.3), bottom-centered. lemon: | |
| // 68.7x95.4x68.3mm @ (100,399.7,1186.3) -- its topY uses its full authored | |
| // height (95.4mm) added to the table's topY, not position.y (399.7mm), | |
| // since position.y already has its own local-origin offset baked in just | |
| // to sit the mesh flush on the table. | |
| export const FRUIT_FLOOR_SURFACES: AabbMm[] = [ | |
| { minX: -65.35, maxX: 65.35, minZ: 1114.05, maxZ: 1238.55, topY: 420.51 }, // bowl | |
| { minX: -168.8, maxX: -71.2, minZ: 1128.35, maxZ: 1224.25, topY: 432.81 }, // apple | |
| { minX: 65.65, maxX: 134.35, minZ: 1152.15, maxZ: 1220.45, topY: 442.81 }, // lemon | |
| ]; | |
| export const FLOOR_SURFACES: AabbMm[] = [ | |
| ...ALL_OBSTACLES.filter((box) => box.topY !== undefined), | |
| { minX: -160, maxX: 1410, minZ: -1185, maxZ: -265, topY: 10.5 }, // rug | |
| ...FRUIT_FLOOR_SURFACES, | |
| ]; | |
| /** | |
| * Free-floor spawn rectangle(s) -- hand-picked to clear every obstacle | |
| * above (with margin), not derived by automatic rectangle subtraction | |
| * (deliberately out of scope: a human picking 1-2 clear rectangles once is | |
| * simpler and just as correct here). | |
| */ | |
| export const FREE_FLOOR_RECTS: AabbMm[] = [{ minX: -350, maxX: 1600, minZ: -1450, maxZ: 0 }]; | |