(Ebook - Pdf) Kick Ass Delphi Programming
.pdfend;
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;