-- File: SVFacesImpl.mesa
-- Last edited by Bier on December 18, 1982 1:37 am
-- Author: Eric Bier on July 3, 1983 1:36 pm
-- Contents: Definitions of various face types such as Cone, Disk Ring, and Cylinder and procedures which make them from simpler data

DIRECTORY
 SV2d,
 SVBoundBox,
 SVFaces,
 SVLines2d,
 SVVector2d,
 SVVector3d;

SVFacesImpl: PROGRAM
IMPORTS SVBoundBox, SVLines2d, SVVector2d, SVVector3d
EXPORTS SVFaces =

BEGIN
TrigLineSeg: TYPE = REF TrigLineSegObj;
TrigLineSegObj: TYPE = SV2d.TrigLineSegObj;
Vector: TYPE = SVVector3d.Vector;
Vector2d: TYPE = SVVector2d.Vector2d;

-- the cone described here is revolute around the y-axis. Otherwise it is very general, allowing for an arbitrary apex point (on the y-axis) and an arbitrary spread ratio.
Cone: TYPE = REF ConeObj;
ConeObj: TYPE = SVFaces.ConeObj;

-- the disk ring described here is revolute around the y-axis. Otherwise it is very general. It may be in an arbitrary horizontal plane (y = yPlane), may have any two radii, rIn and rOut subject to rIn, rOut >= 0 AND rOut > rIn, and may have an upward or downward pointing normal.

DiskRing: TYPE = REF DiskRingObj;
DiskRingObj: TYPE = SVFaces.DiskRingObj;

-- the cylinder described here is revolute around the y-axis. Otherwise it is very general. It may have its top and bottom in arbitrary horizontal planes (y = yHigh and y = yLow) subject to yHigh > yLow and may have an arbitrary radius r.

Cylinder: TYPE = REF CylinderObj;
CylinderObj: TYPE = SVFaces.CylinderObj;

-- the edge on rectangle has a surface normal parallel to the xy plane. That is, it is edge on when viewed from the z direction.

EdgeOnRect: TYPE = REF EdgeOnRectObj;
EdgeOnRectObj: TYPE = SVFaces.EdgeOnRectObj;

Abs: PROC [r: REAL] RETURNS [REAL] = {
RETURN[IF r >= 0 THEN r ELSE -r];
 }; -- end of Abs

ConeFromTrigLineSeg: PUBLIC PROC [seg: TrigLineSeg] RETURNS [cone: Cone] = {
 isParallel: BOOL;
 x, y: REAL;
 signHi, signLo: PosNeg;
 cone ← NEW[ConeObj];
 [cone.h, isParallel] ← SVLines2d.TrigLineMeetsYAxis[seg.line];
IF isParallel THEN ERROR AttemptToCreateDegenerateCone;
  -- seg cannot be vertical
IF seg.pLo[2] = seg.pHi[2] THEN ERROR AttemptToCreateDegenerateCone;
  -- seg cannot be horizontal
-- to find the spread ratio r/|y-h|, we consider the intersection of the cone (meaning the two nose-to-nose "cones" meeting at the apex) with the z = 0 plane. This gives us two lines intersecting at (0,h). Consider the intersection of the line x = seg.pLo[1] or x = seg.pHi[1] (whichever is larger) (an arbitrary choice) with the negatively sloped line (corresponding to the nose-up cone at this point). The cone radius here is x. We can get the y of intersection from the line equation:
-- y * cos(theta) - x*sin(theta) - d = 0;
-- y = (x*sin(theta) + d)/cos(theta);
-- Finally, M = 1/Abs[y - h] .
 x ← IF Abs[seg.pLo[1]] > Abs[seg.pHi[1]] THEN Abs[seg.pLo[1]] ELSE Abs[seg.pHi[1]];
 y ← (x*seg.line.s + seg.line.d)/seg.line.c;
 cone.M ← x/Abs[y - cone.h];
-- debugging. Find the distance from the cone to each of the points of seg:
BEGIN
  coneSeg: TrigLineSeg ← SVLines2d.CreateTrigLineSeg[[x, y], [0, cone.h]];
  d1: REAL ← SVLines2d.DistancePointToTrigLineSeg[seg.pLo, coneSeg];
  d2: REAL ← SVLines2d.DistancePointToTrigLineSeg[seg.pHi, coneSeg];
  d2 ← SVLines2d.DistancePointToTrigLineSeg[seg.pHi, coneSeg];
END;
-- make sure the y's are on the same side of the apex (or on the apex)
 cone.yHi ← seg.pHi[2];
 cone.yLo ← seg.pLo[2];
 signHi ← Sign[cone.yHi - cone.h];
 signLo ← Sign[cone.yLo - cone.h];
IF signHi # signLo AND signHi # zero AND signLo # zero THEN ERROR AttemptToCreateDegenerateCone;
IF cone.yHi > cone.h THEN cone.noseIsUp ← FALSE ELSE cone.noseIsUp ← TRUE;
-- IF seg goes down, normal should point out, else normal should point in.
-- By down, I mean second point is below first point, or equivalently, theta is negative.
-- IF y2<y1 THEN cone.normalPointsOut ← TRUE
    -- ELSE cone.normalPointsOut ← FALSE;
IF seg.line.theta < 0 THEN cone.normalPointsOut ← TRUE ELSE cone.normalPointsOut ← FALSE;
-- made the boundHedron for this cone.
 cone.boundHedron ← SVBoundBox.GeneralConeBoundHedron[seg.pHi[1], seg.pHi[2], seg.pLo[1], seg.pLo[2]];
 }; -- end of ConeFromTrigLineSeg

AttemptToCreateDegenerateCone: PUBLIC ERROR = CODE;

PosNeg: TYPE = {pos, neg, zero};

Sign: PROC [r: REAL] RETURNS [sign: PosNeg] = {
RETURN[IF r > 0 THEN pos ELSE IF r < 0 THEN neg ELSE zero];
 };

DiskRingFromTrigLineSeg: PUBLIC PROC [seg: TrigLineSeg] RETURNS [diskRing: DiskRing] = {
-- a disk ring is the region in the plane y = yPlane inside the disk x^2 + z^2 = rOut^2 and outside the disk x^2 + z^2 = rIn^2.
 x1, x2: REAL;
 diskRing ← NEW[DiskRingObj];
-- the line segment must be horizontal
IF seg.pLo[2] # seg.pHi[2] THEN ERROR AttemptToCreateDegenerateDiskRing;
 diskRing.yPlane ← seg.pLo[2];
IF seg.pLoIsFirst THEN
  {x1 ← seg.pLo[1]; x2 ← seg.pHi[1]} ELSE {x1 ← seg.pHi[1]; x2 ← seg.pLo[1]};
-- to find out if this segment points toward the y axis or away from it, make sure both points are on the same side of the y axis, then mirror the segment to the positive x side (if necessary) and test for x2 > x1;
IF Sign[x1] # Sign[x2] AND Sign[x1] # zero AND Sign[x2] # zero THEN ERROR AttemptToCreateDegenerateDiskRing;
 x1 ← Abs[x1];
 x2 ← Abs[x2];
-- if segment points out, normal points up, else normal points down.
IF x2 > x1 THEN {diskRing.normal ← [0,1,0];
       diskRing.rInSquared ← x1*x1;
       diskRing.rOutSquared ← x2*x2;
       diskRing.boundHedron ← SVBoundBox.DiskBoundHedron[r: x2, h: seg.pLo[2]]}
     ELSE {diskRing.normal ← [0,-1,0];
        diskRing.rInSquared ← x2*x2;
        diskRing.rOutSquared ← x1*x1;
        diskRing.boundHedron ← SVBoundBox.DiskBoundHedron[r: x1, h: seg.pLo[2]]};
 }; -- end of DiskRingFromTrigLineSeg

AttemptToCreateDegenerateDiskRing: PUBLIC ERROR = CODE;

CylinderFromTrigLineSeg: PUBLIC PROC [seg: TrigLineSeg] RETURNS [cylinder: Cylinder] = {
 cylinder ← NEW[CylinderObj];
-- seg must be vertical
IF seg.pLo[1] # seg.pHi[1] THEN ERROR AttemptToCreateDegenerateCylinder;
-- IF seg goes down normal should point out, else normal should point in.
IF seg.line.theta < 0 THEN cylinder.normalPointsOut ← TRUE
     ELSE cylinder.normalPointsOut ← FALSE;
 cylinder.yHi ← seg.pHi[2];
 cylinder.yLo ← seg.pLo[2];
 cylinder.r ← seg.pLo[1];
 cylinder.rSquared ← cylinder.r*cylinder.r;
 cylinder.boundHedron ← SVBoundBox.GeneralConeBoundHedron[seg.pLo[1], seg.pHi[2], seg.pLo[1], seg.pLo[2]];
 };

AttemptToCreateDegenerateCylinder: PUBLIC ERROR = CODE;

EdgeOnRectFromTrigLineSeg: PUBLIC PROC [seg: TrigLineSeg, frontZ, backZ: REAL] RETURNS [edgeOnRect: EdgeOnRect] = {
-- we wish to find the line equation in the from Ax + By + D = 0.
-- trig line segments have the form: y*cos(theta) - x*sin(theta) -d = 0;
-- so A = -sin(theta), B = cos(theta), D = -d;
-- To set valHi and valLo, we look at theta. If pi/4 <= theta < = 3pi/4 or -3pi/4 <=theta <= -pi/4 THEN use y else use x.
 normal2d: Vector2d;
 edgeOnRect ← NEW[EdgeOnRectObj];
 edgeOnRect.frontZ ← frontZ;
 edgeOnRect.backZ ← backZ;
 edgeOnRect.A ← -seg.line.s;
 edgeOnRect.B ← seg.line.c;
 edgeOnRect.D ← -seg.line.d;
  -- assume that the interior of the polygon is to the right of this line segment so that normals should point to the left.
 normal2d ← SVVector2d.LeftNormalOfTrigLineSeg[seg];
 edgeOnRect.normal ← SVVector3d.Vector2DAsXYVector[vXY: normal2d];
IF (45 <= seg.line.theta AND seg.line.theta <= 135) OR (-135 <= seg.line.theta AND seg.line.theta <= -45) THEN {
  edgeOnRect.valIsY ← TRUE;
  edgeOnRect.valLo ← seg.pLo[2];
  edgeOnRect.valHi ← seg.pHi[2];
  }
ELSE {
  edgeOnRect.valIsY ← FALSE;
  edgeOnRect.valLo ← Min[seg.pLo[1], seg.pHi[1]];
  edgeOnRect.valHi ← Max[seg.pLo[1], seg.pHi[1]];
  };
 }; -- end of EdgeOnRectFromTrigLineSeg
  
Max: PRIVATE PROC [a, b: REAL] RETURNS [REAL] = {
RETURN [IF a>b THEN a ELSE b];
 };

Min: PRIVATE PROC [a, b: REAL] RETURNS [REAL] = {
RETURN [IF a<b THEN a ELSE b];
 };
  

AttemptToCreateDegenerateEdgeOnRect: PUBLIC ERROR = CODE;

END.