跪拜 Guibai
← All articles
Frontend

Spec-Driven Geometry, State-Driven Views: An Editable Digital Twin in React Three Fiber

By 前端繁华如梦 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

One shared store and one shared scene component mean the editor and the big screen cannot drift apart, and the pre-allocated `iot` slot on every device turns a static demo into a live data twin by swapping `heatValue`. Any team shipping an R3F scene that must be built once and shown many ways gets a directly reusable pattern.

Summary

One React Three Fiber scene serves a sidebar editor and a big-screen visualization of the same teaching building. Floors are procedural, not imported: `makeFloorPlan` generates a basement garage, ground-floor lobby, N standard floors, and a roof mechanical room from a single count, `stackFloors` computes each floor's `baseY`, and the `Tower` component maps that list to `FloorGroup`s. Devices are plain data in a Zustand store, each with a `floorId`, position, rotation, scale, and an `iot` binding that pre-allocates protocol (none | mqtt | modbus | opcua | http), endpoint, tag, and points.

All view modes ride the same store fields. Explode lifts above-ground floor `level` by `explode * (level - 1) * 4.5`, clipping hides floors above `clipLevel`, solo keeps one floor and lifts basements above ground so the ground plane doesn't block them, and heat coloring drops a semi-transparent plane on the slab. Devices obey the same rules, so they follow floors through every mode, and dragging intersects a horizontal plane at the device's own world Y, so nothing can be pulled onto a different elevation. Persistence is `GET/PUT /api/dt/scene` with a local-storage fallback, and both the editor and the `/screen` route call `initFromServer` on mount. When a real IoT platform arrives, filling `iot.points` and swapping `heatValue` moves the scene from demo hashes to live data; the 3D interaction layer doesn't change.

Takeaways
— Floors generate procedurally from a count via `makeFloorPlan` and `stackFloors`; 1 to 16 standard floors are produced from one number, with geometry and device positions recomputed from the same spec.
— Devices are data in a Zustand store, each with a `floorId`, position, rotation, scale, and an `iot` binding pre-allocating protocol (none | mqtt | modbus | opcua | http), endpoint, tag, and points.
— The editor and the `/screen` visualization mount the same `BuildingScene` and both call `initFromServer` on mount, so they read the same persisted scene.
— Saving PUTs to `/api/dt/scene` after always writing a local copy first; when the backend is down, the local copy is the fallback.
— Explode lifts above-ground floor `level` by `explode * (level - 1) * 4.5`; clipping and solo only skip rendering above `clipLevel`; solo lifts basements above ground so the ground plane doesn't hide them.
— Demo heat values come from a deterministic FNV-1a hash of floor ID and metric name, so the same floor and metric always produce the same color.
— Dragging casts a ray to a horizontal plane at the device's current world Y, keeping devices on their own floor; TransformControls handles rotation and scale, rounding matrices to two decimals on release.
— Focus requests ride a timestamped `focusReq` that `CameraRig` interpolates to, so the search box, floor buttons, and the big screen share one camera-focus path.
— The color scale runs blue to green, orange, red: `#409eff → #67c23a → #e6a23c → #f56c6c`.
Conclusions

Making the building procedurally generated rather than imported turns editing a floor height or adding a standard floor into a data change; the geometry recalculates from the same spec, which is the entire trick of the twin.

One Zustand store plus a shared scene component is what keeps the editor and the big screen consistent; with no sync layer, disagreement is impossible by construction.

The pre-allocated `iot` slot with protocol, endpoint, tag, and points is the deliberate extension point; until a platform connects, the twin runs on demo hashes, and the switch to live data is designed to be a one-field swap.

Deterministic demo heat values trade away realism to keep comparisons stable; the same floor and metric show the same color every time, which is what makes debugging heat bands tolerable.

Concepts & terms
React Three Fiber
A React renderer for Three.js that lets developers build 3D scenes declaratively with JSX components instead of imperative scene-graph code.
Zustand
A small React state-management library with a central store and selector subscriptions; here it holds all scene-editing state and is the single source of truth between the editor and the big screen.
Digital twin
A virtual replica of a physical building whose state mirrors the real one; here the twin runs on procedural geometry and demo data until an IoT platform feeds it live measurements.
FNV-1a hash
A non-cryptographic hash used here to derive a stable demo heat value for a floor from its ID and a metric name, so the same floor and metric always show the same color.
From the discussion
Featured comments
吹风er

How long did this take?

前端繁华如梦

It took 5 days.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗