bake method
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;
}