File: SVPolygon3dImpl.mesa
Author: Eric Bier on August 21, 1982 1:49 pm
Last edited by Bier on August 11, 1987 12:57:20 pm PDT
Contents: Most of the code necessary to describe the behavior of planes and polygons in space. (including the procedures necessary to make planar polygons act as master objects). Part of the Solidviews 3d Illustrator.
DIRECTORY
SVMatrix3d, RealFns, SV2d, SV3d, SVPolygon2d, SVPolygon3d;
SVPolygon3dImpl: CEDAR PROGRAM
IMPORTS SVMatrix3d, RealFns--, SVPolygon2d
EXPORTS SVPolygon3d =
BEGIN
Matrix4by4: TYPE = SVMatrix3d.Matrix4by4;
NextPointProc: TYPE = SVPolygon3d.NextPointProc;
Plane: TYPE = REF PlaneObj;
PlaneObj: TYPE = SVPolygon3d.PlaneObj;
Point3d: TYPE = SV3d.Point3d;
Poly3d: TYPE = REF Poly3dObj;
Poly3dObj: TYPE = SV3d.Poly3dObj;
Polygon: TYPE = REF PolygonObj;
PolygonObj: TYPE = SV2d.PolygonObj;
Vector3d: TYPE = SV3d.Vector3d;
GLOBAL VARIABLES
root3, root3Over3, twoRoot3Over3: REAL; -- constants computed at load time in Init (see below).
CreatePoly: PUBLIC PROC [len: NAT] RETURNS [poly3d: Poly3d] = {
poly3d ← NEW[Poly3dObj[len]];
poly3d.len ← 0;
};
CircumHexagon: PUBLIC PROC [y: REAL, r: REAL] RETURNS [hex: Poly3d] = {
Creates a hexagon which circumscribes the circle (in the Y = y plane) whose center is at [0, y, 0] and whose radius is r.
hex ← CreatePoly[6];
hex ← AddPolyPoint[hex, [r*root3Over3, y, r]]; -- front right
hex ← AddPolyPoint[hex, [r*twoRoot3Over3, y, 0]];-- right
hex ← AddPolyPoint[hex, [r*root3Over3, y, -r]];-- back right
hex ← AddPolyPoint[hex, [-r*root3Over3, y, -r]];-- back left
hex ← AddPolyPoint[hex, [-r*twoRoot3Over3, y, 0]];-- left
hex ← AddPolyPoint[hex, [-r*root3Over3, y, r]];-- front left
};
ClearPoly: PUBLIC PROC [poly: Poly3d] = {
poly.len ← 0;
};
AddPolyPoint: PUBLIC PROC [poly: Poly3d, point: Point3d] RETURNS [polyPlusPoint: Poly3d] = {
IF poly.len = poly.maxVerts THEN {
polyPlusPoint ← CreatePoly[poly.maxVerts+1];
polyPlusPoint ← PartPolyGetsPartPoly[poly, 0, polyPlusPoint, 0, poly.maxVerts];
polyPlusPoint[poly.maxVerts] ← point;
polyPlusPoint.len ← poly.maxVerts + 1;
}
ELSE {
poly[poly.len] ← point;
poly.len ← poly.len + 1;
polyPlusPoint ← poly;
};
}; -- end of AddPolyPoint
PutPolyPoint: PUBLIC PROC [poly: Poly3d, index: NAT, point: Point3d] RETURNS [newPoly: Poly3d] = {
IF index+1 > poly.maxVerts THEN {
newPoly ← CreatePoly[index+1];
newPoly ← PartPolyGetsPartPoly[poly, 0, newPoly, 0, poly.maxVerts];
newPoly[index] ← point;
newPoly.len ← index+1;
}
ELSE {
IF index+1 > poly.len THEN poly.len ← index+1;
poly[index] ← point;
newPoly ← poly};
};
PartPolyGetsPartPoly: PUBLIC PROC [fromPoly: Poly3d, fromStart: NAT, toPoly: Poly3d, toStart: NAT, duration: NAT] RETURNS [newPoly: Poly3d] = {
toPoly in range [toStart..toStart+duration-1] gets fromPoly in range [fromStart..fromStart+duration-1];
j: NAT ← fromStart;
IF toStart+duration > toPoly.maxVerts THEN {
newPoly ← CreatePoly[toStart+duration];
newPoly ← PartPolyGetsPartPoly[toPoly, 0, newPoly, 0, toPoly.len];
newPoly.len ← toStart+duration}
ELSE {
newPoly ← toPoly;
IF toStart+duration > newPoly.len THEN newPoly.len ← toStart+duration};
IF fromStart+duration > fromPoly.len THEN ERROR; -- don't use bad data
FOR i: NAT IN[toStart..toStart+duration-1] DO
newPoly[i] ← fromPoly[j];
j ← j + 1;
ENDLOOP;
};
PlaneFromPoly3d: PUBLIC PROC [poly: Poly3d] RETURNS [plane: Plane] = {
Assumes that the normal of the plane should point in the direction such if the normal points toward a viewer, the viewer will perceive the points of poly to run clockwise.
This algorithm was taken from Foley and Van Dam "Fundamentals of Interactive Computer Graphics" p. 512-513, where the authors note that for the plane equation Ax + By + Cz + D = 0,
A, B, and C have the same relationship to each other as the areas of the polygon when it is projected onto the yz, xz, and xy planes respectively. Hence we can just set A, B, and C equal to these respective values and then plug in a point to solve for D. The areas are computed by the signed areas of the trapezoids formed by each each and its projection onto the y, z, and x axis respectively for A, B, and C.
iPlusOne: NAT;
iPoint, iPlusOnePoint: Point3d;
plane ← NEW[PlaneObj];
plane.A ← 0;
plane.B ← 0;
plane.C ← 0;
FOR i: NAT IN[1..poly.len] DO
iPlusOne ← IF i = poly.len THEN 1 ELSE i + 1;
iPoint ← poly[i - 1];
iPlusOnePoint ← poly[iPlusOne - 1];
plane.A ← plane.A + (iPoint[3] + iPlusOnePoint[3])*(iPlusOnePoint[2] - iPoint[2]);
plane.B ← plane.B + (iPoint[1] + iPlusOnePoint[1])*(iPlusOnePoint[3] - iPoint[3]);
plane.C ← plane.C + (iPoint[2] + iPlusOnePoint[2])*(iPlusOnePoint[1] - iPoint[1]);
ENDLOOP;
Now find D. Use poly[0] as the point to substitute in.
iPoint ← poly[0];
plane.D ← -(iPoint[1]*plane.A + iPoint[2]*plane.B + iPoint[3]*plane.C);
}; -- end of PlaneFromPoly3d
PlaneFromClientPoints: PUBLIC PROC [nextPoint: NextPointProc] RETURNS [plane: Plane] = {
Assumes that the normal of the plane should point in the direction such if the normal points toward a viewer, the viewer will perceive the points of poly to run clockwise.
This algorithm was taken from Foley and Van Dam "Fundamentals of Interactive Computer Graphics" p. 512-513, where the authors note that for the plane equation Ax + By + Cz + D = 0,
A, B, and C have the same relationship to each other as the areas of the polygon when it is projected onto the yz, xz, and xy planes respectively. Hence we can just set A, B, and C equal to these respective values and then plug in a point to solve for D. The areas are computed by the signed areas of the trapezoids formed by each each and its projection onto the y, z, and x axis respectively for A, B, and C.
iPoint, iPlusOnePoint, firstPoint: Point3d;
done: BOOLFALSE;
plane ← NEW[PlaneObj];
plane.A ← 0;
plane.B ← 0;
plane.C ← 0;
[firstPoint, done] ← nextPoint[];
IF done THEN ERROR;
iPoint ← firstPoint;
UNTIL done DO
[iPlusOnePoint, done] ← nextPoint[];
plane.A ← plane.A + (iPoint[3] + iPlusOnePoint[3])*(iPlusOnePoint[2] - iPoint[2]);
plane.B ← plane.B + (iPoint[1] + iPlusOnePoint[1])*(iPlusOnePoint[3] - iPoint[3]);
plane.C ← plane.C + (iPoint[2] + iPlusOnePoint[2])*(iPlusOnePoint[1] - iPoint[1]);
iPoint ← iPlusOnePoint;
ENDLOOP;
BEGIN -- the wrap-around case
iPlusOnePoint ← firstPoint;
plane.A ← plane.A + (iPoint[3] + iPlusOnePoint[3])*(iPlusOnePoint[2] - iPoint[2]);
plane.B ← plane.B + (iPoint[1] + iPlusOnePoint[1])*(iPlusOnePoint[3] - iPoint[3]);
plane.C ← plane.C + (iPoint[2] + iPlusOnePoint[2])*(iPlusOnePoint[1] - iPoint[1]);
END;
plane.D ← -(firstPoint[1]*plane.A + firstPoint[2]*plane.B + firstPoint[3]*plane.C);
}; -- end of PlaneFromClientPoints
PlaneFromPointAndNormal: PUBLIC PROC [point: Point3d, normal: Vector3d] RETURNS [plane: Plane] = {
plane ← NEW[PlaneObj];
plane.A ← normal[1];
plane.B ← normal[2];
plane.C ← normal[3];
Ax + By + Cz + D = 0. D = -(Ax + By + Cz)
plane.D ← -(plane.A*point[1] + plane.B*point[2] + plane.C*point[3]);
}; -- end of PlaneFromPointAndNormal
PlaneFromCoefficients: PUBLIC PROC [A, B, C, D: REAL] RETURNS [plane: Plane] = {
plane ← NEW[PlaneObj ← [A, B, C, D]];
};
ClipPolyToPlane: PUBLIC PROC [poly: Poly3d, plane: Plane] RETURNS [clippedPoly: Poly3d] = {
For each point in poly, plug the point into the plane equation to see which side of poly it is on. Whenever the side changes, compute the intersection with the plane, keeping the line segment on the opposite side of the plane from the side refered to be its surface normal [A, B, C]; Each iteration of the loop will output "thisPoint" if appropriate and/or any plane intersection of the line segment from lastPoint to thisPoint.
lastSide, thisSide: BOOL;
lastDotProduct, thisDotProduct: REAL;
lastPoint, thisPoint, intersectionPoint: Point3d;
clippedPoly ← CreatePoly[poly.len]; -- the clipped poly won't have more vertices than the old poly.
IF poly.len = 0 THEN RETURN;
lastPoint ← poly[poly.len-1]; -- the last point
lastDotProduct ← (plane.A*lastPoint[1] + plane.B*lastPoint[2] + plane.C*lastPoint[3] + plane.D);
lastSide ← lastDotProduct <= 0;
FOR i: NAT IN [0..poly.len) DO
thisPoint ← poly[i];
thisDotProduct ← (plane.A*thisPoint[1] + plane.B*thisPoint[2] + plane.C*thisPoint[3] + plane.D);
thisSide ← thisDotProduct <= 0;
IF thisSide THEN {
IF lastSide THEN clippedPoly ← AddPolyPoint[clippedPoly, thisPoint]
ELSE { -- we go from badSide to keepSide. Add the intersection point first.
intersectionPoint ← LineSegmentMeetsPlaneFast[lastPoint, thisPoint, lastDotProduct, thisDotProduct];
clippedPoly ← AddPolyPoint[clippedPoly, intersectionPoint];
clippedPoly ← AddPolyPoint[clippedPoly, thisPoint];
};
}
ELSE { -- this point is on the bad side.
IF lastSide THEN { -- we have gone from good side to bad side
Output only the intersection point.
intersectionPoint ← LineSegmentMeetsPlaneFast[lastPoint, thisPoint, lastDotProduct, thisDotProduct];
clippedPoly ← AddPolyPoint[clippedPoly, intersectionPoint];
}
ELSE {}; -- bad side to bad side. Do nothing.
};
lastSide ← thisSide;
lastPoint ← thisPoint;
lastDotProduct ← thisDotProduct;
ENDLOOP;
};
ClipPolyToPlanes: PUBLIC PROC [poly: Poly3d, planes: LIST OF Plane] RETURNS [clippedPoly: Poly3d] = {
clippedPoly ← poly;
FOR planeList: LIST OF Plane ← planes, planeList.rest UNTIL planeList = NIL DO
clippedPoly ← ClipPolyToPlane[clippedPoly, planeList.first];
ENDLOOP;
};
LineSegmentMeetsPlaneFast: PRIVATE PROC [p1, p2: Point3d, d1, d2: REAL] RETURNS [intersection: Point3d] = {
This procedure assumes that p1 and p2 are on opposite sides of plane. Hence, we can use just the magnitude of d1 and d2.
total: REAL;
d1 ← IF d1 < 0 THEN -d1 ELSE d1;
d2 ← IF d2 < 0 THEN -d2 ELSE d2;
total ← d1+d2;
intersection[1] ← (d1/total)*(p2[1]-p1[1])+p1[1];
intersection[2] ← (d1/total)*(p2[2]-p1[2])+p1[2];
intersection[3] ← (d1/total)*(p2[3]-p1[3])+p1[3];
};
ClipLineSegmentToPlane: PUBLIC PROC [p1, p2: Point3d, plane: Plane] RETURNS [newP1, newP2: Point3d, newP1isP1, newP2isP2, nullSegment: BOOL] = {
dotProduct1, dotProduct2: REAL;
nullSegment ← FALSE;
dotProduct1 ← (plane.A*p1[1] + plane.B*p1[2] + plane.C*p1[3] + plane.D);
newP1isP1 ← dotProduct1 <= 0;
dotProduct2 ← (plane.A*p2[1] + plane.B*p2[2] + plane.C*p2[3] + plane.D);
newP2isP2 ← dotProduct2 <= 0;
IF newP1isP1 THEN {
newP1 ← p1;
IF newP2isP2 THEN newP2 ← p2
ELSE newP2 ← LineSegmentMeetsPlaneFast[p1, p2, dotProduct1, dotProduct2];
}
ELSE {
IF newP2isP2 THEN {
newP1 ← LineSegmentMeetsPlaneFast[p1, p2, dotProduct1, dotProduct2];
newP2 ← p2;
}
ELSE nullSegment ← TRUE;
};
}; -- end of ClipLineSegmentToPlane
ClipLineSegmentToPlanes: PUBLIC PROC [p1, p2: Point3d, planes: LIST OF Plane] RETURNS [newP1, newP2: Point3d, newP1isP1, newP2isP2, nullSegment: BOOL] = {
plane: Plane;
keepSide1, keepSide2: BOOL;
newP1isP1 ← newP2isP2 ← TRUE;
newP1 ← p1;
newP2 ← p2;
FOR planeList: LIST OF Plane ← planes, planeList.rest UNTIL planeList = NIL DO
plane ← planeList.first;
[newP1, newP2, keepSide1, keepSide2, nullSegment] ← ClipLineSegmentToPlane[newP1, newP2, plane];
IF nullSegment THEN RETURN;
newP1isP1 ← newP1isP1 AND keepSide1;
newP2isP2 ← newP2isP2 AND keepSide2;
ENDLOOP;
}; -- end of ClipLineSegmentToPlanes
NormalOfPlane: PUBLIC PROC [plane: Plane] RETURNS [normal: Vector3d] = {
normal[1] ← plane.A;
normal[2] ← plane.B;
normal[3] ← plane.C;
}; -- end of NormalFromPlane
MaximumDeviationWithinPoly3d: PROC [poly: Poly3d] RETURNS [distance: REAL] = {
Finds the distance of each point in the poly from the plane of the poly and returns the maximum such distance.
plane: Plane ← PlaneFromPoly3d[poly];
SIGNAL NotYetImplemented;-- **** not yet implemented
};
NotYetImplemented: SIGNAL = CODE;
SignedPointToPlaneDistance: PUBLIC PROC [point: Point3d, plane: Plane] RETURNS [distance: REAL] = {
distance ← (plane.A*point[1] + plane.B*point[2] + plane.C*point[3] + plane.D)/
RealFns.SqRt[plane.A*plane.A + plane.B*plane.B + plane.C*plane.C];
}; -- end of SignedPointToPlaneDistance
PointOnNormalSideOfPlane: PUBLIC PROC [point: Point3d, plane: Plane] RETURNS [BOOL] = {
plugInPoint: REAL ← (plane.A*point[1] + plane.B*point[2] + plane.C*point[3] + plane.D);
RETURN [plugInPoint > 0];
};
Quadrant: TYPE = SVPolygon3d.Quadrant;-- {inin, inout, outin, outout};
QuadrantOfPoint: PUBLIC PROC [point: Point3d, plane1, plane2: Plane] RETURNS [Quadrant] = {
Classifies point with respect to two planes. outin means the point is on the normal side of the first plane and the non-normal side of the other plane, etc.
IF PointOnNormalSideOfPlane[point, plane1] THEN {
IF PointOnNormalSideOfPlane[point, plane2] THEN RETURN[outout]
ELSE RETURN[outin]}
ELSE {
IF PointOnNormalSideOfPlane[point, plane2] THEN RETURN[inout]
ELSE RETURN[inin]};
}; -- QuadrantOfPoint
TransformByMat: PUBLIC PROC [poly3d: Poly3d, mat: Matrix4by4] RETURNS [newPoly: Poly3d] = {
Perform the matrix transformation on every point of poly3d to get poly3d in a different coordinate frame.
tPoint: Point3d;
newPoly ← CreatePoly[poly3d.len];
FOR i: NAT IN [0..poly3d.len) DO
tPoint ← SVMatrix3d.Update[poly3d[i], mat];
newPoly ← AddPolyPoint[newPoly, tPoint];
ENDLOOP;
};
ProjectPolyToXYPlane: PUBLIC PROC [poly3d: Poly3d] RETURNS [polygon: Polygon] = {
polygon ← SVPolygon2d.CreatePoly[poly3d.len];
FOR i: NAT IN[0..poly3d.len) DO
polygon[i][1] ← poly3d[i][1];
polygon[i][2] ← poly3d[i][2];
ENDLOOP;
polygon.len ← poly3d.len;
};
ProjectPolyToYZPlane: PUBLIC PROC [poly3d: Poly3d] RETURNS [polygon: Polygon] = {
FOR i: NAT IN[0..poly3d.len) DO
polygon[i][1] ← poly3d[i][2];
polygon[i][2] ← poly3d[i][3];
ENDLOOP;
polygon.len ← poly3d.len;
};
ProjectPolyToXZPlane: PUBLIC PROC [poly3d: Poly3d] RETURNS [polygon: Polygon] = {
FOR i: NAT IN[0..poly3d.len) DO
polygon[i][1] ← poly3d[i][1];
polygon[i][2] ← poly3d[i][3];
ENDLOOP;
polygon.len ← poly3d.len;
};
Init: PROC = {
root3 ← RealFns.SqRt[3];
root3Over3 ← root3/3;
twoRoot3Over3 ← 2*root3Over3;
};
Init[];
END.