(******************************************************************************
*                                    rtObj                                    *
******************************************************************************)
Unit rtObj;


(*
       please notice -
       this unit includes both the object3d unit, and the superobj unit,
       and it is used with no reference to the window GUI library,
       uses project3, instead of prjWind
*)

(*******************************************************************************
*                                 3D Objects                                  *
*                                 ----------                                  *
*                                                                             *
*   A 3D object is a collection of points and lines in the 3D universe,        *
*   that represent the body we want to draw on the screen.                    *
*   A 3D object is allways centered around point (0, 0, 0) in the 3D universe. *
*   (Its center of gravity assuming it is has a uniform weight distribution    *
*    is in point (0, 0, 0)                                                    *
*   At any moment during the object's life, we know it's distance from the     *
*   origin, And it's rotation using reveres rotation CTM.                      *
*                                                                             *
*   3D Object methods:                                                        *
*      constructor Open                                                       *
*      destructor  CloseMe                                                    *
*      procedures: Rotate, Scale, Move, Show, Hide, Load, Save                *
*                  SetToOrigin, Paint                                         *
*                                                                             *
*******************************************************************************)


interface
uses
    Graph
    ,Ctm3d
    ,hdr3d
    ,project3
    ;

type

    f_real = file of real;

    {===================================================================}
    {  Base object is the base class for 3D-objects. some functions    }
    { are dummy virtual do-nothing, which are are implemented only for }
    { the descendend objects derived from BaseObject                   }
    {===================================================================}

    BaseObjectPtr = ^BaseObject;
    BaseObject = object
       MyCtm       : Ctm;      { This CTM applied to the object gives the  }
                               {  objects Position after transformations   }
       Name        : String;   { Identifies the object                     }
       myColor     : word;     { Main color for the object                 }
       Location    : point3d;  { Central of gravity in real space          }
       scrPntUpdt  : boolean;  { True if screen points updated             }

       constructor open(myName : string; color : word);
       destructor  CloseMe; virtual;
       procedure   show; virtual;
       procedure   hide; virtual;
       procedure   paint; virtual; {in specified color}
       procedure   updateScreenPoints; virtual; {transform object 3D -> 2D}
       procedure   move(axis : axisType; by : real); virtual;
       procedure   translate(dx, dy, dz : integer); virtual;
               {multy dimentional move in 1 call}
       procedure   scale(axis : axisType; factor : real); virtual;
       procedure   allScale(sx, sy, sz : real); virtual;
               {multy dimentional scale in 1 call}
       procedure   rotate(axis : axisType; deg : real); virtual;

       procedure   goto3dPos(x, y, z : real); virtual; {translate to absolute place}
       procedure   setToOrigin; virtual;
               {translate to 0,0,0, update points, and set myCtm to unit}
       procedure   calcLocation; virtual; {set Location to central gravity}
       procedure   deleteTransform; virtual; {set MyCtm to unit}

       function load : word; virtual; {from disk}
       function save : word; virtual; {to   disk}
       procedure writeMe(var elementFile : f_real); virtual; {to disk .. without opening file..}
       procedure readMe(var elementFile : f_real); virtual;
    end;
    {===================================================================}
    { Obj3d is an object which represents a 3-D object with a poligon  }
    {  mesh.                                                           }
    {===================================================================}

    Obj3dPtr = ^Obj3d;
    Obj3d = object(BaseObject)
       Points      : array[1..MaxPoints] of point3d;
       Lines       : array[1..MaxLines]   of Line3d;
       scrPoints   : array[1..MaxPoints] of screenPoints;
       NumOfLines  : integer;
       NumOfPoints : integer;
       ReverseRot  : Ctm;  { Saves only the reverse rotations }
       unReverseRot: Ctm;  { reverse of the above}

       constructor open(myName : string; ref : point3d; color : word);
       destructor  CloseMe; virtual;
       procedure   paint; virtual; {in specified color}
       procedure   updateScreenPoints; virtual; {transform object 3D -> 2D}

       procedure   calcLocation; virtual; {set Location to central gravity}
       procedure   setToOrigin; virtual;

       procedure writeMe(var elementFile : f_real); virtual;
       procedure readMe(var elementFile : f_real); virtual;
    end;

const
   maxSubObjects = 15;

type
    complexObjPtr = ^complexObj;
    ComplexObj = object(BaseObject)
       childs      : array [1..maxSubObjects] of obj3dPtr;
       ctms        : array [1..maxSubObjects] of ctm;
       numOfChilds : integer; {counter of # of obj3d childs}

       constructor open(myName : string; color : word);
       destructor  closeMe; virtual;
       procedure   updateScreenPoints; virtual;
       procedure   writeMe(var elementFile : f_real); virtual;
       procedure   readMe(var elementFile : f_real); virtual;
       procedure   calcLocation; virtual;
       procedure   paint; virtual;
       procedure   move(axis : axisType; by : real); virtual;
       procedure   rotate(axis : axisType; deg : real); virtual;
       procedure   scale(axis : axisType; factor : real); virtual;

       function    addSubObject(myName : string; refPoint : point3d) : word;
       function    getChildPtr(index : integer) : obj3dPtr;
       procedure   rotateChild(child : integer; axis : axisType;
                                       deg : real);
       procedure   scaleChild(child : integer; axis : axisType;
                                       factor : real);
       procedure   moveChild(child : integer; axis : axisType;
                               by : real);
    end;


implementation


    {%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%}
    {                     BaseObject implementation                       }
    {%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%}

(*******************************************************************************
*                              BaseObject.Open                                *
*******************************************************************************)
constructor BaseObject.Open;
begin
    name      := myName;
    myColor   := color;
    location  := ZeroPoint;
    MyCtm.SetUnit;
end;

(*******************************************************************************
*                             BaseObject.CloseMe                              *
*******************************************************************************)
destructor BaseObject.CloseMe;
begin
    { I'm here so my children will Close-themselfs properly }
end;

(*******************************************************************************
*                              BaseObject.move                                *
*******************************************************************************)
procedure BaseObject.move(axis : axisType; by: real);
begin
       case axis of
               x : begin
                       myCtm.translateX(by);
                       location.x :=location.x+by;
                   end;
               y : begin
                       myCtm.translateY(by);
                       location.y :=location.y+by;
                   end;
               z : begin
                       myCtm.translateZ(by);
                       location.z :=location.z+by;
                   end;
       end; {case}
       scrPntUpdt := False;
end; {BaseObject.move}

(*******************************************************************************
*                            BaseObject.translate                             *
*******************************************************************************)
procedure BaseObject.translate(dx, dy, dz : integer);
begin
       myCtm.translate(dx,dy,dz);
       location.x :=location.x+dx;
       location.y :=location.y+dy;
       location.z :=location.z+dz;
       scrPntUpdt := False;
end; {BaseObject.translate}

{use this routine when you know you need to translate more then one axis
       before painting, you will use only one call, which will probably be
       faster, and make your program easier to read, and maintain}



(*******************************************************************************
*                              BaseObject.show                                *
*******************************************************************************)
procedure BaseObject.show;
begin
    setColor(myColor);
    paint;
end;

(*******************************************************************************
*                              BaseObject.hide                                *
*******************************************************************************)
procedure BaseObject.hide;
begin
    setColor(0); {backGround}
    paint;      {at this color}
end;

(*******************************************************************************
*                              BaseObject.Paint                               *
*******************************************************************************)
procedure BaseObject.Paint;
begin
    if (not(scrPntUpdt)) then
       updateScreenPoints;
    { alas,  BaseObject cannot really paint becuase it does not have   }
    { screen points (what a shame ...)                                 }
end;

(*******************************************************************************
*                       BaseObject.UpdateScreenPoints                         *
*******************************************************************************)
procedure BaseObject.UpdateScreenPoints;
begin
    scrPntUpdt := True; { Have no screen points or any other points, so     }
                       { points are already updated (I think)              }
end;

(*******************************************************************************
*                              BaseObject.scale                               *
*******************************************************************************)
procedure BaseObject.scale(axis : axisType; factor : real);
begin
       myCtm.translate(-location.x,-location.y,-location.z);
       case axis of
               x : myCtm.scaleX(factor);
               y : myCtm.scaleY(factor);
               z : myCtm.scaleZ(factor);
       end; {scale}
       myCtm.translate(location.x,location.y,location.z);
       scrPntUpdt := False;
end;

(*******************************************************************************
*                            BaseObject.allScale                              *
*******************************************************************************)
procedure BaseObject.allScale(sx,sy,sz : real);
begin
    myCtm.translate(-location.x, -location.y, -location.z);
    myCtm.scale(sx,sy,sz);
    myCtm.translate(location.x, location.y, location.z);
    scrPntUpdt := False;
end;
{call this routine to scale more then one axis at a time, with a single call}


(*******************************************************************************
*                            BaseObject.goto3dPos                             *
*******************************************************************************)
procedure BaseObject.goto3dPos;
begin
       translate(round(x - location.x), round(y - location.y)
                       , round(z - location.z));
end; {obj3d.goto3dPos}

(*******************************************************************************
*                           BaseObject.setToOrigin                            *
*******************************************************************************)
procedure BaseObject.setToOrigin;
begin
    goto3dPos(0, 0, 0);
    myCtm.setUnit;
    location := zeroPoint;
end; {BaseObject.setToOrign}

(*******************************************************************************
*                          BaseObject.CalcLocation                            *
*******************************************************************************)
procedure BaseObject.CalcLocation;
begin
    location := zeroPoint; { What else could it be when there are no points ?}
end;

(*******************************************************************************
*                         BaseObject.deleteTransform                          *
*******************************************************************************)
procedure BaseObject.deleteTransform;
begin
    myCtm.setUnit;
    scrPntUpdt := false;
end;

(******************************************************************************
*                              BaseObject.rotate                              *
******************************************************************************)
procedure BaseObject.rotate;
begin
       myCtm.translate(-location.x,-location.y,-location.z);
       case axis of
               x :     myCtm.rotateX(deg);
               y :     myCtm.rotateY(deg);
               z :     myCtm.rotateZ(deg);
       end; {case}
       myCtm.translate(location.x,location.y,location.z);

       {rotation means : go to origin (translate -location),
                         rotate       (rotate axis degrees),
                         go to prev pos (translate location);
       }

       scrPntUpdt := False;
end; {BaseObject.rotate, see interface for comments}

(******************************************************************************
*                               baseObject.load                               *
******************************************************************************)
function baseObject.load;
var
    elementFile : f_real;
    errC       : word;
begin
    {$i-} {supposed to be so, just making sure}
    assign(elementFile,name);
    reset(elementFile); {o.k. open it}
    errC := ioResult;
    load := errC;
    if (errC = 0) then begin
       readMe(elementFile);
       errC := ioResult;
       load := errC;
       close(elementFile);
       calcLocation;
       scrPntUpdt := false;
    end; {if}
end;

(******************************************************************************
*                               baseObject.save                               *
******************************************************************************)
function baseObject.save;
var
    elementFile : f_real;
    errC       : word;
begin
    {$i-} {supposed to be so, just making sure}
    assign(elementFile,name);
    rewrite(elementFile); {o.k. open it}
    errC := ioResult;
    save := errC;
    if (errC = 0) then begin
       writeMe(elementFile);
       errC := ioResult; save := errC;
       close(elementFile);
    end; {if}
end;

(******************************************************************************
*                             baseObject.writeMe                              *
******************************************************************************)
procedure baseObject.writeMe;
begin
end;

(******************************************************************************
*                              baseObject.readMe                              *
******************************************************************************)
procedure baseObject.readMe;
begin
end;

    {%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%}
    {                        Obj3d implementation                         }
    {%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%}


(*******************************************************************************
*                                 Obj3d.open                                  *
*******************************************************************************)
constructor Obj3d.open;
begin
    BaseObject.Open(myName, color);
    scrPntUpdt := False; {not calculated yet}
    numOfLines := 0;
    numOfPoints := 0;
    myCtm.setUnit; {initialize to unit matrix}
    reverseRot.setUnit;
    unReverseRot.setUnit;
end; {Obj3d.open}

(*******************************************************************************
*                               Obj3d.CloseMe                                 *
*******************************************************************************)
destructor Obj3d.CloseMe;
begin
end;  {Obj3d.Close}


(*******************************************************************************
*                          Obj3d.updateScreenPoints                           *
*******************************************************************************)
procedure Obj3d.updateScreenPoints;
var i : integer;
    p : point3d;
begin
    for i := 1 to numOfPoints do begin
       myCtm.transform(p,points[i]); {transform by ctm}
       calcPoint(p, scrPoints[i]);
    end; {for}
    scrPntUpdt := True; {make sure for next time..}
           {make all points ready}
end;

(*******************************************************************************
*                                Obj3d.paint                                  *
*******************************************************************************)
procedure Obj3d.paint;
{do the actual painting here}
var
    i : integer;
begin
    if ((numOfPoints = 0) or (numOfLines = 0)) then exit;
    if (not(scrPntUpdt)) then
       updateScreenPoints;
    for i := 1 to numOfLines do
       line(   scrPoints[lines[i].fromP].sX,
               scrPoints[lines[i].fromP].sY,
               scrPoints[lines[i].toP].sX,
               scrPoints[lines[i].toP].sY  );
    {it should be noted that calcPoint has to convert points to integers}
end;

(******************************************************************************
*                                obj3d.readMe                                 *
******************************************************************************)
procedure obj3d.readMe;
var
    tmp1,tmp2  : real;
    i,j        : byte;
begin
       read(elementFile, tmp1);
       numOfPoints := trunc(tmp1);
       for j := 1 to numOfPoints do begin
           read(elementFile, points[j].x);
           read(elementFile, points[j].y);
           read(elementFile, points[j].z);
       end; {for}
       read(elementFile, tmp1);
       numOfLines := trunc(tmp1);
       for j := 1 to numOfLines do begin
           read(elementFile, tmp1, tmp2);
           lines[j].fromP := trunc(tmp1);
           lines[j].toP   := trunc(tmp2);
       end; {for}
end; {obj3d.readMe}

(******************************************************************************
*                                obj3d.writeMe                                *
******************************************************************************)
procedure obj3d.writeMe;
var
    tmp1,tmp2  : real;
    i,j        : byte;
begin
       tmp1 := numOfPoints;
       write(elementFile, tmp1);
       for j := 1 to numOfPoints do begin
           write(elementFile, points[j].x);
           write(elementFile, points[j].y);
           write(elementFile, points[j].z);
       end; {for}
       tmp1 := numOfLines;
       write(elementFile, tmp1);
       for j := 1 to numOfLines do begin
           tmp1 := lines[j].fromP;
           tmp2 := lines[j].toP;
           write(elementFile, tmp1, tmp2);
       end;
end;

(*******************************************************************************
*                             obj3d.calcLocation                              *
*******************************************************************************)
procedure obj3d.calcLocation;
var
       ce : point3d;
       p  : point3d;
       i  : integer;
begin
       ce := zeroPoint; { (0, 0, 0) -> ce }
       for i := 1 to numOfPoints do begin
               myCtm.transform(p, points[i]);
               ce.x := ce.x + p.x;
               ce.y := ce.y + p.y;
               ce.z := ce.z + p.z;
       end; {for}
       location.x := ce.x / numOfPoints;
       location.y := ce.y / numOfPoints;
       location.z := ce.z / numOfPoints;
end; {obj3d.calcLocation}

(*******************************************************************************
*                             Obj3d.setToOrigin                               *
*******************************************************************************)
procedure Obj3d.setToOrigin;
var
       i : integer;
       p : point3d;
begin
    goto3dPos(0, 0, 0);
    for i := 1 to numOfPoints do begin
           myCtm.transform(p, points[i]);
           points[i] := p;
    end; {for}
    scrPntUpdt := False; (** Instead of that THING above **)
    myCtm.setUnit;
    location := zeroPoint;
end; {BaseObject.setToOrign}



(*******************************************************************************
*                              ComplexObj.Open                                *
*******************************************************************************)
constructor ComplexObj.Open;
begin
    BaseObject.Open(myName, color);
    numOfChilds := 0;
end;

(*******************************************************************************
*                             ComplexObj.CloseMe                              *
*******************************************************************************)
destructor ComplexObj.CloseMe;
var
       i : integer;

begin
       for i := 1 to numOfChilds do
               dispose(childs[i],closeMe);
       baseObject.closeMe;
end;

(*******************************************************************************
*                          ComplexObj.addSubObject                            *
*******************************************************************************)
function ComplexObj.addSubObject;
var
    ret_code : word;
begin
       if (numOfChilds >= maxSubObjects) then begin
               addSubObject := 255; {signal error}
               exit;
       end;
       inc(numOfChilds);
       childs[numOfChilds] := new(obj3dPtr, open(myName, zeroPoint, myColor));
       ret_code := childs[numOfChilds]^.load;
       if (ret_code = 0) then begin
               with refPoint do
                       childs[numOfChilds]^.translate(round(x),
                                               round(y), round(z));
               ctms[numOfChilds].copy(childs[numOfChilds]^.myCtm);
       end; {if ret_c..}
       addSubObject := ret_code;
end;

(*******************************************************************************
*                       complexObj.updateScreenPoints                         *
*******************************************************************************)
procedure complexObj.updateScreenPoints;
var
    i : integer;
begin
    for i := 1 to numOfChilds do begin
       childs[i]^.myCtm.multiply_2(ctms[i], myCtm);
       childs[i]^.updateScreenPoints;
    end;
    scrPntUpdt := True;
end; {complexObj.updateScreenPoints}

(*******************************************************************************
*                           complexObj.getChildPtr                            *
*******************************************************************************)
function complexObj.getChildPtr;
begin
       getChildPtr := childs[index];
end;

(*******************************************************************************
*                           ComplexObj.rotateChild                            *
*******************************************************************************)
procedure ComplexObj.rotateChild;
begin
    with childs[child]^ do begin
       myCtm.copy(ctms[child]);
       rotate(axis, deg);
       ctms[Child].copy(MyCtm);
       myCtm.Multiply(Self.myCtm);
    end;
end;

(*******************************************************************************
*                           ComplexObj.scaleChild                             *
*******************************************************************************)
procedure ComplexObj.scaleChild;
begin
    with childs[child]^ do begin
       myCtm.copy(ctms[child]);
       scale(axis, factor);
       ctms[Child].copy(MyCtm);
       myCtm.Multiply(Self.myCtm);
    end;
end;

(*******************************************************************************
*                            ComplexObj.moveChild                             *
*******************************************************************************)
procedure ComplexObj.moveChild;
begin
    with childs[child]^ do begin
       myCtm.copy(ctms[child]);
       move(axis, by);
       ctms[Child].copy(MyCtm);
       myCtm.Multiply(Self.myCtm);
    end;
end;

(******************************************************************************
*                              complexObj.paint                               *
******************************************************************************)
procedure complexObj.paint;
var
       i : integer;
begin
    if (not(scrPntUpdt)) then
       updateScreenPoints;
    for i := 1 to numOfChilds do
       childs[i]^.paint;
end;

procedure complexObj.writeMe;
var
       i : integer;
       r : real;
begin
       r := 0.0 + numOfChilds;
       write(elementFile, r);
       for i := 1 to numOfChilds do
               childs[i]^.writeMe(elementFile); {let all the kids write themselvs}
end;

(******************************************************************************
*                              complexObj.readMe                              *
******************************************************************************)
procedure complexObj.readMe;
var
       i : integer;
       r : real;
begin
       read(elementFile, r);
       numOfChilds := round(r);
       for i := 1 to numOfChilds do begin
              childs[i] := new(Obj3dPtr, Open('Child', ZeroPoint, myColor));
              childs[i]^.readMe(elementFile);
              childs[i]^.calcLocation;
              ctms[i].setUnit;
       end; {for}
end; {complexObj.readMe}

(******************************************************************************
*                           complexObj.calcLocation                           *
******************************************************************************)
procedure complexObj.calcLocation;
var
       i : integer;
begin
       location := zeroPoint;
       for i := 1 to numOfChilds do
               with childs[i]^ do begin
                       calcLocation;
                       self.location.x := location.x + self.location.x;
                       self.location.y := location.y + self.location.y;
                       self.location.z := location.z + self.location.z;
               end; {with}
       with location do begin
               x := x / numOfChilds;
               y := y / numOfChilds;
               z := z / numOfChilds;
       end; {with..}
end; {complexObj.calclocation}

(******************************************************************************
*                               complexObj.move                               *
******************************************************************************)
procedure complexObj.move;
var
       i : integer;
begin
(*       for i := 1 to numOfChilds do
               with childs[i]^ do
                       case axis of
                               x : location.x := location.x + by;
                               y : location.y := location.y + by;
                               z : location.z := location.z + by;
                       end; {case}  *)
       baseObject.move(axis, by);
end; {complexObj.move}

(******************************************************************************
*                              complexObj.rotate                              *
******************************************************************************)
procedure complexObj.rotate;
var
       i : integer;
       sint, cost : real;
begin
(*       cost := cos(deg / 180.0 * 3.1415926535897932385);
       sint := sin(deg / 180.0 * 3.1415926535897932385);
       for i := 1 to numOfChilds do
               with childs[i]^ do
                       case axis of
                               x : begin
                                       location.y := location.y * cost -
                                               location.z * sint;
                                       location.z := location.y * sint +
                                               location.z * cost;
                               end; {x}
                               y : begin
                                       location.x := location.x * cost +
                                               location.z * sint;
                                       location.z := location.z * cost -
                                               location.x * sint;
                               end; {y}
                               z : begin
                                       location.x := location.x * cost -
                                               location.y * sint;
                                       location.y := location.y * cost +
                                               location.x * sint;
                               end; {z}
                       end; {case} *)
       baseObject.rotate(axis, deg);
end; {complexObj.rotate}

(******************************************************************************
*                              complexObj.scale                               *
******************************************************************************)
procedure complexObj.scale;
var
       i : integer;
begin
(*       for i := 1 to numOfChilds do
               with childs[i]^ do
                       case axis of
                               x : location.x := location.x * factor;
                               y : location.y := location.y * factor;
                               z : location.z := location.z * factor;
                       end; {case} *)
       baseObject.scale(axis, factor);
end; {complexObj.scale}

end.
