G3dShapeImpl.mesa
Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved.
Bloomenthal, April 17, 1993 1:20 pm PDT
Crow, June 9, 1989 10:23:35 am PDT
DIRECTORY Atom, Basics, CedarProcess, Convert, FileNames, FS, G2dBasic, G3dBasic, G3dIO, G3dMatrix, G3dPolygon, G3dQuaternion, G3dShape, G3dVector, IO, PFS, Process, Real, RealFns, Rope;
G3dShapeImpl: CEDAR PROGRAM
IMPORTS Atom, Basics, CedarProcess, Convert, FileNames, FS, G2dBasic, G3dBasic, G3dIO, G3dMatrix, G3dPolygon, G3dQuaternion, G3dVector, IO, PFS, Process, Real, RealFns, Rope
EXPORTS G3dShape
~ BEGIN
Types and Constants
PropList:    TYPE ~ Atom.PropList;
Box3d:     TYPE ~ G3dBasic.Box;
IntegerSequence:  TYPE ~ G3dBasic.IntegerSequence;
IntegerSequenceRep: TYPE ~ G3dBasic.IntegerSequenceRep;
NatSequence:   TYPE ~ G3dBasic.NatSequence;
NatSequenceRep:  TYPE ~ G3dBasic.NatSequenceRep;
NatTable:    TYPE ~ G3dBasic.NatTable;
Surface:     TYPE ~ G3dBasic.Surface;
SurfaceSequence:  TYPE ~ G3dBasic.SurfaceSequence;
SurfaceSequenceRep: TYPE ~ G3dBasic.SurfaceSequenceRep;
Pair:      TYPE ~ G3dBasic.Pair;
PairSequence:   TYPE ~ G3dBasic.PairSequence;
PairSequenceRep:  TYPE ~ G3dBasic.PairSequenceRep;
Ray:      TYPE ~ G3dBasic.Ray;
RealSequence:   TYPE ~ G3dBasic.RealSequence;
RealSequenceRep:  TYPE ~ G3dBasic.RealSequenceRep;
Triple:     TYPE ~ G3dBasic.Triple;
TripleSequence:  TYPE ~ G3dBasic.TripleSequence;
TripleSequenceRep: TYPE ~ G3dBasic.TripleSequenceRep;
Matrix:     TYPE ~ G3dMatrix.Matrix;
AxisAngle:    TYPE ~ G3dQuaternion.AxisAngle;
Quaternion:   TYPE ~ G3dQuaternion.Quaternion;
Edge:      TYPE ~ G3dShape.Edge;
EdgeRep:    TYPE ~ G3dShape.EdgeRep;
EdgeSequence:   TYPE ~ G3dShape.EdgeSequence;
Face:      TYPE ~ G3dShape.Face;
FaceRep:    TYPE ~ G3dShape.FaceRep;
FaceSequence:   TYPE ~ G3dShape.FaceSequence;
FaceSequenceRep: TYPE ~ G3dShape.FaceSequenceRep;
ScreenSequence:  TYPE ~ G3dShape.ScreenSequence;
Shape:     TYPE ~ G3dShape.Shape;
ShapeRep:    TYPE ~ G3dShape.ShapeRep;
ShapeSequence:  TYPE ~ G3dShape.ShapeSequence;
ShapeSequenceRep: TYPE ~ G3dShape.ShapeSequenceRep;
SurfaceProc:   TYPE ~ G3dShape.SurfaceProc;
Validity:    TYPE ~ G3dShape.Validity;
Vertex:     TYPE ~ G3dShape.Vertex;
VertexRep:    TYPE ~ G3dShape.VertexRep;
VertexSequence:  TYPE ~ G3dShape.VertexSequence;
VertexSequenceRep: TYPE ~ G3dShape.VertexSequenceRep;
STREAM:    TYPE ~ IO.STREAM;
ROPE:     TYPE ~ Rope.ROPE;
huge:      REAL ~ Real.LargestNumber;
Error:     PUBLIC SIGNAL [code: ATOM, reason: ROPE] = CODE;
File IO
ShapeFromFile: PUBLIC PROC [fileName: ROPE] RETURNS [s: Shape] ~ {
in: STREAM ¬ PFS.StreamOpen[PFS.PathFromRope[FileNames.ResolveRelativePath[fileName]]];
! FS.Error => Error[error.code, error.explanation]];
s ¬ ShapeFromStream[in];
s.fileName ¬ fileName;
s.name ¬ FileNames.GetShortName[fileName];
IO.Close[in];
};
ShapeFromStream: PUBLIC PROC [in: STREAM] RETURNS [s: Shape] ~ {
Eq: PROC [r1, r2: ROPE] RETURNS [BOOL] ~ {RETURN[Rope.Equal[r1, r2, FALSE]]};
nVerts, nPolys: INT ¬ 0;
line: G3dIO.Line ¬ G3dIO.ObtainLine[];
countFromOne, triangulated, tallyOnPolys: BOOL ¬ FALSE;
map: IntegerSequence ¬ NIL;
IF in = NIL THEN RETURN;
s ¬ NEW[ShapeRep];
DO
ENABLE G3dIO.Error => G3dIO.Error[reason, lineText, lineNumber]; -- raise error here
Types: TYPE ~ RECORD[SEQUENCE max: INT OF {xyz,nrm,col,tex,tra,map,vtx,prop,tally}];
TestEndData: PROC ~ {
index: INT ¬ IO.GetIndex[in];
line ¬ G3dIO.GetLine[in, line ! IO.EndOfStream => GOTO EOF];
IF line.type = data THEN
G3dIO.Error["Excessive data found", line.rope, G3dIO.LineNumber[line]];
IO.SetIndex[in, index];
EXITS EOF => NULL;
};
PutProp: PROC [key: ROPE, value: REF ANY] ~ {
prop: REF ANY ¬ Convert.AtomFromRope[key];
IF Atom.GetPropFromList[s.props, prop] # NIL THEN {
IO.SetIndex[in, index];
line ¬ G3dIO.GetLine[in, line];
G3dIO.Error[IO.PutFR1["Redundant key (%g)", IO.rope[key]], line.rope, G3dIO.LineNumber[line]];
};
s.props ¬ Atom.PutPropOnList[s.props, Convert.AtomFromRope[key], value];
};
StopAtComma: PROC [rope: ROPE] RETURNS [ROPE] ~ {
RETURN[Rope.Substr[rope, 0, Rope.Index[rope, 0, ","]]];
};
GetIntegerCheckingComma: PROC [line: G3dIO.Line] RETURNS [i: INT] ~ {
word: ROPE ¬ StopAtComma[G3dIO.GetWord[line]];
i ¬ Convert.IntFromRope[word ! Convert.Error => G3dIO.ErrorReport[convert, line]];
};
SubE: PROC [r: ROPE] RETURNS [b: BOOL] ~ {b ¬ Eq[Rope.Substr[rope,, Rope.Size[r]], r]};
GetTriple: PROC [l: G3dIO.Line, n: INT] RETURNS [t: Triple] ~ {
Skip: PROC [l: G3dIO.Line] ~ {
[] ¬ G3dIO.GetWord[l]; [] ¬ G3dIO.GetWord[l]; [] ¬ G3dIO.GetWord[l];
};
IF n < maxNTriples THEN t ← G3dIO.GetTriple[line] ELSE Skip[line];
t ¬ G3dIO.GetTriple[line];
};
SetProps: PROC [fields: G3dIO.FieldSequence, types: REF Types] ~ {
FOR f: INT IN [0..fields.length) DO
IF types[f] = prop THEN PutProp[fields[f].id, fields[f].sequence];
ENDLOOP;
};
AllocateFields: PROC [field: G3dIO.Field, nLines: INT] ~ {
field.sequence ¬ SELECT field.type FROM
integer => NEW[IntegerSequenceRep[nLines]],
real  => NEW[RealSequenceRep[nLines]],
pair  => NEW[PairSequenceRep[nLines]],
triple  => NEW[TripleSequenceRep[nLines]],
nats  => NEW[SurfaceSequenceRep[nLines]],
ENDCASE => NIL;
};
ReadProp: PROC [line: G3dIO.Line, f: G3dIO.Field, n: INT] ~ {
SELECT f.type FROM
integer => NARROW[f.sequence, IntegerSequence][n] ¬ G3dIO.GetInteger[line];
real  => NARROW[f.sequence, RealSequence][n] ¬ G3dIO.GetReal[line];
pair  => NARROW[f.sequence, PairSequence][n] ¬ G3dIO.GetPair[line];
triple  => NARROW[f.sequence, TripleSequence][n] ¬ GetTriple[line, n];
nats  => NARROW[f.sequence, NatTable][n] ¬ G3dIO.GetNats[line];
ENDCASE => NULL;
};
rope: ROPE;
key: ROPE ¬ G3dIO.NextKeyWord[in ! G3dIO.Error => EXIT];
index: INT ¬ IO.GetIndex[in]; -- may need this later to report PutProp error
SELECT TRUE FROM
Eq[key, "SurfaceType"] => { -- read shape information
line ← G3dIO.GetLine[in, line];
line ¬ G3dIO.FindKeyWord[in, key,,, line];
DO
ENABLE G3dIO.Error => CONTINUE;
IF (rope ¬ G3dIO.GetWord[line]) = NIL THEN EXIT;
IF Rope.Find[rope, "--"] = 0 THEN EXIT; -- ignore remaining comment
SELECT TRUE FROM
SubE["Triangulated"]   => triangulated ¬ TRUE;
SubE["InsideVisible"]   => s.showBackfaces ¬ TRUE;
SubE["CountFromOne"]   => countFromOne ¬ TRUE;
SubE["TallyOnPolys"]   => tallyOnPolys ¬ TRUE;
ENDCASE       => s.type ¬ Atom.MakeAtom[StopAtComma[rope]];
ENDLOOP;
};
Eq[key, "DataSize"] => { -- read vertex/surface sizes:
line ← G3dIO.GetLine[in, line];
line ¬ G3dIO.FindKeyWord[in, key,,, line];
DO
ENABLE Error => CONTINUE;
IF (rope ¬ G3dIO.GetWord[line]) = NIL THEN EXIT;
IF Rope.Find[rope, "--"] = 0 THEN EXIT; -- ignore remaining comment
SELECT TRUE FROM
SubE["Vertices"]  => nVerts ¬ GetIntegerCheckingComma[line];
SubE["Polygons"], SubE["Surfaces"], SubE["Patch"], SubE["Bezier"]
     => nPolys ¬ GetIntegerCheckingComma[line];
ENDCASE;
ENDLOOP;
};
Eq[key, "Vertices"] => { -- read vertices
dum: Line ← G3dIO.GetLine[in, line];
dum: G3dIO.Line ¬ line ¬ G3dIO.FindKeyWord[in, key,,, line];
fields: G3dIO.FieldSequence ¬ G3dIO.InitializeFields[line];
types: REF Types ¬ NEW[Types[fields.length]];
IF nVerts = 0 THEN nVerts ¬ G3dIO.NumberOfLinesToConvert[in];
s.vertices ¬ NEW[VertexSequenceRep[nVerts]];
s.vertices.length ¬ nVerts;
FOR n: INT IN [0..fields.length) DO
f: G3dIO.Field ¬ fields[n];
SELECT TRUE FROM
Eq[f.id, "index"]   => types[n] ¬ map;
Eq[f.id, "xyzCoords"] => types[n] ¬ xyz;
Eq[f.id, "normalVec"] => {types[n] ¬ nrm; s.vertices.valid[normal] ¬ TRUE};
Eq[f.id, "rgbColor"]  => {types[n] ¬ col; s.vertices.valid[color] ¬ TRUE};
Eq[f.id, "textureCoords"] => {types[n] ¬ tex; s.vertices.valid[texture] ¬ TRUE};
Eq[f.id, "transmittance"] => {types[n] ¬ tra; s.vertices.valid[transmit] ¬ TRUE};
ENDCASE     => {types[n] ¬ prop; AllocateFields[f, nVerts]};
IF types[n] = map THEN map ¬ NEW[IntegerSequenceRep[nVerts]];
ENDLOOP;
FOR n: INT IN [0..nVerts) DO -- line by line data conversions
v: Vertex ¬ s.vertices[n] ¬ NEW[VertexRep];
CedarProcess.CheckAbort[];
line ¬ G3dIO.GetDataLine[in, line ! IO.EndOfStream => GOTO eof];
FOR f: INT IN [0..fields.length) DO
SELECT types[f] FROM
xyz => v.point ¬ GetTriple[line, n];
nrm => v.normal ¬ GetTriple[line, n];
col => v.color ¬ GetTriple[line, n];
tex => v.texture ¬ G3dIO.GetPair[line];
tra  => v.transmittance ¬ G3dIO.GetReal[line];
map => map[n] ¬ G3dIO.GetInteger[line];
prop => ReadProp[line, fields[f], n];
ENDCASE => NULL;
ENDLOOP;
REPEAT
eof => NULL;
ENDLOOP;
TestEndData[];
SetProps[fields, types];
};
Eq[key, "Surfaces"] => { -- read polygons
line: Line ← G3dIO.GetLine[in, line];
tallied: BOOL ¬ FALSE;
dum: G3dIO.Line ¬ line ¬ G3dIO.FindKeyWord[in, key,,, line];
fields: G3dIO.FieldSequence ¬ G3dIO.InitializeFields[line];
types: REF Types ¬ NEW[Types[fields.length]];
IF nPolys = 0 THEN nPolys ¬ G3dIO.NumberOfLinesToConvert[in];
s.surfaces ¬ NEW[SurfaceSequenceRep[nPolys]];
s.surfaces.length ¬ nPolys;
FOR f: INT IN [0..fields.length) DO
NewFaces: PROC ~ {IF s.faces=NIL THEN s.faces ¬NEW[FaceSequenceRep[nPolys]]};
types[f] ¬ SELECT TRUE FROM
Eq[fields[f].id, "index"]    => map,
Eq[fields[f].id, "nSides"]    => tally,
Eq[fields[f].id, "vertices"]   => vtx,
Eq[fields[f].id, "normalVec"]  => nrm,
Eq[fields[f].id, "rgbColor"]   => col,
Eq[fields[f].id, "transmittance"] => tra,
ENDCASE        => prop;
SELECT types[f] FROM
tally => tallied ¬ TRUE;
nrm => {NewFaces[]; s.faces.valid[normal] ¬ TRUE};
col => {NewFaces[]; s.faces.valid[color] ¬ TRUE};
tra  => {NewFaces[]; s.faces.valid[transmit] ¬ TRUE};
prop => AllocateFields[fields[f], nPolys];
ENDCASE;
ENDLOOP;
FOR n: INT IN [0..nPolys) DO -- line by line data conversions
nSides: INT ¬ 3;
face: Face ¬ IF s.faces # NIL THEN (s.faces[n] ¬ NEW[FaceRep]) ELSE NIL;
CedarProcess.CheckAbort[];
line ¬ G3dIO.GetDataLine[in, line ! IO.EndOfStream => GOTO eof];
FOR f: INT IN [0..fields.length) DO
SELECT types[f] FROM
map => [] ¬ G3dIO.GetWord[line];   -- skip the surface index
tally => nSides ¬ G3dIO.GetInteger[line]; -- should speed things
vtx => SELECT TRUE FROM
triangulated, tallied => {
seq: NatSequence ¬ s.surfaces[n].vertices ¬ NEW[NatSequenceRep[nSides]];
seq.length ¬ nSides;
FOR i: INT IN [0..nSides) DO
seq[i] ¬ Convert.IntFromRope[G3dIO.GetWord[line]
! Convert.Error => G3dIO.ErrorReport[convert, line]];
ENDLOOP;
};
ENDCASE => s.surfaces[n].vertices ¬ G3dIO.GetNats[line];
nrm => face.normal ¬ GetTriple[line, n];
col => face.color ¬ GetTriple[line, n];
tra  => face.transmittance ¬ G3dIO.GetReal[line];
prop => ReadProp[line, fields[f], n];
ENDCASE => NULL;
ENDLOOP;
REPEAT
eof => NULL;
ENDLOOP;
TestEndData[];
SetProps[fields, types];
};
ENDCASE => {
line ¬ G3dIO.GetLine[in, line];
PutProp[key, Rope.Substr[line.rope, Rope.Length[key]+1]];
};
ENDLOOP;
G3dIO.ReleaseLine[line];
IF s.surfaces # NIL THEN {  -- tests
IF map # NIL THEN   -- take care of indexed vertices:
FOR n: INT IN [0..map.length) DO
IF map[n] # n THEN {   -- map is not a no-op
FOR n: INT IN [0..s.surfaces.length) DO
poly: NatSequence ¬ s.surfaces[n].vertices;
FOR nn: INT IN [0..poly.length) DO poly[nn] ¬ map[poly[nn]]; ENDLOOP;
ENDLOOP;
EXIT;
};
ENDLOOP;
IF countFromOne THEN
FOR n: INT IN [0..s.surfaces.length) DO
poly: NatSequence ¬ s.surfaces[n].vertices;
FOR nn: INT IN [0..poly.length) DO poly[nn] ¬ poly[nn]-1; ENDLOOP;
ENDLOOP;
s.triangulated ¬ TRUE;
FOR n: INT IN [0..s.surfaces.length) DO-- check polygon validity:
poly: NatSequence ¬ s.surfaces[n].vertices;
IF poly = NIL THEN G3dIO.Error[IO.PutFR1["non existent polygon %g", IO.int[n]], NIL, 0];
IF poly.length # 3 AND s.triangulated THEN s.triangulated ¬ FALSE;
FOR nn: INT IN [0..poly.length) DO
IF poly[nn] >= nVerts THEN G3dIO.Error[
IO.PutFR1["polygon %g refers to non-existent vertex", IO.int[n]], NIL, 0];
ENDLOOP;
ENDLOOP;
s.matrix ¬ G3dMatrix.Identity[];
s.objectExtent ¬ BoundingBox[s];
};
};
ShapeToFile: PUBLIC PROC [
fileName: ROPE,
shape: Shape,
index: BOOL ¬ FALSE,
tallyOnPolys: BOOL ¬ FALSE,
comment: ROPE ¬ NIL]
~ {
IF fileName # NIL AND shape # NIL THEN {
out: IO.STREAM ¬ FS.StreamOpen[FileNames.ResolveRelativePath[fileName], $create];
IF comment # NIL THEN IO.PutF1[out, "Comment~ %g\n\n", IO.rope[comment]];
ShapeToStream[shape, out, index, tallyOnPolys];
};
};
ShapeToStream: PUBLIC PROC [
shape: Shape,
out: STREAM,
index: BOOL ¬ FALSE,
tallyOnPolys: BOOL ¬ FALSE]
~ {
IO.PutF1[out, "ShapeName~ %g\n", IO.rope[shape.name]];
IO.PutF1[out, "SurfaceType~ %g\n", IO.rope[Atom.GetPName[shape.type]]];
IO.PutF[out, "DataSize~ vertices: %g, polygons: %g\n\n",
IO.int[shape.vertices.length], IO.int[shape.surfaces.length]];
IO.PutRope[out, "Vertices~"];
IF index THEN IO.PutRope[out, " index: integer"];
IO.PutRope[out, " xyzCoords: triple"];
IF shape.vertices.valid[normal] THEN IO.PutRope[out, " normalVec: triple"];
IF shape.vertices.valid[color] THEN IO.PutRope[out, " rgbColor: triple"];
IF shape.vertices.valid[texture] THEN IO.PutRope[out, " textureCoords: pair"];
IO.PutRope[out, "\n\n"];
FOR n: INT IN [0..shape.vertices.length) DO
v: Vertex ¬ shape.vertices[n];
IF index THEN IO.PutF1[out, "%g\t", IO.int[n]];
G3dIO.WriteTriple[out, v.point];
IF shape.vertices.valid[normal] THEN G3dIO.WriteTriple[out, v.normal];
IF shape.vertices.valid[color] THEN G3dIO.WriteTriple[out, v.color];
IF vertices.valid[color] THEN
IO.PutF[out, "%5.4f %5.4f %5.4f",
IO.real[v.color.x], IO.real[v.color.y], IO.real[v.color.z]];
IF shape.vertices.valid[texture] THEN G3dIO.WritePair[out, v.texture];
IO.PutRope[out, "\n"];
ENDLOOP;
IO.PutRope[out, "\nSurfaces~ "];
IF index THEN IO.PutRope[out, " index: integer"];
IO.PutRope[out, " vertices: nats\n\n"];
FOR n: INT IN [0..shape.surfaces.length) DO
polygon: NatSequence ~ shape.surfaces[n].vertices;
IF index THEN IO.PutF1[out, "%g\t", IO.int[n]];
IF tallyOnPolys THEN IO.PutF1[out, "%g\t", IO.int[polygon.length]];
FOR nn: INT IN [0..polygon.length) DO
IO.PutF1[out, "%5g ", IO.int[polygon[nn]]];
ENDLOOP;
IO.PutRope[out, "\n"];
ENDLOOP;
IO.Close[out];
};
ShapeToStreamPerFormat: PUBLIC PROC [shape: Shape, out: STREAM, format: ATOM]
RETURNS [success: BOOL ¬ TRUE]
~ {
WriteShapeAsQuad: PROC ~ {
Quad file format for Pat Hanrahan's MinneView program on the Iris:
File = Header Quadrilateral*
Header = [C][N]POLY (Inclusion of C indicates color information, N indicates normals)
Quadrilateral = Vertex Vertex Vertex Vertex
Vertex = Point [Color] [Normal]
Point = x y z (real numbers)
Color = r g b a (real numbers, 0<=r,g,b,a<=1, a=opacity, a=1 means opaque)
Normal = x y z (real numbers [not necessarily unitized])
WriteQuad: PROC [surface: Surface, start: CARD] ~ {
WriteVertex: PROC [v: Vertex] ~ {
G3dIO.WriteTriple[out, v.point];
IF shape.vertices.valid[normal]
THEN G3dIO.WriteTriple[out, v.normal];
IF shape.vertices.valid[color] THEN {
G3dIO.WriteTriple[out, v.color];
IO.PutRope[out, "1.0 "];
};
IO.PutRope[out, "\n"];
};
WriteVertex[shape.vertices[surface.vertices[0]]];
WriteVertex[shape.vertices[surface.vertices[start]]];
WriteVertex[shape.vertices[surface.vertices[MIN[start+1, surface.vertices.length-1]]]];
WriteVertex[shape.vertices[surface.vertices[MIN[start+2, surface.vertices.length-1]]]];
IO.PutRope[out, "\n"];
};
IF shape.vertices.valid[color] THEN IO.PutRope[out, "C"];
IF shape.vertices.valid[normal] THEN IO.PutRope[out, "N"];
IO.PutRope[out, "POLY\n"];
FOR s: INT IN [0..shape.surfaces.length) DO
surface: Surface ¬ shape.surfaces[s];
lastWritten: INT ¬ 1;
WHILE lastWritten < INT[surface.vertices.length] DO
IF surface.vertices.length-lastWritten > 1 THEN WriteQuad[surface, lastWritten];
lastWritten ¬ lastWritten+2;
ENDLOOP;
ENDLOOP;
};
WriteShapeAsDotNone: PROC ~ {
.none format for University of Calgary's GraphicsJungle
huskFileType:  CHAR ~ 'x;
huskOpenObject: CHAR ~ 'o;
huskCloseObject: CHAR ~ 'O;
huskPolygon:  CHAR ~ 'p;
huskNormalPoly: CHAR ~ 133+0C;
huskColourfulPoly: CHAR ~ 129+0C;
oneDRealRep:  PACKED ARRAY [0..1] OF CARD32 ~ LOOPHOLE[DREAL[1.0]];
drealHi:    [0..1] ~ IF oneDRealRep[1] = 0 THEN 0 ELSE 1;
drealLo:    [0..1] ~ 1-drealHi;
WriteShort: PROC [i: INTEGER] ~ {IO.PutHWord[out, Basics.HFromInt16[i]]}; --16 bit int
WriteDouble: PROC [dreal: DREAL] ~ { -- 64 bit double precision real
see /PCedar/SunRPCRuntime/SunRPCImpl.mesa
a: PACKED ARRAY [0..1] OF CARD32 ¬ LOOPHOLE[dreal];
IO.PutFWord[out, Basics.FFromCard32[a[drealHi]]];
IO.PutFWord[out, Basics.FFromCard32[a[drealLo]]];
};
WritePoint: PROC [p: Triple] ~ {
WriteDouble[p.x];
WriteDouble[p.y];
WriteDouble[p.z];
};
WriteChar: PROC [c: CHAR] ~ {IO.PutChar[out, c]};
WriteString: PROC [r: ROPE] ~ {IO.PutRope[out, r]; IO.PutChar[out, 0C]};
WriteOpcode: PROC [c: CHAR, n: INTEGER] ~ {WriteChar[c]; WriteShort[n]};
WriteOpcode[huskFileType, 0];
WriteOpcode[huskOpenObject, Rope.Length[shape.name]+1];
WriteString[shape.name];
FOR s: INT IN [0..shape.surfaces.length) DO
polygon: G3dShape.NatSequence ¬ shape.surfaces[s].vertices;
dsize: INT ¬ 3*polygon.length*8; -- 8 = sizeof(double)
ssize: INT ¬ 3*polygon.length*2; -- 2 = sizeof(short)
IF polygon.length < 3 THEN LOOP;
WriteOpcode[huskColourfulPoly, ssize+2];
WriteShort[polygon.length];
FOR i: INT IN [0..polygon.length) DO -- color for each vertex
color: Triple ¬ shape.vertices[polygon[i]].color;
IF NOT shape.vertices.valid[color] THEN color ¬ [1.0, 1.0, 1.0];
WriteShort[Real.Round[65535.0*color.x]];
WriteShort[Real.Round[65535.0*color.y]];
WriteShort[Real.Round[65535.0*color.z]];
ENDLOOP;
WriteOpcode[huskNormalPoly, dsize+2];
WriteShort[polygon.length];
FOR i: INT IN [0..polygon.length) DO -- normal for each vertex
WritePoint[shape.vertices[polygon[i]].normal];
ENDLOOP;
WriteOpcode[huskPolygon, dsize+2];
WriteShort[polygon.length];
FOR i: INT IN [0..polygon.length) DO -- position for each vertex
WritePoint[shape.vertices[polygon[i]].point];
ENDLOOP;
ENDLOOP;
WriteOpcode[huskCloseObject, 0];
};
IF shape = NIL OR out = NIL OR
shape.vertices = NIL OR shape.vertices.length = 0 OR
shape.surfaces = NIL OR shape.surfaces.length = 0
THEN RETURN[FALSE]
ELSE SELECT format FROM
$Quad => WriteShapeAsQuad[];
$DotNone => WriteShapeAsDotNone[];
ENDCASE;
};
Creation
ShapeFromData: PUBLIC PROC [
name: ROPE ¬ NIL,
surfaces: SurfaceSequence,
vertices: TripleSequence,
normals: TripleSequence ¬ NIL,
colors: TripleSequence ¬ NIL,
transmittances: RealSequence ¬ NIL,
textures: PairSequence ¬ NIL,
showBackfaces: BOOL ¬ TRUE,
faceted: BOOL ¬ FALSE,
type: ATOM ¬ $ConvexPolygon]
RETURNS [s: Shape]
~ {
If faceted, colors are applied to surfaces (facets) rather than vertices (not implemented).
s ¬ NEW[ShapeRep];
s­ ¬ [name: name, surfaces: surfaces, showBackfaces: showBackfaces, type: type];
s.vertices ¬ NEW[VertexSequenceRep[vertices.length]];
s.vertices.length ¬ vertices.length;
s.vertices.valid[normal] ¬ normals # NIL;
s.vertices.valid[color] ¬ colors # NIL;
s.vertices.valid[texture] ¬ textures # NIL;
s.vertices.valid[transmit] ¬ transmittances # NIL;
FOR n: CARDINAL IN [0..vertices.length) DO
v: Vertex ¬ s.vertices[n] ¬ NEW[VertexRep ¬ [point: vertices[n]]];
IF normals # NIL AND normals.length > n THEN v.normal ¬ normals[n];
IF colors # NIL AND colors.length > n THEN v.color ¬ colors[n];
IF transmittances # NIL AND transmittances.length > n
THEN v.transmittance ¬ transmittances[n];
IF textures # NIL AND textures.length > n THEN v.texture ¬ textures[n];
ENDLOOP;
};
Copying/Combining
CopyShape: PUBLIC PROC [shape: Shape] RETURNS [s: Shape] ~ {
s ¬ NEW[ShapeRep ¬ []];       -- default top-level fields
s.clientData ¬ shape.clientData;
s.sphereExtent ¬ shape.sphereExtent;   -- computed when original read in
s.objectExtent ¬ shape.objectExtent;
s.showBackfaces ¬ shape.showBackfaces;
s.triangulated ¬ shape.triangulated;
s.matrix ¬ G3dMatrix.CopyMatrix[shape.matrix];
s.props ¬ NIL;         -- make physical copy of proplist
FOR list: PropList ¬ shape.props, list.rest UNTIL list = NIL DO
element: Atom.DottedPair ¬ NEW[Atom.DottedPairNode ¬ list.first­];
s.props ¬ CONS[element, s.props];
ENDLOOP;
IF shape.vertices # NIL THEN {
s.vertices ¬ NEW[VertexSequenceRep[shape.vertices.length]];  -- copy vertices
s.vertices.valid ¬ shape.vertices.valid;
s.vertices.length ¬ shape.vertices.length;
FOR i: INT IN [0..shape.vertices.length) DO
s.vertices[i] ¬ NEW[VertexRep ¬ shape.vertices[i]­];
ENDLOOP;
};
IF s.faces # NIL THEN {
s.faces ¬ NEW[FaceSequenceRep[shape.faces.length]];    -- copy face data
s.faces.valid ¬ shape.faces.valid;
s.faces.length ¬ shape.faces.length;
FOR i: INT IN [0..shape.faces.length) DO
s.faces[i] ¬ NEW[FaceRep ¬ shape.faces[i]­];
ENDLOOP;
};
s.surfaces ¬ G3dBasic.CopySurfaceSequence[shape.surfaces]; -- copy surface definition
};
CombineShapes: PUBLIC PROC [shape1, shape2: Shape] RETURNS [s: Shape] ~ {
q1: Quaternion ¬ shape1.orientation;
q2: Quaternion ¬ shape2.orientation;
q2a: Quaternion ¬ IF G3dQuaternion.Dot[q1, q2] < 0 THEN G3dQuaternion.Neg[q2] ELSE q2;
q: Quaternion ¬ G3dQuaternion.Bisect[q1, q2a];
a: AxisAngle ¬ G3dQuaternion.ToAxisAngle[q];
midPos: Triple ¬ G3dVector.Midpoint[shape1.translation, shape2.translation];
midBase: Triple ¬ G3dVector.Midpoint[shape1.rotationBase, shape2.rotationBase];
s ¬ NEW[ShapeRep];
s.clientData ¬ IF shape1.clientData # NIL THEN shape1.clientData ELSE shape2.clientData;
s.name ¬ Rope.Concat[shape1.name, shape2.name];
FOR l: PropList ¬ shape1.props, l.rest WHILE l#NIL DO s.props ¬ CONS[l.first, s.props]; ENDLOOP;
FOR l: PropList ¬ shape2.props, l.rest WHILE l#NIL DO s.props ¬ CONS[l.first, s.props]; ENDLOOP;
TransformShape[s, midPos, [midBase, a.unitAxis], a.theta, 0.5*(shape1.scale+shape2.scale)];
SELECT TRUE FROM
shape1 = NIL OR shape1.vertices = NIL OR shape1.surfaces = NIL => RETURN[shape2];
shape2 = NIL OR shape2.vertices = NIL OR shape2.surfaces = NIL => RETURN[shape1];
ENDCASE => {
CopyVertex: PROC [v: Vertex] RETURNS [vv: Vertex] ~ {vv ¬ NEW[VertexRep ¬ v­]};
s.vertices ¬ NEW[VertexSequenceRep[shape1.vertices.length+shape2.vertices.length]];
s.vertices.length ¬ s.vertices.maxLength;
FOR i: INT IN [0..shape1.vertices.length) DO
s.vertices[i] ¬ CopyVertex[shape1.vertices[i]];
ENDLOOP;
FOR i: INT IN [0..shape2.vertices.length) DO
s.vertices[i+shape1.vertices.length] ¬ CopyVertex[shape2.vertices[i]];
ENDLOOP;
s.surfaces ¬ NEW[SurfaceSequenceRep[shape1.surfaces.length+shape2.surfaces.length]];
s.surfaces.length ¬ s.surfaces.maxLength;
FOR i: INT IN [0..shape1.surfaces.length) DO
s.surfaces[i] ¬ [NIL, G2dBasic.CopyNatSequence[shape1.surfaces[i].vertices]];
ENDLOOP;
FOR i: INT IN [0..shape2.surfaces.length) DO
n: NatSequence ¬ G2dBasic.CopyNatSequence[shape2.surfaces[i].vertices];
FOR j: INT IN [0..n.length) DO
n[j] ¬ n[j]+shape1.vertices.length;
ENDLOOP;
s.surfaces[i+shape1.surfaces.length] ¬ [NIL, n];
ENDLOOP;
};
};
Transformations
ComputeMatrix: PUBLIC PROC [shape: Shape] ~ {
a: AxisAngle ¬ G3dQuaternion.ToAxisAngle[shape.orientation];
TransformShape[shape, shape.translation, [shape.rotationBase, a.unitAxis], a.theta, shape.scale];
};
SetMatrix: PUBLIC PROC [shape: Shape, matrix: Matrix] ~ {
shape.matrix ¬ matrix;
};
MatrixFromPars: PROC [translate: Triple, axis: Ray, rotation, scale: REAL, out: Matrix]
RETURNS [m: Matrix]
~ {
m ¬ G3dMatrix.Scale[out, scale, m];
IF rotation # 0.0 THEN m ¬ G3dMatrix.Rotate[m, axis.axis, rotation,, axis.base, m];
m ¬ G3dMatrix.Translate[m, translate, m];
};
TransformShape: PUBLIC PROC [
shape: Shape,
translate: Triple ¬ [],
axis: Ray ¬ G3dShape.zAxis,
rotation: REAL ¬ 0.0,
scale: REAL ¬ 1.0,
concat: BOOL ¬ FALSE]
~ {
q: Quaternion ¬ shape.orientation;
IF rotation # 0.0 AND axis.axis # []
THEN q ¬ G3dQuaternion.FromAxisAngle[axis.axis, rotation];
IF NOT concat THEN {
shape.rotationBase ¬ axis.base;
shape.orientation ¬ q;
shape.translation ¬ translate;
shape.scale ¬ scale;
};
IF NOT concat THEN shape.matrix ¬ G3dMatrix.Identity[shape.matrix];
shape.matrix ¬ MatrixFromPars[translate, axis, rotation, scale, shape.matrix];
shape.renderValid ¬ FALSE;
};
TransformVertices: PUBLIC PROC [shape: Shape, matrix: Matrix] ~ {
IF shape # NIL AND shape.vertices # NIL THEN {
nMatrix: Matrix ¬ G3dMatrix.MakeVectorTransform[matrix, G3dMatrix.ObtainMatrix[]];
FOR n: INT IN [0..shape.vertices.length) DO
v: Vertex ¬ shape.vertices[n];
v.point ¬ G3dMatrix.Transform[v.point, matrix];
v.normal ¬ G3dMatrix.Transform[v.normal, nMatrix];
ENDLOOP;
G3dMatrix.ReleaseMatrix[nMatrix];
shape.renderValid ¬ FALSE;
};
};
ApplyTransformsToShape: PUBLIC PROC [shape: Shape] ~ {
IF shape = NIL THEN RETURN;
TransformVertices[shape, shape.matrix];
shape.matrix ¬ G3dMatrix.Identity[];
shape.objectExtent ¬ BoundingBox[shape];
shape.sphereExtent ¬ BoundingSphere[shape];
shape.renderValid ¬ FALSE;
IF shape.faces # NIL THEN {
shape.faces.valid[normal] ¬ FALSE;
shape.faces.valid[center] ¬ FALSE;
};
IF shape.vertices # NIL THEN shape.vertices.valid[normal] ¬ TRUE;
};
Vertex Procedures
DoWithVertices: PUBLIC PROC [shape: Shape, vertexProc: G3dShape.VertexProc] ~ {
IF shape # NIL AND shape.vertices # NIL AND vertexProc # NIL THEN
FOR n: INT IN [0..shape.vertices.length) DO
IF NOT vertexProc[shape.vertices[n], n] THEN EXIT;
ENDLOOP;
};
VertexValid: PUBLIC PROC [shape: Shape, test: Validity] RETURNS [b: BOOL ¬ FALSE] ~ {
IF shape # NIL AND shape.vertices # NIL THEN RETURN[shape.vertices.valid[test]];
};
SetVertexNormals: PUBLIC PROC [shape: Shape] ~ {
IF shape.vertices = NIL THEN RETURN;
shape.vertices.valid[normal] ¬ TRUE;
IF NOT FaceValid[shape, normal] THEN SetFaceNormals[shape, FALSE];
FOR i: INT IN [0..shape.vertices.length) DO shape.vertices[i].normal ¬ [0.0, 0.0, 0.0]; ENDLOOP;
FOR i: INT IN [0..shape.surfaces.length) DO
poly: NatSequence ¬ shape.surfaces[i].vertices;
faceNormal: Triple ¬ shape.faces[i].normal;
vPrev: Vertex ¬ shape.vertices[poly[poly.length-1]];
v: Vertex ¬ shape.vertices[poly[0]];
vec1: Triple ¬ G3dVector.Unit[G3dVector.Sub[vPrev.point, v.point]];
FOR j: INT IN [0..poly.length) DO
vNext: Vertex ¬ shape.vertices[poly[(j+1) MOD poly.length]];
computing the angle presumes the angle is always less than 180 degrees
1-cos(a) = 0 for 0°, 1 for 90°, and 2 for 180°
vec2: Triple ¬ G3dVector.Unit[G3dVector.Sub[vNext.point, v.point]];
weight: REAL ¬ 1.0-G3dVector.Dot[vec1, vec2];
v.normal ¬ G3dVector.Mul[G3dVector.Add[v.normal, faceNormal], weight];
vPrev ¬ v;
v ¬ vNext;
vec1 ¬ G3dVector.Negate[vec2];
ENDLOOP;
ENDLOOP;
FOR i: INT IN [0..shape.vertices.length) DO
v: Vertex ¬ shape.vertices[i];
v.normal ¬ G3dVector.Unit[v.normal];
ENDLOOP;
};
SetFwdFacingVertices: PUBLIC PROC [shape: Shape, view: Matrix, screens: ScreenSequence]
~ {
Action: SurfaceProc ~ {
FOR i: INT IN [0..surface.length) DO screens[surface[i]].fwdFacing ¬ TRUE; ENDLOOP;
};
IF shape = NIL OR shape.vertices = NIL OR screens = NIL THEN RETURN;
FOR n: INT IN [0..shape.vertices.length) DO screens[n].fwdFacing ¬ FALSE; ENDLOOP;
DoWithFacingPolygons[shape, view, Action];
};
UnitizeVertexNormals: PUBLIC PROC [shape: Shape] ~ {
IF shape # NIL AND shape.vertices # NIL THEN
FOR i: INT IN [0..shape.vertices.length) DO
shape.vertices[i].normal ¬ G3dVector.Unit[shape.vertices[i].normal];
ENDLOOP;
};
NegateVertexNormals: PUBLIC PROC [shape: Shape] ~ {
IF shape = NIL OR shape.vertices = NIL THEN RETURN;
FOR n: INT IN [0..shape.vertices.length) DO
shape.vertices[n].normal ¬ G3dVector.Negate[shape.vertices[n].normal];
ENDLOOP;
shape.renderValid ¬ FALSE;
shape.normalsNegated ¬ NOT shape.normalsNegated;
};
NumberOfVertices: PUBLIC PROC [shapes: ShapeSequence] RETURNS [num: INT ¬ 0] ~ {
IF shapes # NIL THEN FOR n: INT IN [0..shapes.length) DO
s: Shape ¬ shapes[n];
IF s # NIL AND s.vertices # NIL THEN num ¬ num+s.vertices.length;
ENDLOOP;
};
SetExtent: PUBLIC PROC [screens: ScreenSequence] ~ {
b: G2dBasic.Box ¬ [[huge, huge], [-huge, -huge]];
FOR i: INT IN [0..screens.length) DO
p: Pair ¬ screens[i].pos;
b ¬ [[MIN[b.min.x, p.x], MIN[b.min.y, p.y]], [MAX[b.max.x, p.x], MAX[b.max.y, p.y]]];
ENDLOOP;
screens.extent ¬ b;
screens.extentValid ¬ TRUE;
};
InterpolateVertex: PUBLIC PROC [t: REAL, v0, v1: Vertex] RETURNS [v: Vertex] ~ {
InterpPair: PROC [t: REAL, p0, p1: Pair] RETURNS [Pair] ~ {
RETURN[[p0.x+t*(p1.x-p0.x), p0.y+t*(p1.y-p0.y)]];
};
v ¬ NEW[VertexRep];
v.point ¬ G3dVector.Interp[t, v0.point, v1.point];
IF v0.normal # [] OR v1.normal # []
THEN v.normal ¬ G3dVector.Unit[G3dVector.Interp[t, v0.normal, v1.normal]];
IF v0.color # [] OR v1.color # [] THEN v.color ¬ G3dVector.Interp[t, v0.color, v1.color];
IF v0.texture#[0,0] OR v1.texture#[0,0] THEN v.texture ¬ InterpPair[t, v0.texture, v1.texture];
};
Polygon Procedures
DoWithAllPolygons: PUBLIC PROC [shape: Shape, action: SurfaceProc] ~ {
IF shape # NIL AND action # NIL THEN
FOR n: INT IN [0..shape.faces.length) DO
Process.CheckForAbort[];
IF NOT action[shape.surfaces[n].vertices, n] THEN RETURN;
ENDLOOP;
};
DoWithFacingPolygons: PUBLIC PROC [
shape: Shape,
view: Matrix,
frontFacingAction, backFacingAction: SurfaceProc ¬ NIL]
~ {
IF shape # NIL AND view # NIL AND (frontFacingAction # NIL OR backFacingAction # NIL)
THEN {
persp: BOOL ¬ G3dMatrix.HasPerspective[view];
inverse: Matrix ¬ IF persp THEN G3dMatrix.Invert[view, G3dMatrix.ObtainMatrix[]
! G3dMatrix.singular => GOTO BadMatrix] ELSE NIL;
q: G3dBasic.Quad ¬ [inverse[3][0], inverse[3][1], inverse[3][2], inverse[3][3]];
camera: Triple ¬ [q.x/q.w, q.y/q.w, q.z/q.w];
IF NOT FaceValid[shape, normal] THEN SetFaceNormals[shape];
IF NOT FaceValid[shape, center] THEN SetFaceCenters[shape];
FOR i: INT IN [0..shape.faces.length) DO
visible: BOOL;
poly: NatSequence ¬ shape.surfaces[i].vertices;
Process.CheckForAbort[];
IF poly.length < 3
THEN visible ¬ TRUE
ELSE {
f: Face ¬ shape.faces[i];
visible ¬ IF persp
THEN G3dVector.FrontFacingWithPerspective[f.normal, f.center, inverse]
THEN G3dVector.Dot[G3dVector.Sub[camera, f.center], f.normal] > 0.0
ELSE G3dVector.FrontFacingNoPerspective[f.normal, view];
};
IF visible AND frontFacingAction # NIL
THEN {IF NOT frontFacingAction[poly, i] THEN RETURN};
IF NOT visible AND backFacingAction # NIL
THEN {IF NOT backFacingAction[poly, i] THEN RETURN};
ENDLOOP;
IF persp THEN G3dMatrix.ReleaseMatrix[inverse];
};
EXITS BadMatrix => NULL;
};
FaceValid: PUBLIC PROC [shape: Shape, test: Validity] RETURNS [b: BOOL ¬ FALSE] ~ {
IF shape # NIL AND shape.faces # NIL THEN RETURN[shape.faces.valid[test]];
};
SetFwdFacingFaces: PUBLIC PROC [shape: Shape, view: Matrix] ~ {
BackFaces: SurfaceProc ~ {shape.faces[index].fwdFacing ¬ FALSE};
FrontFaces: SurfaceProc ~ {shape.faces[index].fwdFacing ¬ TRUE};
DoWithFacingPolygons[shape, view, FrontFaces, BackFaces];
};
GetPolygonPoints: PUBLIC PROC [shape: Shape, nPoly: INT, points: TripleSequence ¬ NIL]
RETURNS [TripleSequence]
~ {
poly: NatSequence ¬ shape.surfaces[nPoly].vertices;
IF poly = NIL OR shape.vertices = NIL THEN RETURN[NIL];
IF points = NIL OR points.maxLength < poly.length
THEN points ¬ NEW[TripleSequenceRep[poly.length]];
points.length ¬ poly.length;
FOR n: INT IN [0..poly.length) DO points[n] ¬ shape.vertices[poly[n]].point; ENDLOOP;
RETURN[points];
};
NewFaces: PROC [shape: Shape] ~ {
shape.faces ¬ NEW[FaceSequenceRep[shape.surfaces.length]];
FOR n: INT IN [0..shape.faces.length ¬ shape.surfaces.length) DO
poly: NatSequence ¬ shape.surfaces[n].vertices;
color: Triple ¬ [0.0, 0.0, 0.0];
FOR i: INT IN [0..poly.length) DO
color ¬ G3dVector.Add[color, shape.vertices[poly[i]].color];
ENDLOOP;
shape.faces[n] ¬ NEW[FaceRep ¬ [color: G3dVector.Div[color, REAL[poly.length]]]];
ENDLOOP;
};
SetFaceNormals: PUBLIC PROC [shape: Shape, complyWithVertices: BOOL ¬ TRUE] ~ {
IF NOT FaceValid[shape, normal] THEN {
points: TripleSequence ¬ NEW[TripleSequenceRep[100]];
IF shape.faces = NIL THEN NewFaces[shape];
shape.faces.valid[normal] ¬ TRUE;
FOR n: INT IN [0..shape.surfaces.length) DO
points ¬ GetPolygonPoints[shape, n, points];
shape.faces[n].normal ¬ G3dPolygon.PolygonNormal[points,, TRUE];
ENDLOOP;
IF complyWithVertices AND shape.vertices # NIL
really, this should test all vertices and if two disagree, raise an error
THEN FOR n: INT IN [0..shape.surfaces.length) DO
poly: NatSequence ¬ shape.surfaces[n].vertices;
normal: Triple ¬ shape.faces[n].normal;
IF G3dVector.Dot[normal, shape.vertices[poly[0]].normal] < 0.0
THEN shape.faces[n].normal ¬ G3dVector.Negate[normal];
ENDLOOP;
};
};
SetFaceCenters: PUBLIC PROC [shape: Shape] ~ {
IF NOT FaceValid[shape, center] THEN {
points: TripleSequence ¬ NEW[TripleSequenceRep[100]];
IF shape.faces = NIL OR shape.faces.length < shape.surfaces.length THEN NewFaces[shape];
shape.faces.valid[center] ¬ TRUE;
FOR n: INT IN [0..shape.surfaces.length) DO
points ¬ GetPolygonPoints[shape, n, points];
shape.faces[n].center ¬ G3dPolygon.PolygonCenter[points];
ENDLOOP;
};
};
UnitizeFaceNormals: PUBLIC PROC [shape: Shape] ~ {
IF shape # NIL AND shape.faces # NIL THEN
FOR i: INT IN [0..shape.faces.length) DO
shape.faces[i].normal ¬ G3dVector.Unit[shape.faces[i].normal];
ENDLOOP;
};
ReversePolygons: PUBLIC PROC [shape: Shape] ~ {
IF shape = NIL OR shape.surfaces = NIL THEN RETURN;
FOR n: INT IN [0..shape.surfaces.length) DO
shape.surfaces[n].vertices ¬ G3dPolygon.PolygonReverse[shape.surfaces[n].vertices];
ENDLOOP;
NegateFaceNormals[shape]; -- don't want this (defeats the point of reversing polygons, no?)
shape.surfacesReversed ¬ NOT shape.surfacesReversed;
shape.renderValid ¬ FALSE;
};
NegateFaceNormals: PUBLIC PROC [shape: Shape] ~ {
IF shape = NIL OR shape.vertices = NIL OR NOT shape.vertices.valid[normal] THEN RETURN;
FOR n: INT IN [0..shape.faces.length) DO
face: Face ¬ shape.faces[n];
face.normal ¬ G3dVector.Negate[face.normal];
ENDLOOP;
shape.renderValid ¬ FALSE;
};
Triangulate: PUBLIC PROC [shape: Shape] ~ {
old: SurfaceSequence ¬ shape.surfaces;
IF shape = NIL OR shape.surfaces = NIL THEN RETURN;
shape.surfaces ¬ NEW[SurfaceSequenceRep[old.length]];
FOR i: INT IN [0..old.length) DO
poly: NatSequence ¬ old[i].vertices;
n0: INT ¬ poly[0];
n1: INT ¬ poly[1];
FOR ii: INT IN [2..poly.length) DO
n2: INT ¬ poly[ii];
nats: NatSequence ¬ NEW[NatSequenceRep[3]];
nats[0] ¬ n0;
nats[1] ¬ n1;
nats[2] ¬ n2;
nats.length ¬ 3;
shape.surfaces ¬ G3dBasic.AddToSurfaceSequence[shape.surfaces, [NIL, nats]];
n1 ¬ n2;
ENDLOOP;
ENDLOOP;
shape.renderValid ¬ FALSE;
shape.triangulated ¬ TRUE;
};
NumberOfPolygons: PUBLIC PROC [shapes: ShapeSequence] RETURNS [num: INT ¬ 0] ~ {
IF shapes # NIL THEN FOR n: INT IN [0..shapes.length) DO
s: Shape ¬ shapes[n];
IF s # NIL AND s.surfaces # NIL THEN num ¬ num+s.surfaces.length;
ENDLOOP;
};
Edge Procedures
MakeEdges: PUBLIC PROC [shape: Shape] RETURNS [edges: EdgeSequence] ~ {
EdgeInfo:    TYPE ~ RECORD [maxV, poly0, poly1: INT ¬ -1];
ListSequence:  TYPE ~ RECORD [
length:      INT ¬ 0,
element:      SEQUENCE maxLength: INT OF LIST OF EdgeInfo
];
MaxNEdges: PROC RETURNS [max: INT ¬ 0] ~ {
FOR n: INT IN [0..shape.surfaces.length) DO
max ¬ max+shape.surfaces[n].vertices.length;
ENDLOOP;
};
IF shape # NIL AND shape.surfaces # NIL THEN {
maxNEdges: INT ¬ MaxNEdges[];
lists: REF ListSequence ¬ NEW[ListSequence[maxNEdges]];
edges ¬ NEW[G3dShape.EdgeSequenceRep[maxNEdges]];
FOR poly: INT IN [0..shape.surfaces.length) DO
polygon: NatSequence ~ shape.surfaces[poly].vertices;
n0: INT ¬ polygon[polygon.length-1];
FOR i: INT IN [0..polygon.length) DO
minV, maxV: INT;
n1: INT ¬ polygon[i];
IF n0 < n1 THEN {minV ¬ n0; maxV ¬ n1} ELSE {minV ¬ n1; maxV¬ n0};
minV, maxV now are ordered vertex indices describing an edge
create lists such that there is a list for each min (i.e., a lists[min]):
IF minV+1 > lists.length THEN {
IF lists.maxLength <= minV+1 THEN {
new: REF ListSequence ¬ NEW[ListSequence[2*(minV+1)]]; -- fudge it high
FOR n: INT IN [0..lists.length) DO new[n] ¬ lists[n]; ENDLOOP;
lists ¬ new;
};
lists.length ¬ minV+1;
};
FOR l: LIST OF EdgeInfo ¬ lists[minV], l.rest WHILE l # NIL DO
the edge from min to max already existed, so add the second polygon:
IF l.first.maxV = maxV THEN {
l.first.poly1 ¬ poly;
EXIT;
};
REPEAT
either lists[min] was nil or didn't contain max, so add the first polygon:
FINISHED => {
lists[minV] ¬ CONS[[maxV: maxV, poly0: poly], lists[minV]];
};
ENDLOOP;
n0 ¬ n1;
ENDLOOP;
ENDLOOP;
FOR minV: INT IN [0..lists.length) DO
FOR l: LIST OF EdgeInfo ¬ lists[minV], l.rest WHILE l # NIL DO
e: Edge ¬ edges[edges.length] ¬ NEW[EdgeRep];
e­ ¬ [minV, l.first.maxV, l.first.poly0, l.first.poly1];
edges.length ¬ edges.length+1;
ENDLOOP;
ENDLOOP;
};
};
FindEdge: PUBLIC PROC [edges: EdgeSequence, v0, v1: INT]
RETURNS [index: INT ¬ 0]
~ {
IF edges = NIL OR edges.length = 0 THEN RETURN[-1];
IF v0 > v1 THEN {temp: INT ¬ v0; v0 ¬ v1; v1 ¬ temp};
WHILE edges[index].v0 < v0 DO
index ¬ index+1;
IF index = INT[edges.length] THEN RETURN[-1];
ENDLOOP;
DO
IF index = INT[edges.length] OR v0 # edges[index].v0 THEN RETURN[-1];
IF v1 = edges[index].v1 THEN RETURN[index];
index ¬ index+1;
ENDLOOP;
};
Miscellany
ObjectScale: PUBLIC PROC [shape: Shape] RETURNS [REAL ¬ 1.0] ~ {
IF shape # NIL THEN {
i: G3dBasic.Box ¬ shape.objectExtent;
b:REAL¬MAX[ABS[i.min.x],ABS[i.min.y],ABS[i.min.z],ABS[i.max.x],ABS[i.max.y],ABS[i.max.z]];
RETURN[IF b > 0.0001 THEN 1.0/b ELSE 1.0];
};
};
PointsFromShape: PUBLIC PROC [shape: Shape, points: TripleSequence ¬ NIL]
RETURNS [TripleSequence]
~ {
IF points = NIL OR points.maxLength < shape.vertices.length
THEN points ¬ NEW[TripleSequenceRep[shape.vertices.length]];
FOR n: INT IN [0..shape.vertices.length) DO points[n] ¬ shape.vertices[n].point; ENDLOOP;
points.length ¬ shape.vertices.length;
RETURN[points];
};
FindShape: PUBLIC PROC [shapes: ShapeSequence, shapeName: ROPE] RETURNS [s: Shape] ~ {
IF shapes # NIL THEN
FOR n: INT IN [0..shapes.length) DO
IF Rope.Equal[shapes[n].name, shapeName, FALSE] THEN RETURN[shapes[n]];
ENDLOOP;
};
SetTextureCoords: PUBLIC PROC [shape: Shape, textures: PairSequence] ~ {
IF shape # NIL AND textures # NIL THEN
FOR n: INT IN [0..MIN[shape.vertices.length, textures.length]) DO
shape.vertices[n].texture ¬ textures[n];
ENDLOOP;
};
Bounds
BoundingBox: PUBLIC PROC [shape: Shape] RETURNS [mm: Box3d] ~ {
mm ¬ [[huge, huge, huge], [-huge, -huge, -huge]];
FOR i: INT IN [0..shape.vertices.length) DO
p: Triple ~ shape.vertices[i].point;
IF p.x < mm.min.x THEN mm.min.x ¬ p.x;  IF p.x > mm.max.x THEN mm.max.x ¬ p.x;
IF p.y < mm.min.y THEN mm.min.y ¬ p.y; IF p.y > mm.max.y THEN mm.max.y ¬ p.y;
IF p.z < mm.min.z THEN mm.min.z ¬ p.z; IF p.z > mm.max.z THEN mm.max.z ¬ p.z;
ENDLOOP;
};
BoundingSphere: PUBLIC PROC [shape: Shape] RETURNS [s: G3dShape.Sphere] ~ {
box3d: Box3d ¬ BoundingBox[shape];
s.center ¬ G3dVector.Midpoint[box3d.min, box3d.max];
s.radius ¬ 0.0;
FOR n: INT IN [0..shape.vertices.length) DO -- find radius
r: REAL ¬ G3dVector.SquareDistance[shape.vertices[n].point, s.center];
IF r > s.radius THEN s.radius ¬ r;
ENDLOOP;
s.radius ¬ RealFns.SqRt[s.radius];
};
Sequences
CopyShapeSequence: PUBLIC PROC [shapes: ShapeSequence] RETURNS [s: ShapeSequence] ~ {
IF shapes = NIL THEN RETURN;
s ¬ NEW[ShapeSequenceRep[shapes.length]];
s.length ¬ shapes.length;
FOR n: INT IN [0..shapes.length) DO s[n] ¬ shapes[n]; ENDLOOP;
};
AddToShapeSequence: PUBLIC PROC [shapes: ShapeSequence, shape: Shape]
RETURNS [ShapeSequence]
~ {
IF shapes = NIL THEN shapes ¬ NEW[ShapeSequenceRep[1]];
IF shapes.length = shapes.maxLength THEN shapes ¬ LengthenShapeSequence[shapes];
shapes[shapes.length] ¬ shape;
shapes.length ¬ shapes.length+1;
RETURN[shapes];
};
LengthenShapeSequence: PUBLIC PROC [shapes: ShapeSequence, amount: REAL ¬ 1.3]
RETURNS [new: ShapeSequence]
~ {
newLength: INT ¬ MAX[Real.Ceiling[amount*shapes.maxLength], 3];
new ¬ NEW[ShapeSequenceRep[newLength]];
FOR i: INT IN [0..shapes.length) DO new[i] ¬ shapes[i]; ENDLOOP;
new.length ¬ shapes.length;
};
CopyVertexSequence: PUBLIC PROC [vertices: VertexSequence] RETURNS [i: VertexSequence] ~ {
IF vertices = NIL THEN RETURN;
i ¬ NEW[VertexSequenceRep[vertices.length]];
i.length ¬ vertices.length;
FOR n: INT IN [0..vertices.length) DO i[n] ¬ vertices[n]; ENDLOOP;
};
AddToVertexSequence: PUBLIC PROC [vertices: VertexSequence, vertex: Vertex]
RETURNS [new: VertexSequence]
~ {
IF (new ¬ vertices) = NIL THEN new ¬ NEW[VertexSequenceRep[1]];
IF new.length = new.maxLength THEN new ¬ LengthenVertexSequence[new];
new[new.length] ¬ vertex;
new.length ¬ new.length+1;
};
LengthenVertexSequence: PUBLIC PROC [vertices: VertexSequence, amount: REAL ¬ 1.3]
RETURNS [new: VertexSequence]
~ {
newLength: INT ¬ 5+Real.Round[amount*vertices.length];
new ¬ NEW[VertexSequenceRep[newLength]];
new.valid ¬ vertices.valid;
FOR i: INT IN [0..vertices.length) DO new[i] ¬ vertices[i]; ENDLOOP;
new.length ¬ vertices.length;
};
CopyFaceSequence: PUBLIC PROC [faces: FaceSequence] RETURNS [i: FaceSequence] ~ {
IF faces = NIL THEN RETURN;
i ¬ NEW[FaceSequenceRep[faces.length]];
i.length ¬ faces.length;
FOR n: INT IN [0..faces.length) DO i[n] ¬ faces[n]; ENDLOOP;
};
AddToFaceSequence: PUBLIC PROC [faces: FaceSequence, face: Face]
RETURNS [new: FaceSequence]
~ {
IF (new ¬ faces) = NIL THEN new ¬ NEW[FaceSequenceRep[1]];
IF new.length = new.maxLength THEN new ¬ LengthenFaceSequence[new];
new[new.length] ¬ face;
new.length ¬ new.length+1;
};
LengthenFaceSequence: PUBLIC PROC [faces: FaceSequence, amount: REAL ¬ 1.3]
RETURNS [new: FaceSequence]
~ {
newLength: INT ¬ MAX[Real.Round[amount*faces.length], 3];
new ¬ NEW[FaceSequenceRep[newLength]];
new.valid ¬ faces.valid;
FOR i: INT IN [0..faces.length) DO new[i] ¬ faces[i]; ENDLOOP;
new.length ¬ faces.length;
};
END.