tmpvar.com wrench logo

POC / Isosurface Extraction

Marching Squares

Note: cases 5 (0101) and 10 (1010) are ambiguous, meaning we cannot know which which edges are connected by the corner states alone. The good news is that in 2D this is quite simple to resolve - choose which edges are connected based on the value at the center of the cell. The center value can be found via:

  • sampling - if you have a continuous function like an SDF
  • bilinear interpolation - see: /articles/linear-interpolation
  • computed by taking the average of the corner values

Lookup Table + Basic Usage

If you want to compute a series of continuous contours, you'll need to maintain a priority queue and process the edge connections in order. An example of this can be found in isosurface-extraction-2d.js in the CollectPolyLoops() function. The following is a basic example that collects an unordered array of line segments (segment soup if you will).

//    corners         edges
//
//    0     1           0
//     +---+          +---+
//     |   |        3 |   | 1
//     +---+          +---+
//    3     2           2
//

static const i32 edgeConnectionCounts[16] = {
  0, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0
};

// entry format: edgeStartIndex, endEndIndex, ...
static i32 edgeConnections[16][8] = {
  [-1, -1, -1, -1, -1, -1, -1, -1 ],
  [ 0,  3, -1, -1, -1, -1, -1, -1 ],
  [ 1,  0, -1, -1, -1, -1, -1, -1 ],
  [ 1,  3, -1, -1, -1, -1, -1, -1 ],
  [ 2,  1, -1, -1, -1, -1, -1, -1 ],
  [ 0,  1,  2,  3,  0,  3,  2,  1 ],
  [ 2,  0, -1, -1, -1, -1, -1, -1 ],
  [ 2,  3, -1, -1, -1, -1, -1, -1 ],
  [ 3,  2, -1, -1, -1, -1, -1, -1 ],
  [ 0,  2, -1, -1, -1, -1, -1, -1 ],
  [ 1,  0,  3,  2,  1,  2,  3,  0 ],
  [ 1,  2, -1, -1, -1, -1, -1, -1 ],
  [ 3,  1, -1, -1, -1, -1, -1, -1 ],
  [ 0,  1, -1, -1, -1, -1, -1, -1 ],
  [ 3,  0, -1, -1, -1, -1, -1, -1 ],
  [-1, -1, -1, -1, -1, -1, -1, -1 ],
};


void AddSegments(u32 cellCode) {
  const i32 segmentCount = edgeConnectionCounts[cellCode];

  if (segmentCount == 0) {
    return;
  }

  if (segmentCount == 1) {
    // Add a segment between the two edge indices
    PushSegment(
      edgeConnections[cellCode][0],
      edgeConnections[cellCode][1]
    )
    return;
  }

  Assert(
    (cellCode == 5 || cellCode == 10),
    "Only cases 5,10 should have multiple connections"
  );

  // Asymptote Intersection or SDF Sampling
  const f32 centerValue = GetCellCenterValue();
  if (IsNegative(centerValue)) {
    PushSegment(edgeConnections[cellCode][0], edgeConnections[cellCode][1]);
    PushSegment(edgeConnections[cellCode][2], edgeConnections[cellCode][3]);
  } else {
    PushSegment(edgeConnections[cellCode][4], edgeConnections[cellCode][5]);
    PushSegment(edgeConnections[cellCode][6], edgeConnections[cellCode][7]);
  }
}

Isosurface Extraction (2D)

Debug

draw boundary cell corner state
draw boundary cell edge state
draw loose edge vertices
draw boundary cells
draw grid
draw cell textual info
draw distance values on ambiguous cells
draw asymptote intersection on ambiguous cells
max loop collection steps
epsilon

Params

show timings
timings
isolevel
line search max steps
contour extraction approach
disambiguation approach
subdivide
cell diameter
max subdivision depth

Root Finding (2D)

Given a square cell with sampled values in the corners, find the zero-crossings along a line segment.

Debug

asymptotic decider bug hunt

Corner Values

c00
c10
c01
c11

Interpolated Isosurface Viz (3D)

Debug/Development

render step count
render axes
render corner labels
render all corners (disable occlusion culling)

Scene

000
100
010
110
001
101
011
111

Approach

max fixed steps:
min root:
max root:
max Newton-Raphson steps:
debug w/ fixed steps

This demo requires WebGPU - in other words, you should open this page in Chrome or Edge.