bake method

Lightmap bake(
  1. Level authored, {
  2. LightmapLayout? layout,
  3. void log(
    1. String message
    )?,
})

Bakes level. log hears each stage as it completes; the caller keeps the clock, since this package reads none.

Implementation

Lightmap bake(
  Level authored, {
  LightmapLayout? layout,
  void Function(String message)? log,
}) {
  // Every brush the level is played with, recipes included — and the same
  // instances `addTo` puts in the world, so a hit indexes back to its brush.
  final level = expandRecipes(authored);
  final plan =
      layout ?? LightmapLayout.plan(level, texelsPerMeter: texelsPerMeter);
  final world = CollisionWorld();
  level.addTo(world);
  world.update();

  final indexOf = <Brush, int>{
    for (var i = 0; i < level.brushes.length; i++) level.brushes[i]: i,
  };
  final albedos = <Vector3>[
    for (final brush in level.brushes) _albedoOf(level.materialFor(brush)),
  ];
  log?.call(
    'planned ${plan.faces.length} faces into ${plan.width}x${plan.height} '
    'at $texelsPerMeter texels a metre',
  );

  final direct = _direct(level, plan, world);
  log?.call('direct light done');

  final indirect = Float32List(plan.texelCount * 3);
  var previous = direct;
  for (var bounce = 1; bounce <= bounces; bounce++) {
    final current = _bounce(plan, world, indexOf, albedos, previous);
    for (var i = 0; i < indirect.length; i++) {
      indirect[i] += current[i];
    }
    previous = current;
    log?.call('bounce $bounce done');
  }

  final map = Lightmap(
    width: plan.width,
    height: plan.height,
    texelsPerMeter: plan.texelsPerMeter,
    levelHash: Lightmap.hashOf(level),
  );
  var sumR = 0.0;
  var sumG = 0.0;
  var sumB = 0.0;
  var counted = 0;
  for (final face in plan.faces) {
    for (var j = 0; j < face.height; j++) {
      for (var i = 0; i < face.width; i++) {
        final at = ((face.y + j) * plan.width + face.x + i) * 3;
        final r = indirect[at] + (includeDirect ? direct[at] : 0.0);
        final g = indirect[at + 1] + (includeDirect ? direct[at + 1] : 0.0);
        final b = indirect[at + 2] + (includeDirect ? direct[at + 2] : 0.0);
        map.setIrradiance(face.x + i, face.y + j, r, g, b);
        sumR += indirect[at];
        sumG += indirect[at + 1];
        sumB += indirect[at + 2];
        counted++;
      }
    }
  }
  // The reserved texel: what a ramp, which owns no rectangle, is lit by —
  // the level's average bounce, which is nearer the truth than black.
  if (counted > 0) {
    map.setIrradiance(
      LightmapLayout.reservedX,
      LightmapLayout.reservedY,
      sumR / counted,
      sumG / counted,
      sumB / counted,
    );
  }
  return map;
}