<<>> <> <> <> 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 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 <> <> 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[""]]; 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 < "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 [ ¬] [-option] Options include: convert 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 scale by translate translate by , , exchangeYZ exchange the Y and Z coordinates"; G3dTool.Register["ModelOps", Command, usage]; END.