G3dModelCmdsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Bloomenthal, August 21, 1992 3:51 pm PDT
DIRECTORY Atom, Commander, CommanderOps, Convert, FS, G3dBasic, G3dShape, G3dTool, G3dVector, IO, PFS, Real, RefTab, Rope;
G3dModelCmdsImpl: CEDAR PROGRAM
IMPORTS CommanderOps, Convert, FS, G3dShape, G3dTool, G3dVector, IO, PFS, RefTab, Rope
~ BEGIN
STREAM:   TYPE ~ IO.STREAM;
ROPE:    TYPE ~ Rope.ROPE;
IntegerPair:  TYPE ~ G3dBasic.IntegerPair;
NatSequence:  TYPE ~ G3dBasic.NatSequence;
Triple:   TYPE ~ G3dBasic.Triple;
Edge:    TYPE ~ G3dShape.Edge;
EdgeSequence: TYPE ~ G3dShape.EdgeSequence;
Shape:   TYPE ~ G3dShape.Shape;
IsItError: ERROR = CODE;
Command: Commander.CommandProc ~ {
ENABLE {
IsItError => {msg ¬ usage; GOTO Failure};
PFS.Error => {msg ¬ error.explanation; GOTO Failure};
Convert.Error => {msg ¬ "Conversion error"; GOTO Failure};
};
IsIt: PROC [rope: ROPE, argsNeeded: INT] RETURNS [b: BOOL] ~ {
IF argIndex >= args.argc THEN RETURN[FALSE];
b ¬ Eq[rope, args[argIndex]];
IF b AND args.argc <= argIndex+argsNeeded THEN ERROR IsItError;
};
Eq: PROC [r1, r2: ROPE] RETURNS [BOOL] ~ {RETURN[Rope.Equal[r1, r2, FALSE]]};
SkipArgs: PROC [nSkip: INT] ~ {argIndex ¬ argIndex+nSkip};
GetReal: PROC [index: INT] RETURNS [r: REAL] ~ {r ¬ Convert.RealFromRope[args[index]]};
GetTriple: PROC [index: INT] RETURNS [t: Triple] ~ {
t.x ¬ GetReal[index+1];
t.y ¬ GetReal[index+2];
t.z ¬ GetReal[index+3];
};
s: Shape;
argIndex: INT ¬ 2; 
format: ATOM ¬ NIL;
in, out, convert: ROPE ¬ NIL;
args: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
rewrite, info, index, center, unitize, triangulate: BOOL ¬ FALSE;
reversePolys, negateNormals, exchangeYZ, addNormals, addTexture: BOOL ¬ FALSE;
scale: REAL ¬ 1.0;
translate: Triple ¬ [0.0, 0.0, 0.0];
IF args.argc < 3 THEN RETURN[$Failure, usage];
IF args.argc > 3 AND Eq[args[2], "¬"]
THEN {out ¬ args[1]; in ¬ args[3]; argIndex ¬ 4}
ELSE in ¬ args[1];
WHILE argIndex < args.argc DO
SELECT TRUE FROM
IsIt["-translate", 3]   => {translate ¬ GetTriple[argIndex+1]; SkipArgs[4]};
IsIt["-scale", 1]    => {scale ¬ GetReal[argIndex+1];   SkipArgs[2]};
IsIt["-index", 0]    => {index ¬ TRUE;       SkipArgs[1]};
IsIt["-noIndex", 0]   => {index ¬ FALSE;      SkipArgs[1]};
IsIt["-reversePolys", 0]  => {reversePolys ¬ TRUE;     SkipArgs[1]};
IsIt["-negateNormals", 0] => {negateNormals ¬ TRUE;    SkipArgs[1]};
IsIt["-triangulate", 0]  => {triangulate ¬ TRUE;     SkipArgs[1]};
IsIt["-addNormals", 0]  => {addNormals ¬ TRUE;     SkipArgs[1]};
IsIt["-addTexture", 0]  => {addTexture ¬ TRUE;     SkipArgs[1]};
IsIt["-center", 0]    => {center ¬ TRUE;       SkipArgs[1]};
IsIt["-unitize", 0]   => {unitize ¬ TRUE;      SkipArgs[1]};
IsIt["-exchangeYZ", 0]  => {exchangeYZ ¬ TRUE;     SkipArgs[1]};
IsIt["-info", 0]    => {info ¬ TRUE;       SkipArgs[1]};
IsIt["-convert", 1]   => {convert ¬ args[argIndex+1];   SkipArgs[2]};
ENDCASE => {
IO.PutF1[cmd.err, "Bad option: %g\n", IO.rope[args[argIndex]]];
SkipArgs[1];
};
ENDLOOP;
rewrite ¬
convert # NIL OR translate # [0, 0, 0] OR scale # 1.0 OR triangulate OR reversePolys OR
negateNormals OR addNormals OR addTexture OR center OR unitize OR exchangeYZ;
IF out = NIL AND rewrite THEN RETURN[$Failure, "specify output file name"];
IF convert # NIL THEN SELECT TRUE FROM
Eq[convert, "quad"] => format ¬ $Quad;
Eq[convert, "dotNone"] => format ¬ $DotNone;
ENDCASE => RETURN[$Failure, "bad convert type"];
IO.PutF1[cmd.out, "Reading %g . . . ", IO.rope[in]];
s ¬ G3dShape.ShapeFromFile[in];
IF reversePolys  THEN G3dShape.ReversePolygons[s];
IF negateNormals THEN G3dShape.NegateVertexNormals[s];
IF triangulate  THEN G3dShape.Triangulate[s];
IF addNormals  THEN G3dShape.SetVertexNormals[s];
IF addTexture  THEN G3dMappedAndSolidTexture.MakeTxtrCoordsFromNormals[s];
IF exchangeYZ THEN
FOR n: NAT IN [0..s.vertices.length) DO
v: G3dShape.Vertex ¬ s.vertices[n];
v.point ¬ [v.point.x, v.point.z, v.point.y];
IF s.vertices.valid[normal] THEN v.normal ¬ [v.normal.x, v.normal.z, v.normal.y];
ENDLOOP;
IF center OR unitize THEN { -- takes precedence over scale or translate command
Translate and/or scale shape to be centered on (0,0,0) and to fit tightly within a 2x2x2 box.
The scaling is uniform (equal in x, y, and z).
mm: G3dBasic.Box ¬ s.objectExtent;
range: REAL ¬ MAX[mm.max.x-mm.min.x, mm.max.y-mm.min.y, mm.max.z-mm.min.z];
IF unitize AND range # 0.0 THEN scale ¬ 2.0/range;
IF center THEN translate ¬ G3dVector.Mul[G3dVector.Midpoint[mm.min, mm.max], scale];
};
IF scale # 1.0 OR translate # [0.0, 0.0, 0.0] THEN {
G3dShape.TransformShape[shape: s, scale: scale, translate: translate];
G3dShape.TransformVertices[s, s.matrix];
};
IF info THEN {IO.PutRope[cmd.out, "\n"]; PrintInfo[s, cmd.out]};
IF rewrite THEN {
stream: STREAM ¬ FS.StreamOpen[out, $create];
IO.PutF1[cmd.out, "writing %g . . . ", IO.rope[out]];
IF format = NIL
THEN G3dShape.ShapeToStream[s, stream, index]
ELSE [] ¬ G3dShape.ShapeToStreamPerFormat[s, stream, format];
IO.Close[stream];
IO.PutRope[cmd.out, "\n"];
};
EXITS Failure => result ¬ $Failure;
};
PrintInfo: PROC [s: Shape, out: STREAM] ~ {
b: G3dBasic.Box ¬ s.objectExtent;
stats: ARRAY [0..100) OF NAT ¬ ALL[0];
nats: NatSequence ¬ NEW[G3dBasic.NatSequenceRep[MAX[s.vertices.length, s.surfaces.length]]];
edges: G3dShape.EdgeSequence ¬ G3dShape.MakeEdges[s];
IO.PutF[out, "\t%g vertices, %g polygons, %g edges\n",
IO.int[s.vertices.length], IO.int[s.surfaces.length], IO.int[edges.length]];
IO.PutF1[out, "\ttype = %g, ",
IF s.type # NIL THEN IO.atom[s.type] ELSE IO.rope["<unknown>"]];
IO.PutF[out, "%gtriangulated, %g backfaces\n",
IO.rope[IF s.triangulated THEN NIL ELSE "not "],
IO.rope[IF s.showBackfaces THEN "show" ELSE "hidden"]];
FOR l: Atom.PropList ¬ s.props, l.rest WHILE l # NIL DO
IO.PutF1[out, "\tproperty [%g]\n", IO.atom[NARROW[l.first.key]]];
ENDLOOP;
FOR n: NAT IN [0..nats.length ¬ s.vertices.length) DO nats[n] ¬ 0; ENDLOOP;
FOR n: NAT IN [0..s.surfaces.length) DO
poly: NatSequence ¬ s.surfaces[n].vertices;
FOR nn: NAT IN [0..poly.length) DO nats[poly[nn]] ¬ nats[poly[nn]]+1; ENDLOOP;
ENDLOOP;
FOR n: NAT IN [0..nats.length) DO stats[nats[n]] ¬ stats[nats[n]]+1; ENDLOOP;
FOR n: NAT IN [0..100) DO
IF stats[n] # 0 THEN
IO.PutF[out, "\t%g vertices with %g polygons\n", IO.int[stats[n]], IO.int[n]];
ENDLOOP;
FOR n: NAT IN [0..100) DO stats[n] ¬ 0; ENDLOOP;
FOR n: NAT IN [0..s.surfaces.length) DO
stats[s.surfaces[n].vertices.length] ¬ stats[s.surfaces[n].vertices.length]+1;
ENDLOOP;
FOR n: NAT IN [0..100) DO
IF stats[n] # 0 THEN
IO.PutF[out, "\t%g polygons with %g vertices\n", IO.int[stats[n]], IO.int[n]];
ENDLOOP;
IO.PutFL[out, "\tbounds: min: (%g, %g, %g), max: (%g, %g, %g)\n", LIST[IO.real[b.min.x], IO.real[b.min.y], IO.real[b.min.z], IO.real[b.max.x], IO.real[b.max.y], IO.real[b.max.z]]];
IO.PutF[out, "\tcenter: (%g, %g, %g)\n", IO.real[0.5*(b.min.x+b.max.x)], IO.real[0.5*(b.min.y+b.max.y)], IO.real[0.5*(b.min.z+b.max.z)]];
IO.PutFL[out, "\t2G = 2+E-F-V = 2+%g-%g-%g = %g\n", LIST[
IO.int[edges.length], IO.int[s.surfaces.length], IO.int[s.vertices.length],
IO.int[2+edges.length-INTEGER[s.vertices.length+s.surfaces.length]]]];
PrintDanglers[edges, out];
PrintMultiples[s, out];
};
Equal: RefTab.EqualProc ~ {
RETURN[NARROW[key1, REF IntegerPair]^ = NARROW[key2, REF IntegerPair]^];
};
Hash: RefTab.HashProc ~ {
ip: IntegerPair ¬ NARROW[key, REF IntegerPair]^;
RETURN[ip.x+ip.y];
};
PrintMultiples: PROC [s: Shape, out: STREAM] ~ {
GetKey: PROC [a, b: INTEGER] RETURNS [REF] ~ {
RETURN[NEW[IntegerPair ¬ [MIN[a, b], MAX[a, b]]]];
};
StoreEdge: PROC [a, b: INTEGER] ~ {
edgeFreq: INTEGER ¬ GetEdgeFrequency[a, b];
[] ¬ RefTab.Store[hash, GetKey[a, b], NEW[INTEGER ¬ edgeFreq+1]];
};
GetEdgeFrequency: PROC [a, b: INTEGER] RETURNS [freq: INTEGER ¬ 0] ~ {
ref: REF ¬ RefTab.Fetch[hash, GetKey[a, b]].val;
IF ref # NIL THEN freq ¬ NARROW[ref, REF INTEGER]^;
};
Enumerate: RefTab.EachPairAction ~ {
f: INTEGER ¬ NARROW[val, REF INTEGER]^;
IF f > 2 THEN {
i: IntegerPair ¬ NARROW[key, REF IntegerPair]^;
IO.PutF[out, "\terror: edge [%g, %g] frequency = %g\n", IO.int[i.x], IO.int[i.y], IO.int[f]];
};
};
hash: RefTab.Ref ¬ RefTab.Create[equal: Equal, hash: Hash];
FOR i: INT IN [0..s.surfaces.length) DO
poly: G3dBasic.NatSequence ¬ s.surfaces[i].vertices;
v: INTEGER ¬ poly[poly.length-1];
FOR j: INT IN [0..poly.length) DO
StoreEdge[v, poly[j]];
v ¬ poly[j];
ENDLOOP;
ENDLOOP;
[] ¬ RefTab.Pairs[hash, Enumerate];
};
PrintDanglers: PROC [edges: EdgeSequence, out: STREAM] ~ {
AddToEdges: PROC [e: Edge, edges: EdgeSequence] RETURNS [ret: EdgeSequence] ~ {
IF (ret ¬ edges) = NIL THEN ret ¬ NEW[G3dShape.EdgeSequenceRep[1]];
IF ret.length = ret.maxLength THEN {
old: EdgeSequence ¬ ret;
ret ¬ NEW[G3dShape.EdgeSequenceRep[2*old.maxLength]];
FOR i: INTEGER IN [0..ret.length ¬ old.length) DO ret[i] ¬ old[i]; ENDLOOP;
};
ret[ret.length] ¬ e;
ret.length ¬ ret.length+1;
};
fences, danglers: EdgeSequence ¬ NIL;
FOR i: INTEGER IN [0..edges.length) DO
e: G3dShape.Edge ¬ edges[i];
SELECT TRUE FROM
e.p0 = -1 AND e.p1 = -1 => danglers ¬ AddToEdges[e, danglers];
e.p0 = -1 OR e.p1 = -1 => fences ¬ AddToEdges[e, fences];
ENDCASE;
ENDLOOP;
IF danglers # NIL THEN {
IO.PutRope[out, "edges with no polygons (error):\n"];
FOR i: NAT IN [0..danglers.length) DO
IO.PutF[out, "\t\t%g to %g\n", IO.int[danglers[i].v0], IO.int[danglers[i].v1]];
ENDLOOP;
};
WHILE fences # NIL DO
RemoveEdge: PROC [i: INTEGER] ~ {
FOR j: INTEGER IN [i..fences.length-1) DO fences[j] ¬ fences[j+1]; ENDLOOP;
IF (fences.length ¬ fences.length-1) = 0 THEN fences ¬ NIL;
};
GetNextEdge: PROC [v: INTEGER] RETURNS [Edge ¬ NIL] ~ {
IF fences # NIL THEN
FOR i: INTEGER IN [0..fences.length) DO
e: Edge ¬ fences[i];
IF e.v0 # v AND e.v1 # v THEN LOOP;
RemoveEdge[i];
RETURN[e];
ENDLOOP;
};
AddToTail: PROC [e: Edge, edges: LIST OF Edge] RETURNS [LIST OF Edge] ~ {
IF edges = NIL THEN RETURN[LIST[e]];
FOR l: LIST OF Edge ¬ edges, l.rest WHILE l # NIL DO
IF l.rest = NIL THEN {l.rest ¬ LIST[e]; EXIT};
ENDLOOP;
RETURN[edges];
};
AddToHead: PROC [e: Edge, edges: LIST OF Edge] RETURNS [LIST OF Edge] ~ {
RETURN[CONS[e, edges]];
};
Track: PROC [v: INTEGER, mode: {forward, backward}] ~ {
DO
next: Edge ¬ GetNextEdge[v];
IF next = NIL THEN EXIT;
list ¬ IF mode = forward THEN AddToTail[next, list] ELSE AddToHead[next, list];
v ¬ IF next.v0 = v THEN next.v1 ELSE next.v0;
ENDLOOP;
};
v, v0, v1: INTEGER;
e: Edge ¬ GetNextEdge[fences[0].v0];
list, tail: LIST OF Edge ¬ LIST[e];
Track[e.v1, forward];
Track[e.v0, backward];
FOR tail ¬ list, tail.rest WHILE tail.rest # NIL DO ENDLOOP;
IO.PutF1[out, "\t%g:\n", IO.rope[SELECT TRUE FROM
list.rest = NIL => "dangling edge",
list.first.v0 = tail.first.v0 OR list.first.v0 = tail.first.v1 OR
list.first.v1 = tail.first.v0 OR list.first.v1 = tail.first.v1 =>
"cycle of edges, each with only one polygon (warning)",
ENDCASE => "chain of edges, each with only one polygon (warning)"]];
v ¬ list.first.v0;
FOR l: LIST OF Edge ¬ list, l.rest WHILE l # NIL DO
e: Edge ¬ l.first;
IF e.v0 = v THEN {v0 ¬ e.v0; v ¬ v1 ¬ e.v1} ELSE {v0 ¬ e.v1; v ¬ v1 ¬ e.v0};
IO.PutF[out, "\t\t%g to %g (poly %g)\n",
IO.int[v0], IO.int[v1], IO.int[IF e.p0 = -1 THEN e.p1 ELSE e.p0]];
ENDLOOP;
ENDLOOP;
};
usage: ROPE ¬
"ModelOps [<output name> ¬] <input name> [-option]
Options include:
 convert <type> output converted shape, types include:
       quad: MinneView format (for the Iris)
       none: U. of Calgary format (for GraphicsJungle)
 info    print number vertices, polygons, polygon stats, etc.
 index    output shape with indexing
noIndex   output shape with no indexing
 reversePolys  reverse the polygon vertices
 negateNormals negate the vertex normals
 triangulate  triangulate each polygon)
 addNormals  compute/recompute vertex normals
 center    translate to center to [0, 0, 0]
 unitize   scale to [-1..1] in x,y,z
 scale <amount> scale by <amount>
 translate <xyz> translate by <x>, <y>, <z>
 exchangeYZ  exchange the Y and Z coordinates";
G3dTool.Register["ModelOps", Command, usage];
END.