-- File: SVAngleImpl.mesa
-- Last edited by Bier on December 18, 1982 1:08 am
-- Author: Eric Bier on October 19, 1982 1:35 pm
-- Contents: Solidviews requires a precise set of angle operations defined with angle "theta" in the range -180 < theta <= 180. "theta" is an absolute angle (ie a position around the circle). Given two positions angles T1 and T2 we can find the incremental clockwise angle CT between them or the incremental counter-clockwise angle CCT where -360 < CT <= 0 and 0 <= CCT < 360.
When we add two angles, we are adding an incremental angle to a position angle to get a new position angle. We subtract two position angles to get an incremental angle.

DIRECTORY
 Real,
 RealFns,
 SVAngle;

SVAngleImpl: PROGRAM
IMPORTS Real, RealFns
EXPORTS SVAngle =
BEGIN

Normalize: PUBLIC PROC [anyRange: REAL] RETURNS [range180: REAL] = {
-- takes an angle in degrees and puts it in 180 < theta <= 180 form.
IF anyRange > 180 THEN {
  -- find the integer number of times 360 goes into it and subtract that many 360's.
  realNumberOf360s: REAL ← anyRange/360;
  fixNumberOf360s: NAT ← Real.FixC[realNumberOf360s]; -- FixC truncates
  numberToSubtract: REAL ← fixNumberOf360s*360.0;
  RETURN[anyRange-numberToSubtract];
  };
IF anyRange <= -180 THEN {
  -- find the integer number of times 360 goes into its negative and add (that many + 1) 360's.
  realNumberOf360s: REAL ← -anyRange/360;
  fixNumberOf360s: NAT ← Real.FixC[realNumberOf360s]; -- FixC truncates
  numberToAdd: REAL ← (fixNumberOf360s + 1)*360.0;
  RETURN[anyRange+numberToAdd];
  };
 range180 ← anyRange
 }; -- end of Normalize
  
Add: PUBLIC PROC [position, increment: REAL] RETURNS [finalPosition: REAL] = {
-- all angles in degrees
 finalPosition ← position + increment; -- -540 < finalPosition < 540
IF finalPosition > 180 THEN finalPosition ← finalPosition - 360
  ELSE IF finalPosition <= -180 THEN finalPosition ← finalPosition + 360;
 }; -- end of Add

ClockwiseAngle: PUBLIC PROC [fromPosition, toPostion: REAL] RETURNS [increment: REAL] = {
-- all angles in degrees. -360 < increment <= 0
 increment ← 0;
IF fromPosition < 0 THEN {-- start in lower semi-circle.
  -- proceed clockwise until you encounter theta = 180 or "toPostion".
  IF -180 < toPostion AND toPostion <= fromPosition THEN RETURN[toPostion - fromPosition]
   ELSE increment ← increment + (-180 - fromPosition);
  IF 0 <= toPostion AND toPostion <= 180 THEN RETURN[increment + (toPostion-180)]
   ELSE increment ← increment + 180;
  RETURN[increment + toPostion];
  }
ELSE { -- start in upper semi-circle.
   -- proceed clockwise until you encounter "toPostion" or theta = 180
   IF fromPosition < toPostion AND toPostion <= 180 THEN RETURN [toPostion - fromPosition - 360]
    ELSE RETURN[toPostion - fromPosition];
  };
 }; -- end of ClockwiseAngle

CounterClockwiseAngle: PUBLIC PROC [fromPosition, toPostion: REAL] RETURNS [increment: REAL] = {
-- all angles in degrees. 0 <= increment < 360.
-- For example, if the clockwise angle is -90, the counter-clockwise angle will be 270.
 increment ← 360 + ClockwiseAngle[fromPosition, toPostion];
IF increment = 360 THEN increment ← 0;
 };

ShortestDifference: PUBLIC PROC [position1, position2: REAL] RETURNS [pos1MinusPos2: REAL] = {
-- all angles in degrees. RETURNS ClockwiseAngle or CounterClockwiseAngle. Whichever is smaller. -180< pos1MinusPos2 <= 180.
 clockwise: REAL ← ClockwiseAngle[position1, position2];
 pos1MinusPos2 ← IF clockwise <= -180 THEN clockwise + 360 ELSE clockwise;
 };

Scale: PUBLIC PROC [angle: REAL, scalar: REAL] RETURNS [angleTimesScalar: REAL] = {
-- all angles in degrees. Think of angle as the increment from 0 degrees to angle degrees. Scale this and normalize.
 angleTimesScalar ← angle*scalar;
 angleTimesScalar ← Normalize[angleTimesScalar];
 };

ArcTan: PUBLIC PROC [numerator, denominator: REAL] RETURNS [degrees: REAL] = {
-- has the effect of calling RealFns.ArcTanDegrees and normalizing the result.
 degrees ← RealFns.ArcTanDeg[numerator, denominator];
 degrees ← Normalize[degrees]; -- Normalize may not be needed. RealFns seems to do the right thing
 };

END.