Skip to content
Konjure / spatial intelligence
Browse docs

Procedural geometry

Build bounded meshes, transform them as values, and compose a local hierarchy.

Mesh is a packed local surface: positions and normals are Tensor[f32] values, and indices are Tensor[u32] triangles. Its procedural dimensions and Vec3 transforms are Number values (the language’s f64 representation), then Rust checks the conversion into the packed f32 buffers. Rust owns construction, validation, normal generation and retained-allocation limits. A browser or native adapter receives the resulting geometry and frame; it does not implement another mesh language.

Make a mesh

Procedural methods start with Mesh {} and return Res[Mesh, DataError]. Dimensions are metres; resolution arguments must be valid integers. Use ? to propagate a rejected shape, or use match when the program can recover.

MethodArgumentsResult
sphere(radius, rings, segments)positive radius, two resolutionsUV sphere
torus(major, minor, rings, sides)positive radii, two resolutionsY-axis torus
cone(radius, height, segments)positive dimensions and resolutioncapped cone
grid(width, depth, columns, rows)positive XZ dimensions and resolutionupward-facing grid
lathe(profile, segments)Tensor[f32] shaped [N, 2] of radius/height pointsrevolved profile
suzanne()nonesupplied Suzanne mesh
decode_json(chunks)List[Str] containing SDK Mesh JSONchecked imported mesh
// First scene: move the cyan sphere by editing one number.
let floor = spawn(Mesh {}.grid(3, 3, 6, 6)?);
add(floor, Material {
  color: "#17152a",
  roughness: 0.9
});
let orb = spawn(
  Mesh {}.sphere(0.35, 12, 16)?.translated(Vec3 {
    x: 0.6,
    y: 0.35
  })?
);
add(orb, Material {
  color: "#7ee7ff",
  roughness: 0.18,
  emissive: 0.25
});
let ring = spawn(
  Mesh {}.torus(0.65, 0.06, 20, 8)?.rotated(Vec3 {
    x: 1.5707963267948966
  })?
);
add(ring, Material {
  color: "#ffcb72",
  roughness: 0.28
});

The Kreate examples use these exact methods. A mesh never bypasses its data contract: malformed dimensions, topology and over-budget allocations return a DataError rather than panicking or retaining a partial scene. One mesh is limited to 16,384 vertices and 98,304 indices; the spatial adapter also bounds the aggregate scene geometry.

Blender imports keep transforms, materials and hierarchy in main.kj, with mesh data in separate geometry_N modules. Those modules use decode_json to load positions, normals and indices as data. Each string chunk is limited to 16 KiB, and the combined JSON to 8 MiB; the same mesh and runtime limits still apply. The decoder does not read files, fetch URLs or execute source.

Transform or map it

Meshes are values. translated(Vec3), scaled(Vec3) and rotated(Vec3) each return a new checked mesh. Their vector is respectively metres, per-axis scale, and XYZ radians. Transformed normals are recomputed correctly; negative scale reverses winding.

mapped(func(Vec3) -> Res[Vec3, DataError]) calls a typed mapper once for every vertex, derives smooth normals after the mapping, and rejects the whole result if the mapper returns Err. This is a geometry operation with a bounded work cost, not a renderer shader hook.

// Wave pavilion: change the sine amplitude below to reshape the roof.
func wave(point: Vec3) -> Res[Vec3, DataError] {
  ret Ok(Vec3 {
    x: point.x,
    y: (point.x.sin()? * 0.28)?,
    z: point.z
  });
}
let roof = spawn(
  Mesh {}.grid(5, 3, 20, 12)?
    .mapped(wave)?
    .translated(Vec3 { y: 1.5 })?
);
add(roof, Material {
  color: "#7ee7ff",
  roughness: 0.25,
  emissive: 0.15
});
for x in [-2, -1, 0, 1, 2] {
  let post = spawn(
    Mesh {}.cone(0.08, 1.5, 10)?.translated(Vec3 {
      x: x,
      y: 0.75,
      z: -1.2
    })?
  );
  add(post, Material {
    color: "#27233e",
    roughness: 0.75
  });
}
let floor = spawn(Mesh {}.grid(5.5, 3.5, 8, 6)?);
add(floor, Material {
  color: "#17152a",
  roughness: 0.95
});

Compose local transforms

Attach Parent { entity: root } to make an entity’s Transform local to a parent. Rust resolves the entity reference; the spatial adapter rejects missing targets, self-parenting, cycles and excessive depth before producing the scene frame. An entity with no visible mesh can remain as a group when the hierarchy needs it.

SourceKonjure
let root = spawn(Transform { position: Vec3 { y: 1 } });
let child = spawn(Mesh {}.sphere(0.2, 8, 12)?);
add(child, Parent { entity: root });
add(child, Transform { position: Vec3 { x: -0.7 } });

The child’s authored transform remains local; the SDK composes it with the parent when evaluating the frame. See Spatial applications for materials, controls and physics, and open these scenes from Examples to change them in Kreate.