{Copyright (c) 2008 Ville Krumlinde

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.}

//Meshes and models
unit Meshes;

interface

uses ZClasses, ZBitmap, ZExpressions;

type
  PMeshVertexIndex = ^TMeshVertexIndex;
  TMeshVertexIndex = word;
  PIndicesArray = ^TIndicesArray;
  TIndicesArray = array[0..10000] of TMeshVertexIndex;

  TMesh = class(TContent)
  private
    procedure FreeData;
  protected
    procedure CopyAndDestroy(Source : TContent); override;
    {$ifndef minimal}
    procedure DefineProperties(List: TZPropertyList); override;
    procedure UpdateBounds;
    {$endif}
  public
    //Note: keep fields in sync with CopyAndDestroy-method
    Vertices : PZVector3Array;
    VerticesCount : integer;
    Indices : PIndicesArray;
    IndicesCount : integer;
    Normals : PZVector3Array;
    TexCoords : PZVector2Array;
    Colors : PInteger;
    Style : (msTris,msQuads);
    {$ifndef minimal}
    BoundSphere :
      record
        Center : TZVector3f;
        Radius : single;
      end;
    {$endif}
    destructor Destroy; override;
    procedure Scale(const V : TZVector3f);
    procedure MakeNet(XCount,YCount : integer);
    procedure BeforeRender;
    procedure CreateData(VQuantity,TQuantity : integer; WithTexCoords : boolean = False;
      WithColors : boolean = False);
    procedure ComputeNormals;
  end;

  TMeshProducer = class(TContentProducer)
  protected
    procedure DefineProperties(List: TZPropertyList); override;
  public
    Scale : TZVector3f;
  end;

  TMeshBox = class(TMeshProducer)
  protected
    procedure ProduceOutput(Content : TContent; Stack : TZArrayList); override;
    procedure DefineProperties(List: TZPropertyList); override;
  public
    Grid2DOnly : boolean;
    XCount,YCount : integer;
  end;

  TMeshSphere = class(TMeshProducer)
  protected
    procedure ProduceOutput(Content : TContent; Stack : TZArrayList); override;
    procedure DefineProperties(List: TZPropertyList); override;
  public
    ZSamples,RadialSamples : integer;
  end;

  //Add noise to vertices
  TMeshNoise = class(TMeshProducer)
  protected
    procedure ProduceOutput(Content : TContent; Stack : TZArrayList); override;
    procedure DefineProperties(List: TZPropertyList); override;
  public
    NoiseSpeed : TZVector3f;
    NoiseScale : TZVector3f;
    SymmetryX,SymmetryY,SymmetryZ : boolean;
  end;

  //Change vertices using a zexpression
  TMeshExpression = class(TMeshProducer)
  protected
    procedure DefineProperties(List: TZPropertyList); override;
    procedure ProduceOutput(Content : TContent; Stack: TZArrayList); override;
  public
    Expression : TZExpressionPropValue;
    V,N : TZVector3f;
    AutoNormals : boolean;
  end;

  //Created by 3ds-import
  TMeshImport = class(TMeshProducer)
  protected
    procedure DefineProperties(List: TZPropertyList); override;
    procedure ProduceOutput(Content : TContent; Stack: TZArrayList); override;
  public
    HasVertexColors : boolean;
    HasTextureCoords : boolean;
    MeshData : TZBinaryPropValue;
  end;

  //State for models
  TModelState = class(TStateBase)
  protected
    procedure DefineProperties(List: TZPropertyList); override;
  public
    OnCollision : TZComponentList;
  end;

  TSetModelState = class(TCommand)
  protected
    procedure DefineProperties(List: TZPropertyList); override;
  public
    State : TModelState;
    procedure Execute; override;
    {$ifndef minimal}
    function GetDisplayName: string; override;
    {$endif}
  end;

  TCollisionStyle = (csRect2D,csSphere3D,csBox3D,csRect2D_OBB);
  TCollisionBounds =
    record
      Rect : TZRectf;
      Box : TZBox3D;
      OBB : TOBB_2D;
    end;

  TModel = class(TZComponent)
  private
    CurrentState : TModelState;
  protected
    ChildModelRefs : TZArrayList;  //referenser till models som denna har spawnat
    ParentModel : TModel;
    procedure DefineProperties(List: TZPropertyList); override;
  public
    Definitions : TZComponentList;
    OnRender : TZComponentList;
    OnUpdate : TZComponentList;       //uppdateringslogik
    OnSpawn : TZComponentList;
    OnRemove : TZComponentList;
    OnCollision : TZComponentList;
    CollisionStyle : TCollisionStyle;
    States : TZComponentList;
    Position : TZVector3f;
    Rotation : TZVector3f;
    Velocity : TZVector3f;
    Scale : TZVector3f;
    RotationVelocity : TZVector3f;
    Category : byte;
    CollisionBounds : TZRectf;
    CollisionOffset : TZVector3f;
    IsSpawnedAsReference : boolean;
    Active : boolean;
    Personality : single;  //Varje instans har egen random "personlighet", som kan användas i expressions.
    LastPosition : TZVector3f;  //Används i collision
    procedure Update; override;        //anropas ej ifall active=false
    procedure GetCollisionBounds(out Result : TCollisionBounds);
    procedure Collision(Hit : TModel);
    {$ifndef minimal}
    procedure DesignerUpdate;
    procedure DesignerReset; override;
    {$endif}
    procedure RunRenderCommands;
    constructor Create(OwnerList: TZComponentList); override;
    destructor Destroy; override;
  end;

  //Models-list owned by application
  //Models are divided into collision categories
  TModels = class
  private
    RemoveList : TZArrayList;
    procedure ClearAll;
  public
    Cats : TZArrayList;
    constructor Create;
    destructor Destroy; override;
    procedure Update;
    procedure RegisterCat(Cat : integer);
    function Get(Cat : integer) : TZArrayList;
    procedure Add(M : TModel);
    procedure Remove(M : TModel);
    procedure RemoveAll;
    procedure FlushRemoveList;
  end;

  TSpawnModel = class(TCommand)
  protected
    procedure DefineProperties(List: TZPropertyList); override;
  public
    Model : TModel;
    Position : TZVector3f;
    Rotation : TZVector3f;
    Scale : TZVector3f;
    SpawnStyle : (ssClone,ssReference);
    UseSpawnerPosition : boolean;
    SpawnerIsParent : boolean;  //Spawned model becomes child to currentmodel
    procedure Execute; override;
    {$ifndef minimal}function GetDisplayName: String; override;{$endif}
  end;

  TRemoveModel = class(TCommand)
  public
    procedure Execute; override;
  end;

  TRemoveAllModels = class(TCommand)
  public
    procedure Execute; override;
  end;


var
  CurrentModel : TModel;  //Set to the model that is currently updated


implementation

uses ZMath, ZApplication
{$ifndef minimal}, Animators, Renderer{$endif}
{$ifdef zdebug}, ZLog, Sysutils{$endif}
;

procedure ExecuteWithCurrentModel(M : TModel; CommandList : TZComponentList);
var
  SaveCurrent : TModel;
begin
  SaveCurrent := CurrentModel;
  CurrentModel := M;
  CommandList.ExecuteCommands;
  CurrentModel := SaveCurrent;
end;

{ TMesh }

procedure TMesh.ComputeNormals;
var
  C : array[0..2] of TZVector3f;
  ax,ay,az,bx,by,bz,nx,ny,nz : single;
  I,I1,I2,I3 : integer;
begin
  if Style<>msTris then
    Exit;

  //One normal for each vertex
//  GetMem(Normals,SizeOf(TZVector3f) * VerticesCount);
  FillChar(Normals^,SizeOf(TZVector3f) * VerticesCount,0);

  for I := 0 to (IndicesCount div 3)-1 do
  begin
    I1 := Indices^[(I*3)];
    I2 := Indices^[(I*3)+1];
    I3 := Indices^[(I*3)+2];

    C[0] := Vertices^[ I1 ];
    C[1] := Vertices^[ I2 ];
    C[2] := Vertices^[ I3 ];

    //Calc normal vector
    //http://www.tjhsst.edu/~dhyatt/supercomp/n310.html
    ax:=c[1][0] - c[0][0];
    ay:=c[1][1] - c[0][1];
    az:=c[1][2] - c[0][2];

    bx:=c[2][0] - c[1][0];
    by:=c[2][1] - c[1][1];
    bz:=c[2][2] - c[1][2];

    nx:=ay * bz - az * by;
    ny:=az * bx - ax * bz;
    nz:=ax * by - ay * bx;

    //Add normals for each face
    Normals^[ I1 ][0] := Normals^[ I1 ][0] + NX;
    Normals^[ I1 ][1] := Normals^[ I1 ][1] + NY;
    Normals^[ I1 ][2] := Normals^[ I1 ][2] + NZ;

    Normals^[ I2 ][0] := Normals^[ I2 ][0] + NX;
    Normals^[ I2 ][1] := Normals^[ I2 ][1] + NY;
    Normals^[ I2 ][2] := Normals^[ I2 ][2] + NZ;

    Normals^[ I3 ][0] := Normals^[ I3 ][0] + NX;
    Normals^[ I3 ][1] := Normals^[ I3 ][1] + NY;
    Normals^[ I3 ][2] := Normals^[ I3 ][2] + NZ;
  end;

  //Normalize
  for I := 0 to VerticesCount-1 do
    VecNormalize3(Normals^[I]);
end;

procedure TMesh.CopyAndDestroy(Source: TContent);
var
  M : TMesh;
begin
  {$ifndef minimal}
  if Source=nil then Exit;
  {$endif}
  FreeData;
  M := TMesh(Source);
  Vertices := M.Vertices;
  VerticesCount := M.VerticesCount;
  Indices := M.Indices;
  IndicesCount := M.IndicesCount;
  Normals := M.Normals;
  TexCoords := M.TexCoords;
  Colors := M.Colors;
  Style := M.Style;
  M.Vertices :=nil;
  M.Indices :=nil;
  M.Normals :=nil;
  M.TexCoords :=nil;
  M.Colors :=nil;
  M.Free;
  {$ifdef zlog}
  ZLog.GetLog(Self.ClassName).Write('Triangles ' + IntToStr(Self.IndicesCount div 3) );
  {$endif}
end;

procedure TMesh.CreateData(VQuantity, TQuantity: integer; WithTexCoords : boolean = False;
  WithColors : boolean = False);
begin
  if (Vertices<>nil) then
  begin
    //Already allocated
    if (VerticesCount=VQuantity) then
      Exit; //Same count, just exit
    FreeData; //Otherwise dealloc old
  end;
  VerticesCount := VQuantity;
  IndicesCount := 3*TQuantity;

  GetMem(Vertices, SizeOf(TZVector3f) * VerticesCount);

  //New memory can contain junk, normals must be zeroed out
  GetMem(Normals,SizeOf(TZVector3f) * VerticesCount);
  FillChar(Normals^,SizeOf(TZVector3f) * VerticesCount,0);

  GetMem(Indices, SizeOf(TMeshVertexIndex) * IndicesCount);

  if WithTexCoords then
    GetMem(TexCoords, SizeOf(TZVector2f) * VerticesCount);
  if WithColors then
    GetMem(Colors, SizeOf(Integer) * VerticesCount);
end;

procedure TMesh.FreeData;
begin
  VerticesCount := 0;
  IndicesCount := 0;
  if Vertices<>nil then
    FreeMem(Vertices);
  if Indices<>nil then
    FreeMem(Indices);
  if Normals<>nil then
    FreeMem(Normals);
  if TexCoords<>nil then
    FreeMem(TexCoords);
  if Colors<>nil then
    FreeMem(Colors);
end;

destructor TMesh.Destroy;
begin
  FreeData;
  inherited;
end;

procedure TMesh.BeforeRender;
begin
  if (Vertices=nil) or (Producers.IsChanged) then
  begin
    RefreshFromProducers;
    {$ifndef minimal}
    UpdateBounds;
    {$endif}
  end;
end;

procedure TMesh.Scale(const V: TZVector3f);
var
  I : integer;
begin
  if VecIsIdentity3(V) then
    Exit;
  for I := 0 to VerticesCount-1 do
    VecMult3(Vertices^[I],V);
  if Normals<>nil then
    for I := 0 to VerticesCount-1 do
    begin
      VecMult3(Normals^[I],V);
      VecNormalize3( Normals^[I] );
    end;
end;

{$ifndef minimal}
procedure TMesh.UpdateBounds;
//http://www.mvps.org/directx/articles/using_bounding_spheres.htm
var
  VertP : PZVector3f;
  I : integer;
  C,V : TZVector3f;
  R,DistSq : single;
begin
  // find center
  VertP := pointer(Self.Vertices);
  C := Vector3f(0,0,0);
  for I := 0 to Self.VerticesCount - 1 do
  begin
    VecAdd3(C,VertP^,C);
    Inc(VertP);
  end;
  VecDiv3(C,Self.VerticesCount,C);
  Self.BoundSphere.Center := C;

  // find farthest point in set
  R := 0;
  VertP := pointer(Self.Vertices);
  for I := 0 to Self.VerticesCount - 1 do
  begin
    VecSub3(VertP^,C,V);
    DistSq := ZMath.VecLengthSquared3(V);
    if DistSq>R then
      R := DistSq;
    Inc(VertP);
  end;

  Self.BoundSphere.Radius := sqrt(R);
end;

procedure TMesh.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.GetByName('Producers').SetChildClasses([TMeshProducer]);
end;
{$endif}

procedure TMesh.MakeNet(XCount, YCount: integer);
var
  VertCount,TriCount : integer;
  P : PZVector3f;
  XStep,YStep,CurX,CurY : single;
  Ind : PMeshVertexIndex;
  Tex : PZVector2f;
  CurI,X,Y : integer;
begin
  VertCount := (2+XCount) * (2+YCount);
  TriCount := 2*((XCount+1)*(YCount+1));
  CreateData(VertCount,TriCount,True);
  P := pointer(Vertices);
  XStep := 1/(XCount+1);
  YStep := 1/(YCount+1);

  Tex := pointer(TexCoords);

  //Generate vertices and texcoords
  CurY := -0.5;
  for Y := 0 to YCount+1 do
  begin
    CurX := -0.5;
    for X := 0 to XCount+1 do
    begin
      //Vertex interval -0.5 .. 0.5
      P^[0] := CurX;
      P^[1] := CurY;
      P^[2] := 0;

      //Texcoords interval 1..0
      Tex^[0] := CurX+0.5;
      Tex^[1] := 1.0 - (0.5 - CurY);

      CurX := CurX + XStep;
      Inc(P);
      Inc(Tex);
    end;
    CurY := CurY + YStep;
  end;

  //Generate two triangles for each quad in net
  //CCW direction
  CurI := 0;
  Ind := PMeshVertexIndex(Indices);
  for Y := 0 to YCount do
  begin
    for X := 0 to XCount do
    begin
      Ind^:= CurI; Inc(Ind);
      Ind^:= CurI+1; Inc(Ind);
      Ind^:= CurI+2+XCount; Inc(Ind);

      Ind^:= CurI+1; Inc(Ind);
      Ind^:= CurI+3+XCount; Inc(Ind);
      Ind^:= CurI+2+XCount; Inc(Ind);

      Inc(CurI);
    end;
    Inc(CurI);
  end;
{
      curx=0
      xstep=1/xcount
      while(cury<1)
        while(curx<1)
          addvertice(curx,cury)
      skapa indices
        tri1 är v1,v2,v3
        tri2 är v2,v4,v3
}
  ComputeNormals;
end;

{ TModel }

procedure TModel.Collision(Hit: TModel);
begin
  ZApp.EventState.CollidedCategory := Hit.Category;
  Meshes.CurrentModel := Self;
  OnCollision.ExecuteCommands;
  if (CurrentState<>nil) then
    CurrentState.OnCollision.ExecuteCommands;
end;

procedure TModel.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'Definitions',{$ENDIF}integer(@Definitions) - integer(Self), zptComponentList);
    {$ifndef minimal}List.GetLast.SetChildClasses([TDefineVariable,TDefineConstant,TMesh,TModel,TZBitmap]);{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'States',{$ENDIF}integer(@States) - integer(Self), zptComponentList);
    {$ifndef minimal}List.GetLast.SetChildClasses([TModelState]);{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'OnRender',{$ENDIF}integer(@OnRender) - integer(Self), zptComponentList);
  List.AddProperty({$IFNDEF MINIMAL}'OnUpdate',{$ENDIF}integer(@OnUpdate) - integer(Self), zptComponentList);
//    {$ifndef minimal}List.GetLast.SetChildClasses([TCommand,TZExpression]);{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'OnSpawn',{$ENDIF}integer(@OnSpawn) - integer(Self), zptComponentList);
  List.AddProperty({$IFNDEF MINIMAL}'OnRemove',{$ENDIF}integer(@OnRemove) - integer(Self), zptComponentList);
  List.AddProperty({$IFNDEF MINIMAL}'OnCollision',{$ENDIF}integer(@OnCollision) - integer(Self), zptComponentList);
  List.AddProperty({$IFNDEF MINIMAL}'Position',{$ENDIF}integer(@Position) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'Rotation',{$ENDIF}integer(@Rotation) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'Velocity',{$ENDIF}integer(@Velocity) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'Scale',{$ENDIF}integer(@Scale) - integer(Self), zptVector3f);
    List.GetLast.DefaultValue.Vector3fValue := ZMath.UNIT_XYZ3;
  List.AddProperty({$IFNDEF MINIMAL}'RotationVelocity',{$ENDIF}integer(@RotationVelocity) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'Category',{$ENDIF}integer(@Category) - integer(Self), zptByte);
  List.AddProperty({$IFNDEF MINIMAL}'CollisionBounds',{$ENDIF}integer(@CollisionBounds) - integer(Self), zptRectf);
  List.AddProperty({$IFNDEF MINIMAL}'CollisionOffset',{$ENDIF}integer(@CollisionOffset) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'CollisionStyle',{$ENDIF}integer(@CollisionStyle) - integer(Self), zptByte);
    {$ifndef minimal}List.GetLast.SetOptions(['Rect2D','Sphere3D','Box3D','Rect2D_OBB']);{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'Personality',{$ENDIF}integer(@Personality) - integer(Self), zptFloat);
    List.GetLast.NeverPersist := True;
    List.GetLast.DontClone := True;
    {$ifndef minimal}List.GetLast.IsReadOnly := True;{$endif}
end;

procedure TModel.GetCollisionBounds(out Result: TCollisionBounds);
var
  W,H,A,S,C : single;
  Start : TZVector3f;
  I : integer;
begin
  VecAdd3(CollisionOffset,Position, Start);
  case Self.CollisionStyle of
    csRect2D:
      begin
        W := CollisionBounds.Area[0] * 0.5;
        H := CollisionBounds.Area[1] * 0.5;
        Result.Rect.Left := Start[0] - W;
        Result.Rect.Right := Start[0] + W;
        Result.Rect.Top := Start[1] - H;
        Result.Rect.Bottom := Start[1] + H;
      end;
    csSphere3D:
      begin
        Result.Rect.Area[0] := Start[0];
        Result.Rect.Area[1] := Start[1];
        Result.Rect.Area[2] := Start[2];
        Result.Rect.Area[3] := CollisionBounds.Area[0];
      end;
    csBox3D:
      begin
        VecSub3(Start,PZVector3f(@CollisionBounds.Area)^,Result.Box.Min);
        VecAdd3(Start,PZVector3f(@CollisionBounds.Area)^,Result.Box.Max);
      end;
    csRect2D_OBB:
      begin
        for I := 0 to 1 do
        begin
          Result.OBB.C[I] := Start[I];
          Result.OBB.E[I] := CollisionBounds.Area[I] * Scale[I] * 0.5;
        end;
        A := CycleToRad(Self.Rotation[2]);
        S := Sin(A);
        C := Cos(A);
        Result.OBB.U[0][0] := C;
        Result.OBB.U[0][1] := S;
        Result.OBB.U[1][0] := -S;
        Result.OBB.U[1][1] := C;
      end;
  end;
end;

procedure TModel.Update;
var
  I : integer;
begin
  if Active then
  begin
    //Update movement
    VecCopy3(Position,Self.LastPosition);
    if not VecIsNull3(Self.Velocity) then
    begin
      for I := 0 to 2 do
        Position[I] := Position[I] + (Velocity[I] * ZApp.DeltaTime);
    end;

    //Update rotation
    if not VecIsNull3(Self.RotationVelocity) then
      for I := 0 to 2 do
        Rotation[I] := Rotation[I] + (RotationVelocity[I] * ZApp.DeltaTime);

    OnUpdate.ExecuteCommands;
    //Update renderers: particlesystems, beams etc
    OnRender.Update;

    //Update current state
    if CurrentState<>nil then
      CurrentState.Update;
  end;
end;

procedure TModel.RunRenderCommands;
//Called from render.rendermodel
begin
  OnRender.ExecuteCommands;
  if CurrentState<>nil then
    CurrentState.OnRender.ExecuteCommands;
end;


{$ifndef minimal}
procedure TModel.DesignerUpdate;
begin
  //Update renderers: particlesystems, beams etc
  OnRender.Update;
end;

procedure TModel.DesignerReset;
begin
  inherited;
//  OnRender.DesignerReset;
end;
{$endif}

constructor TModel.Create(OwnerList: TZComponentList);
begin
  inherited Create(OwnerList);
  ChildModelRefs := TZArrayList.CreateReferenced;
  Personality := System.Random;
end;

destructor TModel.Destroy;
begin
  ChildModelRefs.Free;
  inherited;
end;

{ TMeshSphere }

procedure TMeshSphere.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'ZSamples',{$ENDIF}integer(@ZSamples) - integer(Self), zptInteger);
    List.GetLast.DefaultValue.IntegerValue := 10;
  List.AddProperty({$IFNDEF MINIMAL}'RadialSamples',{$ENDIF}integer(@RadialSamples) - integer(Self), zptInteger);
    List.GetLast.DefaultValue.IntegerValue := 10;
end;

procedure TMeshSphere.ProduceOutput(Content : TContent; Stack : TZArrayList);
const
  Radius : single = 1.0;
var
  ZSm1,ZSm2,ZSm3,RSp1,VQuantity,TQuantity : integer;
  R, Z, ZStart, i, Save : integer;
  InvRS,ZFactor,Angle,ZFraction,fZ,SliceRadius{,RadialFraction} : single;
  afSin,afCos : PFloatArray;
  SliceCenter,Normal,Radial : TZVector3f;
  Mesh : TMesh;
  LocalIndex : PIndicesArray;
  i0,i1,i2,i3 : integer;
  VQm1,VQm2,Offset : integer;
begin
  {$ifndef minimal}
  if (ZSamples<=2) or (RadialSamples<=2) then
    Exit;
  {$endif}
  Mesh := TMesh.Create(nil);
  Mesh.Style := msTris;

  ZSm1 := ZSamples-1;
  ZSm2 := ZSamples-2;
  ZSm3 := ZSamples-3;

  RSp1 := RadialSamples+1;
  VQuantity := ZSm2*RSp1 + 2;
  TQuantity := 2*ZSm2*RadialSamples;
  Mesh.CreateData(VQuantity,TQuantity);

  // generate geometry
  InvRS := 1.0/RadialSamples;
  ZFactor := 2.0/ZSm1;

  // Generate points on the unit circle to be used in computing the mesh
  // points on a cylinder slice.
  GetMem(afSin,sizeOf(single) * RSp1);
  GetMem(afCos,sizeOf(single) * RSp1);

  for R := 0 to RadialSamples-1 do
  begin
    Angle := (PI*2)*InvRS*R;
    afCos^[R] := Cos(Angle);
    afSin^[R] := Sin(Angle);
  end;

  afSin^[RadialSamples] := afSin^[0];
  afCos^[RadialSamples] := afCos^[0];

  // generate the cylinder itself
  I := 0;
  for Z := 1 to ZSm1-1 do
  begin
    ZFraction := -1.0 + ZFactor*Z;  // in (-1,1)
    fZ := Radius*ZFraction;

    // compute center of slice
    SliceCenter := Vector3f(0.0,0.0,fZ);

    // compute radius of slice
    SliceRadius := Sqrt(Abs(Radius*Radius-fZ*fZ));

    // compute slice vertices with duplication at end point
    Save := i;
    for R := 0 to RadialSamples-1 do
    begin
//      RadialFraction := R*InvRS;  // in [0,1)
      Radial := Vector3f(afCos^[R],afSin^[R],0.0);
      Mesh.Vertices^[I] := VecAdd3( SliceCenter, VecScalarMult3(Radial,SliceRadius) );

      Normal := Mesh.Vertices^[I];
      VecNormalize3(Normal);

      Mesh.Normals^[I] := Normal;
      I := I +1;
    end;

    Mesh.Vertices^[I] := Mesh.Vertices^[Save];
    Mesh.Normals^[I] := Mesh.Normals^[Save];

    I := I + 1;
  end;

  // south pole
  Mesh.Vertices^[I] := VecScalarMult3(UNIT_Z3,-Radius);
  Mesh.Normals^[I] := VecScalarMult3(UNIT_Z3,-1);

  I := I + 1;

  // north pole
  Mesh.Vertices^[I] := VecScalarMult3(UNIT_Z3,Radius);
  Mesh.Normals^[I] := UNIT_Z3;

//  Assert( i == VQuantity );

  // generate connectivity
  LocalIndex := Mesh.Indices;
  ZStart := 0;
  for Z := 0 to ZSm3-1 do
  begin
    i0 := ZStart;
    i1 := i0 + 1;
    Inc(ZStart,RSp1);
    i2 := ZStart;
    i3 := i2 + 1;
    for I := 0 to RadialSamples-1 do
    begin
      LocalIndex^[0] := i0; Inc(i0);
      LocalIndex^[1] := i1;
      LocalIndex^[2] := i2;
      LocalIndex^[3] := i1; Inc(i1);
      LocalIndex^[4] := i3; Inc(i3);
      LocalIndex^[5] := i2; Inc(i2);
      LocalIndex := @LocalIndex^[6];
    end;
  end;

  // south pole triangles
  VQm2 := VQuantity-2;
  for i := 0 to RadialSamples-1 do
  begin
    LocalIndex^[0] := i;
    LocalIndex^[1] := VQm2;
    LocalIndex^[2] := i+1;
//    Inc(LocalIndex,3);
    LocalIndex := @LocalIndex^[3];
  end;

  // north pole triangles
  VQm1 := VQuantity-1;
  Offset := ZSm3 * RSp1;
  for i := 0 to RadialSamples-1 do
  begin
    LocalIndex^[0] := VQm1;
    LocalIndex^[1] := i+offset;
    LocalIndex^[2] := i+1+offset;
    LocalIndex := @LocalIndex^[3];
  end;

//  assert( aiLocalIndex == m_aiIndex + 3*iTQuantity );
  FreeMem(afCos);
  FreeMem(afSin);

//  Mesh.Scale( Vector3f(0.25,2,1) );
  Mesh.Scale( Self.Scale );

//     Mesh.Normals := nil;
//     Mesh.ComputeNormals;

  Stack.Push(Mesh);
end;

{ TMeshNoise }

procedure TMeshNoise.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'NoiseSpeed',{$ENDIF}integer(@NoiseSpeed) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'NoiseScale',{$ENDIF}integer(@NoiseScale) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'SymmetryX',{$ENDIF}integer(@SymmetryX) - integer(Self), zptBoolean);
  List.AddProperty({$IFNDEF MINIMAL}'SymmetryY',{$ENDIF}integer(@SymmetryY) - integer(Self), zptBoolean);
  List.AddProperty({$IFNDEF MINIMAL}'SymmetryZ',{$ENDIF}integer(@SymmetryZ) - integer(Self), zptBoolean);
end;

procedure TMeshNoise.ProduceOutput(Content : TContent; Stack: TZArrayList);
var
  Mesh : TMesh;
  I : integer;
  V : TZVector3f;
  S : single;
begin
  {$ifndef minimal}
  if Stack.Count=0 then exit;
  {$endif}
  Mesh := TMesh(Stack.Pop);

  for I := 0 to Mesh.VerticesCount-1 do
  begin
    V := Mesh.Vertices^[I];
    if SymmetryX then
      V[0] := Abs(V[0]);
    if SymmetryY then
      V[1] := Abs(V[1]);
    if SymmetryZ then
      V[2] := Abs(V[2]);

    S := PerlinNoise3(
      NoiseSpeed[0]*V[0],
      NoiseSpeed[1]*V[1],
      NoiseSpeed[2]*V[2]
      );
    V[0] := 1.0 + S * NoiseScale[0];
    V[1] := 1.0 + S * NoiseScale[1];
    V[2] := 1.0 + S * NoiseScale[2];
    VecMult3(Mesh.Vertices^[I],V);
    //Mesh.Vertices^[I] := VecAdd3(Mesh.Vertices^[I],V);

    if Mesh.Normals<>nil then
    begin
      VecMult3(Mesh.Normals^[I],V);
      //Mesh.Normals^[I] := VecAdd3(Mesh.Normals^[I],V);
      VecNormalize3( Mesh.Normals^[I] );
    end;
  end;

  Mesh.Scale( Self.Scale );

  Stack.Push(Mesh);
end;

{ TMeshExpression }

procedure TMeshExpression.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'Expression',{$ENDIF}integer(@Expression) - integer(Self), zptExpression);
    {$ifndef minimal}
    List.GetLast.DefaultValue.ExpressionValue.Source := '//V : current vertex'#13#10'//N : current normal (turn off AutoNormals when modifying normals)';
    {$endif}
  List.AddProperty({$IFNDEF MINIMAL}'AutoNormals',{$ENDIF}integer(@AutoNormals) - integer(Self), zptBoolean);
    List.GetLast.DefaultValue.BooleanValue := True;
  List.AddProperty({$IFNDEF MINIMAL}'V',{$ENDIF}integer(@V) - integer(Self), zptVector3f);
    List.GetLast.NeverPersist := True;
  List.AddProperty({$IFNDEF MINIMAL}'N',{$ENDIF}integer(@N) - integer(Self), zptVector3f);
    List.GetLast.NeverPersist := True;
end;

procedure TMeshExpression.ProduceOutput(Content : TContent; Stack: TZArrayList);
var
  Mesh : TMesh;
  I : integer;
begin
  {$ifndef minimal}
  if Stack.Count=0 then exit;
  {$endif}
  Mesh := TMesh(Stack.Pop);

  for I := 0 to Mesh.VerticesCount-1 do
  begin
    VecCopy3(Mesh.Vertices^[I],V);
    VecCopy3(Mesh.Normals^[I],N);
    ZExpressions.RunCode(Expression.Code);
    VecCopy3(V,Mesh.Vertices^[I]);
    VecCopy3(N,Mesh.Normals^[I]);
  end;

  Mesh.Scale( Self.Scale );

  if AutoNormals then
    Mesh.ComputeNormals;

  Stack.Push(Mesh);
end;

{ TMeshProducer }

procedure TMeshProducer.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'Scale',{$ENDIF}integer(@Scale) - integer(Self), zptVector3f);
    List.GetLast.DefaultValue.Vector3fValue := ZMath.UNIT_XYZ3;
end;


{ TSpawnModel }


{$ifndef minimal}
const
  SpawnStyleNames : array[0..1] of string = ('Clone','Reference');
{$endif}

procedure TSpawnModel.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'Model',{$ENDIF}integer(@Model) - integer(Self), zptComponentRef);
    {$ifndef minimal}List.GetLast.SetChildClasses([TModel]);{$endif}
    {$ifndef minimal}List.GetLast.NeedRefreshNodeName := True;{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'Position',{$ENDIF}integer(@Position) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'Rotation',{$ENDIF}integer(@Rotation) - integer(Self), zptVector3f);
  List.AddProperty({$IFNDEF MINIMAL}'Scale',{$ENDIF}integer(@Scale) - integer(Self), zptVector3f);
    VecCopy3(ZMath.UNIT_XYZ3,List.GetLast.DefaultValue.Vector3fValue);
  List.AddProperty({$IFNDEF MINIMAL}'SpawnStyle',{$ENDIF}integer(@SpawnStyle) - integer(Self), zptByte);
    {$ifndef minimal}List.GetLast.SetOptions(SpawnStyleNames);{$endif}
    {$ifndef minimal}List.GetLast.NeedRefreshNodeName := True;{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'UseSpawnerPosition',{$ENDIF}integer(@UseSpawnerPosition) - integer(Self), zptBoolean);
  List.AddProperty({$IFNDEF MINIMAL}'SpawnerIsParent',{$ENDIF}integer(@SpawnerIsParent) - integer(Self), zptBoolean);
end;


procedure TSpawnModel.Execute;
var
  Spawned : TModel;
begin
  {$ifndef minimal}
  AssertNotRenderMode;
  {$endif}
  if Model<>nil then
  begin
    if SpawnStyle=ssClone then
      //Clone copy owned by app
      Spawned := TModel(Model.Clone)
    else
    begin
      //Reference to original, keep ownership
      Spawned := Model;
      Spawned.IsSpawnedAsReference := True;
    end;

    if UseSpawnerPosition and (CurrentModel<>nil) then
    begin
      VecCopy3( VecAdd3(CurrentModel.Position,Self.Position), Spawned.Position);
      //VecCopy3(CurrentModel.Position,Spawned.Position)
    end
    else
    begin
      if not VecIsNull3(Position) then
        VecCopy3(Self.Position,Spawned.Position);
    end;

    if not VecIsIdentity3(Self.Scale) then
      VecCopy3(Self.Scale,Spawned.Scale);
    if not VecIsNull3(Self.Rotation) then
      VecCopy3(Self.Rotation,Spawned.Rotation);

    if (SpawnStyle=ssReference) and Spawned.Active then
      //Do nothing: Respawning a already actice reference should not add the
      //same model instance to model-list
    else
    begin
      Spawned.Active:=True;
      ZApp.AddModel( Spawned );
      if SpawnerIsParent then
      begin
        CurrentModel.ChildModelRefs.Add(Spawned);
        Spawned.ParentModel := CurrentModel;
      end;
    end;

    //CurrentModel must be spawned
    ExecuteWithCurrentModel(Spawned,Spawned.OnSpawn);
  end;
end;

{$ifndef minimal}
function TSpawnModel.GetDisplayName: String;
begin
  Result := inherited GetDisplayName;
  if Assigned(Model) then
  begin
    Result := Result + '  ' + Model.Name;
    if Ord(SpawnStyle)<>0 then
      Result := Result + ' (' + SpawnStyleNames[integer(SpawnStyle)] + ')';
  end;
end;
{$endif}

{ TModels }

procedure TModels.Add(M: TModel);
begin
  TZArrayList(Cats[M.Category]).Add(M);
end;

procedure TModels.Remove(M: TModel);
begin
  if RemoveList.IndexOf(M)=-1 then
  begin
    M.Active := False;
    RemoveList.Add(M);

    if not ZApp.Terminating then
      ExecuteWithCurrentModel(M,M.OnRemove);

    //Also remove child models
    while M.ChildModelRefs.Count>0 do
      //Nested calls to remove will shorten list
      Remove( TModel(M.ChildModelRefs.Last) );

    //Remove reference to parent
    if M.ParentModel<>nil then
      M.ParentModel.ChildModelRefs.Remove(M);
  end;
end;

procedure TModels.ClearAll;
begin
  RemoveAll;
  FlushRemoveList;
end;

constructor TModels.Create;
begin
  Cats := TZArrayList.Create;
  RemoveList := TZArrayList.Create;
end;

destructor TModels.Destroy;
begin
  ClearAll;
  Cats.Free;
  RemoveList.Free;
  inherited;
end;

function TModels.Get(Cat: integer): TZArrayList;
begin
  Result := TZArrayList(Cats[Cat]);
end;

procedure TModels.RegisterCat(Cat: integer);
begin
  while Cats.Count<=Cat do
    Cats.Add( TZArrayList.CreateReferenced );
end;

procedure TModels.Update;
var
  I,J : integer;
  List : TZArrayList;
  M : TModel;
begin
  //Run Update and animators on all models in all categories
  for I := 0 to Cats.Count-1 do
  begin
    List := TZArrayList(Cats[I]);
    for J := 0 to List.Count-1 do
    begin
      M := TModel(List[J]);
      Meshes.CurrentModel := M;
      if M.Active then
        M.Update;
    end;
  end;
  Meshes.CurrentModel := nil;
  //Update can insert models in removelist
  FlushRemoveList;
end;

procedure TModels.RemoveAll;
var
  I,J : integer;
  List : TZArrayList;
  M : TModel;
begin
  //Add models to be removed in removelist, they are freed after next call to models.update
  for I := 0 to Cats.Count-1 do
  begin
    List := TZArrayList(Cats[I]);
    for J := 0 to List.Count-1 do
    begin
      M := TModel(List[J]);
      Remove(M);
    end;
  end;
end;

procedure TModels.FlushRemoveList;
var
  I : integer;
  M : TModel;
begin
  for I := 0 to RemoveList.Count-1 do
  begin
    M := TModel(RemoveList[I]);
    Get(M.Category).Remove(M);
    if M.IsSpawnedAsReference then
      //If referenced, remove from list to keep it from being freed below
      RemoveList[I]:=nil;
  end;
  //Emtpy removelist, instances are freed
  RemoveList.Clear;
end;

{ TRemoveModel }

procedure TRemoveModel.Execute;
begin
  {$ifndef minimal}
  AssertNotRenderMode;
  {$endif}
  ZApp.Models.Remove( Meshes.CurrentModel );
end;

{ TRemoveAllModels }

procedure TRemoveAllModels.Execute;
begin
  {$ifndef minimal}
  AssertNotRenderMode;
  {$endif}
  ZApp.Models.RemoveAll;
end;


{ TSetModelState }

procedure TSetModelState.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'State',{$ENDIF}integer(@State) - integer(Self), zptComponentRef);
    {$ifndef minimal}List.GetLast.SetChildClasses([TModelState]);{$endif}
end;

procedure TSetModelState.Execute;
var
  OldState : TModelState;
begin
  {$ifndef minimal}if (State=nil) or (CurrentModel=nil) then Exit;{$endif}
  OldState := CurrentModel.CurrentState;
  CurrentModel.CurrentState := State;
  if OldState<>nil then
    OldState.OnLeave.ExecuteCommands;
  State.OnStart.ExecuteCommands;
end;

{$ifndef minimal}
function TSetModelState.GetDisplayName: string;
begin
  Result := inherited GetDisplayName;
  if Assigned(State) then
    Result := Result + '  ' + State.Name;
end;
{$endif}


{ TModelState }

procedure TModelState.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'OnCollision',{$ENDIF}integer(@OnCollision) - integer(Self), zptComponentList);
end;

{ TMeshBox }

procedure TMeshBox.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'XCount',{$ENDIF}integer(@XCount) - integer(Self), zptInteger);
  List.AddProperty({$IFNDEF MINIMAL}'YCount',{$ENDIF}integer(@YCount) - integer(Self), zptInteger);
  List.AddProperty({$IFNDEF MINIMAL}'Grid2DOnly',{$ENDIF}integer(@Grid2DOnly) - integer(Self), zptBoolean);
end;

procedure TMeshBox.ProduceOutput(Content: TContent; Stack: TZArrayList);
var
  Mesh,SrcMesh : TMesh;
  VertP : PZVector3f;
  TexP : PZVector2f;
  IndP : PMeshVertexIndex;
  M,DefaultM,RotM : TZMatrix4f;
  IndOffset,I : integer;

  procedure InCopyTransform;
  var
    I : integer;
    SrcVP : PZVector3f;
    SrcIndP : PMeshVertexIndex;
  begin
    SrcVP := pointer(SrcMesh.Vertices);
    for I := 0 to SrcMesh.VerticesCount - 1 do
    begin
      VectorTransform(SrcVP^,M,VertP^);
      Inc(VertP);
      Inc(SrcVP);
    end;
    SrcIndP := pointer(SrcMesh.Indices);
    for I := 0 to SrcMesh.IndicesCount - 1 do
    begin
      IndP^ := SrcIndP^ + IndOffset;
      Inc(IndP);
      Inc(SrcIndP);
    end;
    Inc(IndOffset,SrcMesh.VerticesCount);
    Move(SrcMesh.TexCoords^,TexP^,SrcMesh.VerticesCount * SizeOf(TZVector2f));
    Inc(TexP,SrcMesh.VerticesCount);
  end;

begin
  Mesh := TMesh.Create(nil);
  Mesh.Style := msTris;

  if Grid2DOnly then
  begin
    //Create a simple 2d-grid
    Mesh.MakeNet(Self.XCount,Self.YCount);
    Mesh.Scale(Vector3f(2,2,1));
  end else
  begin
    SrcMesh := TMesh.Create(nil);
    SrcMesh.MakeNet( Self.XCount ,Self.YCount );

    Mesh.CreateData( (SrcMesh.VerticesCount) * 6,(SrcMesh.IndicesCount*6) div 3,True);

    VertP := pointer(Mesh.Vertices);
    TexP := pointer(Mesh.TexCoords);
    IndP := pointer(Mesh.Indices);

    IndOffset := 0;

    //MakeNet gör ett nät -0.5 till 0.5
    //Öka till -1 .. 1, samt flytta fram mot kameran
    CreateScaleAndTranslationMatrix(
      Vector3f(2,2,1),
      Vector3f(0,0,1),
      DefaultM);

    //Sides
    for I := 0 to 3  do
    begin
      CreateRotationMatrixY( (PI/2)*I ,RotM);
      M := MatrixMultiply(DefaultM,RotM);
      InCopyTransform;
    end;

    //Top and bottom
    for I := 0 to 1  do
    begin
      CreateRotationMatrixX( (PI/2) * (-1.0 + I*2) ,RotM);
      M := MatrixMultiply(DefaultM,RotM);
      InCopyTransform;
    end;

    SrcMesh.Free;
  end;

  Mesh.Scale( Self.Scale );
  Mesh.ComputeNormals;

  Stack.Push(Mesh);
end;

{ TMeshImport }

procedure TMeshImport.DefineProperties(List: TZPropertyList);
begin
  inherited;
  List.AddProperty({$IFNDEF MINIMAL}'MeshData',{$ENDIF}integer(@MeshData) - integer(Self), zptBinary);
    {$ifndef minimal}List.GetLast.IsReadOnly := True;{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'HasVertexColors',{$ENDIF}integer(@HasVertexColors) - integer(Self), zptBoolean);
    {$ifndef minimal}List.GetLast.IsReadOnly := True;{$endif}
  List.AddProperty({$IFNDEF MINIMAL}'HasTextureCoords',{$ENDIF}integer(@HasTextureCoords) - integer(Self), zptBoolean);
    {$ifndef minimal}List.GetLast.IsReadOnly := True;{$endif}
end;

procedure TMeshImport.ProduceOutput(Content: TContent; Stack: TZArrayList);
var
  Mesh : TMesh;
  VertP : PZVector3f;
  PrevIndP,IndP : PMeshVertexIndex;
  I,J : integer;
  Stream : TZInputStream;
  TriCount,VertCount : integer;
  MinV,DiffV : TZVector3f;
  W : word;
  Sm,Sm2 : smallint;
  PColor : PInteger;
  PTex : PZVector2f;
begin
  Stream := TZInputStream.CreateFromMemory(MeshData.Data,MeshData.Size);

  Stream.Read(VertCount,4);
  Stream.Read(TriCount,4);

  Mesh := TMesh.Create(nil);
  Mesh.Style := msTris;
  Mesh.CreateData( VertCount,TriCount, Self.HasTextureCoords, Self.HasVertexColors);

  Stream.Read(MinV,3*4);
  Stream.Read(DiffV,3*4);

  //Vertices
  VertP := pointer(Mesh.Vertices);
  for I := 0 to VertCount - 1 do
  begin
    for J := 0 to 2 do
    begin
      Stream.Read(W,2);
      VertP^[J] := W / High(Word);
    end;
    VecMult3(VertP^,DiffV);
    VecAdd3(VertP^,MinV,VertP^);
    Inc(VertP);
  end;

  //Indices
  IndP := pointer(Mesh.Indices);
  PrevIndP := IndP;
  Stream.Read(IndP^,2*3);
  Inc(IndP,3);
  for I := 0 to ((TriCount-1)*3)-1 do
  begin
    Stream.Read(Sm,2);
    IndP^ := PrevIndP^ + Sm;
    Inc(IndP);
    Inc(PrevIndP);
  end;

  //Vertex colors
  if Self.HasVertexColors then
  begin
    PColor := Mesh.Colors;
    Stream.Read(PColor^,VertCount * 4);
  end;

  //Texture coordinates
  if Self.HasTextureCoords then
  begin
    for J := 0 to 1 do
    begin
      PTex := pointer(Mesh.TexCoords);
      Stream.Read(Sm,2);
      PTex^[J] := Sm / High(Smallint);
      for I := 0 to VertCount-2 do
      begin
        Inc(PTex);
        Stream.Read(Sm2,2);
        Inc(Sm,Sm2);
        PTex^[J] := Sm / High(Smallint);
      end;
    end;

//    Stream.Read(PTex^,VertCount * 8);
  end;

  Mesh.Scale( Self.Scale );
  Mesh.ComputeNormals;

  Stream.Free;

  Stack.Push(Mesh);
end;

initialization

  ZClasses.Register(TMesh,MeshClassId);
    {$ifndef minimal}ComponentManager.LastAdded.AutoName := True;{$endif}

  ZClasses.Register(TMeshBox,MeshBoxClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Mesh';{$endif}
  ZClasses.Register(TMeshSphere,MeshSphereClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Mesh';{$endif}
  ZClasses.Register(TMeshNoise,MeshNoiseClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Mesh';{$endif}
  ZClasses.Register(TMeshExpression,MeshExpressionClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Mesh';{$endif}
  ZClasses.Register(TMeshImport,MeshImportClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Mesh';{$endif}
    {$ifndef minimal}ComponentManager.LastAdded.NoUserCreate := True;{$endif}

  ZClasses.Register(TModel,ModelClassId);
    {$ifndef minimal}ComponentManager.LastAdded.ImageIndex := 13;{$endif}
    {$ifndef minimal}ComponentManager.LastAdded.AutoName := True;{$endif}

  ZClasses.Register(TSpawnModel,SpawnModelClassId);
    {$ifndef minimal}ComponentManager.LastAdded.ImageIndex := 14;{$endif}
  ZClasses.Register(TRemoveModel,RemoveModelClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Model';{$endif}
    {$ifndef minimal}ComponentManager.LastAdded.ImageIndex := 15;{$endif}
  ZClasses.Register(TRemoveAllModels,RemoveAllModelsClassId);

  ZClasses.Register(TModelState,ModelStateClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Model';{$endif}
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentList := 'States';{$endif}
    {$ifndef minimal}ComponentManager.LastAdded.AutoName := True;{$endif}
  ZClasses.Register(TSetModelState,SetModelStateClassId);
    {$ifndef minimal}ComponentManager.LastAdded.NeedParentComp := 'Model';{$endif}

end.
