unit wDraw3DFrame0;

{ Feel free to use this unit as you want.
  First developped by Bernard Gisin
  This unit uses object programming to simplify the developpment
  of programs.

  This unit helps drawing bodies (=objects), defined in the space, such as
  cubes, torres, spheres, and more complicated ones.

  The most important directions are :
  vecRight = your right, its also the horizontal of the screen.
  vecUp    = over your head, its also the vertical of the screen.
  vecFront = from the middle between your eyes, to the middle of the screen.

  The directions  (1,0,0), (0,1,0) and (0,0,1) and not natural, and can correspond
  to any directions in the space.

  Mathematics and definitions :
  """"""""""""""""""""""""""""""
  A body is given through faces.
  Each face has 3, 4 or 5 points. (Its a triangle, a quadirlatere or a 5-sides figure)
  Each face has an orientation, thus has an upper side and a lower side.

  The body is defined as an array of TypeFace.

  We want to project this body on a plan corresponding to the screen
  The plan is defined by :
  Q = (vQx, vQy, vQz) = the origine of the plan = center of the screen
  M = (vMx, vMy, vMz), such that M-Q = the normal to the plan.
  If M = Q then exit with error.

  R = (vRx, vRy, vRz)     = the right eye
  L = M - (R-M) = 2*M - R = the left eye

  Projection of the point V will be the intersection of the line R-V with the plan

  Basis of the plan :
  '''''''''''''''''''
  vecFront = Q - M normalised
  vecRight = E1
  E1 = (vE1x, vE1y, vE1z) is the projection of the vector R-M on the plan.
  If (E1 = Q) then E1 = intersection of the plan with the ground z=0
  If there is no intersection, then E1=Q + (1,0,0)

             (R-M * M-Q)
  E1 = R-M - ----------- * M-Q   normalised
              ||M-Q||^2
  E1 is orthogonal to M-Q

  vecUp = E1
  E2 = M-Q x E1  = the vector product.
  Thus (E1, E2, M-Q) is an orthogonal basis of the space.

  Plan = [ Q + u*E1 + v*E2 | u,v in Real ]

  (u,v) will be the coordinate on the screen of the projected point.

  If V = (vX, vY, vZ) is a point of the surface, the intersection of the line
  V-R with the plan will give the coordinate (u, v) on the screen

  Line = [ R + w * V-R | w in Real ]

  Thus we have to solve :
  R + w * V-R = Q + u*E1 + v*E2  to compute  (u, v)

  Scalling and shift of (u, v) is possible to fit well in the screen.

  For the left eye, replace  R  with  L  to compute  (u, v)

  The solution of the equation is :
  '''''''''''''''''''''''''''''''''
  If   D = V-R = (vDx, vDy, vDz) = (vX -vRx, vY -vRy, vZ -vRZ)
  and  S = R-Q = (vSx, vSy, vSz) = (vRx-vQx, vRy-vQy, vRz-vQz)

  The equation becomes :
  u*E1 + v*E2 - w * D - S = 0    to compute  (u, v)

  uNum:=vDx*(vSy*vE2z - vSz*vE2y) +vDy*(vSz*vE2x - vSx*vE2z) +vDz*(vSx*vE2y - vSy*vE2x);
  vNum:=vDx*(vSy*vE1z - vSz*vE1y) +vDy*(vSz*vE1x - vSx*vE1z) +vDz*(vSx*vE1y - vSy*vE1x);
  wNum:=vSx*(vE1y*vE2z- vE2y*vE1z)+vSy*(vE1z*vE2x- vE1x*vE2z)+vSz*(vE1x*vE2y- vE1y*vE2x);
  Den :=vDx*(vE1y*vE2z -vE2y*vE1z)+vDy*(vE1z*vE2x -vE1x*vE2z)+vDz*(vE1x*vE2y -vE1y*vE2x);
  u   := uNum / Den;
  v   :=-vNum / Den;
  w   :=-wNum / Den;

  If the denominator  Den = 0  then the projection line is paralelle to the plan.
  Then taking  (u, v) = (uNum, vNum) * big_number should be ok.

  Many optimisations could be done, but the slowest part is the drawing,
  internal to Windows. DirectX should be used...
  but I don't know how to use it.
----------------------------------}

// To tell FreePascal that the application is a Graphical User Interface
// i.e. is running in windows, not in text mode.
{$APPTYPE GUI}

INTERFACE
//"""""""

uses wDialog0, wControl0, wWindowBase0, xVectR3, wGlobals2, windows;

type  SingleType = Float;

// Possible errors
const
  errDraw3DNoInputs        = 1;
  errDraw3DMemoryOverflow  = 2;

type
  PTypePoint3D = ^TypePoint3D;
  TypePoint3D = record
    vecV      : RVectR3Float; // A point in the 3D-space
    vDistPt   // distance of the point from the viewer. i.e. from (vMx,vMy,vMz)
              : FloatS;
    nU,nV     // the projection of the point on a plan (directly in pixels)
              : integer
    end; // TypePoint3D

    TArrayPoint3D = array[0..100000000] of TypePoint3D; // only pointer on it will be used
    PArrayPoint3D = ^TArrayPoint3D;

type
  TypeFace3D = record
    nNbPoint : integer; // 3, 4 or 5 points, i.e. the face has 3, 4 or 5 sides
    aprPoint : array[1..5] of PTypePoint3D; // points forming the face

    bReserved, // to have an even number of bytes for one face
    bBody    : byte;
    // Index number in  papoBodies  defining to which body this face belongs.
    // bBdoy = 0 is the default, if nothing else is specified.

    vDistF    : FloatS;
              // distance from the viewer, i.e. the (vMx,vMy,vMz) point
              // the values will change while as the viewer moves
    end; // TypeFace3D

    TArrayFace3D = array[0..100000000] of TypeFace3D;
    PArrayFace3D = ^TArrayFace3D;

    TArrayWord3D = array[1..100000000] of word32;
    PArrayWord3D = ^TArrayWord3D;

type
  PDraw3DFrame0 = ^TDraw3DFrame0;

  PBody3D0 = ^TBody3D0;
  TBody3D0 = object
  //"""""""""""""""
  poDraw : PDraw3DFrame0;
  // Pointer on the main 3D-drawing object.

  wIndBody : word32;
  // Index of the body in the  poDraw^.papoBodies^  array, i.e.
  // poDraw^.papoBodies^[wIndBody] = @Self

  wIndPtsMin, wIndPtsMax : Word32;
  // parPoints^[wIndPtsMin..wIndPtsMax] are all the points of this body

  wIndFaceMin, wIndFaceMax : Word32;
  // parFaces^[wIndFaceMin..wIndFaceMax] are all the faces of this body

  vecGravityCenter : RVectR3Float;
  // Center of gravity of this body

  vecBodyVertZ, vecBodyFrontY, vecBodyHorizX : RVectR3Float;
  // each Body has an orientation.
  // The three vectors form an orthonormal right basis
  // It can be usefull when rotating the body

  vBodySize : Float;
  // Distance in centimeters between the center of gravity and
  // the farthest point.

  nGCU,nGCV : int32;
  // Projection on the screen of the center of gravity

  fIsVisible : boolean;
  // If set to FASE, the object cannot be seen, thus is disapear

  fIsShowBodyAxis : boolean;
  // Show or hide the body axis, set in its center of gravity, lengnth = vBodySize

  fUniColor : boolean;
  // = TRUE, if only one color is used.

  ColorSup,
  ColorInf,
  ColorAx
             : TypeColor;
  // Colors of the body
  // ColorSup.wColor is the 32 bits color definition
  // ColorSup.bRed, ColorSup.bGreen, ColorSup.bBlue are the RGB colors

  hPenSup,
  hPenInf,
  hPenAx    : HPEN;
  // Color of the outline of the faces.
  // If set to a different color than the  three standard colors
  // poDraw^.hPenSuperior, poDraw^.hPenInferior and poDraw^.hPenAxes
  // then the pen will be destroyed at the Done.

  strBodyName : string[100];
  // The body can have a name, to reconise it.

  pUserData : Pointer;
  // = NIL by default. Can be used by another application to store more info
  // Will never be used internally.

  constructor Init(poDrawP:PDraw3DFrame0);
  destructor  Done; virtual;

  procedure DestroyPens(wFlags:word); virtual;
  // This destroyes the pens, but not if they come from the standard once
  // wFlags is a  "or"  combinaison of :
  // 1 to destroy the hPenSup
  // 2 to destroy the hPenInf
  // 4 to destroy the hPenAx


  procedure BodySetup(wIndBodyP, wIndPtsMaxP, wIndFaceMaxP:Word32); virtual;
  // After the points and faces has been defines,
  // Calling this procedure with  wIndPtsMaxP=0  and  wIndFaceMaxP=0
  // Sets the values of  wIndPtsMax  and  wIndFaceMax
  // to the actual values of points and faces defines, and
  // computes the center of gravity of the Body.
  // If other values are given to  wIndPtsMaxP and  wIndFaceMaxP, then
  // they sets the values of       wIndPtsMax  and  wIndFaceMax
  // wIndBodyP determines the index of this body in the  papoBodies^  array, i.e.
  // papoBodies^[wIndBodyP]:=@Self
  // If it is set to  0  then it is assumed that  wIndBody:=wNumBodies

//  procedure Create(wIndPtsMinP,wIndPtsMaxP,wIndFaceMinP,wIndFaceMaxP:word32); virtual;
  // Defines which points and faces belongs to the body

  procedure ComputeGravityCenter(wIndPtsMinP,wIndPtsMaxP:Word32);
  // Memorise in  vecGravityCenter  the center of gravity the points
  // Between  wIndPtsMinP  and  wIndPtsMaxP.
  // if both are set to zero, then they are set to  wIndPtsMin  and  wIndPtsMax
  // This allow to restrict the point used to compute the center of gravity.

  procedure BodyResize(vSizeFactor:Float); virtual;
  // Resize the body by the given factor

  procedure BodyTranslate(vecTrans:RVectR3Float); virtual;
  // Translate the body

  procedure BodyRotateGC(vecAxe:RVectR3Float; vAngle:Float); virtual;
  // Rotate the body around the  vecAxe  by an angle of  vAngle radian
  // Center = center of gravity

  procedure BodyRotateVec(vecOrg,vecAxe:RVectR3Float; vAngle:Float); virtual;
  // Rotate the body around the  vecAxe  by an angle of  vAngle radian
  // Rotation center = vecOrg

  procedure BodySetPosition(vecNewPosition:RVectR3Float); virtual;
  // Moves the Body, such that its center of Gravity is at the
  // vecNewPosition

  procedure BodySetOrientation(vecNewVertZ,vecNewHorizX:RVectR3Float); virtual;
  // Rotate the Body around its center of Gravity, such that
  // vecBodyVertZ = Normalised(vecNewVertZ)  and
  // angle( vecBodyHorizX,
  //        vecNewHorizX projected on the plan normal to  vecBodyVertZ) = 0
  // vecBodyFrontY = vecBodyVertZ x vecBodyHorizX  as usual.

  procedure BodyDrawAxis; virtual;
  // Draw the axis of the Body, using the hDCDraw of poDraw.
  end; // TBody3D0 = object

  PArrayPBody3D = ^TArrayPBody3D;
  TArrayPBody3D = array[0..250] of PBody3D0;
////////////////////////////////////////////////////////////////////////////

  TDraw3DFrame0 = object(TControl)
  //""""""""""""""""""""""""""""""
  // Handle on the window. (=0 when not created)
//  hWin     : HWND;  // Handle on the window, got from TWindowBase
//  hParent : HWND;  // Handle on the parent, got from TWindowBase
//  poParent: PWindowBase0;  // Pointer on the parent, got from TWindowBase

  parPoints : PArrayPoint3D;
  // Points defining the tops of the bodies.

  wMaxPoints, // maximum number of points allocated in the array
  wNumPoints  // 1 + number of points defined in the array
              // = maximum index used in the array  parPoints
              : word32;

  parFaces : PArrayFace3D;
  // Faces of the bodies.

  pawSort : PArrayWord3D;
  // pawFaceNum^[pawSort^[ww]], ww=1..wNumFaceNum are sorted.

  wMaxFaces,  // maximum number of faces allocated in the array
  wNumFaces   // 1 + number of faces defined in the array.
              // = maximum index used in the array parFaces
              : word32;

  apoBodies : TArrayPBody3D;
  // Array of pointer on bodies.
  // Do not use it, but use the  papoBodies.

  papoBodies : PArrayPBody3D;
  // Pointer on the previous array. Use this array.
  // This will allow to change the number of bodies allocated, if
  // 250 is not enough.

  wNumBodies : word;
  // Number of bodies defines.

  oBodyDefault : TBody3D0;
  // The body  apoBodies[0] = @oBodyDefault  is the default one

  wBodySelected : Word;
  // The last body selected. To act on it

  fView3DWithGalsses : boolean;
  // If set to TRUE, then using glasses with two colors, you can
  // See the object in three dimensions.

  fView3DAxis : boolean;
  // Determine if the little axis are seen on the upper left of the screen

  vecRotationCenter, // = Center of rotation
  vecQ,       // Position of the center of the screen in the space.
  vecM        // such that M-Q = the normal to the plan.
              // If M = Q then exit with error.
              // All units = centimeters
              : RVectR3Float;
  // All vectors are in the same units as the one given by the user for
  // defining the position of the bodies. This means,
  // multiplying the vetors by  vOneToCentimeters  gives units in centimeters.

  vEyesDistance : Float;
  // Distance between both eyes over the distance from  vecM  to  vecQ
  // Units are centimeters.
  // The distance is about 5 centimeters.

  vMQDistance : Float;
  // Distance between  M  and  Q  in centimeters
  // Its our distance from the screen. It's about 80 centimeters

  vCentimetersToPixels : Float;
  // Is the number of pixels in one centimeter.

  vZoomFactor : Float;
  // Should be equal to one, to respect the dimensions, but
  // to fit the body on the screen, this can be changed

  vAngleRotation : Float;
  // Angle of rotation in degrees around the bodies.
  // Set to 5 degrees by default

  vAngleRotationHead : Float;
  // Angle of rotation in degree of the head. It is usually less than
  // the angle of rotation around the Bodies

  vDeltaTranslation : Float;
  // Number of centimeters when moving

  ColorSuperior,
  ColorInferior,
  ColorAxes,
  colorRightEye, // if view in 3D with glasses
  colorLeftEye   // if view in 3D with glasses
             : TypeColor;
  // ColorSuperior.wColor is the 32 bits color definition
  // ColorSuperior.bRed, ColorSuperior.bGreen, ColorSuperior.bBlue
  // are the RGB colors

  awCustomColors : array[1..16] of TypeColor;
  // To have some custom colors which can be choosen from a dialog box

  hDCDraw : HDC;
  // Device context of the window, where the surface is drawn

   hPenSuperior,  // color of the superior face
   hPenInferior,  // color of the inferior face
   hPenAxes,      // color of the X - Y axis
   hPen3DRightEye, // if view in 3D with glasses
   hPen3DLeftEye,  // if view in 3D with glasses
   hPenBlack      // To delete the lines passing behind another one
                  : HPEN;

   colorBlack,  // Window color, usually black, but can be choosen
   colorFaces   // color of the faces
             : TypeColor;

   hBrushBlack,   // To remember the handle on the black brush
   hBrushFaces    // Color of the faces. Black by default
                  : HBRUSH;

  // (vecRight, vecFront, vecUp) is a right oriented orthonomal basis of the space
  // vecFront = vecUp x vecRight  (Cross product)
  // where the screen correspond to the surface generated by  vecUp  and  vecRight
  // Plan of the screen = { vecQ + a*vecRight + b*vecUp | a,b in Reals }
  vecFront,   // = viewing direction = direction of vecQ - vecM
              // Normalised vector
  vecRight,   // First vector basis of the screen
              // Makes an angle of  vEyesAngle  degrees with the
              // horizontal of the screen
              // = moving direction when going "Right"
              // Normalised vector
  vecUp,      // Second vector basis of the screen
              // Makes an angle of  vEyesAngle  degrees with the
              // vertical of the screen
              // = moving direction when going "Up"
              // Normalised vector
  vecEye      // Exact position of one eye
              // Only used if vision in 3D with glasses.
              // Units = centimeters
              : RVectR3Float;

  vUX, vUY, vUZ, // units = centimeters
  vVX, vVY, vVZ, // units = centimeters
  vDenX, vDenY, vDenZ // no units, its a quotient of two length.
              : Float;

  nXProjCenter, nYProjCenter : int32;
  // = Center of the screen.
  // Used such that the center of the window correspond to the the point vecQ

  vAngleWithVertical, // Angle in degrees between the vecUp and Z axis
  vAngleDirection, // Angle in degrees between the vecFront and Y axis
  vAngleWithHorizontal  // Angle in degrees with the horizontal
               : Float;

  poDialogBoardTable : PDialog;
  // Pointer on the dialog board table, to update it.

  rPtLastClick : POINT;
  // Last position where we clicked, in client coordonate.

  wSlowDrawing, wSlowDrawingMem : word32;
  // To understand the drawing of the bodies, slow down the speed at
  // which the drawing is done

  constructor Init(poParentW:PWindowBase0);
  destructor  Done; virtual;

  procedure CtrlCreate; virtual;
  //============================
  // Create the window of the control.
  // Must have a parent object window, if not, nothing happens.

  procedure AllocateMemory(wMaxPointsP,wMaxFacesP:word32; var wErrorV:word32);
  //==========================================================================
  // Allocate memory, for the points and the faces

  procedure Draw3DSetup(vMx,vMy,vMz,vQx,vQy,vQz:Float); virtual;
  //======================================================
  // Must be called, after the CtrlCreate, to define the initial directions.
  // (vMx,vMy,vMz) = where you are, in [cm]
  // (vQx,vQy,vQz) = where the center of the screen is, in [cm]
  // This will also determine the distance between you and the screen.
  // If this distance is less than 1 [cm], then a default values are taken.

  procedure ComputeProjectionMatrix; virtual;
  // Determination of the orthogonal projection on the plane p1*x+vP2*y+vP3*z=0

  procedure projectOnePoint(vecV:RVectR3Float; var nUV,nVV:int32);
  // Projection of the point  vecV  on the screen, in pixel coordinate

  procedure projectAllPoints;
  // Projection of all the points

  procedure AutoCenter;
  // compute the origine of the projection plane  "Q"  to be
  // on the center of all the points

//  procedure AutoZoom;
  // to set the zoom factor such that the image take all the screen

  procedure QSort(nMin,nMax:int32);
  // Sort the faces from  nMin  to  nMax,  using vDistF as the criterium,
  // do it recursively.
  // We use quicksort, the fastest sorting algorithme.

  procedure SortAllFaces;
  // Sort the faces, from the farthest to the nearest

  procedure DrawBodies;
  // Drawing of the bodies, their projoection

  procedure DrawXYZAxis;
  // Drawing of a little Axis in the top left of the screen

  procedure Draw3DBodies; virtual;
  //==============================
  // Draw all the bodies.
  // Call it when you added a new body, to see it

  procedure Visualise(wPar:WPARAM; wFlagsP:word32); virtual;
  //========================================================
  // To rotate around the surface
  // wFlags indicate if Control and/or Shift are pressed.
  // = 1 => control pressed
  // = 2 => shift pressed
  // = 3 => both pressed

  procedure SetScreenCenter(vecNewCenter:RVectR3Float);
  //===================================================
  // Call it when you want to set the the screen center

  procedure SetViewDirection(vecNewPlace:RVectR3Float);
  //===================================================
  // Call it when you want to set the viewing direction
  // The viewing direction will be from  vecM  to vecNewPlace
  // Without modifying the viewer position : vecM
  // It will modify  vecQ  and the vectors  vecRight, vecFront, vecUp

  procedure DeleteBody(wIndexL:word);
  // Delete the body  papoBodies^[wIndexL]  and
  // delete the corresponding points and faces.
  // This will shift the points, faces and body in the array.
  // dispose the deleted body.

  procedure EraseBackground;
  //========================
  // Erase the background of the window

  procedure ButtonClicked(poCtrl:PWindowBase0; wCtrlIDP:Word32); virtual;

//  procedure CtrlVarUpdate(poCtrl:PWindowBase0; pVar:pointer); virtual;
  // A position or direction has been changed in the dialog box

  procedure AskAcceptDlgQuit(poDlg:PWindowBase0; wCtrlIDP:word32; var wRep:word32); virtual;
  // The board table dialog box is closing.

  function NumberOfVisibleBodies(var wBodyLastV:word):word; virtual;
  // Return the number of visible bodies.
  // wBodyLastV = the last visible body of the list

  function NearestBody(nXPosPixP,nYPosPixP:integer):word; virtual;
  // return the index of the body which center of gravity projection is the
  // nearest from (nXPosPixP, nXPosPixP)
  // Return zero, is no body is defined.

  function NearestBody(vecPosP:RVectR3Float):word; virtual;
  // return the index of the body which center of gravity is the
  // nearest from the given vector  vecPosP

  procedure SelectBody(nXPosPixP,nYPosPixP:integer); virtual;
  // Sets the  wBodySelected  to the nearest body from the given position,
  // and show the selected Body in the status line
  // wBodySelected=0, if no body is selected.

  procedure SelectBody(vecPosP:RVectR3Float); virtual;
  // Sets the  wBodySelected  to the nearest body which center of gravity is the
  // nearest from the given vector  vecPosP
  // wBodySelected=0, if no body is selected.

  procedure WMContextMenu(lPar:LPARAM); virtual;
  // Received if right click. It will open a contextual menu

  procedure WMPaint; virtual;
  // Paint the popup window

  function WMCommand(wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
  // Handles the menu messages

  function CtrlWindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
  // Handles the window messages
  // A SendMessage(hWin, ...) must not be called from here,
  // to avoid calling loops. (SendMessage(hWin...) calls this function

  end; // TDraw3DFrame0 = object(TControl)
  TDraw3DFrame =  TDraw3DFrame0;
  PDraw3DFrame = ^TDraw3DFrame;

IMPLEMENTATION
//""""""""""""

uses wWindow0, wMenus0, wVariableNumLink2, wMessages2,
     wDialogCreatorLOW2, wUtiles0, wDebug0;

var glnNotUsed : integer; // To avoid an error in the DevPascal 1.9 environment

//***************************************************
// TBody3D0 = object
//***************************************************

constructor TBody3D0.Init(poDrawP:PDraw3DFrame0);
//===============================================
begin
poDraw:=poDrawP;

wIndBody:=0;

wIndPtsMin :=poDraw^.wNumPoints+1; // Usually, this is the right number
wIndPtsMax :=poDraw^.wNumPoints; // This must be set after defining the points and faces
// parPoints^[wIndPtsMin..wIndPtsMax] are all the points of this body

wIndFaceMin :=poDraw^.wNumFaces+1; // Usually, this is the right number
wIndFaceMax :=poDraw^.wNumFaces; // This must be set after defining the points and faces

vecGravityCenter :=V3Assign(0,0,0);
nGCU:=0;
nGCV:=0;

// Default orientation vector
vecBodyVertZ  :=V3Assign(0,0,1);
vecBodyFrontY :=V3Assign(0,1,0);
vecBodyHorizX :=V3Assign(1,0,0);
vBodySize :=0;

fIsShowBodyAxis:=FALSE;
fIsVisible:=TRUE;
fUniColor :=FALSE;

// Set the axis view the same as the selected body
if (poDraw^.wBodySelected > 0) then
   fIsShowBodyAxis:=poDraw^.papoBodies^[poDraw^.wBodySelected]^.fIsShowBodyAxis;

ColorSup :=poDraw^.ColorSuperior;
ColorInf :=poDraw^.ColorInferior;
ColorAx  :=poDraw^.ColorAxes;

hPenSup :=0;
hPenInf :=0;
hPenAx  :=0;

strBodyName:=''; // No names by default
pUserData:=NIL;
end;  // TBody3D0.Init

destructor TBody3D0.Done;
//=======================
begin
DestroyPens(7);
end;  // TBody3D0.Done

procedure TBody3D0.DestroyPens(wFlags:word);
//==========================================
// This destroyes the pens, but not if they come from the standard once
// wFlags is a  "or"  combinaison of :
// 1 to destroy the hPenSup
// 2 to destroy the hPenInf
// 4 to destroy the hPenAx
begin
if ((wFlags and 1) = 1) and
   (hPenSup <> 0)                    and (hPenSup <> poDraw^.hPenSuperior) and
   (hPenSup <> poDraw^.hPenInferior) and (hPenSup <> poDraw^.hPenAxes) then
   DeleteObject(hPenSup);

if ((wFlags and 2) = 2) and
   (hPenInf <> 0)                    and (hPenInf <> poDraw^.hPenSuperior) and
   (hPenInf <> poDraw^.hPenInferior) and (hPenInf <> poDraw^.hPenAxes) then
   DeleteObject(hPenInf);

if ((wFlags and 4) = 4) and
   (hPenAx <> 0)                    and (hPenAx <> poDraw^.hPenSuperior) and
   (hPenAx <> poDraw^.hPenInferior) and (hPenAx <> poDraw^.hPenAxes) then
   DeleteObject(hPenAx);
end;  // TBody3D0.DestroyPens

procedure TBody3D0.BodySetup(wIndBodyP, wIndPtsMaxP, wIndFaceMaxP:Word32);
//========================================================================
// After the points and faces has been defines,
// Calling this procedure with  wIndPtsMaxP=0  and  wIndFaceMaxP=0
// Sets the values of  wIndPtsMax  and  wIndFaceMax
// to the actual values of points and faces defines, and
// computes the center of gravity of the Body.
// If other values are given to  wIndPtsMaxP and  wIndFaceMaxP, then
// they sets the values of       wIndPtsMax  and  wIndFaceMax
// wIndBodyP determines the index of this body in the  papoBodies^  array, i.e.
// papoBodies^[wIndBodyP]:=@Self
// If it is set to  0  then it is assumed that  wIndBody:=wNumBodies
var ww : word32;
begin
if (wIndBodyP = 0) then wIndBody :=poDraw^.wNumBodies
else                    wIndBody :=wIndBodyP;

if (wIndPtsMaxP = 0) then wIndPtsMax :=poDraw^.wNumPoints
else                      wIndPtsMax :=wIndPtsMaxP;

if (wIndFaceMaxP = 0) then wIndFaceMax :=poDraw^.wNumFaces
else                       wIndFaceMax :=wIndFaceMaxP;

// Defines that the faces are linked to this Body
for ww:=wIndFaceMin to wIndFaceMax do poDraw^.parFaces^[ww].bBody:=Byte(wIndBody);

// To be sure that they are normalised
if (vecBodyVertZ = gglvecV3Zero) then vecBodyVertZ:=gglvecV3AxisZ;
V3Normalise(vecBodyVertZ);
V3Normalise(vecBodyFrontY);
V3Normalise(vecBodyHorizX);

// If its orientation axis are not othogonal, set them orthogonal
if (abs(vecBodyVertZ  * vecBodyFrontY) > 0.001) or
   (abs(vecBodyVertZ  * vecBodyHorizX) > 0.001) or
   (abs(vecBodyHorizX * vecBodyFrontY) > 0.001) then begin
   vecBodyHorizX:=vecBodyVertZ ** gglvecV3AxisZ;
   if (vecBodyHorizX = gglvecV3Zero) then vecBodyHorizX:=gglvecV3AxisX;

   V3Normalise(vecBodyHorizX);
   vecBodyFrontY:=vecBodyVertZ ** vecBodyHorizX; // It is automatic normalised
   end;

// Compute the center of gravity, and the typical size of the Body
ComputeGravityCenter(0,0);

// Create new pens to draw the bodies
if (hPenSup = 0) then hPenSup :=CreatePen(PS_SOLID, 1, ColorSup.wColor);
if (hPenInf = 0) then hPenInf :=CreatePen(PS_SOLID, 1, ColorInf.wColor);
if (hPenAx  = 0) then hPenAx  :=CreatePen(PS_SOLID, 1, ColorAx.wColor);

// This last created body, is the selected body.
// And show it in the status bar
poDraw^.wBodySelected:=wIndBody;
PWindow( gglpoMainWindow)^.poStatusLine^.SetStatusText(0,
         GetMsg(2100020005) + strBodyName);
end;  // TBody3D0.BodySetup

procedure TBody3D0.ComputeGravityCenter(wIndPtsMinP,wIndPtsMaxP:Word32);
//======================================================================
// Memorise in  vecGravityCenter  the center of gravity the points
// Between  wIndPtsMinP  and  wIndPtsMaxP.
// if both are set to zero, then they are set to  wIndPtsMin  and  wIndPtsMax
// This allow to restrict the point used to compute the center of gravity.
var
  ww          : word32;
  vDistL      : Float;

begin
if (wIndPtsMinP = 0) and (wIndPtsMaxP = 0) then begin
   wIndPtsMinP:=wIndPtsMin;
   wIndPtsMaxP:=wIndPtsMax;
   end;

vecGravityCenter :=V3Assign(0,0,0);
for ww:=wIndPtsMinP to wIndPtsMaxP do
   vecGravityCenter:=vecGravityCenter + poDraw^.parPoints^[ww].vecV;

if (wIndPtsMaxP > wIndPtsMinP) then
   vecGravityCenter:=vecGravityCenter / (1+wIndPtsMaxP - wIndPtsMinP);

// Distance in centimeters between the center of gravity and the farthest point.
vBodySize:=0;
for ww:=wIndPtsMinP to wIndPtsMaxP do begin
   vDistL:=Norme(poDraw^.parPoints^[ww].vecV - vecGravityCenter);
   if (vBodySize < vDistL) then vBodySize:=vDistL;
   end;

end;  // TBody3D0.ComputeGravityCenter

procedure TBody3D0.BodyResize(vSizeFactor:Float);
//===============================================
// Resize the body by the given factor
var ww : Word32;
begin
for ww:=wIndPtsMin to wIndPtsMax do begin
    poDraw^.parPoints^[ww].vecV:= vecGravityCenter +
               vSizeFactor * (poDraw^.parPoints^[ww].vecV - vecGravityCenter);
    end; // for

vBodySize:=vBodySize * vSizeFactor;
end;  // TBody3D0.BodyResize

procedure TBody3D0.BodyTranslate(vecTrans:RVectR3Float);
//===============================================================
// Translate the body
var ww : Word32;
begin
for ww:=wIndPtsMin to wIndPtsMax do begin
    poDraw^.parPoints^[ww].vecV:= poDraw^.parPoints^[ww].vecV + vecTrans;
    end; // for

// Its gravity center moves to
vecGravityCenter:=vecGravityCenter + vecTrans;
end;  // TBody3D0.BodyTranslate

procedure TBody3D0.BodyRotateGC(vecAxe:RVectR3Float; vAngle:Float);
//=================================================================
// Rotate the body around the  vecAxe  by an angle of  vAngle radian
var ww : Word32;
begin
for ww:=wIndPtsMin to wIndPtsMax do begin
    poDraw^.parPoints^[ww].vecV:=
       V3RotationVec(poDraw^.parPoints^[ww].vecV, vecGravityCenter, vecAxe, vAngle);
    end; // for

// Its orientation rotate to
vecBodyVertZ := V3RotationOrg(vecBodyVertZ,  vecAxe, vAngle);
vecBodyFrontY:= V3RotationOrg(vecBodyFrontY, vecAxe, vAngle);
vecBodyHorizX:= V3RotationOrg(vecBodyHorizX, vecAxe, vAngle);
end;  // TBody3D0.BodyRotateGC

procedure TBody3D0.BodyRotateVec(vecOrg,vecAxe:RVectR3Float; vAngle:Float);
//=========================================================================
// Rotate the body around the  vecAxe  by an angle of  vAngle radian
// Rotation center = vecOrg
var ww : Word32;
begin
for ww:=wIndPtsMin to wIndPtsMax do begin
    poDraw^.parPoints^[ww].vecV:=
       V3RotationVec(poDraw^.parPoints^[ww].vecV, vecOrg, vecAxe, vAngle);
    end; // for

// Its orientation rotate to
vecBodyVertZ := V3RotationOrg(vecBodyVertZ,  vecAxe, vAngle);
vecBodyFrontY:= V3RotationOrg(vecBodyFrontY, vecAxe, vAngle);
vecBodyHorizX:= V3RotationOrg(vecBodyHorizX, vecAxe, vAngle);

// Its gravity center moves to
vecGravityCenter:= V3RotationVec(vecGravityCenter, vecOrg, vecAxe, vAngle);
end;  // TBody3D0.BodyRotateVec

procedure TBody3D0.BodySetPosition(vecNewPosition:RVectR3Float);
//==============================================================
// Moves the Body, such that its center of Gravity is at the
// vecNewPosition
begin
BodyTranslate(vecNewPosition - vecGravityCenter);
end;  // TBody3D0.BodySetPosition

procedure TBody3D0.BodySetOrientation(vecNewVertZ,vecNewHorizX:RVectR3Float);
//===========================================================================
// Rotate the Body around its center of Gravity, such that
// vecBodyVertZ = Normalised(vecNewVertZ)  and
// angle( vecBodyHorizX,
//        vecNewHorizX projected on the plan normal to  vecBodyVertZ) = 0
// vecBodyFrontY = vecBodyVertZ x vecBodyHorizX  as usual.
var
  ww           : Word32;
  vecNewFrontY : RVectR3Float;
begin
// Set input Data to orthonormal values
if (vecNewVertZ = gglvecV3Zero) then exit; // Bad rotation data

V3Normalise(vecNewVertZ);

vecNewHorizX:=vecNewHorizX - (vecNewHorizX * vecNewVertZ) * vecNewVertZ;
if (vecNewHorizX = gglvecV3Zero) then exit; // Bad rotation data

V3Normalise(vecNewHorizX);

vecNewFrontY:=vecNewVertZ ** vecNewHorizX;

// Here vecNewHorizX, vecNewFrontY and vecNewVertZ are orthonormal.

// If modification is too small, do nothing.
if (Norme(vecNewVertZ  - vecBodyVertZ)  <= 0.1) or
   (Norme(vecNewHorizX - vecBodyHorizX) <= 0.1) then exit; // modif too small

// rotation of the Body
with  poDraw^ do for ww:=wIndPtsMin to wIndPtsMax do with parPoints^[ww] do begin
   vecV:=vecV - vecGravityCenter;
   vecV:= vecGravityCenter +
          (vecBodyHorizX * vecV) * vecNewHorizX +
          (vecBodyFrontY * vecV) * vecNewFrontY +
          (vecBodyVertZ  * vecV) * vecNewVertZ;
   end;  // with ... for

vecBodyHorizX :=vecNewHorizX;
vecBodyFrontY :=vecNewFrontY;
vecBodyVertZ  :=vecNewVertZ;
end;  // TBody3D0.BodySetOrientation

procedure TBody3D0.BodyDrawAxis;
//==============================
// Draw the axis of the Body, using the hDCDraw of poDraw.
var
  aptP  : array[1..20] of POINT;
  avecP : array[1..20] of RVectR3Float;
  ww    : word;
begin
if not fIsShowBodyAxis then exit;

// Define the points to be projected
avecP[ 1]:=V3Assign(-0.2, 0.0, 1.2);  // Z
avecP[ 2]:=V3Assign( 0.2, 0.0, 1.2);
avecP[ 3]:=V3Assign(-0.2, 0.0, 1.0);
avecP[ 4]:=V3Assign( 0.2, 0.0, 1.0);
avecP[ 5]:=V3Assign( 0.0, 0.0, 1.0);
avecP[ 6]:=V3Assign( 0.0, 0.0, 0.0);
avecP[ 7]:=V3Assign( 0.0, 1.0, 0.0); // Y
avecP[ 8]:=V3Assign( 0.0, 0.6, 0.0);
avecP[ 9]:=V3Assign(-0.2, 1.0, 0.0);
avecP[10]:=V3Assign( 0.0, 0.6, 0.0);
avecP[11]:=V3Assign( 0.0, 0.0, 0.0);
avecP[12]:=V3Assign( 1.0, 0.0, 0.0); // X
avecP[13]:=V3Assign( 1.2, 0.0,-0.2);
avecP[14]:=V3Assign( 0.8, 0.0, 0.2);
avecP[15]:=V3Assign( 1.0, 0.0, 0.0);
avecP[16]:=V3Assign( 1.2, 0.0, 0.2);
avecP[17]:=V3Assign( 0.8, 0.0,-0.2);

for ww:=1 to 17 do begin
   // Axis relative to the Body
   avecP[ww]:= avecP[ww][1]*vecBodyHorizX + avecP[ww][2]*vecBodyFrontY +
               avecP[ww][3]*vecBodyVertZ;

   // Project the axis, and then put it on the top left, of the screen
   poDraw^.projectOnePoint(0.5*vBodySize * avecP[ww] + vecGravityCenter, aptP[ww].x, aptP[ww].y);
   aptP[ww].x:=aptP[ww].x;
   aptP[ww].y:=aptP[ww].y;
   end;

if not poDraw^.fView3DWithGalsses then SelectObject(poDraw^.hDCDraw, hPenAx);
// else, the rigth colors are already selected.

Polyline(poDraw^.hDCDraw, aptP[1], 17);
end;  // TBody3D0.BodyDrawAxis

//***************************************************
// TDraw3DFrame0 = object(TControl)
//***************************************************

constructor TDraw3DFrame0.Init(poParentW:PWindowBase0);
//=====================================================
var ww : word;
begin
inherited Init(poParentW);

wWindowType:=GGLCTE_CTRL_Draw3DFrame;

parPoints   :=NIL;
wMaxPoints  :=0;
wNumPoints  :=0;

parFaces    :=NIL;
pawSort     :=NIL;
wMaxFaces   :=0;
wNumFaces   :=0;

fView3DWithGalsses:=FALSE;
fView3DAxis :=TRUE;

// Set some default parameters, as screen position and viewer position
Draw3DSetup(0,0,0,0,0,0);
vecQ:=V3Assign(0,0,0); // cm

// Center of gravity at the center of the screen
vecRotationCenter:=vecQ;

vEyesDistance :=5; // centimeters;
vCentimetersToPixels:=20; // will be set better during creation

vAngleRotation     :=5; // degrees
vAngleRotationHead :=5; // degree
vDeltaTranslation :=2; // [cm]
vZoomFactor:=1.0;

ColorSuperior.wColor :=RGB(255,255,255); // white
ColorInferior.wColor :=RGB(255,  0,  0); // red
ColorAxes.wColor     :=RGB(  0,255,  0); // Light Green
colorRightEye.wColor :=RGB(255,  0,  0); // Red
colorLeftEye.wColor  :=RGB(  0,255,  0); // Light Green

hPenSuperior :=0;
hPenInferior :=0;
hPenAxes     :=0;
hPen3DRightEye :=0;
hPen3DLeftEye  :=0;
hPenBlack      :=0;

// By default, the faces of the Bodies are black.
colorBlack :=RGB(0,0,0);
colorFaces :=colorBlack;

// Init the Custom colors
for ww:=1 to 16 do awCustomColors[ww].wColor:=RGB(255,255,255);

wSlowDrawingMem:=1;
wSlowDrawing:=1;

// Will be set during the computation of the projection "matrix"
vAngleWithVertical :=0.0;
vAngleDirection :=0.0;
vAngleWithHorizontal :=0.0;

rPtLastClick.x:=0;
rPtLastClick.y:=0;

FloatField(2100020001, vecM[1]);
FloatField(2100020002, vecM[2]);
FloatField(2100020003, vecM[3]);
FloatField(2100020004, vecQ[1]);
FloatField(2100020005, vecQ[2]);
FloatField(2100020006, vecQ[3]);
FloatField(2100020007, vAngleWithVertical);
FloatField(2100020008, vAngleDirection);
FloatField(2100020009, vAngleWithHorizontal);
FloatField(2100020010, vZoomFactor);

FloatField(2100020011, vEyesDistance);
FloatField(2100020012, vMQDistance);
FloatField(2100020013, vCentimetersToPixels);
FloatField(2100020014, vAngleRotation);
FloatField(2100020015, vAngleRotationHead);
FloatField(2100020016, vDeltaTranslation);

ByteField(2100020031, colorRightEye.bRed);
ByteField(2100020032, colorRightEye.bGreen);
ByteField(2100020033, colorRightEye.bBlue);
ByteField(2100020034, colorLeftEye.bRed);
ByteField(2100020035, colorLeftEye.bGreen);
ByteField(2100020036, colorLeftEye.bBlue);
ByteField(2100020037, colorAxes.bRed);
ByteField(2100020038, colorAxes.bGreen);
ByteField(2100020039, colorAxes.bBlue);

ByteField(2100020041, colorBlack.bRed);
ByteField(2100020042, colorBlack.bGreen);
ByteField(2100020043, colorBlack.bBlue);
ByteField(2100020044, colorFaces.bRed);
ByteField(2100020045, colorFaces.bGreen);
ByteField(2100020046, colorFaces.bBlue);

poDialogBoardTable:=NIL;

papoBodies :=@apoBodies;
wNumBodies :=0;

oBodyDefault.Init(@Self);
apoBodies[0]:=@oBodyDefault;
wBodySelected:=0;

// The whole window is used
vXSizeRel  := 1;
vYSizeRel  := 1;
hDCDraw:=0;

dwStyle:=WS_Child or WS_TABSTOP	or WS_VISIBLE;
// hWin :=  will be set in the creation in the wMDIWindow.pp
end;  // TDraw3DFrame0.Init

destructor TDraw3DFrame0.Done;
//============================
var ww : Word;
begin
ClearField(2100020001);
ClearField(2100020002);
ClearField(2100020003);
ClearField(2100020004);
ClearField(2100020005);
ClearField(2100020006);
ClearField(2100020010);

if (hPenSuperior <> 0)   then DeleteObject(hPenSuperior);
if (hPenInferior <> 0)   then DeleteObject(hPenInferior);
if (hPenAxes <> 0)       then DeleteObject(hPenAxes);
if (hPen3DRightEye <> 0) then DeleteObject(hPen3DRightEye);
if (hPen3DLeftEye <> 0)  then DeleteObject(hPen3DLeftEye);
if (hPenBlack <> 0)      then DeleteObject(hPenBlack);
DeleteObject(hBrushBlack);
DeleteObject(hBrushFaces);

oBodyDefault.Done;

// Dispose the bodies.
for ww:=1 to wNumBodies do dispose(papoBodies^[ww], Done);

if (wMaxFaces > 0) then begin
   FreeMem(pawSort,  wMaxFaces*SizeOf(Word32));
   FreeMem(parFaces, wMaxFaces*SizeOf(TypeFace3D));
   end;
wMaxFaces   :=0;

if (wMaxPoints > 0) then FreeMem(parPoints, wMaxPoints*SizeOf(TypePoint3D));
wMaxPoints  :=0;

inherited Done;
end;  // TDraw3DFrame0.Done

procedure TDraw3DFrame0.CtrlCreate;
//=================================
Begin
strClassName:=gglstrClassWinName;
inherited CtrlCreate;

// Get the Number of pixels per logical inch along the screen width.
// 2.54 cm = 1.0 Inch
hDCDraw:=GetDC(hWin);
vCentimetersToPixels:=GetDeviceCaps(hDCDraw, LOGPIXELSX) / 2.54;
ReleaseDC(hWin, hDCDraw);

// Create the pens colors
if (hPenSuperior = 0)   then hPenSuperior  :=CreatePen(PS_SOLID, 1, ColorSuperior.wColor);
if (hPenInferior = 0)   then hPenInferior  :=CreatePen(PS_SOLID, 1, ColorInferior.wColor);
if (hPenAxes = 0)       then hPenAxes      :=CreatePen(PS_SOLID, 1, ColorAxes.wColor);
if (hPen3DRightEye = 0) then hPen3DRightEye:=CreatePen(PS_SOLID, 1, colorRightEye.wColor);
if (hPen3DLeftEye = 0)  then hPen3DLeftEye :=CreatePen(PS_SOLID, 1, colorLeftEye.wColor);
if (hPenBlack = 0)      then hPenBlack     :=CreatePen(PS_SOLID, 1, RGB(0,0,0));
hBrushBlack:=CreateSolidBrush(colorBlack.wColor);
hBrushFaces:=CreateSolidBrush(colorFaces.wColor);

// The body with index 0 is the default one, if nothing else has been defined
oBodyDefault.hPenSup :=hPenSuperior;
oBodyDefault.hPenInf :=hPenInferior;
oBodyDefault.hPenAx  :=hPenAxes;
oBodyDefault.strBodyName:='Default Body';
End;  // TDraw3DFrame0.CtrlCreate

procedure TDraw3DFrame0.AllocateMemory(wMaxPointsP,wMaxFacesP:word32; var wErrorV:word32);
//========================================================================================
// Allocate memory, for the points and the faces
var
  ww : Word32;
begin
if (wMaxPoints < wMaxPointsP) then begin
   // We want more points than what is already allocated.
   if (wMaxPoints > 0) then FreeMem(parPoints, wMaxPoints*SizeOf(TypePoint3D));
   parPoints:=NIL;

   wMaxPoints:=wMaxPointsP;
   GetMem(parPoints, wMaxPoints*SizeOf(TypePoint3D));
   end;

if (wMaxFaces < wMaxFacesP) then begin
   // We want more faces than what is already allocated.
   if (wMaxFaces > 0) then begin
      FreeMem(pawSort, wMaxFaces*SizeOf(Word32));
      FreeMem(parFaces, wMaxFaces*SizeOf(TypeFace3D));
      pawSort :=NIL;
      parFaces:=NIL;
      end;

   wMaxFaces:=wMaxFacesP;
   GetMem(parFaces, wMaxFaces*SizeOf(TypeFace3D));;
   GetMem(pawSort,  wMaxFaces*SizeOf(Word32));;

   for ww:=0 to wMaxFaces do with parFaces^[ww] do begin
      bReserved:=0;
      bBody    :=0;
      end;
   end;

if (parPoints = NIL) or (parFaces = NIL) or (pawSort = NIL) then begin
   // Very rare, we could have not enough memory.
   wErrorV:=errDraw3DMemoryOverflow;
   exit;
   end;

wErrorV:=0;
end;  // TDraw3DFrame0.AllocateMemory

procedure TDraw3DFrame0.Draw3DSetup(vMx,vMy,vMz,vQx,vQy,vQz:Float);
//=================================================================
// Must be called, after the CtrlCreate, to define the initial directions.
// (vMx,vMy,vMz) = where you are, in [cm]
// (vQx,vQy,vQz) = where the center of the screen is, in [cm]
// This will also determine the distance between you and the screen.
// If this distance is less than 1 [cm], then a default values are taken.
begin
vecM:=V3Assign(vMx, vMy, vMz);
vecQ:=V3Assign(vQx, vQy, vQz);

vecFront:=vecQ - vecM;  // normal to the screen = the projection plan
vMQDistance:=Norme(vecFront);

// You are not allowed to be too near from the screen
if (vMQDistance < 1.0) then begin
   // Not normal, it should not be accepted that vecM = vecQ
   // Suppose the screen is at the origin and you are
   // about 10 cm higher as the screen, 78 cm behind the sceen and
   // 7 cm on the left of the center of the scren
   vecQ:=V3Assign(0.0, 0.0, 0.0);
   vecM:=V3Assign(-7.0, -78.0, 10.0);
   vecFront:=vecQ - vecM;  // normal to the screen
   vMQDistance:=Norme(vecFront);
   end;

// Your distance from the screen
V3Normalise(vecFront);

// Compute vecUp, the vertical of your screen, such that it is
// othogonal to vecFront and to the X axis (1,0,0)
vecUp:=gglvecV3AxisX ** vecFront;

if (Norme(vecUp) < 0.001) then begin
   // Special case, where vecFront is nearly paralelle to the X direction.
   // Set vecUp nearly vertical
   vecUp:=gglvecV3AxisZ - (gglvecV3AxisZ * vecFront) * vecFront;
   end;

V3Normalise(vecUp);

// (Normalisation is useless since vecFront and vecUp are othonormal)
vecRight:=vecFront ** vecUp;

//DBW(1, 'RX=' + V3ToStr(vecRight));
end;  // TDraw3DFrame0.Draw3DSetup

procedure TDraw3DFrame0.AutoCenter;
//=================================
// compute the origine of the projection plane  "Q"  to be
// on the center of all the points
var
  vecC  : RVectR3Float; // the center of all points
  ww    : word32;
begin
// If a body has been defined, use the center of gravity of the last one defined
if (wNumBodies >= 1) then begin
   vecRotationCenter:=papoBodies^[wNumBodies]^.vecGravityCenter;
   exit;
   end;

// Set the point  Q  = center of gravity of all points
vecC:=V3Assign(0,0,0);
for ww:=1 to wNumPoints do with parPoints^[ww] do vecC:=vecC + vecV;

vecRotationCenter:=vecC / wNumPoints;
end;  // TDraw3DFrame0.AutoCenter

procedure TDraw3DFrame0.ComputeProjectionMatrix;
//=============================================
// Determination of the orthogonal projection on the plane p1*x+vP2*y+vP3*z=0
var
  vecTemp,
  vecEyeQ     // = vecEye - vecQ
              : RVectR3Float;
  rRec        : RECT;
  vTemp       : Float;

begin
// Center of the screen = vecQ  [cm]
vecQ:=vecM + vMQDistance * vecFront;

if fView3DWithGalsses then begin
   // Exact position of the Eye [cm]
   vecEye:=vecM + (0.5*vEyesDistance) * vecRight;
   end
else vecEye:=vecM; // Eye position = position where we are.

// Coefficient to compute the projection [cm]
vecEyeQ  :=vecEye - vecQ;

// vU = first line of the "projection matrix" = vecUp x vecEyeQ
// Units = pixels
vecTemp:=vZoomFactor * vCentimetersToPixels * (vecUp ** vecEyeQ);
vUX:=vecTemp[1];   vUY:=vecTemp[2];   vUZ:=vecTemp[3];

// vV = second line of the "projection matrix" = vecRight x vecEyeQ
// Units = pixels
vecTemp:=vZoomFactor * vCentimetersToPixels * (vecRight ** vecEyeQ);
vVX:=vecTemp[1];   vVY:=vecTemp[2];   vVZ:=vecTemp[3];

// vDen = vecUp x vecRight = vecFront normalised
// This makes that the projection is not exactly a linear transformation.
// See more comment in the projectionAllPoints
// No units, normalised
vecTemp:=vecUp ** vecRight; // = vecFront
vDenX:=vecTemp[1];   vDenY:=vecTemp[2];   vDenZ:=vecTemp[3];

// To have the center of the screen corresponding to the point Q
GetClientRect(hWin, rRec);
nXProjCenter:=rRec.Right  div 2;
nYProjCenter:=rRec.Bottom div 2;

// Angles to determine the direction where we go
// Angle in degrees between the vecUp and Z axis
vAngleWithVertical :=ArcCos(vecUp[3]) * 180/Pi;

// Direction = Angle of the projection of (0,1,0) on the plan (vecFront, vecRight)
// with the vector vecFront.
vTemp:=sqrt(sqr(vecFront[2]) + sqr(vecRight[2]));
if (vTemp < 1e-30) then vAngleDirection:=90.0 // arbitrary
else begin
   vAngleDirection:=ArcCos(vecFront[2] / vTemp) * 180/Pi;
   if (vecFront[1] > 0) then vAngleDirection:=-vAngleDirection;
   end;

// Angle with the horizontal
vAngleWithHorizontal :=(180/Pi) * V3Angle(vecUp, V3Assign(1,0,0)) - 90;
//DBW(1, FTS(vAngleWithHorizontal) + FTS(vecFront*vecRight) + FTS(vecFront*vecUp) + FTS(vecUp*vecRight));
end;  // TDraw3DFrame0.ComputeProjectionMatrix

procedure TDraw3DFrame0.projectOnePoint(vecV:RVectR3Float; var nUV,nVV:int32);
//============================================================================
// Projection of the point  vecV  on the screen, in pixel coordinate
var
  vDx,vDy,vDz,// = (x,y,z) - (vRx, vRy, vRz)
  vU,vV,      // = the projected point
  vDen        : Float;

begin
vDx := vecV[1] - vecEye[1];
vDy := vecV[2] - vecEye[2];
vDz := vecV[3] - vecEye[3];
vDen :=vDx*vDenX + vDy*vDenY + vDz*vDenZ;

if (vDen > 1e-8) then begin
   vU:=nXProjCenter + (vDx*vUX + vDy*vUY + vDz*vUZ) / vDen;
   vV:=nYProjCenter + (vDx*vVX + vDy*vVY + vDz*vVZ) / vDen;

   // test for overflow
   if (vU >  4000) then vU:= 5000;
   if (vU < -1000) then vU:=-5000;
   if (vV >  4000) then vV:= 5000;
   if (vV < -1000) then vV:=-5000;

   nUV:=Round(vU);
   nVV:=Round(vV);
   end
else begin
   // point in our back, don't show them
   nUV:=5000;
   nVV:=5000;
   end;
end;  // TDraw3DFrame0.projectOnePoint

procedure TDraw3DFrame0.projectAllPoints;
//=======================================
// Projection of all the points
var
  vDx,vDy,vDz,// = (x,y,z) - (vRx, vRy, vRz)
  vU,vV,      // = the projected point
  vDen        : Float;
  ww          : word32;

begin
// Compute the projection of the center of gravity of all bodies.
for ww:=1 to wNumBodies do with papoBodies^[ww]^ do begin
   vDx := vecGravityCenter[1] - vecEye[1];
   vDy := vecGravityCenter[2] - vecEye[2];
   vDz := vecGravityCenter[3] - vecEye[3];
   vDen :=vDx*vDenX + vDy*vDenY + vDz*vDenZ;

   if (vDen > 1e-8) then begin
      vU:=nXProjCenter + (vDx*vUX + vDy*vUY + vDz*vUZ) / vDen;
      vV:=nYProjCenter + (vDx*vVX + vDy*vVY + vDz*vVZ) / vDen;

      // test for overflow
      if (vU >  4000) then vU:= 5000;
      if (vU < -1000) then vU:=-5000;
      if (vV >  4000) then vV:= 5000;
      if (vV < -1000) then vV:=-5000;

      nGCU:=Round(vU);
      nGCV:=Round(vV);
      end
   else begin
      // point in our back, don't show them
      nGCU:=5000;
      nGCV:=5000;
      end;
   end; // for ww:=1 to wNumBodies do begin

// Projection of all points
for ww:=1 to wNumPoints do with parPoints^[ww] do begin
   // Distance from the viewer
   vDistPt:=sqrt(  sqr(vecV[1] - vecM[1]) + sqr(vecV[2] - vecM[2])
                 + sqr(vecV[3] - vecM[3]));

   // Projection on the plan.
   // It's not a linear transformation, but it's nearly one, if the viewer
   // is quite fare
   vDx := vecV[1] - vecEye[1];
   vDy := vecV[2] - vecEye[2];
   vDz := vecV[3] - vecEye[3];

   // vDen = (Point - Eye) * (vecQ - vecM) * coefficient
   // Since  Eye ~= vecM  and  Point ~= vecQ  (~= means "about equal")
   // If the viewer  vecM  is fare from the body,
   // vDen is about the same value for all  "Point"
   // thus the projection is about linear.
   // This becomes wrong when the viewer is near the body.
   vDen :=vDx*vDenX + vDy*vDenY + vDz*vDenZ;

   // If (vDen < 0) then the points are in our back, thus
   // don't show them

   if (vDen > 1e-8) then begin
      vU:=nXProjCenter + (vDx*vUX + vDy*vUY + vDz*vUZ) / vDen;
      vV:=nYProjCenter + (vDx*vVX + vDy*vVY + vDz*vVZ) / vDen;

      // test for overflow
      if (vU >  4000) then vU:= 5000;
      if (vU < -1000) then vU:=-5000;
      if (vV >  4000) then vV:= 5000;
      if (vV < -1000) then vV:=-5000;

      nU:=Round(vU);
      nV:=Round(vV);
      end
   else begin
      // point in our back, don't show them
      nU:= 5000;
      nV:= 5000; // => do not show them.
      end; // else of if (vDen > 0)
   end; // for nn .. with parPoints^[ww] ...

//DBW(1, 'Pos,Neg=' + ITS5(wNumPos) + ITS(wNumNeg));
end;  // TDraw3DFrame0.projectAllPoints

procedure TDraw3DFrame0.QSort(nMin,nMax:int32);
//=============================================
// Sort the faces from wMin to wMax, using vDistF as the criterium,
// do it recursively.
var
  nL,nR
              : int32;
  vPivot      : FloatS;
  wOneSort    : Word32;

begin
nL :=nMin;
nR :=nMax;
vPivot := parFaces^[pawSort^[(nMin + nMax) div 2]].vDistF;

// Separate the distances smaller to the  vPivot  from those greater.
repeat
   // look for two faces to exange their places
   while (parFaces^[pawSort^[nL]].vDistF < vPivot) do inc(nL);
   while (parFaces^[pawSort^[nR]].vDistF > vPivot) do dec(nR);

   if (nL <= nR) then begin
      // In case of equality, (rare), the swap is useless, but
      // the  inc(nL) and  dec(nR) are usefull
      if (nL <> nR) then begin
         // Exange the index of the faces.
         wOneSort    :=pawSort^[nR];
         pawSort^[nR]:=pawSort^[nL];
         pawSort^[nL]:=wOneSort;
         end;

      inc(nL);
      dec(nR);
      end;
   until (nL > nR);

// nMin .. nR vPivot nL .. nMax
// The test assure that the recurence level is at most = Log2(wMax - wMin).
// The smaller interval must always be sorted first, to go out faster
// from recurence levels
if (nR - nMin < nMax - nL) then begin
   if (nMin < nR)   then QSort(nMin, nR); // this is done first
   if (nL   < nMax) then QSort(nL, nMax);
   end
else begin
   if (nL   < nMax) then QSort(nL, nMax);
   if (nMin < nR)   then QSort(nMin, nR); // this is done in second this time
   end;
end;  // TDraw3DFrame0.QSort

procedure TDraw3DFrame0.SortAllFaces;
//===================================
// Sort the faces, from the farthest to the nearest
var
  ww          : word32;
  nP          : integer;
begin
for ww:=1 to wNumFaces do with parFaces^[ww] do begin
   // first compute the distance of the center of the face to the viewer
   vDistF:=0;
   for nP:=1 to nNbPoint do vDistF:=vDistF + aprPoint[nP]^.vDistPt;

   vDistF:= vDistF / nNbPoint;

   // Not sorted
   pawSort^[ww]:=ww;
   end; // for ... with parFaces^[ww]

// Sort the faces, using Quicksort
QSort(1, wNumFaces);
end;  // TDraw3DFrame0.SortAllFaces

procedure TDraw3DFrame0.DrawXYZAxis;
//==================================
// Drawing of a little Axis in the top left of the screen
var
  aptP  : array[1..20] of POINT;
  avecP : array[1..20] of RVectR3Float;
  ww    : word;
begin
// Define the points to be projected
avecP[ 1]:=V3Assign(-0.2, 0.0, 1.2);  // Z
avecP[ 2]:=V3Assign( 0.2, 0.0, 1.2);
avecP[ 3]:=V3Assign(-0.2, 0.0, 1.0);
avecP[ 4]:=V3Assign( 0.2, 0.0, 1.0);
avecP[ 5]:=V3Assign( 0.0, 0.0, 1.0);
avecP[ 6]:=V3Assign( 0.0, 0.0, 0.0);
avecP[ 7]:=V3Assign( 0.0, 1.0, 0.0); // Y
avecP[ 8]:=V3Assign( 0.0, 0.6, 0.0);
avecP[ 9]:=V3Assign(-0.2, 1.0, 0.0);
avecP[10]:=V3Assign( 0.0, 0.6, 0.0);
avecP[11]:=V3Assign( 0.0, 0.0, 0.0);
avecP[12]:=V3Assign( 1.0, 0.0, 0.0); // X
avecP[13]:=V3Assign( 1.2, 0.0,-0.2);
avecP[14]:=V3Assign( 0.8, 0.0, 0.2);
avecP[15]:=V3Assign( 1.0, 0.0, 0.0);
avecP[16]:=V3Assign( 1.2, 0.0, 0.2);
avecP[17]:=V3Assign( 0.8, 0.0,-0.2);

for ww:=1 to 17 do begin
   // Project the axis, and then put it on the top left, of the screen
   projectOnePoint(avecP[ww] + vecQ, aptP[ww].x, aptP[ww].y);
   aptP[ww].x:=aptP[ww].x - nXProjCenter + 8 + Round(1.2*vZoomFactor*vCentimetersToPixels);
   aptP[ww].y:=aptP[ww].y - nYProjCenter + 8 + Round(1.2*vZoomFactor*vCentimetersToPixels);
   end;

if not fView3DWithGalsses then SelectObject(hDCDraw, hPenAxes);
// else, the rigth colors are already selected.

Polyline(hDCDraw, aptP[1], 17);
end;  // TDraw3DFrame0.DrawXYZAxis

procedure TDraw3DFrame0.DrawBodies;
//=================================
// Drawing of the bodies, their projoection
var
  nDiff1, n13X, n13Y, nSign1213, nSign1413, nSign3234 : int32;
  // to compute which side is seen

  wSlow,      // To slow down the drawing speed
  ww          : word32;
  nP          : integer;

  // To define the region to be filled
  arPts       : array[0..15] of POINT;

begin
for ww:=wNumFaces downto 1 do with parFaces^[pawSort^[ww]] do
  if papoBodies^[bBody]^.fIsVisible then begin
   // Copy the points into an array, to draw them.
   for nP:=1 to nNbPoint do begin
      arPts[nP].x:=aprPoint[nP]^.nU;
      arPts[nP].y:=aprPoint[nP]^.nV;
      end;

   // Just to slow down the drawing speed. To see how it works
   if (wSlowDrawing > 1) then begin
      wSlow:=wSlowDrawing;

      while (wSlow > 1) do begin
         dec(wSlow);

         // If Escape was hit, cancel the slowing down.
         if (GetAsyncKeyState(VK_ESCAPE) < 0) then wSlowDrawing:=1;
         end; // while
      end; // if (wSlowDrawing > 1) then begin

   // If we have only two points, then only a line is drawn
   if (nNbPoint >= 3) and (abs(arPts[1].x) < 4000) then begin
      // Here we have 3, 4 or 5 edges.
      arPts[0].x:=aprPoint[nNbPoint]^.nU;
      arPts[0].y:=aprPoint[nNbPoint]^.nV;

      // Test to know if we see the upper or lower face
      nDiff1:=(arPts[0].x - arPts[1].x) * (arPts[2].y - arPts[1].y) -
              (arPts[0].y - arPts[1].y) * (arPts[2].x - arPts[1].x);

      // Selecting the color to draw
      if fView3DWithGalsses then begin
         if (vEyesDistance > 0) then SelectObject(hDCDraw, hPen3DRightEye)
         else                        SelectObject(hDCDraw, hPen3DLeftEye);
         end
      else if (papoBodies^[bBody]^.fUniColor) then begin
         SelectObject(hDCDraw, papoBodies^[bBody]^.hPenSup);
         end
      else if (nNbPoint = 3) then begin
         // Only one side of the face can be seen
         if (nDiff1 >= 0) then begin
            // Superior face is seen
            SelectObject(hDCDraw, papoBodies^[bBody]^.hPenSup);
            end
         else begin
            // Inferior face is seen
            SelectObject(hDCDraw, papoBodies^[bBody]^.hPenInf);
            end;
         end // if (nNbPoint = 3) then begin
      else begin // more than 3 edges, it could be that bothe sides are seen.
         // A face with more than three edges can be twisted and have one
         // part of its upper face and one part of its lower face seen.
         // Sign = Z component of the cross product.
         n13X:=arPts[3].x - arPts[1].x;
         n13Y:=arPts[3].y - arPts[1].y;

         nSign1213:=(arPts[2].x - arPts[1].x) * n13Y -
                    (arPts[2].y - arPts[1].y) * n13X;

         nSign1413:=(arPts[4].x - arPts[1].x) * n13Y -
                    (arPts[4].y - arPts[1].y) * n13X;

         if (nSign1213 < 0) and (nSign1413 > 0) then begin
             // Superior face is seen
             SelectObject(hDCDraw, papoBodies^[bBody]^.hPenSup);
             end
         else if (nSign1213 > 0) and (nSign1413 < 0) then begin
             // Inferior face is seen
             SelectObject(hDCDraw, papoBodies^[bBody]^.hPenInf);
             end
         else begin
             // The face is twisted or has a  V  shape
             // Sign = Z component of the cross product.
             nSign3234:=(arPts[2].x - arPts[3].x) * (arPts[4].y - arPts[3].y) -
                        (arPts[2].y - arPts[3].y) * (arPts[4].x - arPts[3].x);
             if (nSign1213 < 0) and (nSign3234 < 0) then begin
                // V Shape, Inferior face is seen
                SelectObject(hDCDraw, papoBodies^[bBody]^.hPenInf);
                end
             else if (nSign1213 > 0) and (nSign3234 > 0) then begin
                // V Shape, Superior face is seen
                SelectObject(hDCDraw, papoBodies^[bBody]^.hPenSup);
                end
             else begin
                // Face is twisted, both faces are seen.
               if (vecM[3] > aprPoint[1]^.vecV[3]) then begin
                  // In most cases we will see the upper face
                  SelectObject(hDCDraw, papoBodies^[bBody]^.hPenSup);
                  end
               else begin
                  // In most cases we will see the lower face
                  SelectObject(hDCDraw, papoBodies^[bBody]^.hPenInf);
                  end;
               end;
            end; // case where we must be more precise
         end; // more that 3 edges are seen.
              // End of selecting the color

      // Draw one face. Most of the time is spent here !
      Polygon(hDCDraw, arPts[1], nNbPoint);

      // Replacing the previous call by this one, double the global speed.
      // Polyline(hDCDraw, arPts[1], nNbPoint);
      end // if (nNbPoint >= 3) then begin
   else if (abs(arPts[1].x) < 4000) then begin
      // Only two points, only a line is drawn

      // Select the color to draw
      if fView3DWithGalsses then begin
         if (vEyesDistance > 0) then SelectObject(hDCDraw, hPen3DRightEye)
         else                        SelectObject(hDCDraw, hPen3DLeftEye);
         end
      else begin
         // Draw a black line on each side of the line
         if (abs(arPts[2].x - arPts[1].x) > abs(arPts[2].y - arPts[1].y)) then begin
            // The line makes less than 45 degree with the horizontal
            arPts[3].x:=arPts[1].x;
            arPts[3].y:=arPts[1].y+1;
            arPts[4].x:=arPts[2].x;
            arPts[4].y:=arPts[2].y+1;
            arPts[5].x:=arPts[2].x;
            arPts[5].y:=arPts[2].y-1;
            arPts[6].x:=arPts[1].x;
            arPts[6].y:=arPts[1].y-1;
            end
         else begin
            // The line makes more than 45 degree with the horizontal
            arPts[3].x:=arPts[1].x+1;
            arPts[3].y:=arPts[1].y;
            arPts[4].x:=arPts[2].x+1;
            arPts[4].y:=arPts[2].y;
            arPts[5].x:=arPts[2].x-1;
            arPts[5].y:=arPts[2].y;
            arPts[6].x:=arPts[1].x-1;
            arPts[6].y:=arPts[1].y;
            end;

         SelectObject(hDCDraw, hPenBlack);
         Polyline(hDCDraw, arPts[3], 4);

         // Select the color to draw
         SelectObject(hDCDraw, papoBodies^[bBody]^.hPenAx);
         end;

      // paint in one line
      Polyline(hDCDraw, arPts[1], nNbPoint);
      end; // Only two points
   // else the point is too far

   end; // for ... with parFaces^[ww]

// Draw the Body axes
for ww:=1 to wNumBodies do if papoBodies^[ww]^.fIsVisible then
   papoBodies^[ww]^.BodyDrawAxis;

if fView3DAxis then DrawXYZAxis;
end;  // TDraw3DFrame0.DrawBodies

procedure TDraw3DFrame0.Draw3DBodies;
//===================================
var
  rRec        : RECT;
  hPenOld1,
  hPenOld2    : HPEN;
  hBrushOld1,
  hBrushOld2  : HBRUSH;
  hBMAPOld1,
  hBMAPOld2,
  hBMap1,
  hBMap2      : HBITMAP; // BitMap in which we will draw.
  hDCMem1,
  hDCMem2,    // A memory device context
  hDCWin      // The device context of the client window, to draw on it.
              : HDC;

// For timing, to optimise
//nT0,nT1,nT2,nT3,nT4,nT5,nT6,nT7,nT8 : int32;
begin
if (wNumFaces = 0) then begin
   // Nothing to Draw, but clear the window
   GetClientRect(hWin, rRec);
   hDCWin:=GetDC(hWin);

  // Fill the bitmap with a black color
   FillRect(hDCWin, rRec, hBrushBlack);

   ReleaseDC(hWin, hDCWin);
   exit;
   end;

//nT0:=GetTickCount;

// Mathematical part, projection and sorting
ComputeProjectionMatrix;  //nT1:=GetTickCount;
projectAllPoints;        //nT2:=GetTickCount;
SortAllFaces;            //nT3:=GetTickCount;

// Drawing part
GetClientRect(hWin, rRec);
hDCWin:=GetDC(hWin);
hBMAP1:=CreateCompatibleBitmap(hDCWin, rRec.Right, rRec.Bottom);

// Select the bitmap, to draw on it.
// You can only paint on the bit map, if it is selected in a memory device context
// Selecting it in the Window Device context will not work.
hDCMem1   :=CreateCompatibleDC(hDCWin);
hBMAPOld1 :=SelectObject(hDCMem1, hBMAP1);
hPenOld1  :=SelectObject(hDCMem1, hPenSuperior);
hBrushOld1:=SelectObject(hDCMem1, hBrushFaces);       //nT4:=GetTickCount;

// Fill the bitmap with a black color
FillRect(hDCMem1, rRec, hBrushBlack);                 //nT5:=GetTickCount;

// Draw the surface in the memory bitmap
hDCDraw:=hDCMem1;
DrawBodies;                                           //nT6:=GetTickCount;

// If 3D vision with Glasses, then draw a second projection
if fView3DWithGalsses then begin
   // To view in 3D, with glasses
   // Drawing of the object for the second eye
   // Projection, before drawing
   vEyesDistance:=-vEyesDistance;

   // Mathematical part, projection and sorting
   ComputeProjectionMatrix;
   projectAllPoints;
   SortAllFaces;

   hBMAP2:=CreateCompatibleBitmap(hDCWin, rRec.Right, rRec.Bottom);

   // Select the bitmap, to draw on it.
   // You can only paint on the bit map, if it is selected in a memory device context
   // Selecting it in the Window Device context will not work.
   hDCMem2:=CreateCompatibleDC(hDCWin);
   hBMAPOld2:=SelectObject(hDCMem2, hBMAP2);
   hPenOld2:=SelectObject(hDCMem2, hPenSuperior);
   hBrushOld2:=SelectObject(hDCMem2, hBrushFaces);

   // Fill the bitmap with a black color
   FillRect(hDCMem2, rRec, hBrushBlack);

   // Draw the surface in the memory bitmap
   hDCDraw:=hDCMem2;
   DrawBodies;

   vEyesDistance:=-vEyesDistance;

   // Add with "OR" operation the bit map, from the memory DC 2 to the memory DC 1
//--> More than half of the time is spent here !!!
   BitBlt( hDCMem1, // handle of destination device context
             0, // x-coordinate of destination rectangle's upper-left corner
             0, // y-coordinate of destination rectangle's upper-left corner
           rRec.Right, // width of destination rectangle
           rRec.Bottom, // height of destination rectangle
           hDCMem2, // handle of source device context
             0,  // x-coordinate of source rectangle's upper-left corner
             0, // y-coordinate of source rectangle's upper-left corner
           SRCPAINT // raster operation code  (there are many others possibilities)
           );

   // You must delete the created memory device context.
   // But before, you must select the original bit map which was in it.
   SelectObject(hDCMem2, hBrushOld2);
   SelectObject(hDCMem2, hPenOld2);
   SelectObject(hDCMem2, hBMAPOld2);
   DeleteDC(hDCMem2);
   DeleteObject(hBMAP2);
   end; // if fView3DWithGalsses then begin

// Copy the bit map, from the memory DC to the paint DC
// This takes more time than all the mathematical computations
BitBlt( hDCWin, // handle of destination device context
          0, // x-coordinate of destination rectangle's upper-left corner
          0, // y-coordinate of destination rectangle's upper-left corner
        rRec.Right, // width of destination rectangle
        rRec.Bottom, // height of destination rectangle
        hDCMem1, // handle of source device context
          0,  // x-coordinate of source rectangle's upper-left corner
          0, // y-coordinate of source rectangle's upper-left corner
        SRCCOPY // raster operation code  (there are many others possibilities)
        );  //nT7:=GetTickCount;

// You must delete the created memory device context.
// But before, you must select the original bit map which was in it.
SelectObject(hDCMem1, hBrushOld1);
SelectObject(hDCMem1, hPenOld1);
SelectObject(hDCMem1, hBMAPOld1);
DeleteDC(hDCMem1);
DeleteObject(hBMAP1);
hDCDraw:=0;

ReleaseDC(hWin, hDCWin);       //nT8:=GetTickCount;

// Update the content of the dialog box
if (poDialogBoardTable <> NIL) then poDialogBoardTable^.UpdateFromVar;

//DBW(1024, 'T=' + ITS5(nT1-nT0) + ITS5(nT2-nT1) + ITS5(nT3-nT2) + ITS5(nT4-nT3)
//               + ITS5(nT5-nT4) + ITS5(nT6-nT5) + ITS5(nT7-nT6) + ITS5(nT8-nT7));
end;  // TDraw3DFrame0.Draw3DBodies

procedure TDraw3DFrame0.Visualise(wPar:WPARAM; wFlagsP:word32);
//=============================================================
// To rotate around the surface
// wFlags indicate if Control and/or Shift are pressed.
// = 1 => control pressed
// = 2 => shift pressed
// = 3 => both pressed
var
  vTransDist, // Translation distance in [cm]
  vRotRad,    // angle of rotation in radians arount the body
  vRotRadHead // angle of rotation in radians of the head
              : Float;
  vecRotC,    // Rotation center in one case
  vecAxis     : RVectR3Float;
  poBodyL     : PBody3D0; // Selected body
  hBrushOldL  : HBRUSH;
  fShiftDown  : boolean; // Is shift down?
  fCtrlDown   : boolean; // Is Control down?

begin
if (wNumFaces = 0) then exit;

//DBW(1, 'wPar=' + ITS5(wPar));

poBodyL:=papoBodies^[wBodySelected];

vRotRad     :=vAngleRotation * Pi / 180;
vRotRadHead :=vAngleRotationHead * Pi / 180;
vTransDist  :=vDeltaTranslation; // [cm]

// Are control and/ or shift pressed
fCtrlDown:=(wFlagsP and 1 = 1);
fShiftDown:=(wFlagsP and 2 = 2);

// Fine tuning, if Control pressed
if fCtrlDown then begin
   vRotRad     :=vRotRad / 10;
   vRotRadHead :=vRotRadHead / 10;
   vTransDist  :=vTransDist / 10;
   end;

if fShiftDown then begin
   // Rotating around the  X,  Y  and  Z  directions
   vecRotC:=poBodyL^.vecGravityCenter; // or = vecRotationCenter ?

   case wPar of
      VK_DOWN,
      VK_UP : begin
         // Rotate Up or Down around the Body Right (X) direction
         // Rotation center = Body gravity center
         vecAxis:=poBodyL^.vecBodyHorizX;
         end;
      VK_RIGHT,
      VK_LEFT : begin
         // Turn Right or Left around the Body Up (Z) direction
         // Rotation center = Body gravity center
         vecAxis:=poBodyL^.vecBodyVertZ;
         end;
      VK_DELETE,
      VK_NEXT : begin
         // Turn Right or Left around the Body Front (Y) direction
         // Rotation center = Body gravity center
         vecAxis:=poBodyL^.vecBodyFrontY;
         end;
      end; // case wPar of  for rotations around the Body
   end // if fShiftDown then begin
else begin
   // Rotation around the Body
   vecRotC:=vecRotationCenter;

   case wPar of
      VK_DOWN,
      VK_UP : vecAxis:=vecRight;
         // Turn Up or Down around the  vecRight
         // Having vecRotationCenter as the center of rotation
      VK_RIGHT,
      VK_LEFT : vecAxis:=vecUp;
         // Turn Right or Left around the  vecUp
         // Having vecRotationCenter as the center of rotation
      VK_DELETE,
      VK_NEXT : vecAxis:=vecFront;
         // Turn Right or Left around the  vecFront
         // Having vecRotationCenter as the center of rotation
      end; // case wPar of  for rotations around the Body
   end;  // else of if fShiftDown then begin

if (wPar = VK_UP) or
   (wPar = VK_LEFT) or (wPar = VK_DELETE) then vRotRad:=-vRotRad;

if (wPar = VK_UP)   or (wPar = VK_LEFT)  or (wPar = VK_DELETE) or
   (wPar = VK_DOWN) or (wPar = VK_RIGHT) or (wPar = VK_NEXT) then begin
   vecM     :=V3RotationVec(vecM,     vecRotC, vecAxis, vRotRad);
   vecUp    :=V3RotationOrg(vecUp,             vecAxis, vRotRad);
   vecRight :=V3RotationOrg(vecRight,          vecAxis, vRotRad);
   vecFront :=V3RotationOrg(vecFront,          vecAxis, vRotRad);
   end;

// Rotation of the HEAD having  vecM  as a center of rotation, i.e.
// turning around ourself.
//"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
if fShiftDown then case wPar of
   VK_DECIMAL,
   VK_DIVIDE  : vecAxis:=poBodyL^.vecBodyHorizX; // rotate the head Up or Down
   VK_NUMPAD7,
   VK_NUMPAD9 : vecAxis:=poBodyL^.vecBodyVertZ;   // rotate the head Right or Left around Up axis
   VK_NUMPAD1,
   VK_NUMPAD3 : vecAxis:=poBodyL^.vecBodyFrontY;  // rotate the head Right or Left around Front axis
   end // if ... case
else case wPar of
   VK_DECIMAL,
   VK_DIVIDE  :  vecAxis:=vecRight; // rotate the head Up or Down
   VK_NUMPAD7,
   VK_NUMPAD9 : vecAxis:=vecUp;   // rotate the head Right or Left around Up axis
   VK_NUMPAD1,
   VK_NUMPAD3 : vecAxis:=vecFront;  // rotate the head Right or Left around Front axis
   end; // case

if (wPar = VK_DECIMAL) or (wPar = VK_NUMPAD9) or (wPar = VK_NUMPAD1) then vRotRadHead:=-vRotRadHead;

if (wPar = VK_DIVIDE)  or (wPar = VK_DECIMAL) or
   (wPar = VK_NUMPAD1) or (wPar = VK_NUMPAD3) or
   (wPar = VK_NUMPAD7) or (wPar = VK_NUMPAD9) then begin
   vecFront :=V3RotationOrg(vecFront, vecAxis, vRotRadHead);
   vecRight :=V3RotationOrg(vecRight, vecAxis, vRotRadHead);
   vecUp    :=V3RotationOrg(vecUp,    vecAxis, vRotRadHead);
   end;

// Translations of the viwer in space
//"""""""""""""""""""""""""""""""""""

// If the shift key was pressed
if fShiftDown then case wPar of
   VK_NUMPAD8 : begin
      // Move in the + Body Vertical Z direction
      vecM:=vecM + vTransDist * poBodyL^.vecBodyVertZ
      end;
   VK_NUMPAD2 : begin
      // Move in the - Body Vertical Z direction
      vecM:=vecM - vTransDist * poBodyL^.vecBodyVertZ
      end;
   VK_NUMPAD4 : begin
      // Move in the - Body Horizontal X direction
      vecM:=vecM - vTransDist * poBodyL^.vecBodyHorizX
      end;
   VK_NUMPAD6: begin
      // Move in the + Body Horizontal X direction
      vecM:=vecM + vTransDist * poBodyL^.vecBodyHorizX
      end;
   VK_NUMPAD5 : begin
      // Move in the + Body Front direction
      vecM:=vecM + vTransDist * poBodyL^.vecBodyFrontY
      end;
   VK_NUMPAD0 : begin
      // Move in the - Body Front direction
      vecM:=vecM - vTransDist * poBodyL^.vecBodyFrontY
      end;
   end // if .. case
else case wPar of
   VK_NUMPAD8 : begin
      // move Up, i.e. in the direction of vecUp
      vecM:=vecM + vTransDist * vecUp;
      end;
   VK_NUMPAD2 : begin
      // move Down, i.e. in the direction of -vecUp
      vecM:=vecM - vTransDist * vecUp;
      end;
   VK_NUMPAD4 : begin
      // move Left
      vecM:=vecM - vTransDist * vecRight;
      end;
   VK_NUMPAD6: begin
      // move Right
      vecM:=vecM + vTransDist * vecRight;
      end;
   VK_NUMPAD5 : begin
      // Avance to come closer to the body
      vecM:=vecM + 2*vTransDist * vecFront;
      end;
   VK_NUMPAD0 : begin
      // Go back to go further from the body
      vecM:=vecM - 2*vTransDist * vecFront;
      end;

// Translation in the space directions
//'''''''''''''''''''''''''''''''''''''
   VK_HOME,
   VK_END : begin
      if fShiftDown then begin
         end
      else begin
         // Go forward or backward in the direction corresponding to the
         // intersection of the  plan generated by vecFront, vecUp
         // with the  X-Y plan
         if (vecUp[3] <> 0) then
              vecAxis:=V3NormaliseFunc(vecFront - (vecFront[3]/vecUp[3]) * vecUp)
         else vecAxis:=vecFront;

         if (wPar = VK_END) then vTransDist:=-vTransDist;

         vecM:=vecM + vTransDist * vecAxis;
         end;

      end;
   VK_INSERT,
   VK_PRIOR : begin
      if fShiftDown then begin
//         vecM:=vecM + vTransDist * poBodyL^.vecBodyHorizX
         end
      else begin
         // Rotate Right or Left arount the space Z axis,
         // using the intersection of the Front direction with the space X-Y plane
         // as a center of rotation
         vecAxis:=gglvecV3AxisZ;

         if (vecFront[3] <> 0) then vecRotC:=vecM - (vecM[3] / vecFront[3]) * vecFront
         else                       vecRotC:=vecQ;

         if (wPar = VK_PRIOR) then vRotRad:=-vRotRad;

         vecM     :=V3RotationVec(vecM,     vecRotC, vecAxis, vRotRad);
         vecUp    :=V3RotationOrg(vecUp,             vecAxis, vRotRad);
         vecRight :=V3RotationOrg(vecRight,          vecAxis, vRotRad);
         vecFront :=V3RotationOrg(vecFront,          vecAxis, vRotRad);
         end;
      end;

// Zooming
   VK_ADD : begin // Zoom in
      if fCtrlDown then vZoomFactor:=vZoomFactor * 1.02
      else              vZoomFactor:=vZoomFactor * 1.20;
      end;
   VK_SUBTRACT : begin // Zoom out
      if fCtrlDown then vZoomFactor:=vZoomFactor / 1.02
      else              vZoomFactor:=vZoomFactor / 1.20;
      end;

// Special for debugging and slowing down, to view the drawings
//""""""""""""""""""""""
   VK_SPACE : begin
      if fCtrlDown then begin
         if (wSlowDrawingMem < 1000000000) then wSlowDrawingMem:=wSlowDrawingMem * 2
         else MessageBeep(0);
         end
      else if not fShiftDown then begin
         // Shift is not pressed
         wSlowDrawingMem:=wSlowDrawingMem div 2;
         if (wSlowDrawingMem = 0) then begin
            wSlowDrawingMem:=1;
            MessageBeep(0);
            end;
         end;

      // To repaint the drawing
      // To view the painting
      EraseBackground;
      hDCDraw:=GetDC(hWin);
      hBrushOldL:=SelectObject(hDCDraw, hBrushFaces);

      wSlowDrawing:=wSlowDrawingMem;
      DrawBodies;
      SelectObject(hDCDraw, hBrushOldL);
      ReleaseDC(hWin, hDCDraw);
      hDCDraw:=0;
      exit;
      end;
   end; // case movments in space

//"""""""""""""""
// Body Movements
//"""""""""""""""

// Body Translations
//''''''''''''''''''
if (wBodySelected > 0) then case wPar of
   VK_X,
   VK_F : begin // Translate the Body in the + or - Front direction
                // or in  + or -  vecBodyFrontY
      if fShiftDown then vecAxis:=poBodyL^.vecBodyFrontY
      else               vecAxis:=vecFront;

      if (wPar = VK_X) then vTransDist:=-vTransDist;
      poBodyL^.BodyTranslate(vTransDist*vecAxis);
      end;
   VK_G,
   VK_D : begin  // Translate the Body in the Right direction
                // or in  + or -  vecBodyHorizX
      if fShiftDown then vecAxis:=poBodyL^.vecBodyHorizX
      else                                     vecAxis:=vecRight;

      if (wPar = VK_D) then vTransDist:=-vTransDist;
      poBodyL^.BodyTranslate(vTransDist*vecAxis);
      end;
   VK_C, VK_V,
   VK_R : begin // Translate the Body in the Up direction
                // or in  + or -  vecBodyVertZ
      if fShiftDown then vecAxis:=poBodyL^.vecBodyVertZ
      else                                     vecAxis:=vecUp;

      if (wPar = VK_C) or (wPar = VK_V) then vTransDist:=-vTransDist;
      poBodyL^.BodyTranslate(vTransDist*vecAxis);
      end;

// Body Rotations
//'''''''''''''''
   VK_N, VK_M,
   VK_U : begin // Rotate Up or Down the Body around the the Right direction
                // or around vecBodyVertZ
      if fShiftDown then  vecAxis:=poBodyL^.vecBodyHorizX
      else                vecAxis:=vecRight;

      if (wPar = VK_U) then vRotRad:=-vRotRad;
      poBodyL^.BodyRotateGC(vecAxis, vRotRad);
      end;
   VK_H,
   VK_K : begin // Rotate Left or right the Body arount the Up or Down direction
                // or around vecBodyHorizX
      if fShiftDown then  vecAxis:=poBodyL^.vecBodyVertZ
      else                vecAxis:=vecUp;

      if (wPar = VK_H) then vRotRad:=-vRotRad;
      poBodyL^.BodyRotateGC(vecAxis, vRotRad);
      end;

   VK_Y,VK_Z,
   VK_I : begin // Rotate the Body Left or Right around the Front direction
                // or around vecBodyFrontY
      if fShiftDown then  vecAxis:=poBodyL^.vecBodyFrontY
      else                vecAxis:=vecFront;

      if (wPar = VK_Y) or (wPar = VK_Z) then vRotRad:=-vRotRad;
      poBodyL^.BodyRotateGC(vecAxis, vRotRad);
      end;
   end; // Case Body Rotations

// Rotations around the X, Y, Z Axis
//''''''''''''''''''''''''''''''''''
if fShiftDown then begin
   case wPar of
   VK_1 : begin // Rotate the Body Up around the X Axis
          poBodyL^.BodyRotateGC(gglvecV3AxisX, vRotRad);
          end;
   VK_2 : begin // Rotate the Body Down around the X Axis
          poBodyL^.BodyRotateGC(gglvecV3AxisX, -vRotRad);
          end;
   VK_3 : begin // Rotate the Body Up around the Y Axis
          poBodyL^.BodyRotateGC(gglvecV3AxisY, vRotRad);
          end;
   VK_4 : begin // Rotate the Body Down around the Y Axis
          poBodyL^.BodyRotateGC(gglvecV3AxisY, -vRotRad);
          end;
   VK_5 : begin // Rotate the Body Right around the Z Axis
          poBodyL^.BodyRotateGC(gglvecV3AxisZ, vRotRad);
          end;
   VK_6 : begin // Rotate the Body Left around the Z Axis
          poBodyL^.BodyRotateGC(gglvecV3AxisZ, -vRotRad);
          end;
      end; // case
   end // Body Rotations
else begin // Body translation in the X, Y, Z directions
   case wPar of
      VK_1 : begin // Translate Right along the  X  axis
          poBodyL^.BodyTranslate(vTransDist * gglvecV3AxisX);
          end;
      VK_2 : begin // Translate Left along the  X  axis
          poBodyL^.BodyTranslate(-vTransDist * gglvecV3AxisX);
          end;
      VK_3 : begin // Translate Front along the  Y  axis
          poBodyL^.BodyTranslate(vTransDist * gglvecV3AxisY);
          end;
      VK_4 : begin // Translate Back along the  Y  axis
          poBodyL^.BodyTranslate(-vTransDist * gglvecV3AxisY);
          end;
      VK_5 : begin // Translate Up along the  Z  axis
          poBodyL^.BodyTranslate(vTransDist * gglvecV3AxisZ);
          end;
      VK_6 : begin // Translate Down along the  Z  axis
          poBodyL^.BodyTranslate(-vTransDist * gglvecV3AxisZ);
          end;
      end; // case
   end; // case Body movements

wSlowDrawing:=1; // No slowing down.

// Drawing...
Draw3DBodies;
end;  // TDraw3DFrame0.Visualise

procedure TDraw3DFrame0.SetScreenCenter(vecNewCenter:RVectR3Float);
//=================================================================
// Call it when you want to set the viewing direction
begin
// The center of the screen = vecNewCenter
vecQ:=vecNewCenter;

// Position where we are = vecM  [cm]
vecM:=vecQ - vMQDistance * vecFront;

// Update the content of the dialog box
if (poDialogBoardTable <> NIL) then poDialogBoardTable^.UpdateFromVar;
end;  // TDraw3DFrame0.SetScreenCenter

procedure TDraw3DFrame0.SetViewDirection(vecNewPlace:RVectR3Float);
//=================================================================
// Call it when you want to set the viewing direction
// The viewing direction will be from  vecM  to vecNewPlace
// Without modifying the viewer position : vecM
// It will modify  vecQ  and the vectors  vecRight, vecFront, vecUp
begin
if (Norme2(vecNewPlace - vecM) < 0.1) then exit;

// Viewing direction
vecFront:=V3NormaliseFunc(vecNewPlace - vecM);

// New position of the center of the screen
vecQ:=vecM + vMQDistance * vecFront;

Draw3DSetup(vecM[1],vecM[2],vecM[3], vecQ[1],vecQ[2],vecQ[3]);

// Update the content of the dialog box
if (poDialogBoardTable <> NIL) then poDialogBoardTable^.UpdateFromVar;
end;  // TDraw3DFrame0.SetViewDirection

procedure TDraw3DFrame0.DeleteBody(wIndexL:word);
//===============================================
// Delete the body  papoBodies^[wIndexL]  and
// delete the corresponding points and faces.
// This will shift the points, faces and body in the array.
// dispose the deleted body.
var
  wNumPointsDel, // Number of points to delete
  wNumFacesDel,  // Number of faces  to delete
  ww          : word32;
  nn          : integer;
  poBodyDel   : PBody3D0; // the deleted body
begin
if (wIndexL = 0) or (wIndexL > wNumBodies) then exit; // invalide.

// Body to delete
poBodyDel:=papoBodies^[wIndexL];

wNumPointsDel:=1 + poBodyDel^.wIndPtsMax  - poBodyDel^.wIndPtsMin;
wNumFacesDel :=1 + poBodyDel^.wIndFaceMax - poBodyDel^.wIndFaceMin;

// Delete the points, by shifting them
for ww:=1+poBodyDel^.wIndPtsMax to wNumPoints do
    parPoints^[ww - wNumPointsDel]:=parPoints^[ww]; // record equality

wNumPoints:=wNumPoints - wNumPointsDel;

// Delete the faces, by shifting them
for ww:=1+poBodyDel^.wIndFaceMax to wNumFaces do begin
    dec(parFaces^[ww].bBody); // The index of the body owining this face decreased by one

    // Must shift the reference to each points of each face
    for nn:=1 to parFaces^[ww].nNbPoint do
       parFaces^[ww].aprPoint[nn]:=pointer( word32(parFaces^[ww].aprPoint[nn]) -
                                            wNumPointsDel*SizeOf(TypePoint3D));
    // Shift the Faces in the array
    parFaces^[ww - wNumFacesDel]:=parFaces^[ww]; // record equality
    end;

wNumFaces:=wNumFaces - wNumFacesDel;

// Shift the bodies
for ww:=wIndexL+1 to wNumBodies do begin
   // Shift the indexes desining the points and faces of the body
   papoBodies^[ww]^.wIndPtsMax :=papoBodies^[ww]^.wIndPtsMax  - wNumPointsDel;
   papoBodies^[ww]^.wIndPtsMin :=papoBodies^[ww]^.wIndPtsMin  - wNumPointsDel;
   papoBodies^[ww]^.wIndFaceMax:=papoBodies^[ww]^.wIndFaceMax - wNumFacesDel;
   papoBodies^[ww]^.wIndFaceMin:=papoBodies^[ww]^.wIndFaceMin - wNumFacesDel;

   // Change the body index ine the popoBodies array
   papoBodies^[ww]^.wIndBody:=ww-1;

   // Shift the bodies in the array of bodies.
   papoBodies^[ww-1]:=papoBodies^[ww];
   end; // For

// One less body
dec(wNumBodies);

// Finally dispose the deleted body object.
dispose(poBodyDel, Done);

// Select another body
SelectBody(nXProjCenter, nYProjCenter);
end;  // TDraw3DFrame0.DeleteBody

procedure TDraw3DFrame0.EraseBackground;
//======================================
// Erase the background of the window
var
  rRec        : RECT;
  hDCDrawL    : HDC;
Begin
hDCDrawL:=GetDC(hWin);
GetClientRect(hWin, rRec);
FillRect(hDCDrawL, rRec, hBrushBlack);
ReleaseDC(hWin, hDCDrawL);
end;  // TDraw3DFrame0.EraseBackground

procedure TDraw3DFrame0.ButtonClicked(poCtrl:PWindowBase0; wCtrlIDP:Word32);
//==========================================================================
var rChooseCol  : TCHOOSECOLOR;
begin
if (wCtrlIDP < 30801) or (wCtrlIDP > 30900) then exit;

// Structure passed to the choose color dialog box
with rChooseCol do begin
   lStructSize    :=SizeOf(rChooseCol);
   hwndOwner      :=hWin;
   hInstance      :=System.MainInstance;
   if (wCtrlIDP = 30801) then rgbResult:=papoBodies^[wBodySelected]^.colorSup.wColor;
   if (wCtrlIDP = 30802) then rgbResult:=papoBodies^[wBodySelected]^.colorInf.wColor;
   if (wCtrlIDP = 30803) then rgbResult:=papoBodies^[wBodySelected]^.colorAx .wColor;
   if (wCtrlIDP = 30831) then rgbResult:= colorRightEye.wColor;
   if (wCtrlIDP = 30834) then rgbResult:= colorLeftEye.wColor;
   if (wCtrlIDP = 30835) then rgbResult:= colorAxes.wColor;
   if (wCtrlIDP = 30836) then rgbResult:= colorBlack.wColor;
   if (wCtrlIDP = 30837) then rgbResult:= colorFaces.wColor;
   lpCustColors   :=@awCustomColors[1];
   Flags          :=CC_RGBINIT or CC_FULLOPEN;
   lCustData      :=0;   // used if a user hook function is defined
   lpfnHook       :=NIL; // no user hook function is defined
   lpTemplateName :=NIL; // Used only if the flag CC_ENABLETEMPLATE is set.
   end; // with

// Open a dialog box, to choose the color
ChooseColor(@rChooseCol);

// Set the color to the body
if (wCtrlIDP = 30801) then
   papoBodies^[wBodySelected]^.colorSup.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30802) then
   papoBodies^[wBodySelected]^.colorInf.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30803) then
   papoBodies^[wBodySelected]^.colorAx.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30831) then
   colorRightEye.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30834) then
   colorLeftEye.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30835) then
   colorAxes.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30836) then
   colorBlack.wColor:=rChooseCol.rgbResult;

if (wCtrlIDP = 30837) then
   colorFaces.wColor:=rChooseCol.rgbResult;

// Udate the content of the dialog box
PDialog(poCtrl^.poParent)^.UpdateFromVar;
end;  // TDraw3DFrame0.ButtonClicked

{
procedure TDraw3DFrame0.CtrlVarUpdate(poCtrl:PWindowBase0; pVar:pointer);
//=======================================================================
// A position or direction has been changed in the dialog box
begin
// From the angles, recompute the vecUp, vecRight and vecFront vectors
//invalidateRgn(hWin, 0, TRUE);
end;  // TDraw3DFrame0.CtrlVarUpdate
}
procedure TDraw3DFrame0.AskAcceptDlgQuit(poDlg:PWindowBase0; wCtrlIDP:word32; var wRep:word32);
//=============================================================================================
// The board table dialog box is closing.
begin
if (poDlg = Pointer(poDialogBoardTable)) then poDialogBoardTable:=NIL;
wCtrlIDP:=wCtrlIDP; wRep:=wRep; // to avoid a hint
end; // TDraw3DFrame0.AskAcceptDlgQuit

function TDraw3DFrame0.NumberOfVisibleBodies(var wBodyLastV:word):word;
//=====================================================================
// Return the number of visible bodies.
// wBodyLastV = the last visible body of the list
var
  wCountVisible,
  ww            : word;
begin
wCountVisible:=0;
wBodyLastV   :=0; // No bodies visible yet.

// Loop over all bodies
for ww:=1 to wNumBodies do begin
   if papoBodies^[ww]^.fIsVisible then begin
      inc(wCountVisible);
      wBodyLastV:=ww;
      end;
   end; // for

NumberOfVisibleBodies:=wCountVisible;
end;  // TDraw3DFrame0.NumberOfVisibleBodies

function TDraw3DFrame0.NearestBody(nXPosPixP,nYPosPixP:integer):word;
//===================================================================
// return the index of the body which center of gravity projection is the
// nearest from (nXPosPix,nXPosPix)
// Return zero, is no body is defined.
var
  nDistMin2,  // Distance between the nearest body and the given point
  nDist2      // Distance between a body and the given point
              : int32;
  ww          : word;
begin
NearestBody:=0; // if no body exist

if (wNumBodies = 0) then exit;

nDistMin2:=40000; // must not be too fare from the object to select it
for ww:=1 to wNumBodies do begin
   nDist2:= sqr(papoBodies^[ww]^.nGCU - nXPosPixP) +
            sqr(papoBodies^[ww]^.nGCV - nYPosPixP);
   if (nDistMin2 > nDist2) and papoBodies^[ww]^.fIsVisible then begin
      nDistMin2:=nDist2;
      NearestBody:=ww;
      end;
   end; // for
end;  // TDraw3DFrame0.NearestBody

function TDraw3DFrame0.NearestBody(vecPosP:RVectR3Float):word;
//=============================================================
// return the index of the body which center of gravity is the
// nearest from the given vector  vecPosP
var
  vDistMin2,  // Distance between the nearest body and the given point
  vDist2      // Distance between a body and the given point
              : Float;
  ww          : word;
begin
NearestBody:=0; // if no body exist

if (wNumBodies = 0) then exit;

vDistMin2:=10000000; // big
for ww:=1 to wNumBodies do begin
   vDist2:= Norme2(papoBodies^[ww]^.vecGravityCenter - vecPosP);
   if (vDistMin2 > vDist2) and papoBodies^[ww]^.fIsVisible then begin
      vDistMin2:=vDist2;
      NearestBody:=ww;
      end;
   end; // for
end;  // TDraw3DFrame0.NearestBody second version

procedure TDraw3DFrame0.SelectBody(nXPosPixP,nYPosPixP:integer);
//==============================================================
// Sets the  wBodySelected  to the nearest body from the given position,
// and show the selected Body in the status line
// wBodySelected=0, if no body is selected.
begin
// Set the selected Body, and show in the status line the selected one
// If only one body is visible, it is selected.
if (NumberOfVisibleBodies(wBodySelected) <> 1) then
   wBodySelected:=NearestBody(nXPosPixP,nYPosPixP);

if (wBodySelected > 0) then begin
   PWindow( gglpoMainWindow)^.poStatusLine^.SetStatusText(0,
            GetMsg(2100020005) + papoBodies^[wBodySelected]^.strBodyName);
   end
else PWindow( gglpoMainWindow)^.poStatusLine^.SetStatusText(0,
              GetMsg(2100020006));
end;  // TDraw3DFrame0.SelectBody

procedure TDraw3DFrame0.SelectBody(vecPosP:RVectR3Float);
//=======================================================
// Sets the  wBodySelected  to the nearest body which center of gravity is the
// nearest from the given vector  vecPosP
// wBodySelected=0, if no body is selected.
var
  vDistMin2,  // Distance between the nearest body and the given point
  vDist2      // Distance between a body and the given point
              : Float;
  ww          : word;
begin
if (NumberOfVisibleBodies(wBodySelected) <> 1) then begin
   vDistMin2:=10000000; // big
   for ww:=1 to wNumBodies do begin
      vDist2:= Norme2(papoBodies^[ww]^.vecGravityCenter - vecPosP);
      if (vDistMin2 > vDist2) and papoBodies^[ww]^.fIsVisible then begin
         vDistMin2:=vDist2;
         wBodySelected:=ww;
         end;
      end; // for
   end; // if

if (wBodySelected > 0) then begin
   PWindow( gglpoMainWindow)^.poStatusLine^.SetStatusText(0,
            GetMsg(2100020005) + papoBodies^[wBodySelected]^.strBodyName);
   end
else PWindow( gglpoMainWindow)^.poStatusLine^.SetStatusText(0,
              GetMsg(2100020006));
end;  // TDraw3DFrame0.SelectBody

procedure TDraw3DFrame0.WMContextMenu(lPar:LPARAM);
//=================================================
// Received if right click. It will open a contextual menu
var
  poMenuL     : PMenu;
  hSubmenu1L  : HMENU;
  hSubmenu2L  : HMENU;
  rRec        : RECT;
  wNbCentered, // Number of body in the menu "Center on Body"
  ww          : word;
  strS        : ShortString;

begin
// Remember where we clicked
GetCursorPos(rPtLastClick);
ScreenToClient(hWin, rPtLastClick);

// Get the menu object from the main window
poMenuL:=gglpoMainWindow^.poMenu;

poMenuL^.MenuPopupMsgCreate(2100020010);

with poMenuL^ do if (hMenuPopupObj <> 0) then begin
   rRec.Left:=0;   rRec.Top:=0;   rRec.Right:=0;   rRec.Bottom:=0;

   // Get the menu handle to append other sub-menus
   hSubmenu1L:=GetPopupSubMenuByMsgNum(hMenuPopupObj, 2100020012);
   hSubmenu2L:=GetPopupSubMenuByMsgNum(hMenuPopupObj, 2100020014);
   DeleteMenu(hSubmenu1L, 0, MF_BYPOSITION); // Delete the first sub menu
   DeleteMenu(hSubmenu2L, 0, MF_BYPOSITION); // Delete the first sub menu

   wNbCentered:=0;
   // Add a menu line for each Body, to hide or show them
   for ww:=1 to wNumBodies do begin
      strS:=GetMsg(2100020001) + papoBodies^[ww]^.strBodyName + #0;
      AppendMenu(hSubmenu1L,MF_STRING,31000+ww, @strS[1]);

      // List of visible bodies
      if papoBodies^[ww]^.fIsVisible then begin
         inc(wNbCentered);
         strS:=GetMsg(2100020002) + papoBodies^[ww]^.strBodyName + #0;
         AppendMenu(hSubmenu2L,MF_STRING,32000+ww, @strS[1]);
         end;

      // Set or remove checks from menu items
      if papoBodies^[ww]^.fIsVisible then
           CheckMenuItem(hSubmenu1L, 31000+ww, MF_BYCOMMAND or MF_CHECKED)
      else CheckMenuItem(hSubmenu1L, 31000+ww, MF_BYCOMMAND or MF_UNCHECKED);
      end; // for

   // If no bodies, disable this popup sub menu
   if (wNumBodies = 0) then begin
      poMenuL^.EnableMenuItemByMsgNum(hMenuPopupObj, 2100020012, MF_GRAYED);
      end;

   // If no bodies visible, disable this popup sub menu
   if (wNbCentered = 0) then begin
      poMenuL^.EnableMenuItemByMsgNum(hMenuPopupObj, 2100020014, MF_GRAYED);
      end;

   // If no body is selected, disable this popup sub menu
   if (wBodySelected = 0) or
      (not papoBodies^[wBodySelected]^.fIsVisible) then begin
      poMenuL^.EnableMenuItemByMsgNum(hMenuPopupObj, 2100020011, MF_GRAYED);
      EnableMenuItem(hMenuPopupObj, 30005, MF_BYCOMMAND or MF_GRAYED)
      end;

   // Open the popup menu
   TrackPopupMenu( hMenuPopupObj, TPM_RIGHTBUTTON,
                   LoWord(lPar), // horizontal position, in screen coordinates
                   HiWord(lPar), // vertical position, in screen coordinates
                              0, // reserved, must be zero
                           hWin, // handle of owner window
                           rRec);
   end;

// Open the contextual menu :
//poMenuL^.MenuPopupTrack(hWin, 2100020010, lPar, TPM_RIGHTBUTTON);

// Here it is a good idea to delete from memory the created popup menu.
// If you forget it, it doesn't matter, it will be
// destroyed automatically the next time you create the popup menu.
poMenuL^.MenuPopupDestroy;
end;  // TDraw3DFrame0.MContextMenu

procedure TDraw3DFrame0.WMPaint;
//==============================
// Do not use the paint DC given by WMPaint, but use the standard Draw3DBodies
var
  rPaint      : PAINTSTRUCT;

Begin
// Gets the info to paint and call EndPaint to tell Windows that the
// window has been updated
hDCDraw :=BeginPaint(hWin, @rPaint);
EndPaint(hDCDraw, @rPaint);

if (wNumFaces > 0) then Draw3DBodies;
end;  //  TDraw3DFrame0.WMPaint

function TDraw3DFrame0.WMCommand(wPar:WPARAM; lPar:LPARAM):LRESULT;
//=================================================================
// Handles the menu messages
var
  wNumberPointsL,
  wNumberFacesL
              : Word32;
  nRep        : integer;

  vRotAngleL,  // Angle of rotation for body rotation
  vBodyNewSizeL, // To set a new Body size
  vSizeFactorL : Float;  // To resize a body
  colSupL,    // Color of the superior part of the body
  colInfL,    // Color of the inferior part of the body
  colAxL,     // Color of the axes if any
  colAxesOldL, // Color of the global axes
  colorRightEyeOldL,
  colorLeftEyeOldL,
  colorBlackOldL,
  colorFacesOldL
              : TypeColor;
  vecNewVertZL,  // New Body  vecBodyVertZ  direction
  vecNewHorizXL, // New Body  vecBodyHorizX  direction
  vecTranslatL, // Translation vector for body translation
  vecRotAxisL,  // Axis for body rotation
  vecNewPosL   // New Body position
               : RVectR3Float;

begin
WMCommand:=0;

// FloatField(2100020001, vecM[1]); to number  2100020050 are reserved for
// variables of the program.

// Memorise the axes color and others, to see if it changed
colAxesOldL       :=colorAxes;
colorRightEyeOldL :=colorRightEye;
colorLeftEyeOldL  :=colorLeftEye;
colorBlackOldL    :=colorBlack;
colorFacesOldL    :=colorFaces;

with papoBodies^[wBodySelected]^ do begin
   wNumberPointsL:=1 + wIndPtsMax  - wIndPtsMin;
   wNumberFacesL :=1 + wIndFaceMax - wIndFaceMin;
   FloatField( 2100020081, vecNewPosL[1]);
   FloatField( 2100020082, vecNewPosL[2]);
   FloatField( 2100020083, vecNewPosL[3]);
   FloatField( 2100020084, vecNewVertZL[1]);
   FloatField( 2100020085, vecNewVertZL[2]);
   FloatField( 2100020086, vecNewVertZL[3]);
   FloatField( 2100020087, vecNewHorizXL[1]);
   FloatField( 2100020088, vecNewHorizXL[2]);
   FloatField( 2100020089, vecNewHorizXL[3]);
   Word32Field(2100020091, wNumberPointsL);
   Word32Field(2100020092, wNumberFacesL);
   FloatField( 2100020093, vBodySize);

   StrField(   2100020101, strBodyName, SizeOf(strBodyName));
   ByteField(  2100020111, colorSup.bRed);
   ByteField(  2100020112, colorSup.bGreen);
   ByteField(  2100020113, colorSup.bBlue);
   ByteField(  2100020121, colorInf.bRed);
   ByteField(  2100020122, colorInf.bGreen);
   ByteField(  2100020123, colorInf.bBlue);
   ByteField(  2100020131, colorAx.bRed);
   ByteField(  2100020132, colorAx.bGreen);
   ByteField(  2100020133, colorAx.bBlue);
   FloatField( 2100020151, vSizeFactorL);
   FloatField( 2100020152, vBodyNewSizeL);

   FloatField( 2100020201, vecTranslatL[1]);
   FloatField( 2100020202, vecTranslatL[2]);
   FloatField( 2100020203, vecTranslatL[3]);
   FloatField( 2100020204, vecRotAxisL[1]);
   FloatField( 2100020205, vecRotAxisL[2]);
   FloatField( 2100020206, vecRotAxisL[3]);
   FloatField( 2100020207, vRotAngleL);

   // Default values for body translation and rotation
   vecTranslatL :=V3Assign(0,0,0);
   vecRotAxisL  :=V3Assign(0,0,1); // In Body axis coordinate
   vRotAngleL   :=0.0;

   colSupL.wColor :=colorSup.wColor;
   colInfL.wColor :=colorInf.wColor;
   colAxL.wColor  :=colorAx.wColor;
   vBodyNewSizeL  :=vBodySize; // By default, keep the same size
   vSizeFactorL   :=1.0;

   // To set the body position and orientation
   vecNewVertZL  :=vecBodyVertZ;
   vecNewHorizXL :=vecBodyHorizX;
   vecNewPosL    :=vecGravityCenter;

case wPar of
   30001 : begin // Open Properties dialog box
      if (wBodySelected = 0) then exit;

      // Change the Body properties, and show information about it.
      nRep:=DialogExecMSG(2100020299, @Self);

      if (nRep = IDOK) then begin
         if (vSizeFactorL <> 1.0) and
            (abs(vSizeFactorL)  >= 0.099) and (abs(vSizeFactorL) <= 10.1) then begin
            // Resize the body
            BodyResize(vSizeFactorL);
            end; // if (vSizeFactorL ...

         if (colSupL.wColor <> colorSup.wColor) then begin
            // The color of the superior face changed
            DestroyPens(1); // Destroy the pen of the superior face

            // Create a new pen
            hPenSup  :=CreatePen(PS_SOLID, 1, ColorSup.wColor);
            end; // if (colSupL.wColor <> colorSup) then begin

         if (colInfL.wColor <> colorInf.wColor) then begin
            // The color of the inferior face changed
            DestroyPens(2); // Destroy the pen of the inferior face

            // Create a new pen
            hPenInf  :=CreatePen(PS_SOLID, 1, ColorInf.wColor);
            end; // if (colInfL.wColor <> colorInf) then begin

         if (colAxL.wColor <> colorAx.wColor) then begin
            // The color of the axes
            DestroyPens(4); // Destroy the pen of the axes

            // Create a new pen
            hPenAx  :=CreatePen(PS_SOLID, 1, ColorAx.wColor);
            end; // if (colAxL.wColor <> colorAx) then begin

         // To compute the size
         ComputeGravityCenter(0,0);
         Draw3DBodies;
         end; // if (nRep = IDOK)
      end;
   30002 : if (wBodySelected > 0) then begin  // Hide the selected Body
      papoBodies^[wBodySelected]^.fIsVisible:=FALSE;

      // Select the nearest body to the screen center
      SelectBody(nXProjCenter, nYProjCenter);

      Draw3DBodies;
      end;
   30004 : if (wBodySelected > 0) then begin  // Show or hide its axes
      papoBodies^[wBodySelected]^.fIsShowBodyAxis:=
          not papoBodies^[wBodySelected]^.fIsShowBodyAxis;
      Draw3DBodies;
      end;
   30005 : if (wBodySelected > 0) then begin // Set rotation center on Body center
      vecRotationCenter:=papoBodies^[wBodySelected]^.vecGravityCenter;
      end;
   30006 : begin // Set rotation center on mouse click on screen
      vecRotationCenter:= vecQ +
               ((rPtLastClick.x - nXProjCenter) / vCentimetersToPixels) * vecRight -
               ((rPtLastClick.y - nYProjCenter) / vCentimetersToPixels) * vecUp;
      end;
   30008 : begin // Open a dialog box to translate and rotate the Body
      if (wBodySelected = 0) then exit;

      // Change the Body position, orientation and size.
      nRep:=DialogExecMSG(2100020599, @Self);

      if (nRep = IDOK) then begin
         BodyRotateGC( vecRotAxisL[3]*vecBodyVertZ +
                       vecRotAxisL[2]*vecBodyFrontY +
                       vecRotAxisL[1]*vecBodyHorizX, vRotAngleL);
         BodyTranslate( vecTranslatL[3]*vecBodyVertZ +
                        vecTranslatL[2]*vecBodyFrontY +
                        vecTranslatL[1]*vecBodyHorizX);

         if (vSizeFactorL <> 1.0) and
            (abs(vSizeFactorL)  >= 0.099) and (abs(vSizeFactorL) <= 10.1) then begin
            // Resize the body
            BodyResize(vSizeFactorL);
            end; // if (vSizeFactorL ...
         // To compute the size
//         ComputeGravityCenter(0,0);
         Draw3DBodies;
         end; // if (nRep = IDOK)
      end;
   30009 : begin // Open a dialog box to set its position, orientation and size
      if (wBodySelected = 0) then exit;

      // Change the Body position, orientation and size.
      nRep:=DialogExecMSG(2100020699, @Self);

      if (nRep = IDOK) then begin
         BodySetPosition(vecNewPosL);

         // To set the new Body orientation
         BodySetOrientation(vecNewVertZL, vecNewHorizXL);

         if (abs(vBodyNewSizeL - vBodySize) >= 0.06) and
            (abs(vBodySize) >  0.1 * abs(vBodyNewSizeL) ) and
            (abs(vBodySize) < 10.0 * abs(vBodyNewSizeL) )then begin
            // Resize the body
            BodyResize(vBodyNewSizeL / vBodySize);
            end;
         //  refuse the new size, or no changes

         Draw3DBodies;
         end; // if (nRep = IDOK)
      end;
   30051 : if (wBodySelected > 0) then begin // Delete the selected Body
      // Ask confirmation to delete the Body
      nRep:=DialogExecMSG(2100020399, @Self);

      if (nRep = IDOK) then begin
         DeleteBody(wBodySelected);
         Draw3DBodies;
         end;
      end;
   30901 : begin // Switch to view in 3D
      fView3DWithGalsses:=not fView3DWithGalsses;
      Draw3DBodies;
      end;
   30902 : begin // Switch to view or not the Axis
      fView3DAxis:=not fView3DAxis;
      Draw3DBodies;
      end;
   30905 : begin // Some Global parameters, to change rotation angles, translation, colors, ...
      DialogExecMSG(2100020499, @Self);

      if (colAxesOldL.wColor <> colorAxes.wColor) then begin
         // New axis colors
         DeleteObject(hPenAxes);
         hPenAxes:=CreatePen(PS_SOLID, 1, ColorAxes.wColor);
         end;

      if (colorRightEyeOldL.wColor <> colorRightEye.wColor) then begin
         // New color
         DeleteObject(hPen3DRightEye);
         hPen3DRightEye:=CreatePen(PS_SOLID, 1, colorRightEye.wColor);
         end;

      if (colorLeftEyeOldL.wColor <> colorLeftEye.wColor) then begin
         // New color
         DeleteObject(hPen3DLeftEye);
         hPen3DLeftEye:=CreatePen(PS_SOLID, 1, colorLeftEye.wColor);
         end;

      if (colorBlackOldL.wColor <> colorBlack.wColor) then begin
         // New color
         DeleteObject(hBrushBlack);
         hBrushBlack:=CreateSolidBrush(colorBlack.wColor);
         end;

      if (colorFacesOldL.wColor <> colorFaces.wColor) then begin
         // New color
         DeleteObject(hBrushFaces);
         hBrushFaces:=CreateSolidBrush(colorFaces.wColor);
         end;

      Draw3DBodies;
      end;
   30995 : begin // View Board navigation dialog box
      // Board table, navigation positions and directions
      DialogCreateMSG(2100020199, @Self);
      SetFocus(hWin);
      end;
   30999 : begin // View navigation Keys
      // Help, navigation keys.
      DialogCreateMSG(2100020099, @Self);
      SetFocus(hWin);
      end;
   31001..31250 : begin // Show / Hide a Body
      papoBodies^[wPar-31000]^.fIsVisible:=not papoBodies^[wPar-31000]^.fIsVisible;

      if (papoBodies^[wPar-31000]^.fIsVisible) then
         // Select the Body just made visible
         SelectBody(papoBodies^[wPar-31000]^.vecGravityCenter)
      else // Select the nearest body to the screen center
           SelectBody(nXProjCenter, nYProjCenter);

      Draw3DBodies;
      end;
   32001..32250 : begin // Set a Body in the center of the screen
      // The center of the screen = the center of gravity of the Body
      vecQ:=papoBodies^[wPar-32000]^.vecGravityCenter;

     // Position where we are = vecM  [cm]
      vecM:=vecQ - vMQDistance * vecFront;

      // Set also the gravity center
      vecRotationCenter:=papoBodies^[wPar-32000]^.vecGravityCenter;

      Draw3DBodies;
      end;

   else WMCommand:=inherited CtrlWindowProc(hWin, WM_COMMAND, wPar, lPar);
   end; // Case
   end; // with papoBodies^[wBodySelected]^ do begin

ClearField(2100020091);
ClearField(2100020092);
ClearField(2100020101);
ClearField(2100020111);
ClearField(2100020112);
ClearField(2100020113);
ClearField(2100020151);
end;  // TDraw3DFrame0.WMCommand

function TDraw3DFrame0.CtrlWindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT;
//==================================================================================================
// Handles the window messages
const
  strMemStatusLine : ShortString = '';
  // when entering the menu, memorised what was in the status line.
  // Must be const to hold its value when we quit and re-enter the procedure.
  // Could be a globale variable too
var
  rRec        : RECT;
  prML        : PRMenuLine;
  wInfoL      : word32;  // not used here, but must be passed as a parameter
  rMessageL   : MSG;
  wFlagsVisualise : word32;
  // Flags for visualisation, if Control and/or shift are pressed.

begin
CtrlWindowProc:=0;
//if ((wMsg = WM_KEYDOWN)) and (wPar <> VK_SHIFT) then
//   DBW(128, DBMsgToStr(wMsg) + ITS(wPar) + ITS(lPar) + ITS(lPar and $1000000));

case wMsg of
   WM_COMMAND : CtrlWindowProc:=WMCOMMAND(wPar, lPar);

                // Test to not memorise all the keydown pressed
   WM_KEYDOWN : begin
      wFlagsVisualise:=0;

      if ((lPar and $1000000) = 0) then begin
         // Shift is pressed and a Keypad key is pressed. Convert the Numpad keys
         // The shift is not seen where using keypads... -> more complicate
         wFlagsVisualise:=wFlagsVisualise or 2; // Shift ispressed

         case wPar of
            VK_INSERT : wPar:=VK_NUMPAD0;
            VK_END    : wPar:=VK_NUMPAD1;
            VK_DOWN   : wPar:=VK_NUMPAD2;
            VK_NEXT   : wPar:=VK_NUMPAD3;
            VK_LEFT   : wPar:=VK_NUMPAD4;
            VK_CLEAR  : wPar:=VK_NUMPAD5;
            VK_RIGHT  : wPar:=VK_NUMPAD6;
            VK_HOME   : wPar:=VK_NUMPAD7;
            VK_UP     : wPar:=VK_NUMPAD8;
            VK_PRIOR  : wPar:=VK_NUMPAD9;
            VK_DELETE : wPar:=VK_DECIMAL;
            else wFlagsVisualise:=wFlagsVisualise and (not 2); // no shift not pressed
            end; // case
         end; // if ((lPar and $1000000) = 0) then begin

      // Test if control and/or shift are pressed
      if (GetAsyncKeyState(VK_CONTROL) < 0) then wFlagsVisualise:=wFlagsVisualise or 1;
      if (GetAsyncKeyState(VK_SHIFT) < 0)   then wFlagsVisualise:=wFlagsVisualise or 2;

      if not PeekMessage(rMessageL, hWin, WM_KEYDOWN, WM_KEYDOWN, PM_NOREMOVE) then
         Visualise(wPar, wFlagsVisualise);
      end;
   WM_Paint   : WMPAINT;
   WM_Char    : begin
      // Key direction
      if (wPar = ord('?')) then begin
         DialogCreateMSG(2100020099, @Self);
         SetFocus(hWin);
         end;

      // Board table
      if (poDialogBoardTable = NIL) and
         ((wPar = ord('b')) or (wPar = ord('B'))) then begin
         poDialogBoardTable:=PDialog(DialogCreateMSG(2100020199, @Self));
         SetFocus(hWin);
         end;
      end; // WM_Char

   WM_ContextMenu : begin
      // Select the nearest Body from the mouse click
      SelectBody(LOWORD(lPar), HIWORD(lPar));

      WMContextMenu(lPar);
      end; // WM_ContextMenu

   WM_LBUTTONDOWN : begin
      // Remember where we clicked
//      GetCursorPos(rPtLastClick);
//      ScreenToClient(hWin, rPtLastClick);
      rPtLastClick.x:=LOWORD(lPar);
      rPtLastClick.y:=HIWORD(lPar);

      // Select the nearest Body from the mouse click
      SelectBody(LOWORD(lPar), HIWORD(lPar));
      end; // WM_LBUTTONDOWN

   WM_Size : if (wNumFaces > 0) then begin
      // To have the center of the screen corresponding to the point Q
      GetClientRect(hWin, rRec);
      nXProjCenter:=rRec.Right  div 2;
      nYProjCenter:=rRec.Bottom div 2;

      // Just to translate them on the screen
      projectAllPoints;
      end; // WM_SIZE

   WM_ERASEBKGND : begin
      // Set the background to black
      GetClientRect(hWin, rRec);
      FillRect(HDC(wPar), rRec, hBrushBlack);
      CtrlWindowProc:=1;
      end; // WM_ERASEBKGND

   WM_MENUSELECT : with PWindow(gglpoMainWindow)^ do if (poStatusLine <> NIL) and (poMenu <> NIL) then begin
      // DBW0(32, DBMsgToStr(wMsg) + ITS(LoWord(wPar)) + ITS(HiWord(wPar)) + ITS(lPar));
      // display an info in the status line.
      if ((HiWord(wPar) and MF_POPUP) = MF_POPUP) then
           prML:=poMenu^.oHt.GetPointerW2(lPar, word32(65536)*LoWord(wPar), wInfoL)
      else prML:=poMenu^.oHt.GetPointerW2(lPar, LoWord(wPar), wInfoL);

      if (prML <> NIL) then poStatusLine^.SetStatusText(0, prML^.strInfo);
                            // display the info in the status line
      end; // WM_MENUSELECT : begin

   WM_ENTERMENULOOP : with PWindow(gglpoMainWindow)^ do if (poStatusLine <> NIL) and (poMenu <> NIL) then
                         strMemStatusLine:=poStatusLine^.GetStatusText(0);
                         // Before changing the status line, memorise what was in it.
   WM_EXITMENULOOP  : with PWindow(gglpoMainWindow)^ do if (poStatusLine <> NIL) and (poMenu <> NIL) then
                         poStatusLine^.SetStatusText(0, strMemStatusLine);
                         // set back what was in the status line.

   else CtrlWindowProc:=inherited CtrlWindowProc(hWindow, wMsg, wPar, lPar);
   end; // Case wMsg of

end;  // TDraw3DFrame0.CtrlWindowProc

end.  // unit wDraw3DFrame0;

