Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

(Ebook - Pdf) Kick Ass Delphi Programming

.pdf
Скачиваний:
284
Добавлен:
17.08.2013
Размер:
5.02 Mб
Скачать

var

DB: array[0..8384] of TTriple; { Triangular array: Vertices(MEL+1) entries }

NumberOfVertices, TopRow: word;

Envelopes: array[1..MaxPlys] of word;

function Vertices(N: word): word;

{ Vertices in an equilateral triangle with edgelength = N-1 } begin

Vertices := (Sqr(N) + N) shr 1; end;

function Midpoint(A, B: TVertex): TVertex; begin

Result := Vertex( (A.AB + B.AB) shr 1, { Average } (A.BC + B.BC) shr 1,

(A.CA + B.CA) shr 1 );

end;

function Loc(const V: TVertex): word; begin

Loc := NumberOfVertices - Vertices(TopRow - V.AB) + V.BC;

{^^^^^^^^^^^^^^^^^^ This is actually NOT necessary and just

wastes cycles, but I have retained it for compatability with FL2 .FL files. }

end;

 

procedure SetTriple(var V: TVertex; var T: TTriple);

{ DB[V] := T }

begin

 

DB[Loc(V)] := T;

 

end;

 

function GetTriple(const V: TVertex): TTriple; { DB[V] } begin

Result := DB[Loc(V)]; end;

procedure SwapTriples(var A, B: TTriple); var

Tmp: TTriple; begin

Tmp := A; A := B; B := Tmp; end;

procedure SwapZ(var A, B: TTriple); var

C: TCoordinate; begin

C := A.Z; A.Z := B.Z; B.Z := C; end;

const

Uninitialized = -30000; procedure ResetDB;

var

T: TTriple; R, Theta: double;

I, Offset: integer; tA, tB, tC: TTriple;

const

Base_Rotation = - Pi / 2.1; {Rotate point counterclockwise a bit} RotateBy = Pi * 2 / 3; {120|}

begin

{Set Plys dependent stuff } EdgeLength := 1 shl (Plys - 1);

TopRow := EdgeLength + 1; { A "fencepost" situation } NumberOfVertices := Vertices(TopRow);

for I := Plys downto 1 do

Envelopes[I] := Envelope shr Succ(Plys - I);

{Then reset NumberOfVertices vertices in DB }

T.X := Uninitialized;

T.Y := Uninitialized;

T.Z := Uninitialized;

for I := Low(DB) to High(DB) do DB[I] := T;

{ Now, set "defining" (outside) points A, B, and C }

A.AB := 0;

A.BC :=

EdgeLength;

A.CA := 0;

B.AB := 0;

B.BC :=

0;

B.CA := EdgeLength;

C.AB := EdgeLength;

C.BC :=

0;

C.CA := 0;

{ Then, assign them triples }

 

Offset := UnitLength div 2;

 

 

R

:= UnitLength / 2;

 

 

Theta := Base_Rotation;

tA := Triple( Round(R * Cos(Theta)) + Offset,

Round(R * Sin(Theta)) + Offset,

SeaLevel + Rand(Envelope) );

Theta := Theta + RotateBy;

tB := Triple( Round(R * Cos(Theta)) + Offset,

Round(R * Sin(Theta)) + Offset,

SeaLevel + Rand(Envelope) );

Theta := Theta + RotateBy;

tC := Triple( Round(R * Cos(Theta)) + Offset,

Round(R * Sin(Theta)) + Offset,

SeaLevel + Rand(Envelope) );

{ At least one point above sealevel }

if (tA.Z < SeaLevel) AND (tB.Z < SeaLevel) AND (tC.Z < SeaLevel) then repeat

tB.Z := SeaLevel + Rand(Envelope); until tB.Z > SeaLevel;

{ Force A the lowest ... }

if tA.Z > tB.Z then SwapZ(tA, tB); if tA.Z > tC.Z then SwapZ(tA, tC);

SetTriple(A, tA); SetTriple(B, tB); SetTriple(C, tC);

end;

function SaveLandscape(const FileName: TFileName): boolean; var

Handle: integer; begin

Result := False; try

Handle := FileCreate(FileName); try

Result := (FileWrite(Handle, Plys, SizeOf(Plys)) = SizeOf(Plys)) and

(FileWrite(Handle, DB, NumberOfVertices * SizeOf(TTriple))

= NumberOfVertices * SizeOf(TTriple));

finally FileClose(Handle);

end; except

on {any} Exception do Result := False; end;

end;

function LoadLandscape(const FileName: TFileName): boolean; var

Handle: integer; begin

Result := False; try

Handle := SysUtils.FileOpen(FileName, fmOpenRead); try

if FileRead(Handle, Plys, SizeOf(Plys)) = SizeOf(Plys) then begin

ResetDB;

LoadLandscape := FileRead( Handle, DB,

NumberOfVertices * SizeOf(TTriple)) = NumberOfVertices * SizeOf(TTriple);

end; finally

FileClose(Handle);

end; except

on {any} Exception do Result := False; end;

end;

{ Action }

procedure FractureLine( var vM: TVertex; const vA, vB: TVertex; Envelope: integer );

var

A, B, M: TTriple; begin

vM := Midpoint(vA, vB); M := GetTriple(vM);

if M.X = Uninitialized then { Not set yet } begin

A := GetTriple(vA); B := GetTriple(vB); M := Triple( A.X + (B.X - A.X) div 2,

A.Y + (B.Y - A.Y) div 2,

A.Z + (B.Z - A.Z) div 2 + Rand(Envelope) ); { Mean height _ Random(Envelope) }

SetTriple(vM, M); end;

end;

procedure FractureTriangle(const A, B, C: TVertex; Plys: word);

var

Envelope: word;

AB, BC, CA: TVertex; begin

if Plys > 1 then begin

Envelope := Envelopes[Plys]; FractureLine(AB, A, B, Envelope); FractureLine(BC, B, C, Envelope); FractureLine(CA, C, A, Envelope); Dec(Plys);

FractureTriangle(CA, BC, C, Plys); FractureTriangle(AB, B, BC, Plys); FractureTriangle(BC, CA, AB, Plys); FractureTriangle(A, AB, CA, Plys); end;

end;

end.

Products | Contact Us | About Us | Privacy | Ad Info | Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.

All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.

Go!

Keyword

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

Bending

Another subtlety that I didn’t discover until I actually wrote the code is that you shouldn’t apply the same amount of randomness when you bend the larger scale lines as when you bend the smaller scale lines. If you do, you either end up with a bumpy plane or a spiky landscape. You need to apply more randomness to the large outer triangles, which produce the overall shape of the landscape, and to apply less randomness to the smaller inner triangles, which basically control the smoothness of your landscape.

What I ended up using is a function that generates something vaguely like a normal distribution:

function Rand(Envelope: integer): integer; { Pseudonormal (sawtooth) distribution,

in range ±Envelope } begin

Rand := integer(Random(Envelope)) + integer(Random(Envelope)) - Envelope;

end;

Here, the Envelope value for each ply is half that of the next larger ply. This certainly produces plausible-looking landscapes, but real landscapes aren’t always as smooth as FL3’s. Real landscapes do have the occasional sharp edge—cliffs, mesas, canyons, and so on—while FL3 never really produces anything more abrupt than a steep slope.

One approach you may want to experiment with is to replace Rand’s pseudonormal distribution within a constricting envelope with an exponential function. On smaller scales, the function would be more likely to produce a number close to 0 than on larger scales, but it might throw out a large number on any scale.

Draw, Then Display

In the first incarnation of this program, the same recursive routine that built the landscape was responsible for drawing it. If the Plys argument was greater than 1, it broke its input triangle into four new triangles, and then decremented Plys and applied itself to each new triangle. When the Plys argument was equal to 1, it called a routine that drew the triangle.

This was certainly simple enough, but it meant that changing from a “wire mesh” rendering to a filled-triangle rendering required generating a whole new landscape. Similarly, using this simple design in a Windows version would mean that changing the window size also generates a whole new landscape. Clearly, a better approach is to generate the landscape first and then draw it. This requires two parallel recursions from the outermost triangle to the innermost ones (which are the only ones which are actually drawn), but the second recursion doesn’t cost much compared to actually drawing the rectangles, so the price of flexibility is fairly low.

Generating and Displaying the Landscape

After all that prolog, the actual generation code may seem refreshingly simple. FractureTriangle() (present in Listing 6.2) takes a triangle and the number of Plys remaining. If Plys is greater than 1, FractureTriangle() calls FractureLine() to create (or retrieve) a midpoint value, then calls itself on each of the four triangles that these midpoints define. FractureLine() calls Midpoint() (both in Listing 6.2) to calculate the vertex between its two input vertices, and then checks to see if it has been set yet. If the midpoint is still uninitialized, FractureLine() bends the line between the endpoints by raising or lowering its midpoint.

Once the landscape has been generated, FL3 uses the code in Listing 6.3 to display it in the current window, in the current display mode. If the user changes the window size or the display mode, FL3 redraws the landscape.

Listing 6.3 DISPLAY.PAS

unit Display; {Fractal Landscapes 3.0 -- Copyright _ 1987..1996, Jon Shemitz}

interface

uses WinTypes, WinProcs, SysUtils, Graphics, Forms, Global, Database;

const

DrawingNow: boolean = False; AbortDraw: boolean = False;

type

EAbortedDrawing = class (Exception) end;

procedure ScreenColors;

procedure PrinterColors;

procedure DrawTriangle(

Canvas:

TCanvas;

 

const A, B, C:

TVertex;

 

Plys:

word;

 

PointDn:

boolean);

procedure DrawVerticals(Canvas: TCanvas);

{$ifdef Debug}

const DebugString: string = ''; {$endif}

implementation

uses Main;

type

Surfaces = record

Outline, Fill: TColor; end;

const

scrnLand: Surfaces = (Outline: clLime; Fill: clGreen); scrnWater: Surfaces = (Outline: clBlue; Fill: clNavy); scrnVertical: Surfaces = (Outline: clGray; Fill: clSilver);

prnLand: Surfaces = (Outline: clBlack; Fill: clWhite); prnWater: Surfaces = (Outline: clBlack; Fill: clWhite); prnVertical: Surfaces = (Outline: clBlack; Fill: clWhite);

var

Land, Water, Vertical: Surfaces; procedure ScreenColors;

begin

 

Land

:= scrnLand;

Water

:= scrnWater;

Vertical := scrnVertical;

end;

 

procedure PrinterColors;

begin

 

Land

:= prnLand;

Water

:= prnWater;

Vertical := prnVertical;

end;

 

function Surface(Outline,

Fill: TColor): Surfaces;

begin

 

 

Result.Outline := Outline;

Result.Fill

:= Fill;

 

end;

 

 

{ $define Pascal} {$define Float} {$ifdef Pascal}

{$ifdef Float} type

TFloatTriple = record X, Y, Z: double; end;

function FloatTriple(T: TTriple): TFloatTriple; begin

Result.X := T.X / UnitLength;

Result.Y := T.Y / UnitLength;

Result.Z := T.Z / UnitLength; end;

function Project(const P: TTriple): TPixel; { 3D transform a point } var

Delta_Y: double;

Tr, V: TFloatTriple; begin

Tr := FloatTriple(P);

V := FloatTriple(VanishingPoint);

Delta_Y := Tr.Y / V.Y;

Result.X := Round( DisplayWidth *

((V.X - Tr.X) * Delta_Y + Tr.X)); Result.Y := DisplayHeight -

Round( DisplayHeight *

((V.Z - Tr.Z) * Delta_Y + Tr.Z));

end; {$else}

function Project(const Tr: TTriple): TPixel; { 3D transform a point } var

Delta_Y: integer; begin

Delta_Y := MulDiv(Tr.Y, UnitLength, VanishingPoint.Y); Result.X := MulDiv( MulDiv( VanishingPoint.X - Tr.X,

Delta_Y, UnitLength) + Tr.X,

DisplayWidth, UnitLength);

Result.Y := DisplayHeight -

MulDiv( MulDiv( VanishingPoint.Z - Tr.Z, Delta_Y, UnitLength) + Tr.Z, DisplayHeight, UnitLength );

end; {$endif}

{$else}

function Project(const Tr: TTriple): TPixel; assembler; {3D transform a point}

asm

 

 

 

 

{$ifdef Ver80} {Delphi 1.0; 16-bit}

 

 

les

di,[Tr]

 

 

 

mov

si,word ptr UnitLength

{ Scaling factor }

 

mov

ax,[TTriple ptr es:di].Y{ Tr.Y }

 

imul

si

 

{ Scale by LoWord(UnitLength) }

idiv

VanishingPoint.Y

{ Scaled(depth/vanishing.depth) }

{DeltaY

equ

bx }

 

 

mov

bx,ax

 

{ preserve Delta.Y }

 

mov

ax,VanishingPoint.Z

 

 

sub

ax,[TTriple ptr es:di].Z{ Delta.Z }

 

imul

bx

 

{ Delta.Z * Delta.Y }

 

idiv

si

 

{ Unscale(Delta.Z * Delta.Y) }

 

add

ax,[TTriple ptr es:di].Z{ Tr.Z + Unscale(Delta.Z * Delta.Y)

}

 

 

 

 

mov

cx,[DisplayHeight]

{ We'll use it twice here ... }

imul

cx

{ (Tr.Z+Delta.Z*Delta.Y)*Screen.Row }

idiv

si

 

{ Unscale }

 

sub

cx,ax

 

{ Px.Y }

 

mov

ax,VanishingPoint.X

 

 

sub

ax,[TTriple ptr es:di].X{ Delta.X }

 

imul

bx

 

{ Delta.X * Delta.Y }

 

idiv

si

 

{ Unscale(Delta.X * Delta.Y) }

 

add

ax,[TTriple ptr es:di].X{ Tr.X + Unscale(Delta.X * Delta.Y)

}

 

 

 

 

imul

[DisplayWidth]

{ (Tr.X+Delta.X*Delta.Y)*Screen.Col

}

 

 

 

 

idiv

si

 

{ Px.X := Unscale(above) }

 

mov

dx,cx

 

{Return (X,Y) in ax:dx}

 

{$else} {Delphi

2.0 or better; 32-bit}

 

 

push

ebx

 

{ Delphi 2.0 requires that we }

push

esi

 

{ preserve these registers

}

push

edi

 

 

 

mov

edi,eax

 

{ lea edi,[Tr]}

 

push

edx

 

{ Save @ Result }

 

mov

si,word ptr UnitLength

{ Scaling factor }

 

mov

ax,TTriple[edi].Y

{ Tr.Y }

 

imul

si

 

{ Scale by LoWord(UnitLength) }

idiv

VanishingPoint.Y

{ Scaled(depth/vanishing.depth) }

{DeltaY equ bx

}

 

 

 

mov

bx,ax

 

{ preserve Delta.Y }

 

mov

ax,VanishingPoint.Z

 

 

sub

ax,TTriple[edi].Z

{ Delta.Z }

 

imul

bx

 

{ Delta.Z * Delta.Y }

 

idiv

si

 

{ Unscale(Delta.Z * Delta.Y) }

 

add

ax,TTriple[edi].Z

{ Tr.Z + Unscale(Delta.Z *

 

 

Delta.Y) }

mov

cx,[DisplayHeight]

{ We'll use it twice here ... }

imul

cx

{

 

 

(Tr.Z+Delta.Z*Delta.Y)*Screen.Row }

idiv

si

{ Unscale }

sub

cx,ax

{ Px.Y }

mov

ax,VanishingPoint.X

 

sub

ax,TTriple[edi].X

{ Delta.X }

imul

bx

{ Delta.X * Delta.Y }

idiv

si

{ Unscale(Delta.X * Delta.Y) }

add

ax,TTriple[edi].X

{ Tr.X + Unscale(Delta.X *

 

 

Delta.Y) }

imul

[DisplayWidth]

{

 

 

(Tr.X+Delta.X*Delta.Y)*Screen.Col }

idiv

si

{ Px.X := Unscale(above) }

// Now ax=x, cx=y; we want to make them longints and save them to Result

mov

ebx,$0000FFFF

 

and

eax,ebx

{clear the high word}

and

ecx,ebx

 

pop

edx

{ restore @ Result }

mov

TPixel[edx].X,eax

 

mov

TPixel[edx].Y,ecx

 

pop

edi

 

pop

esi

 

pop

ebx

 

{$endif}

 

 

end;

 

 

{$endif}

 

 

procedure DrawPixels(const Canvas:

TCanvas;

 

const A, B, C, D:

TPixel;

 

const N:

word;

 

const Surface:

Surfaces);

begin

if AbortDraw then raise EAbortedDrawing.Create('');

Canvas.Pen.Color := Surface.Outline; if DrawMode = dmOutline

then if N = 3

then Canvas.PolyLine( [A, B, C, A] ) else Canvas.PolyLine( [A, B, C, D, A] )

else begin

Canvas.Brush.Color := Surface.Fill; if N = 3

then Canvas.Polygon( [A, B, C] ) else Canvas.Polygon( [A, B, C, D] )

end;

end;

procedure CalcCrossing(var Low, High, Crossing: TTriple; SetLow: boolean);

var

CrossOverRatio: LongInt; begin

CrossOverRatio := (SeaLevel - Low.Z) * UnitLength div (High.Z - Low.Z);

{ Distance

of crossing point from A, as ratio of total line AB length, }

{ times

UnitLength

}

Crossing := Triple( Low.X + Unscale((High.X - Low.X) * CrossOverRatio),

Low.Y + Unscale((High.Y - Low.Y) * CrossOverRatio),

SeaLevel ); if SetLow then Low.Z := SeaLevel;

end;

procedure DrawVertical(Canvas: TCanvas; const A, B: TTriple; var pA, pB: TPixel);

var

pC, pD: TPixel;

tC, tD: TTriple;

begin

 

tC

:= A;

tC.Z

:= SeaLevel;

pC

:= Project(tC);

tD

:= B;

tD.Z

:= SeaLevel;

pD

:= Project(tD);

DrawPixels(Canvas, pA, pB, pD, pC, 4, Vertical); end;

procedure DrawVerticals(Canvas: TCanvas); type

Triad = record

T: TTriple;

V: TVertex;

P: TPixel; end;

var

Work: Triad;

procedure Step( const Start:

TVertex;

var Front:

Triad;

var StepDn:

GridCoordinate

);

 

var

Idx: word;

Back, Interpolate: Triad; begin

Back.V := Start;

Back.T := GetTriple(Back.V);

if Back.T.Z > SeaLevel then Back.P := Project(Back.T); for Idx := 1 to EdgeLength do

begin

Front.V := Back.V; Inc(Work.V.BC); Dec(StepDn);

Front.T := GetTriple(Front.V);

if Front.T.Z > SeaLevel then Front.P := Project(Front.T);

case (ord(Back.T.Z > SeaLevel) shl 1) + ord(Front.T.Z > SeaLevel) of

1:begin { Back below, front above } CalcCrossing(Back.T, Front.T, Interpolate.T, False); Interpolate.P := Project(Interpolate.T);

DrawVertical(Canvas, Interpolate.T, Front.T, Interpolate.P, Front.P);