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=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.Bottom) or (Offset_Y