Skip to content

thebuggeddev/mechlab

Repository files navigation

MECHLAB — Mechanical Systems Explorer

Interactive 3D platform for learning mechanical systems. Explore eight machines — V8, Inline-4, Diesel, Turbine, Wankel, Air Compressor, Differential and Planetary Gearbox — with orbit/zoom/pan, physically believable animations, exploded views, part selection and wireframe mode.

Built with React + TypeScript + Vite, Three.js / React Three Fiber / Drei, GSAP, Framer Motion, Tailwind CSS v4 and Zustand.


Quick start

npm install
npm run dev        # http://localhost:5173
npm run build      # production build → dist/
npm run preview    # serve the production build locally
npm run lint       # ESLint
npm run smoke      # headless test: builds every machine, runs every animation

Node 18+ recommended.


Deployment (Vercel)

The project is a static Vite SPA — zero configuration needed.

  1. Push the repository to GitHub/GitLab.
  2. In Vercel: New Project → Import. Vercel auto-detects Vite.
    • Build command: npm run build
    • Output directory: dist
  3. Deploy. No environment variables are required.

CLI alternative: npm i -g vercel && vercel --prod.

Any static host works (Netlify, Cloudflare Pages, GitHub Pages): upload dist/.


Architecture

src/
├── components/      UI: TopBar, Sidebar, ControlBar, ViewerOverlay,
│                    InfoPanel, BrowseModal, icons
├── layouts/         AppLayout — composes the page exactly per the design
├── three/           Viewer (R3F canvas, lights, env, camera rig),
│                    MachineRig (build/animate/explode/picking),
│                    thumbnails (offscreen sidebar renders)
├── modules/
│   └── machines/    types (MachineDef / MachineBuild contract),
│                    registry (data: names, stats, animations, cameras),
│                    materials, engineCore (shared crank/piston kinematics),
│                    builders/ (v8, inline, turbineWankel, drivetrain)
├── store/           Zustand store — single source of truth for UI state
├── hooks/           useFullscreen
├── utils/           geometry helpers (gears, bolts, pipes), viewerBus
├── animations/ pages/ shaders/ assets/   reserved per spec

Adding a machine without touching core code

Each machine is one entry in modules/machines/registry.ts plus one builder returning the MachineBuild contract:

interface MachineBuild {
  root: THREE.Group                       // added to the scene
  parts: MachinePart[]                    // name + group + baked explode vector
  update(t: number, mode: string): void   // drive kinematics
  dispose(): void
}

Sidebar, thumbnails, viewer, explode, picking and the control bar all read from the registry — nothing else changes. A GLB-backed machine can implement the same contract via GLTFLoader (drei useGLTF), mapping named nodes to parts.

Animation system

  • The store holds playing / speed / animationId; MachineRig accumulates speed-scaled time each frame and calls build.update(t, mode).
  • Kinematics are physically derived, not keyframed:
    • slider-crank piston position from crank angle, rod length, crank radius
    • cross-plane V8 phasing + firing-order combustion flashes
    • camshafts at half crank speed (four-stroke timing)
    • Wankel rotor: eccentric orbit at shaft speed, rotor spin at 1/3 shaft speed inside a true epitrochoid housing
    • differential: ring/pinion ratio, spider gears walking during "cornering" (±35 % axle speed split)
    • planetary: exact epicyclic ratios (ring-fixed ωc = ωs·Zs/(Zs+Zr), etc.)
  • GSAP drives camera transitions; explode is lerped per-frame from baked per-part vectors.

State

A single Zustand store (store/useAppStore.ts) holds machine selection, animation, playback, explode/wireframe/auto-rotate flags, part hover/selection, modal state and generated thumbnails. Components subscribe with narrow selectors to avoid unnecessary re-renders; the render loop reads via useAppStore.getState() so animation never re-renders React.

Asset loading

Models are procedural (no network 3D assets), generated synchronously in < 50 ms each, so there is nothing to fetch or compress. Sidebar thumbnails are rendered once at startup by an offscreen WebGL canvas (three/thumbnails.ts) and stored as data URLs; skeleton shimmer placeholders prevent layout shift. Fonts are self-hosted via @fontsource/jetbrains-mono. The architecture is GLB-ready (see contract above) — DRACO/KTX2 compression would slot into a loader-based builder without touching the viewer.


Performance report

Bundle (gzip): app ≈ 140 kB, three vendor chunk ≈ 244 kB (split via manualChunks so the app shell parses first), CSS ≈ 32 kB, fonts loaded per-weight on demand.

Runtime:

  • One WebGLRenderer, dpr capped at 2, ACES tone mapping, PMREM RoomEnvironment generated in-memory (no HDR download).
  • Single 2048² shadow map + ContactShadows at 512² — soft shadows without per-light cost.
  • Frustum culling is on by default for every mesh; geometry is low-poly primitives (whole V8 ≈ 60 k triangles).
  • Explode/hover work by mutating existing transforms/materials — zero allocations in the frame loop; damping handled by OrbitControls.
  • Machine switch disposes all geometries/materials of the previous build (disposeTree) — no GPU memory growth when browsing the library.
  • Thumbnails render once on a throwaway 192×144 context which is disposed immediately afterwards.
  • prefers-reduced-motion collapses UI transitions; the canvas honours pause state.

Measured headless (npm run smoke): all 8 machines build, animate through every program and dispose cleanly.


Accessibility

Keyboard reachable controls with visible focus rings, ARIA labels/roles on nav, listbox, dialog and toggles (aria-pressed, aria-current, aria-expanded), Escape closes menus/modals, reduced-motion support, and high-contrast monochrome palette per the design.

About

An interactive learning playground to learn mechanical engines developed using claude fable 5

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages