skinnedBarGlb function

Uint8List skinnedBarGlb({
  1. List<double> rgba = const [0.9, 0.2, 0.2, 1],
  2. double halfWidth = 0.1,
})

A square bar 2 m tall standing on y = 0 (glTF axes), skinned to two joints: root at the origin and tip 1 m up (child of root). Below 0.5 m vertices follow root, above 1.5 m tip, between they blend.

Animations:

  • bend: tip turns 90° about +Z over 1 s (LINEAR), so the top half ends up pointing along -X.
  • hop: root jumps 0.5 m up at 0.5 s and back at 1 s (STEP).
  • grow: root scales from 1 to 2 over 1 s (CUBICSPLINE, flat tangents).

Implementation

Uint8List skinnedBarGlb({
  List<double> rgba = const [0.9, 0.2, 0.2, 1],
  double halfWidth = 0.1,
}) {
  final w = GlbWriter();
  final positions = <double>[],
      normals = <double>[],
      joints = <int>[],
      weights = <double>[];
  final indices = <int>[];
  const levels = [0.0, 0.5, 1.0, 1.5, 2.0];
  const corners = [(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)];
  for (final y in levels) {
    final tip = y <= 0.5 ? 0.0 : (y >= 1.5 ? 1.0 : (y - 0.5));
    for (final (cx, cz) in corners) {
      positions.addAll([cx * halfWidth, y, cz * halfWidth]);
      final n = math.sqrt(2) / 2;
      normals.addAll([cx * n, 0, cz * n]);
      joints.addAll([0, 1, 0, 0]);
      weights.addAll([1 - tip, tip, 0, 0]);
    }
  }
  for (var l = 0; l < levels.length - 1; l++) {
    for (var c = 0; c < 4; c++) {
      final a = l * 4 + c, b = l * 4 + (c + 1) % 4;
      // Counter-clockwise seen from outside.
      indices.addAll([a, b + 4, b, a, a + 4, b + 4]);
    }
  }
  // Caps.
  indices.addAll([0, 1, 2, 0, 2, 3]);
  final top = (levels.length - 1) * 4;
  indices.addAll([top, top + 2, top + 1, top, top + 3, top + 2]);

  final mesh = w.addMesh([
    {
      'attributes': {
        'POSITION': w.addFloats(positions, 'VEC3', withBounds: true),
        'NORMAL': w.addFloats(normals, 'VEC3'),
        'JOINTS_0': w.addInts(joints, 'VEC4', 5121),
        'WEIGHTS_0': w.addFloats(weights, 'VEC4'),
      },
      'indices': w.addInts(indices, 'SCALAR', 5123, indices: true),
      'material': w.addMaterial({
        'pbrMetallicRoughness': {
          'baseColorFactor': rgba,
          'metallicFactor': 0,
          'roughnessFactor': 0.6,
        },
      }),
    },
  ], name: 'Bar');
  final ibm = w.addFloats([
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, //
    1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -1, 0, 1,
  ], 'MAT4');
  final tip = w.addNode({
    'name': 'tip',
    'translation': [0, 1, 0],
  });
  final root = w.addNode({
    'name': 'root',
    'children': [tip],
  });
  w.addSkin({
    'joints': [root, tip],
    'inverseBindMatrices': ibm,
    'skeleton': root,
  });
  final body = w.addNode({'name': 'Bar', 'mesh': mesh, 'skin': 0});
  w.setScene([body, root]);

  final s = math.sin(math.pi / 4), c = math.cos(math.pi / 4);
  final times = w.addFloats([0, 1], 'SCALAR');
  w.addAnimation({
    'name': 'bend',
    'samplers': [
      {
        'input': times,
        'output': w.addFloats([0, 0, 0, 1, 0, 0, s, c], 'VEC4'),
        'interpolation': 'LINEAR',
      },
    ],
    'channels': [
      {
        'sampler': 0,
        'target': {'node': tip, 'path': 'rotation'},
      },
    ],
  });
  w.addAnimation({
    'name': 'hop',
    'samplers': [
      {
        'input': w.addFloats([0, 0.5, 1], 'SCALAR'),
        'output': w.addFloats([0, 0, 0, 0, 0.5, 0, 0, 0, 0], 'VEC3'),
        'interpolation': 'STEP',
      },
    ],
    'channels': [
      {
        'sampler': 0,
        'target': {'node': root, 'path': 'translation'},
      },
    ],
  });
  w.addAnimation({
    'name': 'grow',
    'samplers': [
      {
        'input': times,
        // Per key: in-tangent, value, out-tangent.
        'output': w.addFloats([
          0,
          0,
          0,
          1,
          1,
          1,
          0,
          0,
          0,
          0,
          0,
          0,
          2,
          2,
          2,
          0,
          0,
          0,
        ], 'VEC3'),
        'interpolation': 'CUBICSPLINE',
      },
    ],
    'channels': [
      {
        'sampler': 0,
        'target': {'node': root, 'path': 'scale'},
      },
    ],
  });
  return w.toGlb();
}