// A single cube — the Meep "hello world".
//
// This file is the Getting started guide made real. It reuses the same
// engine-provided pieces the docs show:
//
// - `EntityComponentDataset` owning the entities and components
// - `EntityManager` owning the systems and the loop
// - `Transform` carrying position / rotation / scale
// - A `System` subclass with `dependencies = [...]` and an `update(dt)` body
//
// The one thing this example adds on top of the docs is `EngineHarness`,
// which spins up a canvas, an orbital camera, and a sun + ambient light pair
// so we have something to actually look at.
import * as THREE from "three";
import { EngineHarness } from "@woosh/meep-engine/src/engine/EngineHarness.js";
import Entity from "@woosh/meep-engine/src/engine/ecs/Entity.js";
import { System } from "@woosh/meep-engine/src/engine/ecs/System.js";
import { Transform } from "@woosh/meep-engine/src/engine/ecs/transform/Transform.js";
import { ShadedGeometry } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometrySystem } from "@woosh/meep-engine/src/engine/graphics/ecs/mesh-v2/ShadedGeometrySystem.js";
import Vector3 from "@woosh/meep-engine/src/core/geom/Vector3.js";
// ─── §2 Bootstrap ─────────────────────────────────────────────────────────
//
// EngineHarness.bootstrap creates the EntityManager + dataset, attaches a
// canvas to the document, and gives us a `configuration` hook to register
// systems. ShadedGeometrySystem is what actually draws any entity carrying a
// ShadedGeometry component — without it the cube would have nothing rendering it.
const engine = await EngineHarness.bootstrap({
configuration: (config, engine) => {
config.addSystem(new ShadedGeometrySystem(engine));
},
});
// EngineHarness.buildBasics frames the camera, drops in a directional sun
// light + a low-intensity ambient, and (with default `cameraController: true`)
// wires the orbital drag-to-look / WASD-to-pan / wheel-to-zoom input.
await EngineHarness.buildBasics({
engine,
enableTerrain: false,
enableWater: false,
enableLights: true,
enableShadows: false,
focus: new Vector3(0, 0, 0),
distance: 4,
pitch: 0.6,
yaw: 0.4,
showFps: false,
});
const ecd = engine.entityManager.dataset;
// ─── §3 Spawn one cube ────────────────────────────────────────────────────
//
// The shape and material come from Three.js; ShadedGeometry.from wraps them
// as a Meep component so the cube participates in batching, frustum culling,
// and (when enabled) shadow maps alongside any other ShadedGeometry entity.
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0x4ef0a8 });
const cubeMesh = ShadedGeometry.from(cubeGeometry, cubeMaterial);
const t = new Transform();
t.position.set(0, 0, 0);
new Entity()
.add(t)
.add(cubeMesh)
.build(ecd);
// ─── §4 HUD: fps counter ──────────────────────────────────────────────────
const fpsEl = document.getElementById("fps");
let fpsWindow = 0;
let fpsFrames = 0;
let lastFrameMs = performance.now();
engine.graphics.on.postRender.add(() => {
const nowMs = performance.now();
const dt = (nowMs - lastFrameMs) / 1000;
lastFrameMs = nowMs;
fpsWindow += dt;
fpsFrames++;
if (fpsWindow >= 0.5) {
fpsEl.textContent = (fpsFrames / fpsWindow).toFixed(0);
fpsWindow = 0;
fpsFrames = 0;
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple cube · Meep</title>
<meta name="robots" content="noindex">
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
width: 100%; height: 100%;
overflow: hidden;
background: #07090c;
color: #e6edf3;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
.panel {
position: fixed;
z-index: 100;
background: rgba(7, 9, 12, 0.72);
border: 1px solid #1f2731;
border-radius: 10px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 12px 32px rgba(0,0,0,0.4);
}
.hud {
top: 1rem; left: 1rem;
padding: 0.8rem 1rem;
font-family: ui-monospace, "JetBrains Mono", monospace;
font-size: 0.82rem;
line-height: 1.7;
color: #9aa5b1;
min-width: 180px;
}
.hud .label {
color: #6b7785;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.65rem;
margin-right: 0.5rem;
}
.hud .value { color: #4ef0a8; }
.legend {
bottom: 1rem; left: 1rem;
padding: 0.8rem 1rem;
font-size: 0.82rem;
line-height: 1.55;
max-width: 380px;
color: #9aa5b1;
}
.legend strong { color: #e6edf3; }
.legend kbd {
font-family: ui-monospace, monospace;
font-size: 0.78em;
color: #e6edf3;
background: #11161d;
border: 1px solid #2a3441;
border-bottom-width: 2px;
border-radius: 3px;
padding: 0.05em 0.4em;
}
</style>
</head>
<body>
<div class="panel hud">
<div><span class="label">fps</span><span class="value" id="fps">--</span></div>
</div>
<div class="panel legend">
A <strong>single cube</strong>, spawned with the high-level <code>Entity</code> builder
from the Getting started guide. Drag to orbit · scroll to zoom.
</div>
<script type="module" src="./src/main.js"></script>
</body>
</html>
{
"name": "@meep-examples/simple-cube",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "A single cube — the Getting started guide made real.",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@woosh/meep-engine": "2.158.0",
"three": "0.136.0"
},
"devDependencies": {
"@rollup/plugin-strip": "^3.0.4",
"vite": "^8.0.13"
}
}
# simple-cube
The Meep "hello world" — a single cube, spawned with the same fluent `Entity` builder the [Getting started guide](/docs/getting-started/) opens with. `EngineHarness` wires up the renderer, an orbital camera, and the default sun + ambient lights for us, so there's something to actually look at.
## Run locally
```bash
npm install
npm run dev
```
## Build
```bash
npm run build
```
Output goes to `../../public/examples/simple-cube/demo.html`.
## What this demonstrates
- `EngineHarness.bootstrap` + `EngineHarness.buildBasics` — the smallest possible engine boot
- `new Entity().add(...).build(ecd)` — the fluent builder from the docs
- `Transform` (engine-provided) carrying `position`, `rotation`, `scale`
- `ShadedGeometry.from(threeGeometry, threeMaterial)` — the bridge between a Three.js mesh and Meep's batched renderer
import { defineConfig } from "vite";
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { resolve, dirname } from "node:path";
import strip from "@rollup/plugin-strip";
const __dirname = dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
{
// Copy the committed source thumbnail into the generated gallery folder.
// public/examples/<id>/ is build output (gitignored); thumbnail.png is
// kept in source here and copied through on every build so the gallery
// (src/data/examples.ts) can resolve it.
name: "copy-thumbnail",
apply: "build",
closeBundle() {
const thumb = resolve(__dirname, "thumbnail.png");
const dst = resolve(__dirname, "../../public/examples/simple-cube");
if (existsSync(thumb)) {
mkdirSync(dst, { recursive: true });
copyFileSync(thumb, resolve(dst, "thumbnail.png"));
}
},
},
],
base: "./",
build: {
outDir: resolve(__dirname, "../../public/examples/simple-cube"),
emptyOutDir: false,
rollupOptions: {
input: resolve(__dirname, "demo.html"),
plugins: [
{
// this will remove all assert statements from the production build
...strip(),
apply: 'build'
}
],
},
target: "es2022",
},
});