Skip to content

Lywald/Wireframed.js

Repository files navigation

Wireframed.js

A node-graph wireframe processing suite for the web. Load any 3D model. Chain visual effects. Generate infinite procedural variations.

Download for Windows

Three.js JavaScript No Build Step License


Wireframed.js in action

Wireframed.js interface The full node-graph interface — drag nodes, connect ports, tweak parameters in real time.

What is it?

Wireframed.js is a visual node-graph engine built entirely in the browser with Three.js and vanilla JavaScript. Think Houdini or Blender's geometry nodes — but zero installation, zero build step, and tuned specifically for generating stunning wireframe art from any 3D model.

You connect nodes in a directed graph. The pipeline re-evaluates automatically whenever anything changes, streaming the result into a live Three.js scene. Every node exposes real-time controls. Save and load the whole graph as a compact JSON preset.

Key properties:

  • Open index.html — that's it. No npm install, no bundler, no server required.
  • Import any GLTF / GLB / OBJ / FBX file directly from your disk.
  • 30+ processing nodes across 6 categories.
  • wireframed-runtime.js lets you embed any saved preset into your own Three.js project in ~15 lines of code.

Gallery

Preview 01 Preview 02 Preview 03
Wireframe Preview Deep Randomize Composite

Deep Randomize — one model, infinite looks

Hit Deep Randomize and the engine swaps to a new graph topology drawn from 20 curated layouts, then randomizes every parameter. Each click is a completely different aesthetic.

Deep 2 Deep 3 Deep 4
Deep 5 Deep 6 Deep 7
Deep 8

Quick Start

Option A — Windows portable .exe (recommended, no server needed)

Build once, distribute anywhere. The .exe bundles everything — no install, no browser, no server.

npm install          # downloads Electron (~200 MB, one time only)
npm run dist         # builds dist/Wireframed*.exe

Then double-click dist/Wireframed*.exe. Done.

Requires Node.js on your machine to build. The resulting .exe requires nothing from the end user.


Option B — Local server (no npm required)

Windows

launch.bat          ← double-click this

Mac / Linux

chmod +x launch.sh
./launch.sh

Both launchers try Node.js first (node server.js), then fall back to Python's built-in server. Either way, your default browser opens at http://localhost:8080 automatically.

Once the browser is open:

  1. Use Load Model in the top bar to import a .glb, .gltf, .obj, or .fbx file.
  2. Drag nodes from the left panel onto the canvas and connect their ports.
  3. Select a node to edit its parameters in the right panel.
  4. Hit Save Preset to export your graph as a .json file.

Manual alternatives (if you prefer):

node server.js                  # Node.js — opens browser automatically
python -m http.server 8080      # Python 3 — then open http://localhost:8080
npx serve .                     # if you have npm available

Interface Overview

Area Description
Top bar Load Model · Save/Load Preset · Render HD · Superficial Randomize · Deep Randomize · GLB path input
Left panel Node library, organised by category. Drag any node onto the canvas.
Centre canvas Node graph editor. Drag nodes, draw connections by clicking ports, orbit the 3D viewport with the mouse.
Right panel Parameters for the selected node — sliders, colour pickers, dropdowns, live-updating.
Preset pills One-click built-in presets: CyberCrystal, OrganicNeural, FractalVoid, ElegantMinimal.

Randomize buttons

Button Effect
Superficial Randomize Randomizes all numeric and colour parameters on the current graph, keeping the node topology unchanged.
Deep Randomize Picks a new graph topology from 20 curated layouts, rebuilds the graph, then randomizes all parameters. Your loaded model is preserved and re-injected automatically.

Node Library

Base

Node Type key Description
Model Loader base/ModelLoader Load GLTF/GLB/OBJ/FBX from file or URL. Auto-centers and scales.
Wireframe base/Wireframe Extracts edges + vertices into a glowing line mesh. Custom gradient colours, noise displacement, vertex dots.
Merge base/Merge Combines two geometry streams into one.
Transform base/Transform Translate, rotate, scale, with optional animation.
Output base/Output Sink node — writes geometry and post-passes into the Three.js scene. Every graph must end here.

Fractal

Node Type key Description
L-System fractal/LSystem Parametric L-system turtle-graphics tree. Axiom, rules, depth, angle, step all editable.
Mandelbulb fractal/Mandelbulb 3D Mandelbulb raymarched to a triangle mesh.
Sierpinski fractal/Sierpinski Recursive Sierpinski tetrahedron.
Koch fractal/Koch Koch snowflake, lifted into 3D.
Dragon Curve fractal/DragonCurve Folded paper-dragon iterated fractal as a line geometry.

Mesh Generation

Node Type key Description
Tube meshgen/Tube Sweeps input edges into tapered organic tubes.
Ribbon meshgen/Ribbon Extrudes edges into flat ribbon strips with twist.
Voronoi meshgen/Voronoi Voronoi cell mesh seeded from input geometry vertices.
Metaballs meshgen/Metaballs Isosurface metaballs positioned at input vertices.
Particle Swarm meshgen/ParticleSwarm GPU-instanced particles swarming around input geometry.
Instanced Geo meshgen/InstancedGeo Places a primitive instance at every input vertex.

Aesthetic

Node Type key Description
Glow aesthetic/Glow Unreal Bloom post-processing pass. Strength, radius, threshold.
Chromatic Aberration aesthetic/ChromaticAber RGB channel split with optional radial vignette.
Noise aesthetic/Noise Perlin / Simplex / Worley animated vertex displacement.
Distortion aesthetic/Distortion Screen-space ripple, wave, or swirl distortion.
Gradient aesthetic/Gradient Maps a 3-stop colour gradient onto geometry by axis.
Line Bevel aesthetic/LineBevel Thickens line geometry into bevelled tubes.

Animation

Node Type key Description
Verlet Physics animation/VerletPhysics Cloth-like Verlet simulation on input edges. Gravity, wind, damping, stiffness.
Growth animation/Growth Animated branch-growth algorithm that progressively reveals geometry.
Subdivision animation/Subdivision Adaptive mesh subdivision with noise jitter.
Attractor animation/Attractor Strange attractor trail (Lorenz, Halvorsen, Thomas, etc.).
Camera Path animation/CameraPath Procedural orbital camera animation with configurable radius and duration.

Export

Node Type key Description
Image Export export/Image Captures the current frame at any resolution and downloads as PNG.
GLTF Export export/GLTF Exports the processed scene as a GLTF file.

Built-in Presets

Preset Description
CyberCrystal Electric cyan wireframe with Unreal Bloom glow and RGB chromatic split.
OrganicNeural Pulsing neural mesh — Verlet physics on tube-wrapped edges, warm amber tones.
FractalVoid Recursive cosmic horror in deep violet — Mandelbulb merged with an L-system, maximum chaos.
ElegantMinimal Museum-quality monochrome wireframe with slow orbital camera. Clean, restrained, hypnotic.

Embedding in Your Own Project

Wireframed.js ships with wireframed-runtime.js — a self-contained headless engine that lets you load any saved preset into an existing Three.js scene. No UI, no dependencies beyond Three.js itself.

The sampleProject/ boilerplate

sampleProject/index.html is the minimal reference integration. Below is the full file with every line explained:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Wireframed Sample</title>

  <!--
    Keep the canvas flush with the window edges and set a dark background
    that matches the wireframe aesthetic.
  -->
  <style>body { margin: 0; overflow: hidden; background: #05080f }</style>

  <!--
    Import map — tells the browser where to resolve bare specifiers like
    'three' and 'three/addons/'. This is how Wireframed.js loads Three.js
    without a bundler. Pin the version to r168 to match the engine.
  -->
  <script type="importmap">
    {
      "imports": {
        "three":          "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js",
        "three/addons/":  "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
<script type="module">

// ── Imports ───────────────────────────────────────────────────────────────────
// Standard Three.js scene primitives.
import * as THREE           from 'three'

// OrbitControls — lets the user rotate/zoom/pan with the mouse.
import { OrbitControls }    from 'three/addons/controls/OrbitControls.js'

// GLTFLoader — loads .glb / .gltf files. Swap for OBJLoader or FBXLoader
// if your model uses a different format.
import { GLTFLoader }       from 'three/addons/loaders/GLTFLoader.js'

// EffectComposer pipeline — required for Wireframed's post-processing nodes
// (Glow, ChromaticAber, Distortion) to work. The composer wraps the renderer
// and runs the registered passes in sequence.
import { EffectComposer }   from 'three/addons/postprocessing/EffectComposer.js'
import { RenderPass }       from 'three/addons/postprocessing/RenderPass.js'
import { OutputPass }       from 'three/addons/postprocessing/OutputPass.js'

// WireframedRuntime — the headless Wireframed engine. No UI, just the node
// graph execution pipeline. Import path is relative to this HTML file.
import { WireframedRuntime } from '../wireframed-runtime.js'


// ── Renderer ──────────────────────────────────────────────────────────────────
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(innerWidth, innerHeight)
renderer.setPixelRatio(devicePixelRatio)

// ACESFilmic tone mapping gives bloom and HDR colours a cinematic look —
// strongly recommended when using the Glow node.
renderer.toneMapping = THREE.ACESFilmicToneMapping

document.body.appendChild(renderer.domElement)


// ── Scene & Camera ────────────────────────────────────────────────────────────
const scene  = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 1000)
camera.position.set(0, 0.5, 2)   // pull back slightly so the model is in frame

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true     // smooth inertia on mouse release


// ── Post-processing composer ──────────────────────────────────────────────────
// Build the composer with exactly two passes before handing it to Wireframed:
//   1. RenderPass  — renders the scene normally
//   2. OutputPass  — tone-maps and writes to the screen
//
// WireframedRuntime will inject its own passes (Bloom, ChromaticAber, etc.)
// between these two whenever the graph contains post-processing nodes.
const composer = new EffectComposer(renderer)
composer.addPass(new RenderPass(scene, camera))
composer.addPass(new OutputPass())   // ← Wireframed inserts its passes before this


// ── Responsive resize ─────────────────────────────────────────────────────────
window.addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(innerWidth, innerHeight)
  composer.setSize(innerWidth, innerHeight)
})


// ── Wireframed runtime ────────────────────────────────────────────────────────
// Create a runtime instance, passing your renderer/scene/camera/composer.
// Each instance owns its own independent node graph — you can create multiple
// runtimes in the same page for different objects.
const wf = new WireframedRuntime({ renderer, scene, camera, composer })

// Load a preset JSON file (the graph topology + all node parameters).
// You can save presets from the Wireframed.js editor via Save Preset.
// loadPreset() accepts a URL string or a plain JSON object.
await wf.loadPreset('./sampleProject/testPreset.wf.json')

// Load your model, then tell the runtime which object to process.
// applyToModel() finds the ModelLoader node in the preset and feeds it
// the geometry extracted from the GLTF scene. The node graph re-evaluates
// immediately and the result appears in the scene.
const gltf = await new GLTFLoader().loadAsync('./sampleProject/testModel.glb')
wf.applyToModel(gltf.scene)

// Other apply helpers:
//   wf.applyToScene()           — processes everything currently in the scene
//   wf.applyToModels([a, b])    — maps an array of models to multiple ModelLoader nodes


// ── Animation loop ────────────────────────────────────────────────────────────
renderer.setAnimationLoop(() => {
  controls.update()   // required for damping to work

  // wf.tick() advances all time-based nodes: Verlet physics, Growth algorithm,
  // Attractor trails, Camera Path animation, animated noise, etc.
  // Call it every frame. Optionally pass a delta time in seconds: wf.tick(dt)
  wf.tick()

  // Render through the composer, not renderer.render(), so that all
  // Wireframed post-processing passes are executed.
  composer.render()
})

</script>
</body>
</html>

testPreset.wf.json — the preset format

{
  "name": "testPreset",
  "version": "0.1.0",
  "nodes": [
    { "id": "n1", "type": "base/ModelLoader", "position": { "x": 60,  "y": 200 }, "params": {} },
    { "id": "n2", "type": "base/Wireframe",   "position": { "x": 340, "y": 200 },
      "params": { "colorA": "#00d4ff", "colorB": "#7b2fff", "opacity": 1.0, "glowWidth": 0.6 } },
    { "id": "n3", "type": "aesthetic/Glow",   "position": { "x": 340, "y": 380 },
      "params": { "strength": 1.5, "radius": 0.4, "threshold": 0.05 } },
    { "id": "n4", "type": "base/Output",      "position": { "x": 620, "y": 280 }, "params": {} }
  ],
  "connections": [
    { "id": "c1", "fromNodeId": "n1", "fromPort": "geometry", "toNodeId": "n2", "toPort": "geometry" },
    { "id": "c2", "fromNodeId": "n2", "fromPort": "geometry", "toNodeId": "n4", "toPort": "geometry" },
    { "id": "c3", "fromNodeId": "n3", "fromPort": "postpass", "toNodeId": "n4", "toPort": "postpass" }
  ]
}

A preset is a plain JSON file. Every node has an id, a type (matching the registry key), a position for the graph canvas, and a params object with its settings. Connections link fromNodeId/fromPorttoNodeId/toPort. The graph is evaluated in topological order — no cycles allowed.

Runtime API reference

const wf = new WireframedRuntime({ renderer, scene, camera, composer })

// Load a preset
await wf.loadPreset(urlOrJsonObject)    // from URL string or parsed JSON
wf.loadPresetFromString(jsonString)     // from a raw JSON string

// Inject geometry
wf.applyToModel(object3D)              // first ModelLoader node gets this model
wf.applyToModels([obj1, obj2])         // multiple models → multiple ModelLoader nodes in order
wf.applyToScene()                      // every mesh in the scene

// Animation
wf.tick(dt?)                           // advance by dt seconds (defaults to clock delta)

// Live parameter editing
wf.setParam('base/Wireframe', 'colorA', '#ff0000')   // by node type
wf.setParam('My Label', 'strength', 2.0)             // by node label

// Cleanup
wf.dispose()                           // removes scene objects, clears composer passes

Architecture

wireframedC/
├── index.html                  Entry point — importmap + UI mount
├── wireframed.js               App bootstrap: creates Engine, mounts UI panels
├── wireframed-runtime.js       Headless runtime for embedding in external projects
│
├── core/
│   ├── Engine.js               Three.js scene manager, render loop, post-pass chain
│   ├── NodeGraph.js            DAG: add/remove nodes, connect ports, dirty propagation
│   ├── Pipeline.js             Topological sort (Kahn's algorithm), node execution
│   ├── NodeRegistry.js         Central node type registry — maps type keys to classes
│   ├── PresetManager.js        Serialize / deserialize / download graph JSON
│   └── Store.js                Global pub/sub event bus + key-value state
│
├── nodes/
│   ├── BaseNode.js             Base class: paramDefs, inputPorts, outputPorts, execute()
│   ├── base/                   ModelLoader, Wireframe, Merge, Transform, Output
│   ├── fractal/                LSystem, Mandelbulb, Sierpinski, Koch, DragonCurve
│   ├── meshgen/                Tube, Ribbon, Voronoi, Metaballs, ParticleSwarm, InstancedGeo
│   ├── aesthetic/              Glow, ChromaticAber, Noise, Distortion, Gradient, LineBevel
│   ├── animation/              VerletPhysics, Growth, Subdivision, Attractor, CameraPath
│   └── export/                 Image, GLTF
│
├── ui/
│   ├── TopBar.js               Top toolbar, Deep/Superficial Randomize, preset pills
│   ├── LeftPanel.js            Categorised node library with drag-to-canvas
│   ├── NodeEditorCanvas.js     Canvas node editor, arc drawing, port hit-testing
│   ├── RightPanel.js           Property inspector for selected node
│   └── PropertyWidgets.js      Slider, colour picker, dropdown, text, action widgets
│
├── utils/
│   ├── LoaderUtils.js          Unified GLTF/OBJ/FBX loader with format detection
│   ├── GeometryUtils.js        Edge extraction, geometry merging, centering helpers
│   ├── MathUtils.js            seededRandom, smoothstep, lerp, noise functions
│   └── ColorUtils.js           Hex ↔ RGB, gradient sampling, palette generation
│
├── presets/
│   ├── CyberCrystal.json
│   ├── OrganicNeural.json
│   ├── FractalVoid.json
│   └── ElegantMinimal.json
│
└── sampleProject/
    ├── index.html              Minimal Three.js integration boilerplate
    ├── testPreset.wf.json      Example preset (ModelLoader → Wireframe → Glow)
    └── testModel.glb           Placeholder model (replace with your own)

How the pipeline works

  1. Dirty propagation — when a node's parameter changes, NodeGraph.markDirty(id) cascades forward through the DAG using BFS, marking every downstream node dirty.
  2. ExecutionPipeline.run() performs a topological sort (Kahn's algorithm) and calls node.execute(inputs, engine, dt) only on dirty nodes, in dependency order.
  3. Scene managementOutputNode receives the final geometry and calls engine.addToScene(). The engine maintains a nodeId → Object3D map; adding the same object reference twice is a no-op.
  4. Post-processing — nodes that produce a post-pass (Glow, ChromaticAber, Distortion) return Output.postpass(pass). OutputNode collects them and engine.setPostPassChain() splices them into the EffectComposer before OutputPass.

Ideas for Future Extensions

  1. GLSL shader node — an in-browser shader editor node (Monaco or CodeMirror) that lets you write custom vertex/fragment code and wire it into the pipeline.
  2. Audio-reactive parameter binding — map Web Audio API frequency bands to any node parameter in real time, turning the engine into a music visualiser.
  3. MP4 / image-sequence export — use the MediaRecorder API to capture the animation loop and export a video directly from the browser.
  4. Collaborative presets — a lightweight backend (Cloudflare Worker + KV) where users can share, browse, and remix community presets by URL.
  5. WebGPU compute path — offload heavy geometry generation (Mandelbulb, Voronoi, particle swarms) to WebGPU compute shaders for 10–100× faster evaluation on capable devices.

About

A node-graph wireframe processing suite for the web -- Load any 3D model. Chain visual effects. Generate infinite procedural variations.

Resources

Stars

Watchers

Forks

Packages