StrategyVisuals constructor
- required StrategySimulation simulation,
- required GraphicsDevice device,
- int capacity = 4096,
- int? viewer,
- RenderMaterial? ground,
- RenderMaterial? units,
- RenderMaterial? buildings,
- RenderMaterial? unseen,
- RenderMaterial? remembered,
- UnitSize unitSize = const UnitSize(),
- DeviceMesh? unitMesh,
- DeviceMesh? buildingMesh,
- Vector3? buildingMeshSize,
- List<
UnitLook> looks = const <UnitLook>[], - List<
RenderMaterial> ? buildingSides, - bool buildingStandsTall = false,
- MeshLook? resource,
- List<
PropBatch> props = const <PropBatch>[], - double? waterLevel,
- RenderMaterial? water,
- double groundMetersPerTexture = 8.0,
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.
looks dress units by kind, one batch per kind and side; see UnitLook.
buildingSides gives each side's buildings their own material — a
castle in its owner's colours — in place of buildings; and
buildingStandsTall lets a shared building mesh keep its proportions,
rising by as much as its footprint stretches rather than being pressed
to the fixed height a box is given.
resource is drawn at every deposit, props are the map's furniture,
and waterLevel floods everything below it with a sheet of water.
groundMetersPerTexture is how far the ground goes before its texture
repeats — the width of the map, for a texture painted to fit it.
Implementation
StrategyVisuals({
required this.simulation,
required GraphicsDevice device,
int capacity = 4096,
this.viewer,
RenderMaterial? ground,
RenderMaterial? units,
RenderMaterial? buildings,
RenderMaterial? unseen,
RenderMaterial? remembered,
this.unitSize = const UnitSize(),
DeviceMesh? unitMesh,
DeviceMesh? buildingMesh,
Vector3? buildingMeshSize,
List<UnitLook> looks = const <UnitLook>[],
List<RenderMaterial>? buildingSides,
bool buildingStandsTall = false,
MeshLook? resource,
List<PropBatch> props = const <PropBatch>[],
double? waterLevel,
RenderMaterial? water,
double groundMetersPerTexture = 8.0,
}) : 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',
),
assert(
buildingSides == null || buildingSides.isNotEmpty,
'a building in nobody\'s colours',
),
_buildingMaterial = buildings ?? _stone(),
_buildingSides = buildingSides,
_buildingMesh = buildingMesh,
_buildingMeshSize = buildingMeshSize,
// Not `this._…`: a private named parameter puts the private name in
// the API and needs the newest language version to call.
// ignore: prefer_initializing_formals
_buildingStandsTall = buildingStandsTall,
// ignore: prefer_initializing_formals
_resource = resource,
waterLevel = waterLevel,
// A real model stands on its own feet at its local origin, once
// grounded the way the strategy demo grounds its workers; 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',
metersPerTexture: groundMetersPerTexture,
),
),
),
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',
);
_adopt(_crowd);
for (final UnitLook look in looks) {
_byKind[look.kind] = <int>[
for (final MeshLook side in look.sides)
_adopt(
InstancedMeshNode(
side.mesh,
side.material,
capacity: capacity,
name: look.kind,
),
),
];
}
_filled = Int32List(_batches.length);
for (final PropBatch batch in props) {
_props.add(
_reserve(
InstancedMeshNode(
batch.look.mesh,
batch.look.material,
capacity: math.max(1, batch.placements.length),
name: batch.name,
),
),
);
_placements.add(batch.placements);
_placed.add(List<bool>.filled(batch.placements.length, false));
_slots.add(
Int32List(batch.placements.length)
..fillRange(0, batch.placements.length, -1),
);
}
final Heightfield field = simulation.ground;
if (waterLevel != null) {
if (_wetBounds(field, waterLevel) case (
final double x0,
final double z0,
final double x1,
final double z1,
)) {
_water = MeshNode(
DeviceMesh.upload(
device,
PlaneShape(width: x1 - x0, depth: z1 - z0).build(),
),
water ?? _pond(),
name: 'water',
)..setPosition((x0 + x1) / 2.0, waterLevel, (z0 + z1) / 2.0);
}
}
if (viewer == null) return;
final FogOfWar fog = simulation.fog;
_tileTop = Float32List(fog.cellCount);
for (var cell = 0; cell < fog.cellCount; cell++) {
// Over a pond the fog has to cover the water rather than the bed under
// it, or the sheet shows through a square of dark as a square of blue.
final double top = _topOf(fog, cell);
_tileTop[cell] = waterLevel != null && waterLevel > top
? waterLevel
: top;
}
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,
);
}