unit ex4;

{$mode objfpc}{$H+}

interface

{ This example demonstrate how to load 3D objects and show them with
  appropriate material and color. 
  
  The LoadObjectFromFile returns an 3D object defined by an OBJ file.
  Some things are ignored, like texture information or normal information.
  But when a material name is used, the function UseMaterial is called.  
  So here it is overriden in order to give the best material possible.
  
  Some objects have specific effects. For example, the teapot is defined
  as Biface so that the reflected light is computed outside and inside
  the glass. 
  
  The helicopter is divided into two parts. The upper part contains the
  rotor in order to rotate it, so that the helicopter seems to fly.
  
  The lamp contains in fact 4 lamps, and so 4 light sources are created. 
  
  }

uses
  Classes, SysUtils, BGRAScene3D, BGRABitmapTypes,
  BGRAOpenGL3D, BGRAOpenGL;

type

  { TExample4 }

  TExample4 = class(TBGLScene3D)
  protected
    lamp,shiny,reflect: IBGRAMaterial3D;
    rotated: IBGRAPart3D;
    rotateCenter: TPoint3D;
    message: string;
    procedure UseMaterial(materialname: string; face: IBGRAFace3D); override;
    procedure CreateScene;
  public
    procedure Render; override;
    procedure Elapse;
    procedure RenderGL(ACanvas: TBGLCustomCanvas; AMaxZ: single=1000); override;
    procedure NextModel;
    constructor Create;
  end;

implementation

uses BGRATextFX;

var
    numObj: integer= 3;

const
   objList: array[0..9] of string = ( 'ciseau.obj',
      'fourche.obj', 'pelle.obj', 'helico.obj', 'mario.obj', 'helice.obj',
      'lampe.obj', 'teapot.obj', 'roue.obj', 'trumpet.obj');

{ TExample4 }

constructor TExample4.Create;
begin
  inherited Create;

  //create shiny material using saturation of diffusion (1.3 .. 1.5)
  shiny := CreateMaterial;
  shiny.SaturationLow := 1.3;
  shiny.SaturationHigh := 1.5;
  reflect := CreateMaterial(50);
  lamp := CreateMaterial;
  lamp.LightThroughFactor := 0.05;

  CreateScene;
end;

procedure TExample4.UseMaterial(materialname: string; face: IBGRAFace3D);
var color : TBGRAPixel;
begin
  if (materialname = 'globes') then
  begin
    color := BGRA(255,240,220);
    face.Material := lamp;
  end else
  if (materialname = 'bone') then
  begin
    color := BGRA(255,240,220);
  end else
  if (materialname = 'bronze') then
  begin
    color := CSSSaddleBrown;
    face.Material := reflect;
  end else
  if materialname = 'grey' then
  begin
    color := BGRA(230,192,80);
    face.Material := shiny;
  end else
  begin
     color := StrToBGRA(materialname);
     if (objList[numObj] <> 'helice.obj') and (color.red = color.green) and (color.green = color.blue) then
     begin
       if (color.alpha <> 255) or (color.red = 0) then
         face.Material := reflect
       else
         face.Material := shiny;
     end else
       if color.alpha <> 255 then
         face.Material := reflect;
  end;
  face.SetColor( color );
end;

procedure TExample4.CreateScene;
var obj: IBGRAObject3D;
    r: single;
    i: integer;
    basePath, filename: string;
begin
  Clear;

  basePath := ExtractFilePath(Paramstr(0));
  filename := 'obj'+PathDelim+objList[numObj];
  if not fileexists(basePath+filename) and fileexists(basePath+'..'+PathDelim+'..'+PathDelim+filename) then
    filename := '..'+PathDelim+'..'+PathDelim+filename;
  filename := basePath+ filename;
  if not FileExists(filename) then
    begin
      message := 'File not found : '+ filename;
      exit;
    end;

  obj := LoadObjectFromFile(filename, objList[numObj] <> 'teapot.obj');

  if objList[numObj] = 'helico.obj' then
  begin
    with obj.MainPart do
    begin
      rotated := CreatePart;
      rotateCenter := Point3D(0,0,0);
      for i := VertexCount-1 downto 0 do
          if (Vertex[i].SceneCoord.y >= 22.2) then
          begin
            rotated.Add(Vertex[i]);
            rotateCenter.Offset(Vertex[i].SceneCoord);
          end;
      rotateCenter.Scale(1/rotated.VertexCount);
      obj.SeparatePart(rotated);
      obj.MainPart.Scale(2,2,2);
    end;
  end else
    rotated := nil;

  obj.LightingNormal := lnVertex;
  if objList[numObj] = 'teapot.obj' then
    for i := 0 to obj.FaceCount-1 do
      obj.Face[i].Biface := true;

  with obj.MainPart.BoundingBox do
    obj.MainPart.Translate((min+max)*(-1/2), False);
  r := obj.MainPart.Radius;
  if r <> 0 then obj.MainPart.Scale(40/r, False);
  if objList[numObj] = 'lampe.obj' then
  begin
    obj.MainPart.RotateXDeg(180, False);
    obj.MainPart.Scale(1.5,1.5,1.5);
  end else
  if objList[numObj] = 'mario.obj' then
    obj.MainPart.RotateXDeg(90, False)
  else
  begin
    obj.MainPart.RotateXDeg(180-20, False);
    obj.MainPart.RotateYDeg(-20, False);
    if objList[numObj] = 'trumpet.obj' then
       obj.MainPart.Scale(2,2,2,False);
  end;

  if objList[numObj] = 'lampe.obj' then
  begin
    AmbiantLightness := 0.7;
    AddPointLight(obj.MainPart.Add(0,7.7,0),10);
    AddPointLight(obj.MainPart.Add(1.9,6.5,0),10);
    AddPointLight(obj.MainPart.Add(-0.9,6.5,1.5),10);
    AddPointLight(obj.MainPart.Add(-0.9,6.5,-1.7),10);
  end
  else
  begin
    //set ambiant lightness to dark (1 is normal lightness)
    AmbiantLightness := 0.5;
    if objList[numObj] = 'helice.obj' then
      AddDirectionalLight(Point3D(1,1,1),0.75,-0.5)
    else
      AddDirectionalLight(Point3D(1,1,1),1,-0.5); //add a directional light from top-left, maximum lightness will be 0.5 + 1 = 1.5
  end;

  RenderingOptions.PerspectiveMode:= pmZBuffer;
  if objList[numObj] = 'helice.obj' then
    RenderingOptions.LightingInterpolation := liAlwaysHighQuality
  else
    RenderingOptions.LightingInterpolation := liSpecularHighQuality;
end;

procedure TExample4.Render;
var fx: TBGRATextEffect;
begin
  if objList[numObj] = 'teapot.obj' then
    Surface.GradientFill(0,0,Surface.Width,Surface.Height,BGRABlack,BGRA(70,100,100),gtLinear,PointF(0,0),PointF(0,Surface.Height),dmSet) else
  if objList[numObj] = 'lampe.obj' then
    Surface.Fill(BGRA(0,0,60));

  inherited Render;

  if message <> '' then
  begin
    fx := TBGRATextEffect.Create(message,'Arial',20,True);
    fx.DrawOutline(Surface,Surface.Width div 2,Surface.Height div 2-fx.TextHeight div 2,BGRABlack,taCenter);
    fx.Draw(Surface,Surface.Width div 2,Surface.Height div 2-fx.TextHeight div 2,BGRAWhite,taCenter);
    fx.Free;
  end else
    Surface.TextOut(Surface.Width,0,objList[numObj],BGRAWhite,taRightJustify);
end;

procedure TExample4.Elapse;
begin
  if rotated <> nil then
  begin
    rotated.Translate(-rotateCenter,false);
    rotated.RotateYDeg(20,False);
    rotated.Translate(rotateCenter,false);
  end;
end;

procedure TExample4.RenderGL(ACanvas: TBGLCustomCanvas; AMaxZ: single);
begin
  if objList[numObj] = 'teapot.obj' then
    ACanvas.FillRectLinearColor(0,0,BGLCanvas.Width,BGLCanvas.Height,
       BGRABlack,BGRABlack,
       BGRA(70,100,100),BGRA(70,100,100),
       False) else
  if objList[numObj] = 'lampe.obj' then
    ACanvas.Fill(BGRA(0,0,60));

  inherited RenderGL(ACanvas, AMaxZ);
end;

procedure TExample4.NextModel;
begin
  inc(numObj);
  if numObj = length(objList) then numObj := 0;

  CreateScene;
end;

end.

