cole@utah-cs.UUCP (Allen Cole) (03/05/86)
This is the source to the ICOSAHEDRON ROTATE program already posted in binhex form. (C) Copyright 1986 by the University of Utah Computer Center. Written by John Halleck (NSS 20620) This program was written in TML pascal. -------------------------------------------------------------------------- PROGRAM Univ_of_Utah (INPUT, OUTPUT); { Icosahedron display program } { Copyright 1986 University of Utah Computer Center, } { Written by John B. Halleck (NSS 20620) } {$i MemTypes.ipas } {$i QuickDraw.ipas } {$i Osintf.ipas } {$i ToolIntf.ipas } {$T APPL UoUb} CONST Full_Height = 128; { How big is our screen image? } Half_Height = 64; { Height of half of a screen image } Byte_Height = 16; { Full_Height covered divide 8} PI = 3.141592653; { Pi } Num_VERTICES = 12; { Vertices in an Icosahedron } Num_FACES = 20; { Faces in an Icosahedron } Num_EDGES = 30; { Edges in an Icosahedron } Num_Views = 20; { Rotation in how many steps?} TYPE Transform = Array [1..3, 1..3] of Real; { Transformation matrices } Coordinates = Array [1..3] of Real; { 3 space coordinates. } View = Packed Array [1..Full_Height, 1..Byte_Height] of 0..255; { Storage for the views. } Apoint = Record { Information we keep for each point } DX, DY : Integer; { Display Coordinates. } Where : Coordinates; { Original Coordinates. } NowAt : Coordinates; { Final Coordinates. } End; AnEdge = Record { Information for each edge } Visible: Boolean; { Is the edge visible? } Start, Finish: Integer; { Which vertices does it connect? } End; Aface = Record { Information about each face } BEdges: Array [1..3] of integer; { What bounding edges } BVert: Array [1..3] of integer; { What corner vertices } ONormal: Coordinates; { Original Surface Normal} Normal: Coordinates; { Final Surface Normal } Shows: Boolean; {Is it visible? } End; VAR Index: Integer; { General loop index} { How does the Icosahedron connect together? } Vertices: Array [1..Num_Vertices] of Apoint; Edges: Array [1..Num_Edges] of AnEdge; Faces: Array [1..Num_Faces] of Aface; Light: Coordinates; {Where is the light source?} Patterns: Array [0..64] of Pattern; {Brightness patterns for shading} ImageTransform: Transform; { How to get to our viewing point. } RotationTransform: Transform; { How far we have rotated it. } TotalTransform: Transform; { Composition of the above. } OurBitMaps : Array [1..Num_Views] of Bitmap; { Storage for the frames } SystemGrafPtr: GrafPtr; { Where is TML pascal's window? } SystemBitMap: Bitmap; { Copy of that windows original bitmap } Limits: Rect; { Boundrys of the window, more or less } Fifth : Real; { Fractions of a complete circle } Tenth : Real; Axis_X: Real; { Axis of rotation that we should rotate around. } Axis_Y: Real; Axis_Z: Real; { ******************************************************************** } { Identity rotation matrix } Procedure IdentTransform (Var Atransform:Transform); Var Row, Column: Integer; Begin For Row := 1 to 3 do For Column := 1 to 3 do Atransform[Row,Column] := 0.0; For Row := 1 to 3 do Atransform[Row,Row] := 1.0 End; { ******************************************************************** } { Form rotation matrices } { Rotation matrices for rotation around } { X Y Z } { 1 0 0 C 0 S C S 0 } { 0 C S 0 1 0 -S C 0 } { 0 -S C -S 0 C 0 0 1 } { Where C= COS (Angle) and S= SIN (angle) } { Around 1 means around X, 2 means around Y, and 3 means around Z} Procedure FormRot (Angle: Real; Around: Integer; Var Result: Transform); Var S, C: Real; Left, Right: Integer; { The lower and upper row and column to fill } Begin IdentTransform (Result); S := SIN (Angle); C := COS (Angle); case Around of 1: Begin Left:=2; Right:=3 end; 2: Begin Left:=1; Right:=3 end; 3: Begin Left:=1; Right:=2 end; end; Result [Left, Left] := C; Result [Left, Right] := S; Result [Right,Left] :=-S; Result [Right,Right] := C; End; { ******************************************************************** } { Multiply two transformation matricies together forming a third } Procedure TTransform (First, Second: Transform; Var Result: Transform); Var Row, Column: integer; begin For Row := 1 to 3 do For Column := 1 to 3 do Result [Row, Column] := First[Row,1]*Second[1,Column]+ First[Row,2]*Second[2,Column]+ First[Row,3]*Second[3,Column] end; { ******************************************************************** } { Add the effect of doing a given rotation onto a transformation matrix } Procedure AddRot (Angle: Real; Around: Integer; Var Result: Transform); Var Temp, Final: Transform; Begin FormRot (Angle, Around, Temp); TTransform (Result, Temp, Final); Result := Final End; { ******************************************************************** } { Transform a point by the Total transformation matrix. } Procedure TPoint (What: Coordinates; Var Into:Coordinates); Var Dimension: Integer; begin For Dimension := 1 to 3 do Into[Dimension] := What[1]*TotalTransform[1,Dimension]+ What[2]*TotalTransform[2,Dimension]+ What[3]*TotalTransform[3,Dimension] end; { ******************************************************************** } { Assuming the point given discribes a vector from the origin, produce } { a point that discribes a unit length vector from the origin.} Procedure Normalize (Var ThePoint: Coordinates); var Length: Real; begin Length := SQRT(ThePoint[1]*ThePoint[1] + ThePoint[2]*ThePoint[2] + ThePoint[3]*ThePoint[3]); ThePoint[1] := ThePoint[1] / Length; ThePoint[2] := ThePoint[2] / Length; ThePoint[3] := ThePoint[3] / Length end; { ******************************************************************** } PROCEDURE INITIALIZE; var Edges_So_Far: Integer; PROCEDURE INITPOINTS; { Where are the coordinates of an icosahedron? } { (Icosahedron with unit edges, with center at the origin) } BEGIN With Vertices[ 1] do begin Where[1]:= 0.00000000; Where[3]:= 0.00000000; Where[2]:=-0.95105650 end; With Vertices[ 2] do begin Where[1]:= 0.00000000; Where[3]:= 0.85065080; Where[2]:=-0.42532537 end; With Vertices[ 3] do begin Where[1]:= 0.80901699; Where[3]:= 0.26286555; Where[2]:=-0.42532537 end; With Vertices[ 4] do begin Where[1]:= 0.49999999; Where[3]:=-0.68819096; Where[2]:=-0.42532537 end; With Vertices[ 5] do begin Where[1]:=-0.50000001; Where[3]:=-0.68819094; Where[2]:=-0.42532537 end; With Vertices[ 6] do begin Where[1]:=-0.80901698; Where[3]:= 0.26286557; Where[2]:=-0.42532537 end; With Vertices[ 7] do begin Where[1]:= 0.49999999; Where[3]:= 0.68819095; Where[2]:= 0.42532537 end; With Vertices[ 8] do begin Where[1]:= 0.80901699; Where[3]:=-0.26286556; Where[2]:= 0.42532537 end; With Vertices[ 9] do begin Where[1]:= 0.00000000; Where[3]:=-0.85065080; Where[2]:= 0.42532537 end; With Vertices[10] do begin Where[1]:=-0.80901699; Where[3]:=-0.26286555; Where[2]:= 0.42532537 end; With Vertices[11] do begin Where[1]:=-0.50000001; Where[3]:= 0.68819094; Where[2]:= 0.42532537 end; With Vertices[12] do begin Where[1]:= 0.00000000; Where[3]:= 0.00000000; Where[2]:= 0.95105650 end END; PROCEDURE INITFACES; { How are those vertices connected? } BEGIN With Faces[ 1] do begin Bvert[1]:= 1; Bvert[2]:= 3; Bvert[3]:= 2 end; With Faces[ 2] do begin Bvert[1]:= 1; Bvert[2]:= 4; Bvert[3]:= 3 end; With Faces[ 3] do begin Bvert[1]:= 1; Bvert[2]:= 5; Bvert[3]:= 4 end; With Faces[ 4] do begin Bvert[1]:= 1; Bvert[2]:= 6; Bvert[3]:= 5 end; With Faces[ 5] do begin Bvert[1]:= 1; Bvert[2]:= 2; Bvert[3]:= 6 end; With Faces[ 6] do begin Bvert[1]:= 2; Bvert[2]:= 7; Bvert[3]:=11 end; With Faces[ 7] do begin Bvert[1]:= 2; Bvert[2]:= 3; Bvert[3]:= 7 end; With Faces[ 8] do begin Bvert[1]:= 3; Bvert[2]:= 8; Bvert[3]:= 7 end; With Faces[ 9] do begin Bvert[1]:= 3; Bvert[2]:= 4; Bvert[3]:= 8 end; With Faces[10] do begin Bvert[1]:= 4; Bvert[2]:= 9; Bvert[3]:= 8 end; With Faces[11] do begin Bvert[1]:= 4; Bvert[2]:= 5; Bvert[3]:= 9 end; With Faces[12] do begin Bvert[1]:= 5; Bvert[2]:=10; Bvert[3]:= 9 end; With Faces[13] do begin Bvert[1]:= 5; Bvert[2]:= 6; Bvert[3]:=10 end; With Faces[14] do begin Bvert[1]:= 6; Bvert[2]:=11; Bvert[3]:=10 end; With Faces[15] do begin Bvert[1]:= 6; Bvert[2]:= 2; Bvert[3]:=11 end; With Faces[16] do begin Bvert[1]:= 11; Bvert[2]:= 7; Bvert[3]:=12 end; With Faces[17] do begin Bvert[1]:= 7; Bvert[2]:= 8; Bvert[3]:=12 end; With Faces[18] do begin Bvert[1]:= 8; Bvert[2]:= 9; Bvert[3]:=12 end; With Faces[19] do begin Bvert[1]:= 9; Bvert[2]:=10; Bvert[3]:=12 end; With Faces[20] do begin Bvert[1]:= 10; Bvert[2]:=11; Bvert[3]:=12 end; END; PROCEDURE INITnormals; { A normal vector to a face is a vector perpendicular to the face } { In this case, defined to point outwards. } var ThisFace: Integer; { One could compute the normal from the three edge vertices, and } { in general this is correct. But, since the Icosahedron is } { defined around the origin, the normal is in the direction of } { the average of the directions to the vertices } Procedure FindNormal (Vertex1, Vertex2, Vertex3: Integer; VAR Norm: Coordinates); Var Index: Integer; begin { Find the average of the vertices } For Index := 1 to 3 do Norm[Index]:=(Vertices[Vertex1].Where[Index] +Vertices[Vertex2].Where[Index] +Vertices[Vertex3].Where[Index])/3.0; { Make it a unit normal } Normalize (Norm) end; Begin { For each face, find the surface normal } for ThisFace := 1 to Num_Faces do With Faces[ThisFace] do FindNormal (Bvert[1],Bvert[2],Bvert[3],ONormal) End; PROCEDURE INITEDGES; { Given the face information, derive the edges } var ThisFace: Integer; { IF an edge is not in the table, add it. } Function ADDedge (Vertex1, Vertex2: Integer):Integer; Var First, Second: Integer; ThisEdge: Integer; Found: Boolean; Begin { Put edge in standard order } if Vertex1<Vertex2 then Begin First := Vertex1; Second := Vertex2 end else Begin First := Vertex2; Second := Vertex1 end; { Search the table for it } ThisEdge := 0; Found:= False; Repeat ThisEdge := ThisEdge+1; if ThisEdge<=Edges_so_far then With Edges[ThisEdge] do Found := (First = Start) AND (Second = Finish); until (ThisEdge>=Edges_so_far) OR FOUND; { If we don't have one, add it on. } if Not Found then Begin Edges_So_far := Edges_So_far + 1; ThisEdge := Edges_So_far; With Edges[ThisEdge] do begin Start:=First; Finish:=Second end end; { Return an index to it.} AddEdge := ThisEdge End; BEGIN Edges_So_Far := 0; { For each face, add its edges to the list } For ThisFace := 1 to Num_Faces do With Faces [ThisFace] do Begin Bedges[1] := AddEdge (Bvert[1], Bvert[2]); Bedges[2] := AddEdge (Bvert[2], Bvert[3]); Bedges[3] := AddEdge (Bvert[1], Bvert[3]) End; END; { Come up with some shading patterns. } Procedure InitPat; var Row, Column, Entry, Sample: integer; Loc, Temp, Size: Integer; TwoToThe: Array [0..7] of 0..255; Begin { Initialize a table of powers of 2 } Sample := 1;For Temp := 0 to 7 do Begin TwoToThe [Temp] := Sample; Sample := Sample + Sample End; { Start shading patterns Black } For Entry := 0 to 64 do For Row := 0 to 7 do Patterns[Entry][Row] := 0; { Place dots in as evenly as practical } { The Macintosh has the convention that a bit =1 is black, and a } { a bit = 0 is white. } For Entry := 63 Downto 0 do Begin Loc:= Entry; Row:=0; Column:=0; Size:=8; For Temp := 1 to 3 do Begin Row := Row+Row; Column := Column+Column; case Loc Mod 4 of { Dither matrix recursively applied: } { 0 3 } { 2 1 } 0: ; 1: Begin Row:=Row+1; Column := Column+1 End; 2: Row:=Row+1; 3: Column := Column+1; end; Loc := Loc div 4 end; Sample := TwoToThe [Column]; For Temp := Entry Downto 0 do Patterns[Temp][Row]:=Patterns[Temp][Row]+Sample end end; { Start out with no transformations } Procedure InitTransforms; Begin IdentTransform (TotalTransform); IdentTransform (RotationTransform); IdentTransform (ImageTransform); End; { Get memory for the frames } Procedure InitFrames; Type Kludge = Record Case Boolean of true: (ViewP: ^View); false: (NoneP: QDPtr); end; Var Index: Integer; Hack: Kludge; Begin { Obtain and Initialize frame records } For Index := 1 to Num_Views do With OurBitMaps [Index] do Begin Bounds := Limits; RowBytes := Byte_Height; New (Hack.ViewP); BaseAddr := Hack.NoneP End; end; { What axis should this thing seem to rotate around? } Procedure InitAxis; begin { The direction } Axis_X := -Tenth; Axis_Y := 0.0; Axis_Z := Tenth; { Matrix to get us there } FormRot (Axis_X, 1, ImageTransform); AddRot (Axis_Y, 2, ImageTransform); AddRot (Axis_Z, 3, ImageTransform); end; Procedure InitLight; { Set up the light source } { Shading is going to be Cosine shading. Brightness is proportional to } { the cosine of the angle between Bright vector and the Eye. Bright } { Vector is the direction of the bright spot on the object, which is } { Half way between the Eye and the light. } Var Eye: Coordinates; { Direction to the Eye } Begin { Intended direction of light} Light[1] := 3.0; Light[2] := -1.0; Light[3] := 1.0; Normalize (Light); { Unit directions only. } { Direction of Eye. Forced by physical model, Don't Change this. } Eye [1] := 0.0; Eye [2] := 0.0; Eye [3] := 1.0; Normalize (Eye); { Average of unit vector to the eye and the light } Light[1]:=(Light[1]+Eye[1])/2.0; Light[2]:=(Light[2]+Eye[2])/2.0; Light[3]:=(Light[3]+Eye[3])/2.0; Normalize (Light) { Make it a unit direction} End; BEGIN { Get everything we need } Fifth := (2*PI)/5.0; Tenth := PI/5.0; GetPort (SystemGrafPtr); SystemBitMap := SystemGrafPtr^.PortBits; SetRect (Limits, 0, 0, Full_Height, Full_Height); INITPOINTS; INITFACES; InitNormals; INITEDGES; InitPat; InitTransforms; InitFrames; InitAxis; InitLight END; { ******************************************************************** } { Find the visible faces and edges } Procedure FindVisible; Var ThisFace: Integer; ThisEdge: Integer; begin For ThisEdge := 1 to Num_Edges do With Edges[ThisEdge] do Visible := False; { For each face, if the face is visible, mark it and it's edges visible } For ThisFace := 1 to Num_Faces do With Faces[ThisFace] do Begin { Assuming that we have a CONVEX object, Then the face pointing towards } { us means that it MUST be visible } Shows := Normal [3] >= 0.0; if Shows then begin Edges[Bedges[1]].Visible:=true; Edges[Bedges[2]].Visible:=true; Edges[Bedges[3]].Visible:=true end End end; { ******************************************************************** } { Compute Display Coordinates for each point} { (with the current transformation) } Procedure SetDisplay; Var ThisPoint: Integer; Begin { We assume that the Object is defined centered around the origin. } For ThisPoint := 1 to Num_Vertices do With Vertices[ThisPoint] do Begin DX := ROUND ((NowAt[1] + 1.0) * Half_Height); DY := ROUND ((NowAt[2] + 1.0) * Half_Height) End; End; { ******************************************************************** } { Display the visible edges } Procedure DrawEdges; Var ThisEdge : Integer; Begin SetDisplay; For ThisEdge := 1 to Num_Edges Do With Edges[ThisEdge] do if Visible then BEGIN With Vertices[Start] do MoveTo (DX, DY); With Vertices[Finish] do LineTo (DX, DY) END End; { ******************************************************************** } { Compute the brightnesses of the faces. } Procedure ShadeFaces; Var ThisFace:Integer; Aregion: RgnHandle; Level:Integer; Function Bright (PlaneNorm, LightNorm: Coordinates):Real; begin { Brightness should be proportional to the cosine of the angle } { between the face normal and the Bright spot. The dot } { product of the Normal and the Bright spot vectors would give } { Cosine angle * Length Bright * Length Face Normal, } { But since we have arranged for both lengths to be 1, this } { gives just Cosine Angle which is what we want. } Bright := ((PlaneNorm[1]*LightNorm[1] + PlaneNorm[2]*LightNorm[2] + PlaneNorm[3]*LightNorm[3] ) + 1.0)/2.0 { We scale the value to lie between 0 (Black) and 1 (White) } end; Begin Aregion:=NewRgn; { For each visible face... } For ThisFace := 1 to Num_Faces do With Faces[ThisFace] do if Shows then Begin { Form the region for the face for the MacIntosh primitives } OpenRgn; With Vertices[Bvert[3]] do MoveTo (DX, DY); With Vertices[Bvert[1]] do LineTo (DX, DY); With Vertices[Bvert[2]] do LineTo (DX, DY); With Vertices[Bvert[3]] do Lineto (DX, DY); CloseRgn (Aregion); { Fill with the computed brightness } Level := Round (Bright (Normal, Light) * 64.0); FillRgn (Aregion, Patterns[Level]); SetEmptyRgn(Aregion) end; DisposeRgn(Aregion) End; { ******************************************************************** } { Transform the faces and vertices by the current transformation } Procedure DoTransform; Var ThisFace, ThisPoint: Integer; Begin For ThisFace := 1 to Num_Faces do With Faces[ThisFace] do TPoint (ONormal, Normal); For ThisPoint:= 1 to Num_Vertices do With Vertices[ThisPoint] do Tpoint (Where, NowAt) End; { ******************************************************************** } { Build the current transformation from its parts, apply the transform, } { and compute the visible faces and edges. } Procedure SetupFrame; Begin TTransform (RotationTransform, ImageTransform, TotalTransform); DoTransform; SetDisplay; FindVisible End; { ******************************************************************** } { Draw one frame } Procedure OutFrame; Begin SetupFrame; FillRect (Limits, Patterns[0]); ShadeFaces; DrawEdges end; { ******************************************************************** } { Draw the frames of the Object in each orientation. } Procedure ComputeFrames; Var Index: Integer; This_Angle, Step_Angle: Real; Begin Step_Angle := Fifth / Num_Views; { Assume 5 fold rotational symetry } For Index:=1 to Num_Views do Begin This_Angle := Index * Step_Angle; FormRot (This_Angle, 2, RotationTransform); SetPortBits (OurBitMaps[Index]); OutFrame; CopyBits (OurBitMaps[Index], SystemBitMap, Limits, Limits, srcCopy, SystemGrafPtr^.visRgn); end; SetPortBits (SystemBitMap) end; { ******************************************************************** } { Thumb through the frames, copying each to the screen. Change the } { Aiming point (and thumb direction ) to mimic bouncing } Procedure Thumb; Var Index: Integer; Dest: Rect; Offset_X, Direction_X: Integer; Offset_Y, Direction_Y: Integer; Direction_Rot: Integer; Bounce: Rect; Begin Index := 0; Direction_Rot:= 1; Offset_X:= 0; Direction_X := 1; Offset_Y:= 0; Direction_Y := 1; SetOrigin (0,0); { Use TML pascals window } Bounce := SystemGrafPtr^.PortBits.Bounds; Bounce.Right := Bounce.Right - Full_Height; Bounce.Bottom := Bounce.Bottom - Full_Height; Dest := Limits; While Not Button do Begin { Select frame, Force wrap if off ends of frame list. } Index := Index + Direction_Rot; If Index > Num_Views then Index := 1 else if Index < 1 then Index := Num_Views; { Copy this frame to screen } CopyBits (OurBitMaps[Index], SystemBitMap, Limits, Dest, srcCopy, SystemGrafPtr^.visRgn); { Update X, check for bounce } Offset_X := Offset_X + direction_X; if (Offset_X >Bounce.Right) or (Offset_X <Bounce.Left) Then Begin Direction_X := -Direction_X; Direction_Rot := Direction_X*Direction_Y; end; { Update Y, check for bounce } Offset_Y := Offset_Y + direction_Y; if (Offset_Y >Bounce.Bottom) or (Offset_Y <Bounce.Top) Then Begin Direction_Rot := Direction_X*Direction_Y; Direction_Y := -Direction_Y; end; { Update current location for transfer. } Dest := Limits; OffsetRect (Dest, Offset_X, Offset_Y); End; While Button do { Nothing }; end; { ******************************************************************** } BEGIN ObscureCursor; Writeln (' Icosahedron Version 0.6'); Writeln (' Copyright 1986 By the University of Utah Computer Center'); Writeln (' Written by John Halleck (NSS 20620)'); INITIALIZE; For Index := 64 Downto 0 do FillRect (SystemGrafPtr^.PortBits.Bounds, Patterns[Index]); BackPat (Patterns[0]); SetupFrame; PenPat (Patterns[64]); DrawEdges; PenPat (Patterns[0]); ShadeFaces; DrawEdges; ComputeFrames; Thumb END.