Objects
Forge scenes are built from object literals, transforms, CSG composition, and custom SDF definitions.
Built-in Primitives
Current built-ins include:
SphereBoxCylinderTorusExtrudePolygonRoom- semantic skeleton assets such as
Robot
Custom Forge SDF assets can also be parameterized and instantiated with per-instance overrides, just like materials:
import "SoftBlob";
let blob = SoftBlob {
radius: 1.4,
warp_frequency: 6.0,
warp_amount: 0.22
};
The same mechanism also works for more semantic library objects:
import "Cupboard";
import "Lamp";
import "Table";
let cupboard = Cupboard {
width: 1.8,
height: 2.2,
depth: 0.65,
open_amount: 0.35
};
let table = Table {
width: 1.8,
depth: 0.9,
height: 0.76,
top_thickness: 0.08,
leg_radius: 0.05
};
let lamp = Lamp {
shade_material: Lambert { color: #efe2cf },
bulb_material: Lambert {
color: #fff3dd,
emission_color: #fff1d6,
emission_strength: 5.0
}
};
Custom object assets can also define their own default anchors inside the asset itself, so instances inherit meaningful placement points like TopSurface or FrontCenter automatically. Table is a good early adjective-style asset because it exposes obvious shape parameters like width, depth, height, tabletop thickness, and leg radius. Lowered semantic assets can also expose named part material slots like top_material, leg_material, door_material, or bulb_material.
Forge also supports part-oriented assignment syntax for these semantic assets:
table.top.material = Lambert { color: #7a4c35 };
table.legs.material = Metal { color: #2b3138, roughness: 0.22 };
cupboard.door.material = Lambert { color: #d7c4a8 };
lamp.bulb.material = Lambert {
color: #fff3dd,
emission_color: #fff1d6,
emission_strength: 5.0
};
Today this path is focused on material assignment for named semantic parts. It is the first slice of future part access like table.legs.material = ... and later deeper part editing.
The same named parts can also be used as layout targets, which keeps scene code semantic instead of forcing manual offsets from whole-object pivots.
For example, a room scene can place both a cupboard and table into a corner, then attach another object to the table's own anchor:
import "Cupboard";
import "Lamp";
import "Table";
let room = Room {
width: 8.0,
height: 4.0,
depth: 8.0,
wall_thickness: 0.18
};
var cupboard = Cupboard {
width: 1.8,
height: 2.2,
depth: 0.62,
open_amount: 0.2
}
.attach(room, BackRightCorner)
.offset_x(-0.12)
.offset_z(0.12);
var table = Table {
width: 1.7,
depth: 0.9,
height: 0.78
}
.attach(room, BackRightCorner)
.offset_x(-2.1)
.offset_z(1.35);
var lamp = Lamp {
bulb_material: Lambert {
color: #fff3dd,
emission_color: #fff1d6,
emission_strength: 5.0
}
}
.attach(cupboard.body, Top, Bottom);
var vase = Sphere {
radius: 0.18
}
.attach(table.top, Top)
.offset_x(-0.35);
Forge also has a custom-modeling layer for symmetry, repetition, clipping, carved noise, primitive distance intrinsics, and programmable SDF hooks.
See Modeling for the full helper list and examples.
Orientation-aware layout can then aim an asset toward another object or anchor:
var wall_lamp = Lamp {}
.attach(cupboard.body, Top, Bottom)
.face_to(table.top);
Skeleton Assets
Skeletons now have their own page.
See Skeletons for:
skeletondefinitions- joints and bones
bind(...)- built-in
Robot/RobotBody
Supported fields today:
Sphere
radiusorrshellpos.x,pos.y,pos.zor legacyx,y,zrot.x,rot.y,rot.zor legacyrot_x,rot_y,rot_zmaterial
let ball = Sphere {
radius: 1.0,
shell: 0.04,
material: Metal {
color: #ebc757,
roughness: 0.18
}
};
Box
size: vec3(...)roundfor edge rounding that preserves the intended overall sizeshellfor hollowing the form inward while keeping the outer dimensionspos.*rot.*material
let block = Box {
size: vec3(1.2, 0.8, 1.2),
round: 0.08,
shell: 0.04
};
Cylinder
radiusorrheightorhroundshellpos.*rot.*material
let column = Cylinder {
radius: 0.5,
height: 2.4,
round: 0.04,
shell: 0.03
};
Torus
major_radiusorRminor_radiusorrpos.*rot.*material
let ring = Torus {
major_radius: 1.0,
minor_radius: 0.2
};
ExtrudePolygon
sidesornwith a minimum of3radiusorrheightorhroundshellpos.*rot.*material
This is a regular N-gon extruded along the Y axis.
let hex = ExtrudePolygon {
sides: 6,
radius: 0.8,
height: 0.35,
round: 0.03,
shell: 0.02
};
Room
widthheightdepthwall_thicknessfloor_materialwall_materialback_wall_materialfront_wall_materialleft_wall_materialright_wall_materialceiling_materialshow_floorshow_back_wallshow_front_wallshow_left_wallshow_right_wallshow_ceilingpos.*rot.*
Room is a semantic built-in object that expands to a floor and optional walls/ceiling with separate material slots.
let room = Room {
width: 8.0,
height: 4.0,
depth: 8.0,
wall_thickness: 0.18,
floor_material: CheckerFloor {},
wall_material: Lambert { color: #f2efe8 },
show_back_wall: 1.0,
show_right_wall: 1.0,
show_front_wall: 0.0,
show_left_wall: 0.0,
show_ceiling: 0.0
};
Example:
var sphere = Sphere {
radius: 1.0
};
sphere.pos.y = 0.6;
sphere.rot.z = 12.0;
let scene = sphere;
Transforms are currently driven with nested properties like pos.x, pos.y, rot.x, and rot.z. For relational placement like “on top of floor” or “right of sphere”, see the layout section in Language.
For boolean composition, see the dedicated Booleans page.
Custom SDFs
For geometry that goes beyond the built-ins, define a Forge SDF with a single distance contract:
sdf SoftBlob {
fn bounds() {
return vec3(1.2, 1.2, 1.1);
}
fn warp(p) {
return vec3(p.x, p.y + sin(p.x * 4.0) * 0.16, p.z);
}
fn distance(p) {
let q = warp(p);
return length(q) - 1.0;
}
};
let scene = SoftBlob {};
Rules:
fn distance(p)is requiredpis evaluated in local/object space- helper functions can be reused inside the SDF block
fn bounds()is optional but strongly recommended- optional
fn domain(p)can transform point space beforedistance(p) - optional
fn distance_post(d, p)can modify the computed distance afterward
For programmable modifier patterns and direct object-level domain / distance_post hooks, see Modeling.
Why bounds() Matters
Without bounds(), custom SDFs fall back to a very conservative bound. That keeps rendering correct, but acceleration gets much worse and scenes can become noticeably slower.
Current bounds() behavior:
- it returns a local half-extent as
vec3(...), or a numeric radius - the renderer currently turns that into a conservative bounding sphere radius
- that is already much better than the old giant fallback bound
So for most custom shapes, add bounds() early even if it is only approximate.
Current Limits
- custom SDFs currently expose
distance(p)and optionalbounds() - exact rotated bounds are not computed yet; the current implementation uses a conservative radius from
bounds() - custom SDF distance code now uses the VM/JIT path for the supported numeric and vec3 subset, with interpreter fallback for the rest
Imports and Reuse
Objects and SDFs can be shared through imports:
import "./shared/blob.ft";
import "SoftBlob" as blob;
Use relative imports for project-local assets and built-in library imports for reusable bundled definitions.