WeaponView constructor
models is one root node per WeaponDef.name. Each is parented to the
holder and hidden; initial is the one shown first.
A map rather than a builder callback because the nodes are built once and
the bridge has nothing to add to their construction — it only needs to own
where they hang.
environment is what the held weapon reflects, and environmentLevels is
its roughness scale — see EnvironmentMap.
Its own, not the world's. A weapon is held in front of the eye and lit by this scene's two lights rather than by the room's, so the room's sky would be the wrong thing to reflect even where there is one — and in a crypt there is none at all. What a game hands in here is a studio: a small environment that makes a barrel read as metal.
Without one a metal barrel is very nearly black, because a metal has no diffuse response. That is why the games' own weapon models were built from dark dielectrics pretending to be gunmetal.
Implementation
WeaponView({
required Map<String, SceneNode> models,
WeaponDef? initial,
TextureHandle? environment,
int environmentLevels = 0,
}) {
_scene
..environment = environment
..environmentLevels = environmentLevels;
_scene.add(
LightNode(color: Vector3(1.0, 0.92, 0.82), intensity: 4.5, name: 'key')
..setPosition(1.0, 1.4, 0.8)
..lookAt(Vector3(0.0, -0.2, -1.0)),
);
_scene.add(
LightNode(
type: LightType.point,
color: Vector3(1.0, 0.7, 0.4),
intensity: 3.0,
range: 5.0,
name: 'fill',
)..setPosition(-0.8, -0.4, 0.6),
);
_camera.setPosition(0.0, 0.0, 0.0);
_camera.lookAt(Vector3(0.0, 0.0, -1.0));
_camera.projection = PerspectiveProjection(
// 55 degrees against the world's 90.
fovYRadians: 55.0 * math.pi / 180.0,
near: 0.01,
far: 10.0,
);
// At rest from the first frame. The holder is only positioned by [step],
// and a frame drawn before the first simulation step — the frame every
// start shows — otherwise draws the weapon at the origin, inside the
// camera: an unlit shape filling the middle of the screen.
_holder.setPosition(_restPosition.x, _restPosition.y, _restPosition.z);
_scene.add(_holder);
for (final entry in models.entries) {
_byWeapon[entry.key] = entry.value..visible = false;
_holder.add(entry.value);
}
if (initial != null) selectWeapon(initial);
}