-- File: CSGGraphicsImpl.mesa
-- Last edited by Bier on July 6, 1983 4:20 pm
-- Author: Eric Allan Bier in the summer of 1982
-- Contents: Implementation of a simple graphics package for 3d renderings.

DIRECTORY
 CoordSys,
 CGColor,
 CSGGraphics,
 DisplayList3d,
 Graphics,
 GraphicsColor,
 Inline,
 Matrix3d,
 Shading,
 SVArtwork,
 SVPolygon3d,
 Real,
 SVVector3d;

CSGGraphicsImpl: PROGRAM
IMPORTS CoordSys, Graphics, GraphicsColor, Matrix3d, Shading, SVVector3d
EXPORTS CSGGraphics =
BEGIN

Artwork: TYPE = REF ArtworkObj;
ArtworkObj: TYPE = SVArtwork.ArtworkObj;

Color: TYPE = GraphicsColor.Color;
CoordSysObj: TYPE = CoordSys.CoordSysObj;
CoordSystem: TYPE = REF CoordSysObj;
DrawStyle: TYPE = CSGGraphics.DrawStyle; -- {wire, shaded, rayCast, normals};
LightSource: TYPE = REF LightSourceObj;
LightSourceList: TYPE = Shading.LightSourceList; -- LIST OF LightSource;
LightSourceObj: TYPE = Shading.LightSourceObj;
Matrix4by4: TYPE = Matrix3d.Matrix4by4;
Point2d: TYPE = Matrix3d.Point2d;
Point3d: TYPE = Matrix3d.Point3d;
Poly3d: TYPE = REF Poly3dObj;
Poly3dObj: TYPE = SVPolygon3d.Poly3dObj;
QualityMode: TYPE = CSGGraphics.QualityMode;
StrokeEnds: TYPE = Graphics.StrokeEnds;
Vector: TYPE = SVVector3d.Vector;

Camera: TYPE = REF CameraObj;
CameraObj: TYPE = CSGGraphics.CameraObj;

FrameBox: TYPE = REF FrameBoxObj;
FrameBoxObj: TYPE = CSGGraphics.FrameBoxObj;

globalCPx, globalCPy: REAL ← 0;
globalStrokeWidth: REAL ← 0;
globalLine: Graphics.Path ← Graphics.NewPath[2];

CreateCamera: PUBLIC PROC [coordSys: CoordSystem, screenCS: CoordSystem, focalLength: REAL, style: DrawStyle, resolution: REAL ← 72.0] RETURNS [camera: Camera] = {
 frameBox: FrameBox ← NEW[FrameBoxObj ← [[0,0], [0,0], TRUE]];
 camera ← NEW[CameraObj ← [coordSys, screenCS, focalLength, style, TRUE, resolution, frameBox, fast]];
 }; -- end of CreateCamera

PlaceCamera: PUBLIC PROC [camera: Camera, focus: Point3d, origin: Point3d] = {
-- for now, tilt angle is zero.
 zAxis: Vector ← SVVector3d.Difference[origin, focus];
-- xAxis should be normal to zAxis, parallel to the xz plane, and counter-clockwise from the projection of z onto the xz plane. If z is vertical, I arbitrarily chose x axis aligned with world x axis.
 camera.coordSys.mat ← Matrix3d.MakeHorizontalMatFromZAxis[zAxis, origin];
 };

SetFocalLengthCamera: PUBLIC PROC [camera: Camera, focalLength: REAL] = {
 camera.focalLength ← focalLength;
 };

SetQualityCamera: PUBLIC PROC [camera: Camera, qual: QualityMode] = {
-- how you wish to make the speed/print quality trade off
 camera.quality ← qual;
 };

ColorFilmCamera: PUBLIC PROC [camera: Camera, colorFilm: BOOL] = {
 camera.colorFilm ← colorFilm;
 };

Clip: PUBLIC PROC [dc: Graphics.Context, camera: Camera] = {
 clipPath: Graphics.Path;
 downLeft, upRight: Point2d;
IF camera.frame.fullScreen THEN RETURN;
 clipPath ← Graphics.NewPath[4];
 downLeft ← CoordSys.CameraToScreen[camera.frame.downLeft, camera.screenCS];
-- puts into SCREEN coords
 upRight ← CoordSys.CameraToScreen[camera.frame.upRight, camera.screenCS];
 Graphics.MoveTo[clipPath, downLeft[1], downLeft[2]];
 Graphics.LineTo[clipPath, downLeft[1], upRight[2]];
 Graphics.LineTo[clipPath, upRight[1], upRight[2]];
 Graphics.LineTo[clipPath, upRight[1], downLeft[2]];
 Graphics.ClipArea[dc, clipPath, FALSE, FALSE];
 };

DrawFrame: PUBLIC PROC [dc: Graphics.Context, camera: Camera] = {
 downLeft, upRight: Point2d;
IF camera.frame.fullScreen THEN RETURN;
 downLeft ← CoordSys.CameraToScreen[camera.frame.downLeft, camera.screenCS];
 upRight ← CoordSys.CameraToScreen[camera.frame.upRight, camera.screenCS];
 Graphics.SetCP[dc, downLeft[1], downLeft[2]];
 Graphics.DrawTo[dc, downLeft[1], upRight[2]];
 Graphics.DrawTo[dc, upRight[1], upRight[2]];
 Graphics.DrawTo[dc, upRight[1], downLeft[2]];
 Graphics.DrawTo[dc, downLeft[1], downLeft[2]];
 }; -- fast, low quality

GetPerspective
: PUBLIC PROC [point3d: Point3d, camera: Camera] RETURNS [point2d: Point2d] = {
 point2d ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 }; -- assumes point3d expressed in CAMERA coordinate system.

LocalToCamera: PUBLIC PROC [localPoint: Point3d, localCS: CoordSystem] RETURNS [cameraPoint: Point3d] = {
 cameraPoint ← Matrix3d.Update[localCS.wrtCamera,localPoint]; -- puts in CAMERA
 };

LocalToWorld: PUBLIC PROC [localPt: Point3d, localCS: CoordSystem] RETURNS [worldPt: Point3d] = {
 worldPt ← Matrix3d.Update[localCS.wrtWorld, localPt];
 };

VectorToWorld: PUBLIC PROC [vector: Vector, localCS: CoordSystem] RETURNS [worldVector: Vector] = {
 worldVector ← Matrix3d.UpdateVectorWithInverse[localCS.worldWRTlocal,vector];
 };

SetCP: PUBLIC PROC [dc: Graphics.Context, point3d: Point3d, camera: Camera, localCS: CoordSystem] = {
 screenPoint: Point2d;
 point3d ← LocalToCamera[point3d, localCS]; -- puts in CAMERA
 screenPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 screenPoint ← CoordSys.CameraToScreen[screenPoint, camera.screenCS]; -- puts into SCREEN coords 
IF camera.quality = fast THEN Graphics.SetCP[dc, screenPoint[1], screenPoint[2]]
ELSE {Graphics.MoveTo[globalLine, screenPoint[1], screenPoint[2]];
   Graphics.SetCP[dc, screenPoint[1], screenPoint[2]]};
 };

MoveTo: PUBLIC PROC [path: Graphics.Path, point3d: Point3d, camera: Camera, localCS: CoordSystem] = {
 objPoint: Point2d;
 point3d ← LocalToCamera[point3d, localCS]; -- puts in CAMERA
 objPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords 
 Graphics.MoveTo[path, objPoint[1], objPoint[2]];
 };

SetCPAbsolute: PUBLIC PROC [dc: Graphics.Context, point3d: Point3d, camera: Camera] = {
 objPoint: Point2d;
 objPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
IF camera.quality = fast THEN Graphics.SetCP[dc, objPoint[1], objPoint[2]]
ELSE {Graphics.MoveTo[globalLine, objPoint[1], objPoint[2]];
   Graphics.SetCP[dc, objPoint[1], objPoint[2]];
   };
 };

MoveToAbsolute: PUBLIC PROC [path: Graphics.Path, point3d: Point3d, camera: Camera] = {
 objPoint: Point2d;
 objPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
 Graphics.MoveTo[path, objPoint[1], objPoint[2]];
 };
-- assumes point3d is a point in CAMERA coordinate system

LineTo: PUBLIC PROC [path: Graphics.Path, point3d: Point3d, camera: Camera, localCS: CoordSystem] = {
 objPoint: Point2d;
 point3d ← LocalToCamera[point3d, localCS]; -- puts in CAMERA
 objPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
 Graphics.LineTo[path, objPoint[1], objPoint[2]];
 };

DrawTo: PUBLIC PROC [dc: Graphics.Context, point3d: Point3d, camera: Camera, localCS: CoordSystem] = {
 screenPoint: Point2d;
 point3d ← LocalToCamera[point3d, localCS]; -- puts in CAMERA
 screenPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 screenPoint ← CoordSys.CameraToScreen[screenPoint, camera.screenCS]; -- puts into SCREEN coords
IF camera.quality = fast THEN Graphics.DrawTo[dc, screenPoint[1], screenPoint[2]]
ELSE {Graphics.LineTo[globalLine, screenPoint[1], screenPoint[2]];
   Graphics.DrawStroke[dc, globalLine, 1, FALSE, round];
   Graphics.MoveTo[globalLine, screenPoint[1], screenPoint[2]];
   -- flush path
   };
 };

LineToAbsolute: PUBLIC PROC [path: Graphics.Path, point3d: Point3d, camera: Camera] = {
 objPoint: Point2d;
 objPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords 
 Graphics.LineTo[path, objPoint[1], objPoint[2]];
 };

DrawToAbsolute: PUBLIC PROC [dc: Graphics.Context, point3d: Point3d, camera: Camera] = {
 objPoint: Point2d;
 objPoint ← Matrix3d.GetPerspective[point3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords 
IF camera.quality = fast THEN Graphics.DrawTo[dc, objPoint[1], objPoint[2]]
ELSE {Graphics.LineTo[globalLine, objPoint[1], objPoint[2]];
   Graphics.DrawStroke[dc, globalLine, 1, FALSE, round];
   };
 };

DrawStroke: PUBLIC PROC [dc: Graphics.Context, path: Graphics.Path, width: REAL ← 1,
closed: BOOLEANFALSE, ends: StrokeEnds ← butt] = {
 Graphics.DrawStroke[dc, path, width, closed, ends];
 };

DrawLine: PUBLIC PROC [dc: Graphics.Context, start: Point3d, end: Point3d, camera: Camera, localCS: CoordSystem] = {
 objPoint: Point2d;
 start ← LocalToCamera[start, localCS]; -- puts in CAMERA
 objPoint ← Matrix3d.GetPerspective[start, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
IF camera.quality = quality THEN Graphics.MoveTo[globalLine, objPoint[1], objPoint[2]]
ELSE Graphics.SetCP[dc, objPoint[1], objPoint[2]];
 end ← LocalToCamera[end, localCS]; -- puts in CAMERA
 objPoint ← Matrix3d.GetPerspective[end, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
IF camera.quality = quality THEN {
  Graphics.LineTo[globalLine, objPoint[1], objPoint[2]];
  Graphics.DrawStroke[dc, globalLine, 1, FALSE, butt];
  }
ELSE Graphics.DrawTo[dc, objPoint[1], objPoint[2]];
 };

-- assumes point3d is a point in CAMERA coordinate system

SetColor: PRIVATE PROC [dc: Graphics.Context, camera: Camera, color: Color] = {
IF camera.colorFilm THEN Graphics.SetColor [dc, color]
ELSE {
  intensity: REAL ← GraphicsColor.ColorToIntensity[color];
  Graphics.SetColor[dc, GraphicsColor.IntensityToColor[intensity]]};
 };

DrawArea: PUBLIC PROC [dc: Graphics.Context, normal: Vector, poly3d: Poly3d, artwork: Artwork, lightSources: LightSourceList, camera: Camera, localCS: CoordSystem] = {
-- given an ordered list (array) of Point3d's, defined in the current local coordinate system, find the corresponding WORLD points, and project the polygon they define onto the image plane with a perspective projection.
 worldPoint3d, cameraPoint3d: Point3d;
 objPoint: Point2d;
 colorshade: Graphics.Color;
 cameraNormal: Vector;
 worldNormal: Vector;
 eyepoint: Point3d;
 outline: Graphics.Path ← Graphics.NewPath[poly3d.len];

-- Find the real normal
 cameraNormal ← Matrix3d.UpdateVectorWithInverse[localCS.cameraWRTlocal, normal];
 worldNormal ← Matrix3d.UpdateVectorWithInverse[localCS.worldWRTlocal, normal];
  -- a reverse-facing surface is one which is not visible to the camera. In our current world of opaque surfaces, we need not draw reverse-facing surfaces. To detect a reverse-facing surface, take its normal N. Find the dot product of N with the vector from the a point on the surface to the camera lens. If the result is negative, the surface is reverse-facing.
  -- a simpler test which removes most reverse-facing surfaces is this one:
IF cameraNormal[3]<=0 THEN RETURN; 
-- don't draw reverse-facing surfaces

-- Move to the first point
 worldPoint3d ← Matrix3d.Update[localCS.wrtWorld, poly3d[0]];
 cameraPoint3d ← LocalToCamera[poly3d[0], localCS];
 objPoint ← Matrix3d.GetPerspective[cameraPoint3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
 Graphics.MoveTo[outline, objPoint[1], objPoint[2]];

-- use this point to estimate surface color
 eyepoint ← LocalToWorld[[0,0,camera.focalLength], camera.coordSys];
SELECT artwork.material FROM
 plastic => colorshade ← Shading.DiffuseAndSpecularReflectance
  [eyepoint, worldNormal, worldPoint3d, artwork.color, lightSources];
 chalk => colorshade ← Shading.DiffuseReflectance
  [worldNormal, worldPoint3d, artwork.color, lightSources];
ENDCASE => ERROR;
-- create a path which forms the edges of this area
FOR i: NAT IN[1..poly3d.len) DO
  cameraPoint3d ← LocalToCamera[poly3d[i], localCS];
  -- now that we know where the point really is, calculate brightness (implement later)
  objPoint ← Matrix3d.GetPerspective[cameraPoint3d, camera.focalLength];
  objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
  Graphics.LineTo[outline, objPoint[1], objPoint[2]];
ENDLOOP;
 SetColor[dc, camera, colorshade];
 Graphics.DrawArea[dc, outline];
 };

DrawAreaNormalAbsolute: PUBLIC PROC [dc: Graphics.Context, normal: Vector, poly3d: Poly3d, artwork: Artwork, lightSources: LightSourceList, camera: Camera,
 localCS: CoordSystem] = {
-- normal is assumed to be in Camera coordinates. poly3d in local coordinates.
-- given an ordered list (array) of Point3d's, defined in the current local coordinate system, find the corresponding WORLD points, and project the polygon they define onto the image plane with a perspective projection.
 worldPoint3d, cameraPoint3d: Point3d;
 objPoint: Point2d;
 colorshade: Graphics.Color;
 worldNormal: Vector;
 eyepoint: Point3d;
 outline: Graphics.Path ← Graphics.NewPath[poly3d.len];

-- Find the real normal
 worldNormal ← Matrix3d.UpdateVectorWithInverse[camera.coordSys.worldWRTlocal, normal];
-- a reverse-facing surface is one which is not visible to the camera. In our current world of opaque surfaces, we need not draw reverse-facing surfaces. To detect a reverse-facing surface, take its normal N. Find the dot product of N with the vector from the a point on the surface to the camera lens. If the result is negative, the surface is reverse-facing.
-- a simpler test which removes most reverse-facing surfaces is this one:
IF normal[3]<=0 THEN RETURN; 

-- Move to the first point
 worldPoint3d ← Matrix3d.Update[localCS.wrtWorld, poly3d[0]];
 cameraPoint3d ← LocalToCamera[poly3d[0], localCS];
 objPoint ← Matrix3d.GetPerspective[cameraPoint3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
 Graphics.MoveTo[outline, objPoint[1], objPoint[2]];

-- use this point to estimate surface color
 eyepoint ← LocalToWorld[[0,0,camera.focalLength], camera.coordSys];
SELECT artwork.material FROM
 plastic => colorshade ← Shading.DiffuseAndSpecularReflectance
  [eyepoint, worldNormal, worldPoint3d, artwork.color, lightSources];
 chalk => colorshade ← Shading.DiffuseReflectance
  [worldNormal, worldPoint3d, artwork.color, lightSources];
ENDCASE => ERROR;
-- create a path which forms the edges of this area
FOR i: NAT IN[1..poly3d.len) DO
  cameraPoint3d ← LocalToCamera[poly3d[i], localCS];
  -- now that we know where the point really is, calculate brightness (implement later)
  objPoint ← Matrix3d.GetPerspective[cameraPoint3d, camera.focalLength];
  objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
  Graphics.LineTo[outline, objPoint[1], objPoint[2]];
ENDLOOP;
 SetColor[dc, camera, colorshade];
 Graphics.DrawArea[dc, outline];
 };

DrawAreaAbsolute: PUBLIC PROC [dc: Graphics.Context, poly3d: Poly3d, camera: Camera] = {
-- assumes poly3d in camera coords
 outline: Graphics.Path ← Graphics.NewPath[poly3d.len];
 cameraPoint3d: Point3d;
 objPoint: Point2d;
 cameraPoint3d ← poly3d[0];
 objPoint ← Matrix3d.GetPerspective[cameraPoint3d, camera.focalLength];
 objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
 Graphics.MoveTo[outline, objPoint[1], objPoint[2]];
FOR i: NAT IN[1..poly3d.len) DO
  cameraPoint3d ← poly3d[i];
  objPoint ← Matrix3d.GetPerspective[cameraPoint3d, camera.focalLength];
  objPoint ← CoordSys.CameraToScreen[objPoint, camera.screenCS]; -- puts into SCREEN coords
  Graphics.LineTo[outline, objPoint[1], objPoint[2]];
ENDLOOP;
 Graphics.DrawArea[dc, outline];
 }; -- end of DrawAreaAbsolute

END.