External GRAPHICS::MAKE(3); (*$E+ *) procedure Start; { clear screen } var I,J : counter; begin for I := 0 to DotsAcross do for J := 0 to DotsDown do Screen[I,J] := false end; { start } (*$L+ *) procedure Finish; { display output for H-19 terminal } var I,J : counter; begin write(chr(escape),'E'); { clear screen & home cursor } write(chr(escape),'F'); { put terminal into graphics mode } write(chr(escape),'w'); { no wraparound at end of line } J := DotsDown; while J>0 do begin for I := 0 to DotsAcross do if (Screen[I,J] and Screen[I,J-1]) then write('q') else if Screen[I,J-1] then write('l') else if Screen[I,J] then write('o') else write(' '); if J>1 then J:=J-2 { count down by two } else J := 0; if J>0 then writeln { CR/LF unless last line } end; { while } write(chr(escape),'G'); { exit graphics mode } write(chr(escape),'j'); { save cursor position } write(chr(escape),'x','1');{ enable 25th line } write(chr(escape),'Y','8',' ');{ put cursor at start of 25th } with EyePt do write('eye:',X:4:1,Y:4:1,Z:4:1); with CntrInt do write(' cent:',X:4:1,Y:4:1,Z:4:1); readln(CmdChar); { get before continuing } write(chr(escape),'l'); { erase entire line } write(chr(escape),'k'); { restore cursor position } write(chr(escape),'v') { permit wraparound } end; { Finish } (*$L+ *) procedure MoveTo( X,Y : real); begin ScreenX := X; ScreenY := Y; end; { MoveTo } (*$L+ *) procedure DrawTo( X,Y : real); var I : counter; Dx,Dy,Length,StepX,StepY,Xpos,Ypos : real; begin Dx := X - ScreenX; Dy := Y - ScreenY; if abs(Dx) > abs(Dy) then Length := abs(Dx) else Length := abs(Dy); if Length < 1.0 then Length := 1.0; { catch zero length lines } StepX := Dx/Length; StepY := Dy/Length; Xpos := ScreenX; Ypos := ScreenY; for I := 0 to trunc(Length) do begin Screen[round(Xpos),round(Ypos)] := true; Xpos := Xpos + StepX; Ypos := Ypos + StepY; end; { for } ScreenX := X; ScreenY := Y; end; { DrawTo } (*$L+ *) procedure MakePicture; { transform and clip, then display polygons } var I,J,NumClp : counter; TmpPoly : OnePoly; function DotProd( Pt1,Pt2 : Point) : real; begin { vector dot product } DotProd := Pt1.X * Pt2.X + Pt1.Y * Pt2.Y + Pt1.Z * Pt2.Z; end; { DotProd } procedure Ident(var Mtx : Matrix); var I,J : counter; begin { initialize matrix to identity matrix } for I := 1 to 4 do for j := 1 to 4 do if I=J then Mtx[I,J] := 1.0 else Mtx[I,J] := 0.0; end; { Ident } procedure MatrixMult(Mt1,Mt2 : Matrix; var Result : Matrix); var I,J,K : counter; begin { multiply two 4 by 4 matrices } for I := 1 to 4 do for J := 1 to 4 do begin Result[I,J] := 0.0; for K := 1 to 4 do Result[I,J] := Result[I,J] + Mt1[K,J]*Mt2[I,K] end end; (*$L+ *) { This procedure will transform the vertices of a polygon using a four-by-four matrix. } procedure Transform(Pt : Point; Mtx : Matrix; var NewPt : Point ); begin NewPt.X := Pt.X*Mtx[1,1]+Pt.Y*Mtx[1,2]+Pt.Z*Mtx[1,3]+Mtx[1,4]; NewPt.Y := Pt.X*Mtx[2,1]+Pt.Y*Mtx[2,2]+Pt.Z*Mtx[2,3]+Mtx[2,4]; NewPt.Z := Pt.X*Mtx[3,1]+Pt.Y*Mtx[3,2]+Pt.Z*Mtx[3,3]+Mtx[3,4]; end; { Transform } (*$L+ *) { Distance and veiwing angle transforms are determined by this this procedure, which builds a transformation matrix based on the relationship between the coordinates of the eyepoint and those of the center of interest. } procedure GetEyeSpace( EyePt,Cntrint : Point); var Mtx : Matrix; C1,C2 : Point; Hypotenuse,CosA,SinA : real; begin Ident(Eyespace); with EyePt do { load eyepoint translation } begin EyeSpace[1,4] := -X; EyeSpace[2,4] := -Y; EyeSpace[3,4] := -Z end; Transform(Cntrint,EyeSpace,C1); {translate center of interest } Ident(Mtx); {load rotation about Z-axis } with C1 do Hypotenuse := sqrt( X*X + Y*Y); if Hypotenuse > 0.0 then begin CosA := C1.Y / Hypotenuse; SinA := C1.X / Hypotenuse; Mtx[1,1] := CosA; Mtx[2,1] := SinA; Mtx[1,2] := -SinA; Mtx[2,2] := CosA; MatrixMult(EyeSpace,Mtx,EyeSpace) end; Transform(CntrInt,EyeSpace,C2); {rotate center of interest } Ident(Mtx); {load rotation about X-axis } with C2 do Hypotenuse := sqrt(Y*Y + Z*Z); if Hypotenuse > 0.0 then begin CosA := C2.Y / Hypotenuse; SinA := -C2.Z / Hypotenuse; Mtx[2,2] := CosA; Mtx[3,2] := SinA; Mtx[2,3] := -SinA; Mtx[3,3] := CosA; MatrixMult(EyeSpace,Mtx,Eyespace) end; Ident(Mtx); { load switch between Y and Z axes } Mtx[2,2] := 0.0; Mtx[3,3] := 0.0; Mtx[2,3] := 1.0; Mtx[3,2] := 1.0; MatrixMult(EyeSpace,Mtx,EyeSpace) end; { GetEyeSpace } (*$L+ *) Procedure MakeDisplayable(Var Pt : Point); { This procedure achieves a perspective effect by dividing the x and y coordinates of each vertex by the z coordinate. } begin Pt.X := ScreenScale.X * Pt.X / Pt.Z + ScreenCtr.X; Pt.Y := ScreenScale.Y * Pt.Y / Pt.Z + ScreenCtr.Y; end; (* MakeDisplayable *) (*$L+ *) Function FacesEye( Poly : OnePoly ) : boolean; { This function determines whether or not a polygon will be hidden by another part of the same surface in a three- dimensional display. } var TmpPt : Point; TmpPoly : OnePoly; begin with Poly[2] do { make copy of second vertex } begin TmpPt.X:=X; TmpPt.Y:=Y; TmpPt.Z:=Z end; TmpPoly[1].X := Poly[1].X - Poly[2].X; { directed vector } TmpPoly[1].Y := Poly[1].Y - Poly[2].Y; { from 2nd to 1st } TmpPoly[1].Z := Poly[1].Z - Poly[2].Z; { vertex } TmpPoly[2].X := Poly[3].X - Poly[2].X; { directed vector } TmpPoly[2].Y := Poly[3].Y - Poly[2].Y; { from 2nd to 3rd } TmpPoly[2].Z := Poly[3].Z - Poly[2].Z; { vertex } GetPlanes( TmpPoly,2 ); { get plane coefficients } if (DotProd( TmpPt,TmpPoly[1] ) <= 0.0 ) then FacesEye := false else FacesEye := true end; (* FacesEye *) (*$L+ *) Procedure ClipIn(Var Poly : OnePoly; Var NumPts : counter); { Procedure to determine if any vertices of a polygon lie outside previously defined clipping planes; if so the polygon is modified accordingly. } var I,J,LstJ,TmpPts : counter; D1,D2,A : Real; TmpPoly : OnePoly; begin for I := 1 to WindowSize do (* for each window edge *) if NumPts > 0 then begin D1 := DotProd( Poly[NumPts],Window[I] ); LstJ := NumPts; TmpPts := 0; for J:= 1 to NumPts do (* for each polygon edge *) begin if D1 > 0.0 then (* is leading vertex inside? *) begin TmpPts := TmpPts +1; with TmpPoly[TmpPts] do begin (* copy leading vertex *) X:=Poly[LstJ].X; Y:=Poly[LstJ].Y; Z:=Poly[LstJ].Z end end; (* if leading vertex inside *) D2:=DotProd(Poly[J],Window[I] ); if D1 * D2 < 0.0 then (* does edge straddle window? *) begin A := D1 / (D1 - D2); TmpPts := TmpPts + 1; with TmpPoly[TmpPts] do begin X:=A*Poly[J].X + (1.0-A)*Poly[LstJ].X; Y:=A*Poly[J].Y + (1.0-A)*Poly[LstJ].Y; Z:=A*Poly[J].Z + (1.0-A)*Poly[LstJ].Z end end; LstJ := J; D1 := D2 end; (* NumPts loop *) for J:=1 to TmpPts do (* copy polygon back to input *) with TmpPoly[J] do begin Poly[J].X:=X; Poly[J].Y:=Y; Poly[J].Z:=Z end; NumPts := TmpPts end (* WindowSize Loop *) end; (* ClipIn *) (*$L+ *) Procedure InsertSort(Poly : OnePoly ; NumPts : counter); { Based on the average value of their z coordinates, polygons are sorted by their distance from the eyepoint in this binary insertion sort procedure. } var I,J,K : counter; AvDepth : real; begin (* binary insertion sort on average depth *) AvDepth:= 0.0; for I := 1 to NumPts do with Poly[I] do (* store vertices and find averge depth *) begin OutVtces[NumVtxOut + I + 1].X := X; OutVtces[NumVtxOut + I + 1].Y := Y; OutVtces[NumVtxOut + I + 1].Z := Z; AvDepth := AvDepth + Z { sum depths } end; AvDepth := AvDepth / NumPts; { divide for average } OutVtces[NumVtxOut + 1].Z := AvDepth; { store for later } J:=0; (* initialize for insertion search *) I:=(NumDisplay + 1) div 2; K:=NumDisplay; while (J<>I) do (* binary search for insertion point *) if (AvDepth < OutVtces[OutPolys[I].Start ].Z) then begin K:=I; I:=(I+J) div 2 end else begin J:=I; I:=(I+K+1) div 2 end; for J:=NumDisplay downto I+1 do { found it, now insert } begin OutPolys[J+1].Start := OutPolys[J].Start; { move everything above } OutPolys[J+1].NumVtx := OutPolys[J].NumVtx { insertion point up one } end; OutPolys[I+1].Start := NumVtxOut + 1; { store new entry } OutPolys[I+1].NumVtx := NumPts; NumVtxOut := NumVtxOut + NumPts + 1; { vertex count } NumDisPlay := NumDisplay + 1 { polygons stored } end; (* InsertSort *) (*$L+ *) procedure ClipOut(Poly : OnePoly; var NumPts : Vertex; Place : counter); { Once sorted polygons are checked to determine if a polygon closer to the eyepoint hides all or part of one that is farther away. } Var I,LstI,NumDrawn : Counter; Pt1,Pt2 : Point; Drawn : boolean; procedure ClipAfter(Index : counter; Pt1,Pt2 : Point); var I : counter; D1,D2,A : Real; Out : boolean; Pt3 : Point; begin (* recursively check polygons for oaverlap with input edge *) if (Index < Place) then (* is polygon closer than edge? *) with OutPolys[Index] do begin I:=Start + NumVtx; Out:=false; repeat (* for each polygon edge *) D1:=DotProd( Pt1,OutVtces[I]); D2:=DotProd( Pt2,OutVtces[I]); if ( (D1 <= 0.0) and (D2 <= 0.0) ) then begin (* both points visible *) Out := true; ClipAfter(Index+1,Pt1,Pt2) end else if (D1 * D2 < 0.0) then begin (* one point visible *) A:=D1/(D1-D2); Pt3.X:=A*Pt2.X+(1.0-A)*Pt1.X; Pt3.Y:=A*Pt2.Y+(1.0-A)*Pt1.Y; Pt3.Z:=A*Pt2.Z+(1.0-A)*Pt1.Z; if (D1 < 0.0) then begin (* Pt1 visible *) ClipAfter(Index+1,Pt1,Pt3); with Pt3 do begin Pt1.X:=X; Pt1.Y:=Y; Pt1.Z:=Z end end else begin (* Pt2 visible *) ClipAfter(Index+1,Pt3,Pt2); with Pt3 do begin Pt2.X:=X; Pt2.Y:=Y; Pt2.Z:=Z end end end; (* one point visible *) I:=I-1; until (Out or (I=Start)) { all visible of edges exhausted } end else begin (* reached end of list of closer polygons *) MakeDisplayable(Pt1); MakeDisplayable(Pt2); Moveto(Pt1.X,Pt1.Y); Drawto(Pt2.X,Pt2.Y); Drawn := true (* as mark is displayed *) end end; (* Clipafter *) { Clipout procedure body } begin (* clip each poly edge by all closer polys, draw what's left *) NumDrawn := 0; LstI := NumPts; for I:= 1 to NumPts do begin with Poly[LstI] do begin Pt1.X:=X; Pt1.Y:=Y; Pt1.Z:=Z end; with Poly[I] do begin Pt2.X:=X; Pt2.Y:=Y; Pt2.Z:=Z end; Drawn := false; ClipAfter(1,Pt1,Pt2); (* check closer polys, then display *) if Drawn then NumDrawn := NumDrawn + 1; LstI := I end; (* for loop *) if NumDrawn = 0 then NumPts := 0 (* mark as hidden *) end; (* ClipOut *) (*$L+ *) begin (* MakePicture procedure body *) GetEyeSpace(EyePt,CntrInt ); (* get eyespace matrix *) NumDisplay :=0; NumVtxOut := 0; (* set output counters *) for I:=1 to NumPols do with Polygons[I] do begin for J:=1 to NumVtx do (* get polygon *) begin with Points[Vertices[Start+J]] do begin TmpPoly[J].X:=X; TmpPoly[J].Y:=Y; TmpPoly[J].Z:=Z end; Transform(TmpPoly[J],EyeSpace,TmpPoly[J]); (* transform *) end; if FacesEye(TmpPoly) then begin NumClp:=NumVtx; (* protect original data *) ClipIn(TmpPoly,NumClp); (* clip to veiw window *) if NumClp>0 then InsertSort(TmpPoly,NumClp); (* store in sorted order for display *) end end; (* loop for each polygon *) (* display surviving polygons, clipping each be closer polygons *) Start; (* initialize and clear display *) for I:=1 to NumDisplay do with OutPolys[I] do begin for J:=1 to NumVtx do with OutVtces[Start+J] do begin TmpPoly[J].X:=X; TmpPoly[J].Y:=Y; TmpPoly[J].Z:=Z end; ClipOut(TmpPoly,NumVtx,I); (* clip and display *) if NumVtx > 0 then begin GetPlanes(TmpPoly,NumVtx); (* convert to planes *) for J:=1 to NumVtx do (* copy back for later clipping *) with OutVtces[Start+J] do begin X:=TmpPoly[J].X; Y:=TmpPoly[J].Y; Z:=TmpPoly[J].Z end end end; (* for loop (1 to NumDisplay) *) Finish (* finalize picture *) end; (* MakePicture *) . .