This page describes how DomiDo renders three-dimensional (3D) designs on the web and how augmented reality (AR) appears later in the product. DomiDo is built by Avvyland Limited (UK) and sells universal blocks and fasteners only; every construction shown on the platform is a user-generated design, and the 3D and AR layer is what makes those designs visible, inspectable, and trustworthy on a phone or in a browser. The launch surface is a Progressive Web Application (PWA), the canonical 3D output is a Meshopt-compressed binary glTF file, and the viewer architecture is engineered around two facts that dominate the use case: a single design can contain thousands of identical interlocking blocks, and most users will see it on a mid-range or budget phone with a thermally constrained mobile GPU. Native AR is a later-phase consideration, and the architecture stays compatible with a native iOS and Android build whenever Phase B planning calls for it.
The viewer is built on Three.js, the dominant web 3D library, accessed through React Three Fiber (R3F). Three.js provides a WebGL 2 renderer with stable cross-browser support and a WebGPU renderer that is production-ready in current versions and provides large performance gains on draw-call-heavy scenes; physically based rendering (PBR) materials including standard, physical, and toon variants together with custom shaders; instancing through InstancedMesh and InstancedBufferGeometry with frustum culling per instance; post-processing through both the WebGL effect composer and the WebGPU node-based pipeline; shadow mapping with percentage-closer filtering (PCF), percentage-closer soft shadows (PCSS), and variance shadow maps (VSM); animation through AnimationMixer, morph targets, and skeletal animation; a complete glTF ecosystem covering loading, Draco geometry compression, Meshopt geometry compression, and KTX2 texture compression; and WebXR support for AR and virtual reality (VR) sessions through the renderer's XR module. The viewer renders hundreds to thousands of block voxels in a single draw call via InstancedMesh, and a greedy meshing surface extraction further reduces a multi-thousand-voxel structure to a single optimised draw call when the user does not need to inspect each block.
Babylon.js was evaluated as the alternative. Babylon.js is a full-featured web game engine with built-in physics plugins, a built-in scene inspector, and built-in VR and AR experience helpers, but it is heavier on bundle size and has a less mature React integration. Three.js wins for the DomiDo use case because the codebase, the team's React expertise, the R3F ecosystem, and the much larger community make it the natural choice; switching is unjustifiable unless built-in physics or a visual scene editor becomes a core requirement. React Three Fiber sits on top of Three.js and provides a declarative scene graph where block instances, lighting, cameras, and controls are expressed as JSX components; R3F renders outside React's reconciliation when frames are drawn and participates in React scheduling only for state changes. Hooks such as useThree, useFrame, and useLoader provide idiomatic React access to the renderer, the camera, and the animation loop. The Drei helper library provides ready-made controls, model loaders, environment helpers, edges, HTML overlays, and a performance monitor, and @react-three/xr adds WebXR session management, controllers, hands, plane detection, hit testing, anchors, and DOM overlay as React components.
Alongside the primary viewer the platform also uses Google's <model-viewer> web component as a lightweight share-preview embed. The web component is a zero-configuration drop-in for showing a glTF or GLB model with built-in AR: on Android Chrome it uses WebXR with DOM overlay or falls back to Google's Scene Viewer, and on iOS it launches Apple's Quick Look AR via automatically generated USDZ. It supports glTF and GLB loading, animations, environment maps, and camera controls, and stays lightweight at around one hundred kilobytes gzipped. It is not suitable as the primary viewer because it offers no programmatic control over individual block instances, no assembly animation, no explode view, no custom shaders, and only a view-only AR mode, but it is exactly right for a public preview where a visitor can view a completed design and launch native AR on iOS with zero code.
The viewer is engineered around four mutually reinforcing optimisations. Level of detail uses THREE.LOD to swap mesh detail based on camera distance: for DomiDo, simplified merged meshes render at distance for large structures while full instanced blocks render at close range, AR begins with a low-polygon proxy and loads detailed meshes when the user approaches, and unnecessary detail is dropped when the camera is moving fast. Instancing uses InstancedMesh for block rendering across the viewer, while for static views (a completed assembly) the engine merges all instances into a single buffer geometry through greedy meshing and eliminates per-instance overhead entirely; BatchedMesh can render multiple geometries in a single draw call when blocks have different shapes. Occlusion culling combines the renderer's built-in frustum culling — which automatically skips objects outside the camera view and removes a large fraction of non-visible objects — with GPU-based occlusion culling through a WebGPU compute shader that renders a low-resolution depth buffer and tests each instance's bounding box against it, which is effective for dense structures where inner blocks are hidden by outer ones; the greedy-meshing pipeline already eliminates interior faces as a form of static occlusion. Draw-call reduction is the single most impactful optimisation in the viewer: greedy meshing reduces a ten-thousand-voxel structure from ten thousand draw calls of instanced cubes to a single draw call against a merged mesh, and for assembly playback, where blocks appear one by one, InstancedMesh.count progressively reveals instances without recreating the geometry.
The platform uses glTF 2.0 as the universal interchange format — the "JPEG of 3D" — and users upload .glb or .gltf. The loading pipeline runs the file through GLTFLoader, with DRACOLoader for Draco-compressed geometry, MeshoptDecoder for Meshopt-compressed geometry, and KTX2Loader for GPU-compressed textures. For export, the platform offers a set of compression options whose properties are best read as a table, and Meshopt is the preferred default because decoding is faster, the decoder is small, vertex ordering is preserved for instanced rendering, and meshoptimizer is already a project dependency.
| Method | Geometry size reduction | Decode speed | Browser support |
|---|---|---|---|
| Draco | Up to ninety-five percent for geometry. | Around two hundred megabytes per second (WebAssembly). | All modern browsers. |
| Meshopt | Sixty to eighty percent for geometry. | Around one gigabyte per second (WebAssembly SIMD). | All modern browsers. |
| KTX2 / Basis Universal | More than seventy-five percent for textures. | GPU-native decode. | Via loader; universal. |
| gzip or brotli (transport) | An additional thirty to fifty percent. | Native browser decode. | Universal. |
Mobile is the dominant viewing surface, so the viewer is tuned against device-tier baselines and a set of mobile-specific behaviours. The baseline targets — frame rate, draw-call budget, and triangle budget — differ by device tier and are summarised below.
| Device tier | Target frame rate | Maximum draw calls | Maximum triangles |
|---|---|---|---|
| High-end (recent flagship) | Sixty frames per second. | Around two hundred. | Around five hundred thousand. |
| Mid-range | Thirty frames per second. | Around one hundred. | Around two hundred thousand. |
| Low-end (budget) | Thirty frames per second. | Around fifty. | Around one hundred thousand. |
Beyond the per-tier budgets, mobile-specific behaviour shapes how the viewer renders. Thermal throttling kicks in after a few minutes of sustained rendering on mobile GPUs, so on-demand rendering, adaptive resolution scaling, and frameloop="demand" keep the load down. Mobile browsers have small heap budgets, so aggressive geometry disposal, greedy mesh merging, and shared texture atlases keep memory in budget. Touch interaction needs careful damping for finger input, and browser scroll and zoom gestures are disabled on the canvas. Mobile browsers aggressively evict WebGL contexts when a tab is backgrounded, so the webglcontextlost and webglcontextrestored events are handled explicitly. Rendering at sixty frames per second uses about twice the power of thirty frames per second, so static assembly views target thirty frames per second with on-demand rendering.
The viewer plays the assembly back as a step sequence. A stepIndex ranges from zero (empty scene) to N (fully assembled), assembly steps are ordered by the server-side assembly planner, InstancedMesh.count is set to the current step index to progressively reveal blocks, and a playback timer auto-advances steps with controls for play, pause, jump, and speed. Each new block animates into position rather than appearing instantly: the animation lifts the block above its target, tweens it down with an ease-out cubic curve, optionally bounces slightly on landing, pulses an emissive highlight for a brief moment, and re-centres the camera if needed. Backward stepping reverses the motion, and jump-to-step batches the in-between animations.
Explode view spreads blocks apart from the assembly centre to reveal internal structure. The explode factor runs from zero (fully assembled) to one (fully exploded), and two algorithms are useful: a radial explosion that translates each block outward along the line from the centre, scaled by distance and the explode factor, and a layer-based explosion where the bottom layer stays and each layer above lifts proportionally, which is more intuitive for block structures. Explode offsets are computed once when the assembly data changes and stored as vectors; interpolation runs in useFrame while the explode factor is animating. Explode view combined with assembly playback shows only placed blocks in exploded form, which reveals both the structure and the construction order at once.
Individual block instances support selection through ray-casting against the instanced mesh: a selected block is highlighted by emissive colour or outline, selection drives auxiliary panels (block type, position, suggested next steps), and the assembly companion uses the same mechanism to focus on the next block. A separate camera-path animation moves the camera through a scripted path to give the viewer a guided tour of the assembled structure, and the path is a sequence of keyframes — position, target, easing — that the same path-animation system also reuses for time-lapse recording.
Phase A is web-only and the native AR experience is a Phase B consideration; what makes the in-browser path practical is WebXR. A user grants the AR session, the device exposes the camera feed and the platform-tracked anchors, and the renderer composites the three-dimensional content over the real world. Hit testing places the design on a detected horizontal plane, anchoring keeps the design pinned as the user moves, and DOM overlay places UI controls on top of the AR view. The placement flow is straightforward: the user launches AR, scans the floor, taps to place the design at scale, drags and rotates to position, and confirms. The design appears with the same renderer that powers the in-browser view, so visual consistency is preserved. In a later phase, AR assembly guidance will overlay the next block on the partially built physical structure — the user holds up the phone, sees the next block in its correct position relative to the real-world blocks, picks up the matching physical block, and places it — and real-time hit testing keeps the overlay anchored as the build progresses.
The native-versus-web decision sits with the Phase B planning rather than with the viewer architecture. Web AR via WebXR has cross-platform reach with zero install friction and is enough for Phase A and Phase A.5, while a native iOS and Android build adds higher-fidelity AR (ARKit and ARCore), better camera and sensor access, deeper integration with the device, and store presence; the architecture stays compatible with both targets so neither decision is locked in by the viewer. A longer-horizon XR headset roadmap evaluates mixed-reality (MR) headsets for design and assembly use, and the same WebXR foundation works on visionOS, Meta Quest, and other MR devices that support immersive web sessions; a native XR build is on the longer horizon.
A time-lapse recording feature captures the assembly playback as a video by rendering to an off-screen frame buffer, encoding the frames into a video stream, and offering the result as a downloadable or shareable file. The same scripted camera path that powers the guided tour produces a polished result, and the recorded video stays small enough to share on social platforms while remaining high quality on a phone screen. The 3D-model optimisation pipeline that produces the input to all of this runs offline on the server when an artefact is produced: mesh simplification reduces triangle count without changing the silhouette, attribute compression with Meshopt packs vertices efficiently, texture compression with KTX2 and Basis Universal provides GPU-native textures, separate level-of-detail meshes are exported at different polygon budgets, and the output is gzip- or brotli-compressed for transport. The combined effect typically reduces a multi-megabyte raw GLB to a small mobile-friendly artefact while preserving visual fidelity at typical viewing distances.
Putting these pieces together yields the recommended architecture for the viewer layer: React Native with Expo for the universal app shell, React Three Fiber on top of Three.js for the viewer, Drei helpers for cameras, controls, environment, edges, HTML overlays, and performance monitoring, and @react-three/xr for WebXR sessions with a clean upgrade path to native ARKit and ARCore later. A strict bundle-size budget on the viewer (around one megabyte gzipped at Phase A) is maintained through code splitting, lazy loading, and tree shaking; Meshopt-compressed GLB artefacts are the canonical 3D output format; KTX2 textures are used where textures appear; and a performance monitor such as r3f-perf provides development-time draw-call, triangle, and frames-per-second visibility.
Accessibility is a first-class concern in the viewer rather than a layer applied at the end. The canvas exposes ARIA roles and labels, keyboard navigation, and screen-reader-friendly descriptions of the scene; camera controls support keyboard input as well as touch and pointer input; high-contrast mode adjusts edges and outlines for low-vision users; and motion-reduction settings disable or simplify animation for users who prefer reduced motion.