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

(Ebook - Pdf) Kick Ass Delphi Programming

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

end;

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

Interpolate.P);

end;

3:DrawVertical(Canvas, Back.T, Front.T, Back.P, Front.P);

{Both above }

end;

Back := Front; end;

end;

begin

Step(C, Work, Work.V.AB ); Step(B, Work, Work.V.CA );

end;

function InnerProduct({const} A, B: TTriple): LongInt; begin

InnerProduct := IMUL(A.X, B.X) + IMUL(A.Y, B.Y) + IMUL(A.Z, B.Z) ; { Damn but this >should< be UnScaled ... }

end;

function Delta(A, B: TTriple): TTriple; begin

Result := Triple(A.X - B.X, A.Y - B.Y, A.Z - B.Z); end;

function LandColor(const A, B, C: TTriple): TColor; var

Center, ToA, ToLight: TTriple; Cos, Angle: double; GrayLevel: integer;

begin

Center := Triple( (A.X + B.X + C.X) div 3, (A.Y + B.Y + C.Y) div 3, (A.Z + B.Z + C.Z) div 3 );

ToA := Delta(A, Center);

ToLight := Delta(Center, LightSource);

{$ifopt R-} {$define ResetR} {$endif} {$R+}

try

Cos := InnerProduct(ToA, ToLight) / (Sqrt({Abs(}InnerProduct(ToA, ToA){)}) * Sqrt({Abs(}InnerProduct(ToLight, ToLight){)}) );

try

Angle := ArcTan (Sqrt (1 - Sqr (Cos)) / Cos); except

on Exception do Angle := Pi / 2; {ArcCos(0)} end;

{$ifdef HighContrast}

GrayLevel := 255 - Round(255 * (Abs(Angle) / (Pi / 2))); {$else}

GrayLevel := 235 - Round(180 * (Abs(Angle) / (Pi / 2))); {$endif}

except

on {any} Exception do GrayLevel := 255; {division by 0 ...}

end;

{$ifdef ResetR} {$R-} {$undef ResetR} {$endif}

Result := PaletteRGB(GrayLevel, GrayLevel, GrayLevel); end;

procedure Draw3Vertices( Canvas: TCanvas;

const A, B, C: TVertex; Display: boolean);

var

Color: TColor;

pA, pB, pC, pD, pE: TPixel;

tA, tB, tC, tD, tE: TTriple; aBelow, bBelow, cBelow: boolean;

begin

tA := GetTriple(A); tB := GetTriple(B); tC := GetTriple(C); {$ifdef FloatingTriangles}

ta.z := ta.z + random(Envelope shr Plys) - random(Envelope shr Plys); tb.z := tb.z + random(Envelope shr Plys) - random(Envelope shr Plys); tc.z := tc.z + random(Envelope shr Plys) - random(Envelope shr Plys); {$endif}

aBelow := tA.Z <= SeaLevel; bBelow := tB.Z <= SeaLevel; cBelow := tC.Z <= SeaLevel;

case ord(aBelow) + ord(bBelow) + ord(cBelow) of

0:if Display then {All above}

begin

pA := Project(tA); pB := Project(tB); pC := Project(tC); if DrawMode = dmRender

then begin

Color := LandColor(tA, tB, tC); DrawPixels( Canvas,

pA, pB, pC, pC, 3, Surface(Color, Color));

end

else DrawPixels( Canvas,

pA, pB, pC, pC, 3, Land);

end;

3:if Display then {All below}

begin

tA.Z := SeaLevel; tB.Z := SeaLevel; tC.Z := SeaLevel; pA := Project(tA); pB := Project(tB); pC := Project(tC); DrawPixels( Canvas, pA, pB, pC, pC, 3, Water);

end;

2:begin {One vertex above water}

{ First ensure it's tA } if aBelow then

if bBelow

then SwapTriples(tA, tC) else SwapTriples(tA, tB);

CalcCrossing(tB, tA, tD, True);

CalcCrossing(tC, tA, tE, True);

pA := Project(tA); pB := Project(tB); pC := Project(tC); pD := Project(tD); pE := Project(tE);

DrawPixels( Canvas, pD, pB, pC, pE, 4, Water); if Drawmode = dmRender

then begin

Color := LandColor(tD, tA, tE); DrawPixels( Canvas, pD, pA, pE, pE, 3,

Surface(Color, Color));

end

else DrawPixels( Canvas, pD, pA, pE, pE, 3, Land); end;

1:begin {One vertex below water} { First ensure it's tA }

if bBelow

then SwapTriples(tA, tB)

else if cBelow then SwapTriples(tA, tC); CalcCrossing(tA, tB, tD, False); CalcCrossing(tA, tC, tE, True);

pA := Project(tA); pB := Project(tB); pC := Project(tC); pD := Project(tD); pE := Project(tE);

DrawPixels( Canvas, pD, pA, pE, pE, 3, Water); if DrawMode = dmRender

then begin

Color := LandColor(tD, tB, tC); DrawPixels( Canvas,

pD, pB, pC, pE, 4, Surface(Color, Color));

end

else DrawPixels( Canvas, pD, pB, pC, pE, 4, Land); end;

end;

end;

procedure DrawTriangle(

Canvas:

TCanvas;

 

const A, B, C:

TVertex;

 

Plys:

word;

 

PointDn:

boolean);

var

 

 

AB, BC, CA: TVertex;

 

 

begin

if Plys = 1

then Draw3Vertices(Canvas, A, B, C, (DrawMode <> dmOutline) OR PointDn)

else begin

AB := Midpoint(A, B);

BC := Midpoint(B, C);

CA := Midpoint(C, A);

if Plys = 3 then FractalLandscape.DrewSomeTriangles(16); Dec(Plys);

if PointDn then begin

DrawTriangle(Canvas, CA, BC, C, Plys, True);

DrawTriangle(Canvas, AB, B, BC, Plys, True);

DrawTriangle(Canvas, BC, CA, AB, Plys, False);

DrawTriangle(Canvas, A, AB, CA, Plys, True); end

else begin

DrawTriangle(Canvas, A, CA, AB, Plys, False);

DrawTriangle(Canvas, BC, CA, AB, Plys, True);

DrawTriangle(Canvas, CA, C, BC, Plys, False);

DrawTriangle(Canvas, AB, BC, B, Plys, False); end;

end;

end;

begin ScreenColors;

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.

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

Kick Ass Delphi Programming

Go!

Keyword

(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!

-----------

There are three display modes: Outline, Filled, and Rendered. All three display the landscape as a collection of triangles by doing a single-point perspective transform of the triangle’s three TTriples to screen TPixels, then doing either a PolyLine or Polygon draw. The only difference between the Outline mode and Filled and Rendered modes is that where Outline mode draws a simple “wire mesh” picture of the landscape with no hidden line removal, Filled and Rendered mode rely on drawing order and polygon filling to do a brute force hidden line removal. (This is the so-called “Painter’s Algorithm.”) Rendered mode in turn differs from Filled mode in basing each triangle’s color on the angle it presents to a ray from the “sun.”

Draw3Vertices() implements a simplistic concept of “sea level” to lend a bit of verisimilitude to the display. Any triangle that’s completely above sea level is drawn normally, while any triangle that’s completely below sea level, is drawn in blue, at sea level. If the triangle crosses sea level, FL3 interpolates the crossing points, and draws part of the triangle above water and part below. While this is plausible enough for “coastlines,” it’s a bit less plausible for lakes: FL3 doesn’t detect the lowest lip of a basin, but colors as water only the part(s) of a basin that are under sea level.

Once all the triangles are drawn, FL3 draws vertical lines along the two front edges from sea level to any vertices which are above sea level. This is particularly effective in the two filled modes, where the filled vertical quadrilaterals obscure the undersides of the landscape surface.

The Project() Routine

The Project() routine is the workhorse on which all drawing operations depend. It converts a 3D TTriple to a 2D TPixel using single-point perspective and the current window dimensions.

In effect, it draws a line through the point and a vanishing point, and marks the

intersection of that line and the screen. Most of the complexity in Listing 6.3 comes from the use of fixed-point math. (This is primarily a legacy of FL3’s origins in the days when floating-point math was very slow, but it does also serve to reduce the vertex database’s size to the point where it fits in the data segment.) If we strip away the fixed point math, we end up with this:

{ 3D transform a point: }

function Project(const P: TTriple): TPixel; var

Ratio: double;

Pt, V: TFloatTriple; begin

Pt := FloatTriple(P);

V := FloatTriple(VanishingPoint);

Ratio := Pt.Y / V.Y;

Result.X := Round( DisplayWidth *

((V.X - Pt.X) * Ratio + Pt.X)); Result.Y := DisplayHeight -

Round( DisplayHeight *

((V.Z - Pt.Z) * Ratio + Pt.Z));

end;

That is, it calculates the ratio of the point’s depth to the vanishing point’s depth, and applies this to the difference between the point’s x and z coordinates and the vanishing point’s x and z coordinates. Since the TTriple coordinate system ranges from 0 to 1, we can simply multiply the projected coordinates by the window size to obtain the proper screen coordinates.

Outline Mode

Outline mode is the simplest of all the drawing modes. It simply outlines ‘land’ triangles in green and water triangles in blue. (See Figure 6.1; alas, we don’t have color here.) About all that’s worth really noting here is how simple and clear Delphi makes DrawPixels(). The API version of DrawPixels (for either C or Borland Pascal) would replace the single, simple statement Canvas.PolyLine( [A, B, C, A] ) with a local array declaration and four assignments—not to mention all the bother creating and destroying Windows device contexts (DCs) and pens.

Filled Mode

Outline mode is relatively fast and does a pretty good job of suggesting texture, but it has one big problem: You can see right through it. This means that the mesh on the backside of a hill, say, shows through to the front side.

Sophisticated graphics applications have complicated algorithms for “hidden line removal,” but FL3 is not sophisticated, and relies on brute force techniques to remove hidden lines by drawing over them. (See Figure 6.2.)

That is, DrawTriangle() takes care to draw the rearmost triangles first, so that any triangles in front will be drawn later, obscuring the triangles in back. On

the initial call to DrawTriangle(), the triangle is “point down.” Vertex A is closest to the front and the bottom of the window, and B and C are in back, towards the top of the window. (See Figure 6.8.) Thus

FIGURE 6.8 Drawing Order and Triangle Orientation.

DrawTriangle(Canvas, CA, BC, C, Plys, True); DrawTriangle(Canvas, AB, B, BC, Plys, True); DrawTriangle(Canvas, BC, CA, AB, Plys, False); DrawTriangle(Canvas, A, AB, CA, Plys, True);

draws the leftmost subtriangle first, then the rightmost. Both of these “outside” subtriangles face the same way as triangle ABC, and the order of the parameters to the recursive call to DrawTriangle() ensures that the new A will also be in front, with the new B and C in back.

The third call draws the “inside” subtriangle, as this is visually in front of the second, rightmost triangle. In every triangle, the inside subtriangle is upside down to its outer triangle, so on this initial call, the inside triangle is “point up.” The parameter ordering ensures that on point-up calls, A is still the “point” facing up, with B and C in front, towards the bottom of the screen. If you step through DrawTriangle()’s not PointDn set of recursive calls, you’ll see that point-up triangles are drawn back-to-front, right-to-left.

The fourth call draws the last, frontmost subtriangle.

Rendered Mode

Rendered mode attempts to look more realistic than filled mode by coloring each triangle based on the angle between the triangle and a ray from the “sun,” so that triangles orthogonal to the rays from the sun are lighter than triangles that present a more oblique angle to the sun’s rays. (See Figure 6.3.) While it does seem to do a good job of showing, say, the curvature of a basin, overall it’s a bit...gray. You may wish to experiment with a palette and some sort of LandColor() function that use a little false color.

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.

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

Kick Ass Delphi Programming

Go!

Keyword

(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!

-----------

Create Your Own Worlds

In summary, FL3 is a simple demo of fractal landscape generation techniques. You may wish to try to improve the random number distribution or the rendering code, or to modify the algorithm so as to generate entire fractal planets.

It may not be obvious that “plasma” routines are basically just a rectangular version of a fractal landscape generation algorithm: They fracture rectangles instead of triangles, and use color to show the 3rd dimension instead of perspective.

The full source code suite, which includes all Delphi files necessary to build and run Fractal Landscapes 3., is present on the CD-ROM in the directory for this chapter.

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.

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

Kick Ass Delphi Programming

Go!

Keyword

(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!

-----------

CHAPTER 7

Problems with Persistents, and Other Advice

JON SHEMITZ and ED JORDAN

Component Read And Write Methods

Pentium RDTSC Instruction

Converting Comp Values To Strings

Changing A Drag And Drop Cursor

Delphi apps That Set Up Themselves

Using “Inherited” With Redeclared Properties

Disabling Individual Radio Buttons

Sometimes you’ll discover that Delphi sets a component property’s value with its read method and not its write method. Creating these methods carelessly means big trouble! Jon and Ed share their thoughts on avoiding hassles like this and making the most of what Delphi offers.

Delphi properties have a simple, powerful interface: They look like variables, but you have full

control over how (and whether) your class’s users can read and write your class’s properties. You can allow direct read access, just as if the property were a variable, or you can specify a read method that will be called whenever the property’s value is read. You can allow direct write access, or you can specify a write method that will be called whenever the property’s value is set.

Right?

Wrong.

Reading to Write?

What actually happens is this: While the simple model described above does apply in most cases, things get weird when the property is a TPersistent descendent like a TBitmap or a TFont. For TPersistent descendents, the write method is called when you set the property at design time or change it at runtime—but not when a component is created and loaded from its form’s .DFM

stream. Instead, the runtime library calls the property’s read method to get a pointer to the property’s private field variable, then uses that pointer to call the property’s read-from-stream method. The write method is not called when the component is loaded!

Of course, in most cases this doesn’t matter—the property gets loaded, and it has the same value at runtime as it had at design time. However, there are a couple of ways this can affect you.

First, the read method should never return Nil. While it may seem sensible to use a strategy of not actually creating a private field variable until the write method supplies an actual value to copy, Delphi’s component loading code is not quite smart enough to notice that it has no TPersistent to tell to load. If your read method returns Nil, you will get GPF’s when the component is loaded. (For what it’s worth, this behavior is what alerted me to the problem, though I’ll confess that it took me a while to find out what was going on.)

Second, don’t rely on your write method to extract information from the field variable and save it in runtime-only fields elsewhere in your component. The write method will be called when you set the property directly at design time or at runtime, but not when you indirectly set the property at component load time. If you expect your write method to update your component’s internal state, your component will not load properly.

Reasonable Workarounds

Now, I think there’s a very good chance that Borland will conclude that read-to-write is a bug, and will change this behavior in future releases of Delphi. That is, you should avoid any solutions to the problems of GPF-on-load or partial-loading that will fail if the write method does get called at component load time.

It’s easy to prevent GPF-on-load in a “forward-compatible” way. We now know that if a TPersistent property is stored, Delphi will call its read method when the object is loaded from a stream. So, as shown in Listing 7.1, the object’s Create constructor must create an object of the appropriate type, and set the property’s private field variable. This is a bit wasteful if the property is not always set or stored, but a few hundred bytes of storage or a few hundred instructions of Create code are just plain insignificant to a sixteen or thirty-two megabyte Pentium.

Listing 7.1 PERSIST.SRC

{interface}

type DemoComponent = class(TComponent)

private

fGlyph: TBitmap; fGlyphWritten: boolean;

procedure SetGlyph(Glyph: TBitmap); {not shown}

protected

constructor Create(Owner: TComponent); override; procedure Loaded; override;

public

published

property Glyph: TBitmap read fGlyph write SetGlyph;

end;