Model Assembling

A model assembler controls proxy instances of a basis mesh (source object).

The component is a mesh itself, into which proxies are merged. Much faster rendering is possible if proxy meshes are merge into one mesh, instead of rendering each mesh.

Restriction: multi-mesh models will be used as multiple source objects and not merged as one object. For example: if you have a model including a cube and sphere mesh, the cube and sphere will be registered as source objects. They will not be handled as single mesh.

We typically use this technique for grass or trees. It is not suitable for animated meshes.

Example: Grass Model Assembler

In this example we will display grass on multiplied planes inside of the model assembler component. After applying the TGorillaGrassMaterial we are able to render different textures onto those planes and to manipulate vertices.

The result should be a varying grass landscape with waving grass.

Form1.pas
procedure TForm1.FormCreate(Sender: TObject);
var LSrcObj : TPlane;
    LPoolE : TGorillaBitmapPoolEntry;
    LGrassMat : TGorillaGrassMaterialSource;
    LAlg : TGorillaAssemblerFlatFilling;
begin
  // creating the source object - here a plane
  LSrcObj := TPlane.Create(fGorilla);
  LSrcObj.Scale.Point := Point3D(4, 4, 4);
 
  // creating the grass material source
  FGrassMat := TGorillaGrassMaterialSource.Create(FGorilla);
  FGrassMat.Parent := FGorilla;
 
  // now we load a pool of grass textures, the material shader
  // randomly chooses from
  with FGrassMat do
  begin
    LPoolE := Bitmaps.Add() as TGorillaBitmapPoolEntry;
    LPoolE.DisplayName := 'Grass1';
    LPoolE.Bitmap.LoadFromFile('grass1.png');
 
    LPoolE := Bitmaps.Add() as TGorillaBitmapPoolEntry;
    LPoolE.DisplayName := 'Grass2';
    LPoolE.Bitmap.LoadFromFile('grass2.png');
 
    LPoolE := Bitmaps.Add() as TGorillaBitmapPoolEntry;
    LPoolE.DisplayName := 'Grass3';
    LPoolE.Bitmap.LoadFromFile('grass3.png');
 
    LPoolE := Bitmaps.Add() as TGorillaBitmapPoolEntry;
    LPoolE.DisplayName := 'Grass4';
    LPoolE.Bitmap.LoadFromFile('grass4.png');
  end;  
 
  // creating the model assembling control
  FGrass := TGorillaModelAssembler.Create(FGorilla);
  FGrass .Parent := FGorilla;
  FGrass.AddSourceObject(LSrcObj);
  FGrass.MaterialSource := LGrassMat;
  FGrass.SetSize(GORILLA_ASSEMBLER_SIZE, GORILLA_ASSEMBLER_SIZE, GORILLA_ASSEMBLER_SIZE);
 
  // create an individual filling algorithm to multiply the grass planes
  // on elder Gorilla3D version TGorillaAssemblerRectFilling
  LAlg := TGorillaAssemblerFlatFilling.Create(FGrass);
  try
    LAlg.Count := 1000;
    FGrass.Fill(LAlg, true);
  finally
    FreeAndNil(LAlg);
  end;

Interact with grass shader

The default grass shader provides functionality to bend the grass planes at a certain point. To enable interaction, f.e. at mouse position, we can simply configure the TGorillaGrassMaterialSource:

Form1.pas
  FGrassMat.SpotRadius := 0.5;
  FGrassMat.SpotEnabled := true;

And in the OnMouseMove event we can set the current interaction point like this:

Form1.pas
procedure TForm1.DoOnViewportMouseMove(ASender : TObject; AShiftState : TShiftState;
  X, Y : Single);
var LPt3D : TPoint3D;
    LRayPos, LRayDir : TVector3D;
begin
  if FMove then
  begin
    if (ssLeft in AShiftState) then
    begin
      FGorilla.BeginUpdate();
      try
        LPt3D := FGorilla.ScreenToWorld(PointF(X, Y));
        FGorilla.Context.Pick(X, Y, TProjection.Camera, LRayPos, LRayDir);
        FGrass.RayCastIntersect(LRayPos, LRayDir, LPt3D);
 
        // apply scaled light position
        FGrassMat.Spot := TPoint3D(LPt3D);
      finally
        FGorilla.EndUpdate();
      end;
    end;
 
    FLatest := PointF(X, Y);
  end;
end;

Example: Terrain Grass Model Assembling

Since v0.8.1+ we've implemented a lot of new backend features (bounding volume hierarchy for TMeshDef, multiple source objects, TGorillaModel support), which allows to multiple meshes and place those onto a terrain surface.

The following example will create 64 assemblers, filled up with 64 copies of a grass template model. The assemblers will be attached as childs to the terrain and rendered above. All assemblers will be placed as grid above the complete terrain.

Notice: We could multiply the template model into a single assembler component, but the idea behind it, is to use the performance optimization of frustum culling. By frustum culling only the visible assembler chunks will be rendered, therefore we can reduce vertex rendering a lot.

So at first we will load a grass template model. Keep the mesh as simple as possible, because vertex count will increase fastly.

To place a grass model copy in correct y-position on the terrain, we need to know vertices of the terrain. The new feature of bounding volume hierarchy (BVH) computation for TMeshDef's helps here a lot. It is 1000 times faster, than iterating of all vertices to find the correct position.

For template model multiplication we use the new TGorillaAssemblerTerrainFilling class, which automatically reads y-position from BVH.

const
  MAP_SIZE = 900; // terrain size (quadratic)
  MAP_HEIGHT = MAP_SIZE / 4;
 
var FGrassTemps : Array[0..0] of TGorillaModel;
 
procedure TForm1.CreateGrass(ATerrain : TGorillaTerrain; const AAssetsPath : String);
const  GORILLA_ASSEMBLER_SIZE = 1;
      GORILLA_MODEL_GRASS_1 = 'lowpoly-grass-planes.obj';
 
var LTexPath      : String;
    LGrass        : TGorillaModelAssembler;
    LFillAlg      : TGorillaAssemblerTerrainFilling;
    LChunks       : Integer;
    LChunksPerRow : Integer;
    i             : Integer;
    LChunkSize    : TPoint3D;
    LX, LZ        : Single;
begin
  if not Assigned(ATerrain) then
    raise Exception.Create('terrain not available - grass will be as child of the terrain');
 
  // load the grass templates
  LTexPath := IncludeTrailingPathDelimiter(AAssetsPath + 'grass');
  FGrassTemps[0] := TGorillaModel.LoadNewModelFromFile(FViewport, nil,
    LTexPath + GORILLA_MODEL_GRASS_1, []);
  FGrassTemps[0].Visible := false; // it do not need to be shown
 
  // create a list for all 64 assembler chunks
  FGrassAssemblers := TList<TGorillaModelAssembler>.Create();
 
  // acquire a bounding volume hierarchy for much faster raytracing
  // after usage, it will be destroyed, because it consumes a lot memory
  // and we only need it for the moment
  TMeshDef(ATerrain.Def).AcquireBVH();
  try
    LChunks := 64;
    LChunksPerRow := Ceil(Sqrt(LChunks));
 
    LChunkSize.X := (MAP_SIZE / LChunksPerRow);
    LChunkSize.Y := MAP_HEIGHT;
    LChunkSize.Z := (MAP_SIZE / LChunksPerRow);
 
    for i := 0 to LChunks - 1 do
    begin
      // create multiple grass assemblers to use frustum culling
      LGrass := TGorillaModelAssembler.Create(FViewport);
      LGrass.Parent := FViewport;
      LGrass.Opaque := false; // allow translucent rendering
      LGrass.TwoSide := true;
      LGrass.Name := 'Grass' + IntToStr(i + 1);
 
      // add the grass model as source object
      // multiple source objects are possible and will be chosen randomly
      LGrass.AddSourceObject(FGrassTemps[0]);
 
      // set the size of assembler chunk
      LGrass.SetSize(LChunkSize.X, LChunkSize.Y, LChunkSize.Z);
 
      LX := -(MAP_SIZE / 2) + ((i mod LChunksPerRow) * LChunkSize.X);
      LZ := (MAP_SIZE / 2) - (Floor(i / LChunksPerRow) * LChunkSize.Z);
 
      // place assembler chunk over terrain
      LGrass.Position.Point := Point3D(LX, 0, LZ);
 
      // take the template model material source as material for the assembler
      // this could be replaced by a grass material shader of course
      LGrass.MaterialSource := FGrassTemps[0].Meshes[0].MaterialSource;
      FGrassAssemblers.Add(LGrass);
 
      // fill up assembler chunk with 64 copies of our grass template
      LFillAlg := TGorillaAssemblerTerrainFilling.Create(LGrass, ATerrain);
      try
        LFillAlg.Count := 64;
        LGrass.Fill(LFillAlg, true);
      finally
        FreeAndNil(LFillAlg);
      end;
 
      // we put grass assembler into terrain model
      LGrass.Parent := ATerrain;
    end;
  finally
    // at the end we destroy the bounding volume hierarchy again to free memory
    TMeshDef(ATerrain.Def).ReleaseBVH();
  end;
end;

Next step: Inventory