@trap_stevo/morphal
v0.0.5
Published
The flagship entity framework for building scalable, reusable, and visually stunning entities.
Maintainers
Keywords
Readme
🌌 @trap_stevo/morphal
The flagship entity framework for building scalable, reusable, and visually stunning entities.
Compose entities from parts, drive them with behaviors, style them with flexible materials, and scale with instancing and performance primitives.
🚀 Features
- 🧩 Declarative Entities – JSON-like specs for parts, transforms, collisions, and behaviors
- 🎨 Material Factories – Presets like
basic,emissive,pbr-normal,sprite, etc., with unlimited extra options - 🎭 Behavior System – Lifecycle-driven behaviors (
fadeIn,fadeOut,float,rotate,pulseEmissive, …) - 📦 Instancing Pools – Massive scenes via
InstancedSpritePoolandInstancedMeshPool - 🧭 Performance Toolkit – Distance culling, impostor swapping, and entity pooling
- 🧰 Anchors & Metadata – Built-in anchors (
tip,center, …) and collision accessors - 🌍 2D & 3D Unified – Mix HUD overlays (orthographic) with world-space entities (perspective)
⚙️ System Requirements
| Requirement | Version | | -------------- | ------------------------- | | Browser | Chrome ≥ 91, Firefox ≥ 90 | | JavaScript | ES2020+ | | npm | ≥ 9.x (recommended) | | OS | Windows, macOS, Linux |
🧩 Spec Reference
Entity Spec (Morphal.create(spec, ctx)) — Core Fields
| Key | Type | Description | Default |
| --------------- | -------- | ----------------------------------------- | ------------------ |
| name | string | Entity name | "morphal-entity" |
| orient | "view" \| "yaw" \| "none" | Camera alignment mode | undefined |
| zOffset | number | Layering priority (higher draws on top) | undefined |
| materialProps | object | Props applied to all root-level materials | {} |
| transform | object | { position?, rotation?, scale? } | {} |
| parts | array | List of part specs | [] |
| behaviors | array | List of behavior configs | [] |
transform structure:
position:{ x, y, z }rotation:{ x, y, z }(radians)scale:numberor{ x, y, z }
Part Spec — Common Fields
| Key | Type | Description | Default |
| --------------- | -------- | -------------------------------------- | ------------------------------------------------ |
| name | string | Unique per entity | auto |
| type | string | Registered part type | required |
| material | string | Material preset name | preset-specific |
| color | string | Base color | preset-specific |
| opacity | number | 0..1 | preset-specific |
| orient | "view" \| "yaw" \| "none" | Per-part camera alignment | undefined |
| zOffset | number | Layering priority for the part | undefined |
| transform | object | { position?, rotation?, scale? } | {} |
| behaviors | array | [{ type, ...options }] | [] |
| materialProps | object | Extra material overrides | {} |
| collision | object | { dim:2\|3, tags:[], idPrefix?, isStatic?, metadata? } | undefined |
| ...rest | any | Forwarded to the specific part factory | — |
Behavior Config — Common Fields
| Key | Type | Description | Default |
| ------------ | -------- | ------------------------- | ------------ |
| type | string | Behavior name | required |
| ...options | any | Behavior-specific options | — |
Included behaviors (examples):
fadeIn({ duration, fromScale })fadeOut({ duration, scaleTo })float({ axis, speed, amplitude })rotate({ axis, speed })pulseEmissive({ frequency, intensity })
📚 Methods Overview
Morphal (Core) — Methods
| Method | Description | Async |
| --------------------------------- | -------------------------------------------------------------------- | :---: |
| registerPart(name, factory) | Register a part factory | ❌ |
| registerBehavior(name, factory) | Register a behavior factory | ❌ |
| registerMaterial(name, factory) | Register a material factory | ❌ |
| getRegistry() | Access registries (parts, behaviors, materials) | ❌ |
| traverseMaterials(object, cb) | Visit all materials in a subtree | ❌ |
| setMaterialProps(object, props) | Assign props to all materials in a subtree | ❌ |
| makeMaterial(name, params, ctx) | Create a material from a registered factory | ❌ |
| create(spec, ctx) | Build an entity; returns { group, update, destroy, bus, findPart } | ❌ |
Entity — Methods
| Method | Description | Async |
| --------------------- | -------------------------------------- | :---: |
| group | Root node | — |
| update(time, delta) | Advance all behaviors | ❌ |
| destroy() | Dispose resources & detach from parent | ❌ |
| findPart(name) | Retrieve a child part by name | ❌ |
Collision Accessors (per-part)
part.userData.morphal.collision (if enabled in the part spec):
| Method | Description | Async |
| ------------------------------- | --------------------------------------------------------------------------- | :---: |
| snapshotMatrix() | Copy world matrix into a stable buffer | ❌ |
| getMatrixRef() | Get the stable Float32Array(16) matrix reference | ❌ |
| getWorldOBB() | Compute world OBB or small sphere | ❌ |
| forEachWorldTriangle(consume) | Stream all world-space triangles into consume(ax,ay,az,bx,by,bz,cx,cy,cz) | ❌ |
Performance Toolkit
| Method / Class | Description | Async |
| ---------------------------------------------------------------------------------------- | --------------------------------------- | :---: |
| Perf.culling.register({ node, maxDist, onCull?, setVisible? }) | Distance-based visibility control | ❌ |
| Perf.culling.unregister(entry) | Remove from culling set | ❌ |
| Perf.culling.update(camera) | Update all culling entries | ❌ |
| Perf.impostorSwitch.register({ node, camera, near, far, poolKey, poolTexture, scene }) | Swap far meshes for billboard instances | ❌ |
| Perf.impostorSwitch.unregister(rec) | Remove impostor entry | ❌ |
| Perf.impostorSwitch.update() | Evaluate swaps | ❌ |
| EntityPool({ create, warm }) | Simple pooling of entities | ❌ |
| EntityPool.acquire() | Acquire an entity from the pool | ❌ |
| EntityPool.release(entity) | Return entity to the pool | ❌ |
Instancing Pools
| Class / Method | Description | Async |
| --------------------------------------------------------------- | -------------------------------- | :---: |
| Instancing.pools | Registry of named pools | — |
| Instancing.get(key) | Get a pool by key | ❌ |
| Instancing.set(key, pool) | Register a pool | ❌ |
| Instancing.delete(key) | Dispose + unregister a pool | ❌ |
| new InstancedSpritePool({ key, capacity, texture, faceMode }) | Instanced sprites billboard pool | ❌ |
| pool.alloc({ x, y, z, sx, sy, opacity }) | Allocate an instance | ❌ |
| pool.freeId(id) | Free an instance by id | ❌ |
| pool.updateBillboards(camera) | Align billboards to camera | ❌ |
| pool.dispose() | Dispose pool resources | ❌ |
| new InstancedMeshPool({ key, capacity, geometry, material }) | Instanced mesh pool | ❌ |
| pool.alloc(matrix4) | Allocate an instance | ❌ |
| pool.update(id, matrix4) | Update instance transform | ❌ |
| pool.freeId(id) | Free an instance | ❌ |
| pool.dispose() | Dispose pool resources | ❌ |
🧱 Part Catalog (Built-ins)
vectorShape
| Option | Type | Description | Default |
| ----------------- | -------- | --------------------------------------------------------- | ---------- |
| svg | string | Inline SVG markup | "<svg/>" |
| curveSegments | number | Tessellation detail | 48 |
| common fields | — | name, material, transform, behaviors, collision | — |
billboardPlane
| Option | Type | Description | Default |
| --------------------------- | -------------------- | ---------------------------------------- | ----------- |
| src / map | string / Texture | Base texture | — |
| normalSrc/normalMap | string / Texture | Normal map | null |
| emissiveSrc/emissiveMap | string / Texture | Emissive mask | map |
| tint | string | Multiply tint color | "#ffffff" |
| emissiveColor | string | Emissive color | "#ffffff" |
| emissiveIntensity | number | Emissive strength | 1.0 |
| alphaTest | number | Alpha cutout threshold | 0.0 |
| parallaxDepth | number | Parallax depth effect | 0.0 |
| common fields | — | transform, behaviors, collision | — |
tentacle
| Option | Type | Description | Default |
| ----------- | -------- | ----------------------------------- | ------- |
| segments | number | Number of curve subdivisions (≥ 2) | 12 |
| length | number | Tentacle length | 1.2 |
| thickness | number | Tube radius | 0.05 |
| curvature | number | Sinusoidal curvature factor | 0.6 |
| noise | number | Noise factor for wavy displacement | 0.0 |
| common | — | color, material, behaviors, collision | — |
sprite
| Option | Type | Description | Default |
| ------------ | -------------------- | ----------------------- | --------- |
| src / map| string / Texture | Sprite texture | — |
| color | string | Tint color | "#fff" |
| opacity | number | Alpha | 1.0 |
| size | number | Uniform sprite size | 1.0 |
| common | — | transform, behaviors, collision | — |
rippleRing
| Option | Type | Description | Default |
| ------------ | -------- | ----------------------- | ------- |
| radius | number | Ring radius | 1.0 |
| thickness | number | Band thickness | 0.1 |
| color | string | Base color | "#ffffff" |
| opacity | number | Alpha | 0.5 |
| segments | number | Radial subdivisions | 32 |
| common | — | transform, behaviors, collision | — |
energyBlob
| Option | Type | Description | Default |
| ------------ | -------- | -------------------- | ------- |
| radius | number | Blob sphere radius | 0.32 |
| color | string | Blob emissive color | "#b38cff" |
| opacity | number | Alpha | 0.2 |
| common | — | material, transform, behaviors, collision | — |
gltf
| Option | Type | Description | Default |
| ------------ | -------- | ----------------------------- | ------- |
| src | string | Path to GLTF/GLB file | — |
| scale | number \| {x,y,z} | Scale factor(s) | 1.0 |
| center | boolean| Auto-center loaded model | false |
| common | — | transform, behaviors, collision | — |
🎨 Materials (Built-ins)
basic
| Option | Type | Description | Default |
| ------------- | --------- | ---------------------- | ----------- |
| color | string | Base color | "#ffffff" |
| emissive | string | Forces additive blend | null |
| opacity | number | Alpha | 1.0 |
| transparent | boolean | Transparency | true |
| additive | boolean | Additive blending | false |
emissive
| Option | Type | Description | Default |
| ----------- | -------- | -------------------- | ----------- |
| mode | string | "unlit" or "pbr" | "unlit" |
| color | string | Base color | "#000000" |
| emissive | string | Emissive color | "#ffffff" |
| intensity | number | Emissive intensity | 1.0 |
pbr-normal
| Option (subset) | Type | Description |
| ----------------| ---- | -------------------------------- |
| color, map | — | Base color & texture |
| emissive* | — | Emissive params |
| normalMap* | — | Normal mapping |
| roughness*/metalness* | — | PBR surface controls |
| aoMap* | — | Ambient occlusion |
| displacement* | — | Height mapping |
| envMap* | — | Environment reflections |
| transmission*/ior/thickness | — | Glass parameters |
| clearcoat*/sheen* | — | Coatings & fabric sheen |
📦 Installation
npm install @trap_stevo/morphal
---
## 🏗️ Quick Start (HTML)
```js
import { Morphal } from "@trap_stevo/morphal";
// Renderer bootstrap
const appEl = document.getElementById("app");
const renderer = new WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = SRGBColorSpace;
appEl.appendChild(renderer.domElement);
// 3D scene & camera
const scene = new Scene();
scene.background = new Color("#0b0b12");
const camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 500);
camera.position.set(0, 0.8, 3);
scene.add(camera);
scene.add(new HemisphereLight(0xffffff, 0x334466, 0.35));
// 2D HUD scene (orthographic)
const scene2D = new Scene();
const ortho = new OrthographicCamera();
scene2D.add(ortho);
function resizeOrtho() {
const w = window.innerWidth, h = window.innerHeight;
ortho.left = -w / 2;
ortho.right = w / 2;
ortho.top = h / 2;
ortho.bottom = -h / 2;
ortho.near = -1000;
ortho.far = 1000;
ortho.updateProjectionMatrix();
}
resizeOrtho();
// Responsive resize
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
resizeOrtho();
});
// Minimal entity
const entity = Morphal.create({
name: "demo",
orient: "view",
parts: [
{ type: "billboardPlane", src: "/flare.png", scale: 2 },
{ type: "tentacle", color: "#C655FF", segments: 12 }
],
behaviors: [
{ type: "fadeIn", duration: 1.5 },
{ type: "float", axis: "y", speed: 0.4 }
]
}, { parent: scene, camera });
// Basic loop
let last = performance.now() / 1000;
(function tick() {
const now = performance.now() / 1000;
const delta = now - last;
last = now;
entity.update(now, delta);
renderer.render(scene, camera);
requestAnimationFrame(tick);
})();🧪 Examples
1) Vector Shapes (SVG)
Morphal.create({
parts: [
{
type: "vectorShape",
svg: "<svg><circle cx='0' cy='0' r='0.6'/></svg>",
material: "basic",
color: "#a6b2ff",
opacity: 0.9,
transform: { scale: { x: 1.2, y: 1.2, z: 1 } },
zOffset: -1,
behaviors: [{ type: "pulseEmissive", frequency: 1.25, intensity: 0.25 }],
materialProps: { transparent: true, opacity: 0.9 },
collision: { dim: 3, tags: ["eye", "sclera"] }
}
]
}, { parent: scene, camera });2) Image Billboards
Morphal.create({
parts: [
{
type: "billboardPlane",
src: "/textures/flare.png",
scale: 1.25,
tint: "#9f67ff",
emissiveIntensity: 2.0,
zOffset: 2
}
]
}, { parent: scene, camera });3) Procedural Mesh Part
Morphal.registerPart("energyBlob", (cfg = {}, ctx) => {
const geometry = new SphereGeometry(cfg.radius ?? 0.32, 48, 48);
const material = Morphal.makeMaterial(
cfg.material || "emissive",
{ color: cfg.color || "#b38cff", opacity: 0.2, transparent: true }
);
const mesh = new Mesh(geometry, material);
mesh.userData.anchors = { center: mesh };
return mesh;
});4) 2D HUD Overlay (Orthographic)
const hudEntity = Morphal.create(
{ name: "hud", orient: "view", parts: [ /* vector parts… */ ] },
{ parent: scene2D, camera: ortho }
);5) Collision Export
function gatherCollidable(root) {
const out = [];
root.traverse(o => {
const c = o.userData?.morphal?.collision;
if (c?.snapshotMatrix) out.push(o);
});
return out;
}
const collidables = gatherCollidable(entity.group);
for (const part of collidables) {
const c = part.userData.morphal.collision;
c.snapshotMatrix();
const matrix = new Float32Array(c.getMatrixRef());
const obb = c.getWorldOBB();
const tris = [];
c.forEachWorldTriangle((ax, ay, az, bx, by, bz, cx, cy, cz) => {
tris.push(ax, ay, az, bx, by, bz, cx, cy, cz);
});
}📜 License
See License in LICENSE.md
✨ Morph, Render, Scale
From SVG paths to billboards to procedural meshes, Morphal gives you one unified way to define, manage, and optimize entities at any scale — in both 2D and 3D contexts.
