StrategyVisuals constructor

StrategyVisuals({
  1. required StrategySimulation simulation,
  2. required GraphicsDevice device,
  3. int capacity = 4096,
  4. int? viewer,
  5. Material? ground,
  6. Material? units,
  7. Material? buildings,
  8. Material? unseen,
  9. Material? remembered,
  10. UnitSize unitSize = const UnitSize(),
  11. DeviceMesh? unitMesh,
  12. DeviceMesh? buildingMesh,
  13. 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,
  );
}