StrategyVisuals constructor
- required StrategySimulation simulation,
- required GraphicsDevice device,
- int capacity = 4096,
- int? viewer,
- Material? ground,
- Material? units,
- Material? buildings,
- Material? unseen,
- Material? remembered,
- UnitSize unitSize = const UnitSize(),
- DeviceMesh? unitMesh,
- DeviceMesh? buildingMesh,
- Vector3? buildingMeshSize,
Builds the ground and the batch the crowd is drawn in.
capacity is the largest crowd this will draw. A batch's size is fixed
when it is made — the buffer behind it is allocated once — so a game that
intends thousands says so here rather than discovering the ceiling when it
reaches it.
unitMesh replaces the boxy default with a real model, already scaled
and grounded — its own local origin is where a unit's feet stand, not
its centre, since sync() places it with no added lift once one is
set. buildingMesh and buildingMeshSize do the same for a hall, but
as a shared mesh rather than a shape rebuilt at each footprint: a
building's width and depth come from the level document, and one
upload stretched per instance with SceneNode.setScale costs nothing
extra past the second building, where a fresh CuboidShape would cost
one upload apiece.
Implementation
StrategyVisuals({
required this.simulation,
required GraphicsDevice device,
int capacity = 4096,
this.viewer,
Material? ground,
Material? units,
Material? buildings,
Material? unseen,
Material? remembered,
this.unitSize = const UnitSize(),
DeviceMesh? unitMesh,
DeviceMesh? buildingMesh,
Vector3? buildingMeshSize,
}) : assert(
(buildingMesh == null) == (buildingMeshSize == null),
'a shared building mesh needs its own natural size to scale from, '
'and a size with nothing to scale is dead weight',
),
_buildingMaterial = buildings ?? _stone(),
_buildingMesh = buildingMesh,
_buildingMeshSize = buildingMeshSize,
// A real model stands on its own feet at its local origin, once
// prepared the way `tool/prepare_models.py` prepares `worker.glb`; the
// default `CuboidShape` is centred instead, so only it needs lifting
// by half its height to stand on the ground `at.y` names.
_unitGroundLift = unitMesh == null ? unitSize.height / 2.0 : 0.0,
_device = device {
_ground = MeshNode(
DeviceMesh.upload(
device,
meshDataOf(
const HeightfieldGeometry().build(
simulation.ground,
material: 'ground',
),
),
),
ground ?? _turf(),
name: 'ground',
);
_crowd = InstancedMeshNode(
unitMesh ??
DeviceMesh.upload(
device,
CuboidShape(
size: Vector3(unitSize.width, unitSize.height, unitSize.width),
).build(),
),
units ?? _cloth(),
capacity: capacity,
name: 'crowd',
);
for (var i = 0; i < capacity; i++) {
_crowd.addInstance(Matrix4.identity());
}
_crowd.count = 0;
if (viewer == null) return;
final FogOfWar fog = simulation.fog;
_tileTop = Float32List(fog.cellCount);
for (var cell = 0; cell < fog.cellCount; cell++) {
_tileTop[cell] = _topOf(fog, cell);
}
final DeviceMesh tile = DeviceMesh.upload(
device,
CuboidShape(
size: Vector3(fog.cellSize, _tileDepth, fog.cellSize),
).build(),
);
_unseen = _tileBatch(tile, unseen ?? _night(), 'unseen', fog.cellCount);
_remembered = _tileBatch(
tile,
remembered ?? _dusk(),
'remembered',
fog.cellCount,
);
}