SMECO 2.0 is a browser-based 3D digital twin viewer built for the inspection and documentation of complex ship engine room assemblies. The platform renders a full-scale marine propulsion system in real-time WebGL, allowing engineers and technical staff to navigate component hierarchies, isolate individual parts, trigger animated exploded views per ship system, and access linked PDF documentation — all within a single-page web application requiring no plugins or native installation.
The project was designed from the ground up with a modular architecture that cleanly separates the 3D rendering core, model-specific logic, controller behaviours, and UI — making it straightforward to extend with new vessel models, additional documentation modules, or different engineering domains entirely.
- Features
- Architecture
- Tech Stack
- Project Structure
- Getting Started
- API Reference
- Ship Systems
- Design Decisions
- Roadmap
- WebGL-accelerated rendering via Three.js with ACESFilmic tone mapping and PCF soft shadows
- DRACO-compressed GLB asset loading (~14 MB model delivered efficiently via local WASM decoder)
- Adaptive pixel ratio capping at 2× for performance/quality balance across devices
- Transparent canvas with full light/dark theme switching — scene background and light intensities update live
The platform allows engineers to isolate individual components, inspect technical metadata, and access linked documentation directly from the 3D environment.
- Raycaster picking — click any mesh in the 3D scene to select and inspect it
- Focus / isolation mode — selected component is highlighted while the rest of the model fades; camera animates to fit the part in frame
- Hover highlighting — emissive color overlay on mouseover for instant part identification
- 3D floating labels — CSS2DObject labels anchored to component positions in world space
- Smooth camera snap — one-click camera rotation to orthographic-aligned views (front, back, left, right, top, bottom) with animated transition
The platform supports both full-model and subsystem-level exploded views, enabling engineers to better understand spatial relationships, assembly structure and component positioning.
-
Global explode — separates all 13 ship systems simultaneously using a hand-authored CAD-style displacement config with per-part offset vectors and rotations; animated with a custom smoothstep RAF loop (2.2 s duration)
-
Per-system explode — isolates and explodes a single selected system, with each subgroup configured for a specific direction and travel distance; uses GSAP when available with a RAF fallback
- Sidebar driven by a logical tree rebuilt from the raw GLB Object3D graph
- 13 top-level ship systems, each expandable to full assembly → subassembly → component depth
- Visibility toggles per node, propagating down the subtree
- Full reset: camera, visibility, explode state, focus mode — restored in a single action
- Every component links to its metadata stored in the backend: title, description, and three PDF document slots (documentation, schematics, maintenance)
- Inline edit mode — engineers can update titles and descriptions directly in the viewer
- PDF upload via multipart form — files stored server-side and linked to the component key
- Fuzzy key resolution — normalised Unicode matching handles Croatian/English diacritics, underscore/hyphen/dot separators, and case differences
- JWT-based login screen gating the entire viewer
- Token persisted in
localStorage; silently validated on page load — valid sessions skip the login screen entirely
- Emissive warning light simulation on specific mesh groups, representing active alarm states in the engine room
The application follows a strict layered architecture with no framework — intentionally, to demonstrate direct command of the underlying browser APIs and Three.js internals.
┌─────────────────────────────────────────────────────┐
│ index.html (SPA) │
│ Single HTML shell — all UI inline │
└────────────────────┬────────────────────────────────┘
│ ES module entry
▼
┌─────────────────────────────────────────────────────┐
│ app.entry.js │
│ Orchestrator: auth flow, model loading, │
│ theme, camera controls, explode state machine │
└──────┬───────────────────────┬──────────────────────┘
│ │
▼ ▼
┌─────────────┐ ┌──────────────────────────────────┐
│ auth.js │ │ viewer.core.js │
│ JWT login │ │ Three.js runtime factory │
│ VPS API │ │ Scene · Renderer · Camera │
└─────────────┘ │ OrbitControls · Lighting │
│ GLTFLoader · DRACOLoader │
└──────────────┬───────────────────┘
│ loadModelModule(mod)
▼
┌──────────────────────────────────┐
│ engine.model.js │
│ Model Module (pluggable) │
│ Loads GLB · Wires controllers │
└──┬───────────────────────────────┘
│
┌────────────┼─────────────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌──────────────────────┐ ┌────────────────┐
│ tree.js │ │ controllers/ │ │ ui/sidebar/ │
│ Rebuilds │ │ picking · focus │ │ Render tree │
│ logical │ │ hover · labels │ │ Edit/save │
│ hierarchy │ │ explode · visibility│ │ components │
│ from GLB │ │ reset · systemExp. │ │ Components │
└────────────┘ └──────────────────────┘ │ REST API │
└───────┬────────┘
│
▼
┌───────────────────┐
│ backend/ │
│ Express API │
│ components.json │
│ PDF storage │
└───────────────────┘
Any new vessel model can be integrated by creating a module that satisfies this contract and registering it in MODEL_REGISTRY:
export const id = 'engine';
export const name = '3D Engine';
export const url = '/glb/FIXED_ENGINE_ROOM.glb';
export const viewPreset = { dir, distanceMul, offset, targetOffset };
export async function load(ctx) { /* receives viewer API, sets up scene */ }
export async function dispose() { /* cleanup all listeners and references */ }
export function toggleExplode() { /* returns Boolean or Promise<Boolean> */ }
export function toggleSystemExplode() { ... }The architecture currently ships with one model (engine). Adding a second requires only the new module file and a one-line registry entry.
| Layer | Technology | Version | Notes |
|---|---|---|---|
| 3D Rendering | Three.js | 0.160.0 | Scene, renderer, camera, controls, loaders |
| Build Tool | Vite | ^7.3.1 | Dev server, HMR, path aliases, bundle analyser |
| Language | Vanilla JS | ES2022 | Pure ES modules, no framework |
| Animation | GSAP | CDN (optional) | Camera transitions, explode sequences. Graceful fallback if absent |
| Compression | DRACOLoader | Three.js addon | Local WASM decoder — no CDN dependency |
| Backend | Node.js + Express | 18+ / ^5.2.1 | REST API, ESM ("type": "module") |
| File Upload | multer | ^2.1.1 | PDF documents, max 25 MB |
| Data Storage | JSON flat file | — | backend/data/components.json — zero-config, human-readable |
| Auth | JWT | — | Bearer token, localStorage persistence |
Ship_engine/
├── index.html # SPA shell — all UI elements inline
├── vite.config.js # Build config + path aliases (@app, @engine, @ui, @shared)
├── package.json # Frontend dependencies
├── start.sh # One-command setup and launch script
├── .env # VITE_API_BASE_URL (auth backend, not committed)
│
├── config/
│ └── api.js # Components API base URL (dev/prod auto-switch)
│
├── viewer/
│ ├── auth.js # JWT auth: login, logout, validateToken, authFetch
│ ├── preloader.js # Loading overlay controller
│ │
│ ├── app/
│ │ └── app.entry.js # Application entry — orchestrates everything
│ │
│ ├── engine/
│ │ ├── core/
│ │ │ └── viewer.core.js # Three.js runtime factory
│ │ └── models/
│ │ └── engine/
│ │ ├── engine.model.js # Model root — load / dispose / explode API
│ │ ├── tree.js # GLB → logical tree with paths and groups
│ │ ├── names.js # Node name formatting utilities
│ │ ├── utils.js # isRenderablePart() and helpers
│ │ ├── controllers/
│ │ │ ├── picking.js # Raycaster click detection
│ │ │ ├── focus.js # Component isolation + info panel + API
│ │ │ ├── hover.js # Emissive highlight on mouseover
│ │ │ ├── labels.js # CSS2DObject floating labels
│ │ │ ├── visibility.js # Show / hide mesh subtrees
│ │ │ ├── explode.js # Global explode animation (smoothstep RAF)
│ │ │ ├── explode.config.js # Per-system displacement vectors + rotations
│ │ │ ├── systemExplode.js # Per-system explode (GSAP / RAF)
│ │ │ ├── system-explode.config.js # Config for all 13 systems
│ │ │ └── reset.js # Full scene reset
│ │ └── ui/
│ │ └── sidebar/
│ │ └── engine.sidebar.js # Bridges tree → sidebar UI
│ │
│ └── ui/
│ └── sidebar/ # Generic sidebar component (model-agnostic)
│ ├── render.js # DOM rendering + API integration
│ ├── state.js # Reactive sidebar state
│ ├── dom.js # DOM references and helpers
│ ├── panels.js # Accordion expand / collapse
│ ├── events.js # All event listeners
│ ├── icons.js # SVG icon factories
│ └── api.js # Sidebar API helpers
│
├── css/
│ ├── viewer-base.css # Reset, root variables, layout base
│ ├── viewer-ui.css # All UI: sidebar, toolbar, info panel, login, menus
│ ├── viewer-preloader.css # Preloader overlay animation
│ └── viewer-sidebar.css # Sidebar-specific overrides
│
├── public/
│ ├── glb/
│ │ └── FIXED_ENGINE_ROOM.glb # Primary 3D asset (14 MB, DRACO-compressed)
│ ├── docs/
│ │ ├── components/ # Dynamically uploaded component PDFs
│ │ ├── main_docs/ # Static ship system technical drawings
│ │ └── help/ # Platform user guide
│ └── draco/gltf/ # DRACOLoader WASM runtime (local)
│
└── backend/
├── server.js # Express REST API
├── package.json
└── data/
└── components.json # Component metadata store (auto-created)
- Node.js 18 or newer
- npm
Clone the repository:
git clone https://github.com/Armin-000/Ship_engine.git
cd Ship_engineCreate a .env file in the project root:
VITE_API_BASE_URL=http://your-auth-backend-urlRun the startup script — it handles everything automatically:
# macOS / Linux
chmod +x start.sh
./start.sh
# Windows (Git Bash or WSL)
./start.shThe script will:
- Verify your Node.js installation
- Install frontend dependencies (
npm install) - Install backend dependencies (
cd backend && npm install) - Create required directories (
backend/data/,public/docs/components/) - Start the backend API server on port
3001 - Start the Vite frontend server on port
5173 - Open the application in your default browser
# Terminal 1 — backend
cd backend
npm start
# Terminal 2 — frontend
npm run devnpm run buildOutput is written to dist/. To analyse the bundle:
npm run build:analyze| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend API | http://localhost:3001 |
| Health check | http://localhost:3001/api/health |
The backend exposes a REST API for component metadata and PDF document management.
Base URL: http://localhost:3001 (dev) · https://ship-engine.onrender.com (prod)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Service health check |
GET |
/api/components |
Retrieve all stored component metadata |
GET |
/api/components/resolve |
Fuzzy-resolve a component by key + candidate names |
GET |
/api/components/:key |
Retrieve one component by exact key |
PUT |
/api/components/:key |
Create or update a component record |
POST |
/api/upload/document |
Upload a PDF file (multipart, max 25 MB) |
DELETE |
/api/components/:key |
Delete a component record |
GET |
/docs/components/:filename |
Serve a stored PDF file |
Every 3D mesh is identified by its path through the scene graph, built at runtime by tree.js:
path:Scene/FULL/1_Structure/11_Floor/113_Engine_Room_Base/Object_5_3
{
"path:Scene/FULL/1_Structure/11_Floor/113_Engine_Room_Base/Object_5_3": {
"title": "Transverse foundation girder",
"description": "Transverse bracing element providing lateral stiffness and structural integrity to the machinery bed.",
"documents": {
"documentation": "/docs/components/1780433957970-c32_1000_hp_specifications.pdf",
"schematics": null,
"maintenance": null
},
"updatedAt": "2026-06-03T09:02:34.043Z"
}
}The engine room model is organised into 13 independently navigable and explodable ship systems:
| # | System |
|---|---|
| 1 | Structure |
| 2 | Exhaust System |
| 3 | Plate Heat Exchanger |
| 4 | Port Generator |
| 5 | Main Engine No. 1 |
| 6 | Transmission |
| 7 | Pipes |
| 8 | Valves |
| 9 | Propeller |
| 10 | Fire System |
| 11 | Lube Oil Tank |
| 12 | Service Air Receiver |
| 13 | Duplex Oil Strainer |
Each system contains a hierarchy of assemblies, subassemblies, and individual named components. Every level of the hierarchy is separately navigable, isolatable, and documentable.
No frontend framework. The decision to use vanilla ES modules rather than React or Vue was deliberate. The application's primary complexity is 3D scene management, not component state trees — Three.js already provides the rendering abstraction, and introducing a virtual DOM layer would add overhead without solving a real problem here.
GSAP as an optional peer dependency. Rather than adding a hard npm dependency, GSAP is loaded via CDN and accessed as window.gsap || null throughout the codebase. Every animation code path has a functional RAF fallback, which means the application runs correctly — with slightly less polished easing curves — if the CDN load fails or is blocked.
Two separate API origins. Authentication (JWT login, token validation) runs against a dedicated VPS. Component metadata and PDF storage run against a separate service on Render. These concerns are intentionally decoupled: swapping or scaling the document storage service does not affect user authentication.
JSON flat file as the database. For this use case — a single-writer system where an engineer updates component metadata — a flat JSON file is operationally simpler than a database, trivially human-readable and editable, and trivially version-controllable. Migration to SQLite or PostgreSQL is a well-defined future step.
Local DRACO decoder. The DRACOLoader WASM runtime is bundled locally in public/draco/gltf/ rather than pointing to the Google CDN. This eliminates a cross-origin dependency for an asset critical to the initial load path.
- JWT middleware on the components API (currently open CRUD)
- Database migration — SQLite or PostgreSQL
- Additional vessel models — architecture is ready, requires only a new model module
- 3D annotation billboards anchored to specific mesh positions
- TypeScript migration — incremental, starting from
viewer.core.js - CI/CD pipeline with static build and automated deployment
- Mobile-optimised touch controls and responsive sidebar layout
ISC — see LICENSE for details.



