flutter3d
Showcase Changelog 38 packages API reference

Debug drawing

since 0.7.0 Scene and geometry

Debug overlays expose the data behind a frame without changing the model. This page can draw the capsule's bounding box and normals, mark the world axes and light, and trace a small joint hierarchy.

Step 1: Keep the source mesh #

The capsule uses VertexLayout.skinned, so its buffer has the joint and weight attributes expected by the skinned vertex stage. It is uploaded with its source data, which is the default. Bounds can be read from the device mesh, but the normal overlay needs the original vertex positions and normals.

final MeshData bodyData = const CapsuleShape(
  radius: 0.58,
  height: 1.4,
  segments: 20,
  rings: 6,
).build(layout: VertexLayout.skinned);
final MeshNode body = MeshNode(
  DeviceMesh.upload(context.device, bodyData),
  Material(
    name: 'blue body',
    baseColor: Vector4(0.12, 0.42, 0.78, 1.0),
    roughness: 0.48,
  ),
  name: 'inspected capsule',
)..setPosition(0.0, 1.25, 0.0);

Step 2: Attach a skeleton #

Skeleton debug drawing follows ordinary SceneNode joints. The example links three joints, records their inverse bind matrices, and assigns the resulting Skeleton to the mesh. The overlay will draw a small bone between parents and children, plus a cross at a leaf joint.

final SceneNode hip = SceneNode(name: 'hip')..setPosition(0.0, 0.45, 0.0);
final SceneNode chest = SceneNode(name: 'chest')
  ..setPosition(0.0, 0.8, 0.0);
final SceneNode head = SceneNode(name: 'head')
  ..setPosition(0.25, 0.72, 0.0);
hip.add(chest);
chest.add(head);
_rig = Skeleton(
  name: 'three-joint rig',
  joints: <SceneNode>[hip, chest, head],
  inverseBindMatrices: <Matrix4>[
    Matrix4.copy(hip.worldMatrix)..invert(),
    Matrix4.copy(chest.worldMatrix)..invert(),
    Matrix4.copy(head.worldMatrix)..invert(),
  ],
);
body.skeleton = _rig;

Step 3: Assemble the scene #

The joint root, capsule, and point light all belong to the same scene. This is enough information for the renderer to find the mesh bounds, vertex normals, light position, and current joint transforms.

final Scene scene = Scene()
  ..ambientColor = Vector3(0.38, 0.46, 0.62)
  ..ambientIntensity = 0.14
  ..add(hip)
  ..add(body)
  ..add(
    LightNode(
      type: LightType.point,
      intensity: 14.0,
      range: 8.0,
      name: 'debugged light',
    )..setPosition(-2.2, 3.2, 2.0),
  );

Step 4: Select the overlays #

DebugDrawOptions is part of RenderSettings, so it can change from one frame to the next. A fixed normalLength keeps the orange normal segments readable on this model; zero would let the renderer choose a length from the scene size.

DebugDrawOptions get _debug => DebugDrawOptions(
  bounds: _bounds,
  normals: _normals,
  axes: _axes,
  lightGizmos: _lights,
  skeletons: _skeleton,
  normalLength: 0.18,
);

@override
RenderSettings settings(DemoContext context) => RenderSettings(debug: _debug);

Step 5: Toggle each layer #

The controls change only the option fields. They do not rebuild the scene or its meshes. This makes it practical to isolate one kind of diagnostic while the camera and model stay put.

ToggleControl(
  'Bounds',
  value: () => _bounds,
  onChanged: (bool value) => _bounds = value,
),
ToggleControl(
  'Normals',
  value: () => _normals,
  onChanged: (bool value) => _normals = value,
),
ToggleControl(
  'World axes',
  value: () => _axes,
  onChanged: (bool value) => _axes = value,
),
ToggleControl(
  'Light gizmo',
  value: () => _lights,
  onChanged: (bool value) => _lights = value,
),
ToggleControl(
  'Skeleton',
  value: () => _skeleton,
  onChanged: (bool value) => _skeleton = value,
),

Step 6: Check the overlay pass #

FrameResult.debugLines reports how many line segments the renderer built. The page checks that the rig has three joints and that the active overlays produced more lines than the skeleton would produce on its own.

if (!_debug.anyEnabled ||
    _rig.jointCount != 3 ||
    frame.debugLines <= 15 ||
    frame.drawCalls < 2) {
  throw StateError('the requested debug overlays were not drawn');
}