File: GGAngleImpl.mesa
Last edited by Bier on June 4, 1985 6:15:52 pm PDT
Author: Eric Bier on February 10, 1987 1:33:38 pm PST
Contents: Gargoyle 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.
Pier, December 6, 1985 10:06:16 am PST
Kurlander August 25, 1986 4:45:40 pm PDT
DIRECTORY
Real, RealFns, GGAngle;
GGAngleImpl:
CEDAR
PROGRAM
IMPORTS Real, RealFns
EXPORTS GGAngle =
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.Fix[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.Fix[realNumberOf360s];-- FixC truncates
numberToAdd: REAL ← (fixNumberOf360s + 1)*360.0;
RETURN[anyRange+numberToAdd];
};
range180 ← anyRange
}; -- end of Normalize
AsSlope:
PUBLIC
PROC [anyRange:
REAL]
RETURNS [pos180:
REAL] = {
Takes an angle in degrees and puts it in 0 <= theta < 180 form. This may require adding 180 degrees for negative angles.
range180: REAL ← Normalize[anyRange]; -- -180 < theta <= 180
IF range180 < 0.0 THEN range180 ← range180 + 180.0; -- 0 <= theta <= 180
pos180 ← IF range180 = 180.0 THEN 0.0 ELSE range180; -- 0 <= theta < 180.0
};
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, toPosition:
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 < toPosition AND toPosition <= fromPosition THEN RETURN[toPosition - fromPosition]
ELSE increment ← increment + (-180 - fromPosition);
increment ← increment + (toPosition-180);
}
ELSE {
-- start in upper semi-circle.
Proceed clockwise until you encounter "toPostion" or theta = 180
IF fromPosition < toPosition AND toPosition <= 180 THEN RETURN [toPosition - fromPosition - 360]
ELSE RETURN[toPosition - fromPosition];
};
}; -- end of ClockwiseAngle
CounterClockwiseAngle:
PUBLIC
PROC [fromPosition, toPosition:
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, toPosition];
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
};
IsInCCWInterval:
PUBLIC
PROC [testPosition:
REAL, fromPosition, toPosition:
REAL]
RETURNS [
BOOL] = {
Consider the set of angles encountered when traveling counterclockwise from angle "fromPosition" to angle "toPosition" around a circle. Is testPosition one of the angles encountered?
IF fromPosition <= toPosition THEN -- the angle doesn't cross the -x axis
RETURN[fromPosition<= testPosition AND testPosition <= toPosition]
ELSE
RETURN[
(fromPosition<= testPosition AND testPosition <= 180.0) OR
(-180.0 < testPosition AND testPosition <= toPosition)];
};
IsInCCWInterval2:
PUBLIC
PROC [testPosition:
REAL, fromPosition, increment:
REAL]
RETURNS [
BOOL] = {
toPosition: REAL;
IF increment = 360.0 THEN RETURN[TRUE];
toPosition ← Add[fromPosition, increment];
RETURN[IsInCCWInterval[testPosition, fromPosition, toPosition]];
};
AlmostParallel:
PUBLIC
PROC [pos1, pos2:
REAL, epsilon:
REAL]
RETURNS [
BOOL] = {
pos1 and pos2 are assumed to be in -180 < theta <= 180. Return TRUE if they are the same angle +- epsilon or if they differ by 180+- epsilon.
diff: REAL;
diff ← ABS[pos1 - pos2];
IF diff < epsilon THEN RETURN[TRUE];
IF ABS[diff - 180.0] < epsilon THEN RETURN[TRUE];
RETURN[FALSE];
};
END.