ORI-POP
MANSION’s personal art-making toolset and hyper-minimal creative-coding workspace in Rust.
Built around the tension between order and emergence, it draws on ori — fold as structure — and pop — points in space — to explore how discrete particles organize within invisible fields.
What it provides
- A Processing-style drawing API —
background(),stroke(),fill(),line(),rect(),ellipse(), etc. - GPU-backed rendering via wgpu (WebGPU standard) with configurable MSAA anti-aliasing.
- 2D transforms —
push(),pop(),translate(),rotate(),scale(). - Alpha transparency on all colors.
- A sketch-per-file workflow: each sketch is a standalone Rust binary.
Philosophy
Begin with force rather than image. Employ scalar gradients, attractors, and compression zones to generate geometric landscapes through density and flow — compositions that feel both constructed and discovered.
Installation
Prerequisites
- Rust (stable toolchain, 1.80+)
- A GPU that supports Vulkan, Metal, or DX12
Clone and build
git clone https://github.com/mmansion/ori-pop.git
cd ori-pop
cargo build
That’s it. All dependencies are managed through Cargo.toml.
Run a sketch
cargo run -p sketches --bin hello-ori-pop
Generate API docs
cargo doc --no-deps -p oripop-canvas --open
Your First Sketch
Sketch structure
Every sketch is a standalone Rust binary with two parts:
main()— configure the window (like Processing’ssetup()).draw()— called every frame.
use oripop_canvas::prelude::*;
fn main() {
size(800, 600);
title("my sketch");
smooth(4);
run(draw);
}
fn draw() {
background(20, 20, 30);
stroke(255, 200, 100);
stroke_weight(3.0);
line(100.0, 100.0, 700.0, 500.0);
}
Adding a new sketch
- Create
sketches/my-sketch.rswithfn main(). - Add a
[[bin]]entry insketches/Cargo.toml:
[[bin]]
name = "my-sketch"
path = "my-sketch.rs"
- Run it:
cargo run -p sketches --bin my-sketch
Coordinate system
- Origin is top-left (0, 0).
- x grows to the right.
- y grows downward.
- All values are in logical pixels (DPI-independent).
Colors & Background
All color values use the 0–255 range, matching Processing.
Background
Clear the canvas each frame:
#![allow(unused)]
fn main() {
background(30, 30, 40); // opaque RGB
background_a(30, 30, 40, 128); // with alpha
}
Stroke
Set the outline color for shapes and lines:
#![allow(unused)]
fn main() {
stroke(255, 100, 50); // opaque
stroke_a(255, 100, 50, 128); // semi-transparent
no_stroke(); // disable outlines
}
Fill
Set the interior color for shapes:
#![allow(unused)]
fn main() {
fill(60, 200, 120); // opaque
fill_a(60, 200, 120, 80); // semi-transparent
no_fill(); // disable fill (outlines only)
}
Stroke weight
Control line and outline thickness:
#![allow(unused)]
fn main() {
stroke_weight(3.0); // 3 logical pixels wide
}
Anti-aliasing
Configure MSAA sample count before run(). Valid values: 1 (off), 2, 4, 8.
#![allow(unused)]
fn main() {
smooth(4); // 4x MSAA (default)
}
Shapes & Primitives
All shapes respect the current fill and stroke settings.
Line
#![allow(unused)]
fn main() {
line(x1, y1, x2, y2);
}
Drawn with the current stroke color and weight. Ignored when stroke is disabled.
Point
#![allow(unused)]
fn main() {
point(x, y);
}
A small filled square at the given position, sized by stroke_weight.
Rectangle
#![allow(unused)]
fn main() {
rect(x, y, width, height);
}
Top-left corner at (x, y). Filled and/or stroked based on current settings.
Ellipse
#![allow(unused)]
fn main() {
ellipse(x, y, width, height);
}
Bounded by the rectangle at (x, y) with given dimensions. Use equal width and height for a circle.
Triangle
#![allow(unused)]
fn main() {
triangle(x1, y1, x2, y2, x3, y3);
}
Three vertices. Filled and/or stroked based on current settings.
Transforms
The 2D transform system works like Processing’s pushMatrix() / popMatrix(), but shortened to push() / pop().
Push & Pop
Save and restore the current transform:
#![allow(unused)]
fn main() {
push();
// ... transforms and drawing here ...
pop();
// back to the previous transform
}
Translate
Move the origin:
#![allow(unused)]
fn main() {
translate(dx, dy);
}
Rotate
Rotate around the current origin, in radians:
#![allow(unused)]
fn main() {
rotate(angle);
}
Positive angle rotates counter-clockwise. Use std::f32::consts::TAU for a full turn, FRAC_PI_2 for 90 degrees, etc.
Scale
Scale the coordinate system:
#![allow(unused)]
fn main() {
scale(sx, sy);
}
Order matters
Transforms apply in the order you call them. This is the same as Processing:
#![allow(unused)]
fn main() {
push();
translate(400.0, 300.0); // move origin to center
rotate(0.5); // rotate around center
rect(-25.0, -25.0, 50.0, 50.0); // draw at origin
pop();
}
Reset
The transform resets to identity at the start of each frame, so there is no carry-over between frames.
Roadmap
ori-pop began as a minimal creative-coding workspace. The longer ambition is a GPU-first generative design framework that serves both art-making and real fabrication — producing robotic toolpaths, 3D-printed forms, and CNC programs from the same generative models used to create visuals.
This page records the architectural direction being built toward, so that patterns established early stay consistent as the codebase grows.
Coordinate System — Z-Up Right-Handed ✓
The framework uses the CAD / robotics / 3D-printing standard: Z is up, XY is the build plane, right-handed orientation (ISO 80000-2, ROS, STEP, STL, Rhino, Grasshopper, FreeCAD, most fabrication toolchains).
X = right, Y = depth/forward, Z = up, XY = ground plane. The camera’s default
up vector is Vec3::Z. All mesh primitives (sphere, cube, plane) are
generated in this convention.
Live Inspector Panel ✓
A real-time egui inspector panel renders as a fourth GPU pass on top of every 3D frame. Press Tab to toggle it. Shows and edits camera, lighting, texture-generation parameters, and the current frame’s named scene objects.
Sketch-first evolution
The core stays a thin Processing-like toolkit. Richer mutators, editors, and
distribution experiments land in sketches first; successful patterns are
promoted into oripop-canvas (or future crates) with tests. See
ROADMAP.md §0b for
the full wording.
oripop-math — Geometry Without GPU
A new crate with no GPU dependency — no wgpu, no winit. It is the
foundational layer that every other crate depends on for shared geometric types:
Frame (coordinate frames for robot poses and workpiece datums), Mesh
(CPU-side vertex and index data), Ray, BoundingBox, Plane, and the Sdf
primitive tree described below.
Because it carries no GPU weight it can be tested headlessly on any machine, including CI, with ordinary Rust unit tests.
Signed Distance Fields
An SDF is a function that takes a point in space and returns a single number: how far that point is from the surface of a shape, with a sign that tells you whether you are inside or outside.
positive → outside the shape
zero → exactly on the surface
negative → inside the shape
Boolean operations — union, subtraction, intersection — reduce to arithmetic on
two distance values (min, max). No polygon clipping, no mesh repair, no
degenerate triangles. The shape is described as a tree of primitives and
combinators, evaluated per point.
Built-in primitives include Sphere, Box, Cylinder, Torus, and
Gyroid — a triply periodic minimal surface that is the dominant infill
structure in modern FDM and SLA printing, load-bearing in all directions.
Modifiers include Shell (hollow with a uniform wall thickness), Offset
(expand or contract the surface — useful for fit tolerances), Twist, and
Bend.
Surface-Aware Texture Generation
The current generative texture pipeline produces a flat 2D image that is applied
to a mesh by UV coordinates. The next step is texture generation that is
parameterized in the same (u, v) coordinate system as the surface itself.
A parametric surface is a function surface(u, v) → point in 3D. If the
texture is generated as a function of the same (u, v) — sharing the surface’s
own coordinate system — the alignment is mathematically guaranteed. The texture
is not projected onto the surface from outside; it grows from the same
parameterization that defines the geometry.
Three levels of alignment:
UV sampling (current). The compute shader generates a flat image without knowledge of the 3D shape. Distortion follows from the UV layout.
Surface-parameterized generation. The compute shader receives surface-specific data: profile curve, arc length reparameterization, principal curvatures. The pattern is generated as a function of surface parameters, not flat image space.
Simulation on the surface. Reaction-diffusion equations (Turing patterns, branching, cellular spots) run directly in UV space with diffusion that is isotropic in surface coordinates — the way biological surface patterns actually form.
For fabrication, the texture is not decoration. It encodes material or process information: a gradient that maps to multi-material print zones, a pattern that defines sparse versus dense infill tied to structural stress, a field that encodes CNC cut directions aligned to the dominant surface feature.
Physics Simulation
Two complementary backends:
Rigid body — Rapier (pure Rust, CPU) for collision
detection, joints, and constraints. Results visualized via oripop-3d each
frame.
Particles, soft body, cloth, fluid — Position-Based Dynamics on GPU compute. Each iteration is a compute dispatch; the output position buffer binds directly as a vertex buffer for rendering with no CPU readback.
Genetic and Evolutionary Optimization
The genome is a flat array of floats encoding SDF parameters, force field strengths, structural lattice parameters — any continuous design variable. The entire population is evaluated in a single GPU compute dispatch, one invocation per individual. The fitness function is a WGSL shader.
For fabrication objectives: minimize material volume, maximize stiffness-to-weight, enforce minimum wall thickness, penalize overhangs beyond the printable threshold — all evaluable per-voxel in the SDF volume.
Fabrication Output
- Watertight mesh extraction from SDF volumes via marching cubes on GPU.
- Overhang angle analysis — a printability heatmap computed from surface normals versus the Z-up build direction, visualized as a texture overlay.
- Layer slicing — plane-mesh intersection per layer.
- Export to STL, 3MF (preferred by Bambu, Prusa, Cura — supports materials, colors, and lattice metadata), and OBJ.
- G-code generation from slice data.
Crate Structure
oripop-math no GPU — pure Rust geometry kernel
├── oripop-geo computational geometry, some GPU compute
│ ├── oripop-evo genetic / evolutionary optimization
│ └── oripop-fab fabrication output
├── oripop-physics simulation
└── oripop-3d rendering and visualization
└── oripop-canvas 2D drawing API
oripop-3d depends on both oripop-canvas and oripop-math. oripop-canvas does
not depend on oripop-math yet. Higher-level crates depend on oripop-math but
not necessarily on each other.