Sculptcore

Sculptcore is a new engine for digital sculpting systems. It’s meant to be integrated into a host 3D digital content creator (DCC); currently it supports Blender and a small DCC app I wrote for research purposes. The goal is to have a sculpting system that follows you to whichever app you do your work in.

Feature Highlight

Sculptcore aims to be a feature-complete mesh sculpting system; it supports:

  • Dynamic topology (similar to Blender’s DynTopo or ZBrush’s Sculptris).
  • Catmull-Clark multires.
  • A domain-specific language for creating brushes that can be run on both the CPU and the GPU.
  • Vector displacement maps (including PTex).
  • Sculpt layers (in object space).
  • Boundary constraint system to preserve e.g. polygroup boundaries, uv charts boundaries, hard edges, etc.
  • UV reprojection during smoothing to prevent distortion.
  • WebGPU support.

What makes this hard?

Digital sculpting is both technically difficult and hard on software organizations. The sheer amount of memory sculpting systems must process puts severe limitations on code structure–and worse, these constraints vary between different data backends. They also tend to cross-cut an organization’s boundaries in uncomfortable ways.

Data Backends

A purely mesh-based sculpting system needs to support at least three fundamental workflows that each need their own tailored data structure:

  • Dynamic remeshing (e.g. Blender’s DynTopo, ZBrush’s SculptTris).
  • Multiresolution Catmul-Clark subdivision surfaces.
  • Vector displacement maps: these come in two variants, UV-mapped textures and PTex (the latter is how Blender stores multires data).

It’s also important to be able to modify mesh data directly on the GPU in certain key cases (mostly sculpt tools that deform the entire mesh, e.g. Kelvinlet deformers, cloth sim, etc).

Sculptcore’s Design

Meshes

Sculptcore uses a paged struct-of-arrays boundary-representation (BREP) to represent meshes. Topological pointers (e.g. linked list of edges around vertices) use 32-bit integer attributes on the mesh. This structure is highly flexible and can be tailored into different mesh structures:

  • Full BREP suitable for dynamic remeshing
  • Ligher BREP suitable for fast processing and multires.

In the future the underlying paged attribute system could be used to also implement e.g. pure triangle meshes.

Defragmentation

The mesh structure can be incrementally defragmented to increase spatial locality. This is done to combat the tendency of dynamic topology to massively decrease CPU cache coherency of the mesh.

Remeshing

Sculptcore supports dynamic remeshing similar to Blender’s DynTopo or ZBrush’s SculpTris (technically this called a “Botsch-Kobbelt” type system, from the paper “A Remeshing Approach to Multiresolution Modeling”).

There is a boundary constraint system used to:

  • Keep Poly group boundaries smooth. ZBrush does this.
  • Keep ‘marked sharp’ edges sharp for hard edge sculpting.
  • Mark edges as ‘preserved’ so sculpting tools preserves their rough shape.
  • Preserve UV chart boundaries (no one currently does this).

Brushes

Sculptcore has a highly flexible brush system. Sculptcore provides its own set of brushes and the plugins that integrate Sculptcore into each host DCC can also provide their own brush implementations.

SBrush

Brushes are written using a small domain-specific language (similar to slang) to drive its brushes. They are compiled to C++ and GPU backends at compile time.

As I have written a fair number of compilers in my life I felt no shame at all in asking Claude Code to design a domain-specific language suitable for sculpting with auto-diff support. Since this is not that different from the design requirements behind Slang I may switch to it in the future.

Currently sbrushc has backends for C++, WGSL, and SPIR-V. There is also some support for apple Metal via cross-compilation of the generated WGSL scripts.

Brush Programs

There is some support for stacking brushes together into brush ‘programs’, though this is currently a bit primitive.

Brush Properties

Brushes have properties. Sculptcore has a number of built-in properties (e.g. radius, strength, color) and plugins can register their own.

Vector Displacement Maps

Vector displacement maps come in two flavors, UV-mapped textures and PTex (which is applied over subdivision surfaces). PTex is deliberately kept completely separate from multires; Blender’s attempt to merge them has led to numerical instability (the infamous Blender multires spikes) that is mathematically impossible to fix. Sculptcore is more in line with the rest of the industry that separates object-space multires editing from tangent-space PTex textures.

Sculpt Layers

Sculptcore has support for sculpt layers (these are always applied before any vertex displacement maps). These are in object space (the industry standard).

Porting JS to TypeScript with Claude Code

By default Claude Code struggles to port JavaScript to TypeScript. Often the types it generates are filled with usages of any or inline cast like x as unknown as { bleh(): void }. In the worst cases it abandons entire files and inserts @ts-nocheck disabling typechecking for those files altogether.

The problem is Claude Code prioritizes a passing typecheck after every single edit. It treats a successful tsc run as a strict requirement; if a cast is the quickest way to resolve an immediate error, it will use one. Each edit is locally successful but the overall result is poor. This leads to a codebase cluttered with any and as unknown as.

This is the wrong approach. Effective typing requires knowledge about the shared architecture of a program not just reacting to typecheck errors from tsc.

Two Pass Workflow

The solution is straightforward: port the codebase in two passes and prevent Claude from running tsc during the first pass. By removing the immediate requirement for a successful typecheck we force Claude Code to focus on designing types from its own reasoning rather then simply silencing errors.

  • Pass 1: Add types: Claude is forced to add type annotations without invoking tsc, forcing it to build the types entirely from its own reasoning and the context window it creates by scanning the codebase.
  • Pass 1.5: Coherence Review: Claude reviews the ported files for global type coherency. Typechecking is still disabling.
  • Pass 2: Resolving Errors (Typechecker Enabled). Only now does Claude run tsc and resolve (any remaining) type errors.

Hard Limits

It’s also useful to set a few hard limits for claude code:

  • No use of unknown or any during phase 1 and 1.5 at all.
  • any limited to X usages across the entire codebase for pass 2.
  • Enable strictNullChecks from the beginning.

Establish a Testing Baseline

It’s important to make sure the old JS codebase has adequate unit tests. You can have Claude Code generate these for you, e.g.: First, review the core logic in [target file/directory] and generate a comprehensive suite of unit tests for the existing JavaScript. Ensure the tests pass. Once established, use the js-to-ts-port skill to port the code to TypeScript, using the test suite to verify the logic remains intact.


I have packaged this methodology as a Claude Code skill: js-to-ts-port.

CAD Curve Basics

A boundary representation (BREP) is a data structure used to represent all things topological: 3D meshes, 2d vector curves, volumes, etc. This post is about what makes a good boundary representation for CAD, tailored to support complex curves in a mathematically intutive (and correct) way.

What Is A Curve

What is a curve? A curve is a multidimensional function that is parameterized with one variable.

fx(t),fy(t)fx(t), fy(t)
Continue reading CAD Curve Basics

Simple GPU Vector Graphics With Spatial Trees

Rendering 2D vector graphics on the GPU is a notoriously vexing problem. The most popular publicly available methods are either slow (stencil and cover) or highly memory intensive (cached prerendered buffers, e.g. most SVG renderers). In truth none of this is necessary; rendering vector graphics on the GPU is not hard if you use, as the saying goes, this one neat trick.

Continue reading Simple GPU Vector Graphics With Spatial Trees

Clothoid Splines

A century ago engineers had very good and robust means of drafting 2D curves using specialized spline sets and curve templates (e.g. French curves).  But these proved difficult to replicate in early computers; the need for fast, simple algorithms drove first Bezier, and then de Boor to embrace the idea of polynomial-based splines.

Creating a spline from independent polynomial functions along the x/y/z axes has a number of problems.  Such splines cannot be represented in arc length form and, more importantly, controlling their curvature functions is not easy.   Polynomial splines are computationally simple but mathematically complex.  

Continue reading Clothoid Splines