TweetFollow Us on Twitter

Jul 97 Challenge

Volume Number: 13 (1997)
Issue Number: 7
Column Tag: Programmer's Challenge

Jul 97 - Programmer's Challenge

by Bob Boonstra, Westford, MA

Disambiguator

The Challenge this month is to write a string completion routine loosely patterned after the keyword lookup facility in the QuickView utility. QuickView will suggest a completion of the keyword as you begin to type it, and update that suggested completion as you continue to type. In the Toolbox Assistant, for example, if you are looking for documentation on InitGraf and type "i", the suggested completion is "iconIDToRgn". As you continue by typing "n", the suggestion becomes "index2Color". Adding "i" yields "initAllPacks"; adding "t" leaves the suggestion intact; adding "g" changes it to "initGDevice". Finally, typing "r" gives the desired "initgraf".

For our disambiguator, you will be given an unsorted list of words and an opportunity to preprocess them. Then you will be given a string to match and asked to return a list of words matching findString. To make the problem more interesting, the match string can contain wild card characters, as described below.

The prototype for the code you should write is:

typedef unsigned long ulong;

void InitDisambiguator(
   const char *const wordList[],   /* words to match against */
   ulong numWords,                 /* number of words in wordList */
   void *privStorage,              /* private storage preinitialized to zero */
   ulong storageSize               /* number of bytes of privStorage */
);

ulong /*numMatch*/ Disambiguator(
   const char *const wordList[],   /* words to match against */
   ulong numWords,                 /* number of words in wordList */
   void *privStorage,              /* private storage */
   ulong storageSize,              /* number of bytes of privStorage */
   char *findString,               /* string to match, includes wild cards */
   char *matchList[]               /* return matched words here */
);

Your InitDisambiguator routine will be called with an unsorted list wordList of numWords null-terminated words to match. The wordList words will include alphanumeric characters, spaces, and underscores. You will also be provided with a pointer privStorage to storageSize bytes of preallocated memory initialized to zero. The amount of storage provided will be at least 20 bytes for each word in wordList, plus one byte for each character in the wordList (including the null byte, and rounded up to a multiple of 4). In other words, storageSize will be no smaller than minStorage, calculated as:

for (minStorage=0,i=0; i<numWords; i++)
   minStorage += 20 + 4*(1+strlen(wordList[i])/4);

InitDisambiguator is not allowed to modify the wordList, but you may store a sorted version of wordList, or pointers to the words in sorted order, in privStorage. The first four parameters provided to Disambiguator will be identical as those provided to InitDisambiguator. In addition, you will be provided with the null-terminated findString and a preallocated array matchList with numWords entries where you are to store pointers to the words that match findString. Your string matches should be case insensitive (i.e., "initgr" matches "InitGraf". The matchList should be returned with the strings ordered in case-insensitive ASCII order (i.e., space < [0..9] < [A-Za-z] < underscore).

The findString may also contain zero or more of the wildcard characters '?', '*', and '+'. The wildcard '?' matches any single character, '*' matches zero or more characters, and '+' matches one or more characters. So, for example, "*graf" matches any string ending in the (case-insensitive) string "graf", while "+1Ind+" matches any string containing "1Ind" between the first and last characters of a word.

For each call to InitDisambiguator, your Disambiguator routine will be called an average of 100 to 1000 times. The winner will be the solution that finds the correct matchList in the minimum amount of time, including the time taken by the initialization routine.

This will be a native PowerPC Challenge, using the latest CodeWarrior environment. Solutions may be coded in C, C++, or Pascal. The problem is based on a suggestion by Charles Kefauver, who pointed me to an April, 1995, AppleDirections article discussing the user interface for a disambiguator. Charles wins 2 Challenge points for his suggestion.

Three Months Ago Winner

Congratulations to ACC Murphy (Perth, Australia), for submitting the faster (and smaller) of the two entries I received for the Projection Challenge. This problem required contestants to calculate the image of a set of input polygons, including the shadows cast by one polygon on another, given an observation viewpoint and an illumination point.

Both of the submitted solutions used a ray-tracing technique. The winning solution calculates, for each point on the projection plane, the nearest polygon to the viewpoint among those intersecting the ray from the plane to the viewpoint. It then does another ray-trace to determine if there are any other polygons between the illumination point and the projected polygon, identifying the point as being in shadow if an intervening polygon is found.

I ran three test cases, moving the polygons 10 times for a given viewpoint in each case, using a GWorld bounds rectangle slightly smaller than my 1024x768 monitor. As you can see from the execution times, considerable refinement would be needed before this code could be used for animation.

A good discussion of the projection and hidden surface removal algorithms applicable to this problem can be found in the Black Art of Macintosh Game Programming, by Kevin Tieskoetter. In addition to discussing the z-buffer ray-tracing algorithm, it describes another technique for hidden surface removal called the Painter's algorithm. This approach breaks the polygons to be displayed into pieces such that each piece is entirely in front of or entirely behind any other piece, as seen from the viewpoint. The polygons can then be sorted and displayed without looking at each pixel in the image. For our application, two polygon decompositions would be required, one for the image, and one for the shadows.

The table below lists, for each entry, the execution time for each case and the code size. The number in parentheses after the entrant's name is the total number of Challenge points earned in all Challenges to date prior to this one.

                      Case 1   Case 2   Case 3    Total       Code
Name                   Time     Time     Time   Time (secs)   Size
A.C.C. Murphy (10)    29.02    23.64    81.61     134.27      4196
Ernst Munter (232)    20.87    58.11    89.76     168.74      7192

Top 20 Contestants

Here are the Top Contestants for the Programmer's Challenge. The numbers below include points awarded over the 24 most recent contests, including points earned by this month's entrants.

Rank    Name             Points   Rank     Name             Points
   1.   Munter, Ernst      194       11.   Beith, Gary         24
   2.   Gregg, Xan         114       12.   Cutts, Kevin        21
   3.   Cooper, Greg        54       13.   Nicolle, Ludovic    21
   4.   Larsson, Gustav     47       14.   Picao, Miguel Cruz  21
   5.   Lengyel, Eric       40       15.   Brown, Jorg         20
   6.   Boring, Randy       37       16.   Gundrum, Eric       20
   7.   Mallett, Jeff       37       17.   Higgins, Charles    20
   8.   Lewis, Peter        32       18.   Kasparian, Raffi    20
   9.   Murphy, ACC         30       19.   Slezak, Ken         20
   10.  Antoniewicz, Andy   24       20.   Studer, Thomas      20

There are three ways to earn points: (1) scoring in the top 5 of any Challenge, (2) being the first person to find a bug in a published winning solution or, (3) being the first person to suggest a Challenge that I use. The points you can win are:

1st place   20 points             5th place   2 points
2nd place   10 points           finding bug   2 points
3rd place   7 points   suggesting Challenge   2 points
4th place   4 points

Here is A.C.C. Murphy's winning solution:
Challenge.p
A.C.C. Murphy

unit Challenge;

(*

Assumptions:
   Storage space must be big enough for 13 floats per polygon
   All points must be significantly smaller in magnitude than BIG_FLOAT = 
      1000000.0
   Polygons are translucent (their colour based uplon lighting is independent 
      of the side of the polygon that is lit)
   50% attenuation of colour is used
   50% attenuation of black is black
      
Method:
   InitProjection is not used
   
   First we precalculate a small bounding sphere for the polygon points.
   Next we get the information about the GWorld to allow direct pixel access.
   Then for each point on the GWorld, we trace the ray from the point to the 
      eye, intersecting it with each polygon and finding the one closes to 
      the eye (furthest forward, since the eye is infront of all polygons).  
      That determines the colour.  We then trace the ray from that intersection 
      point to the light source to determine whether the point is in shadow, 
      and if so we halve the intensity. We set the colour of the pixel and 
      move on.
   
   Optimizations:
      Direct pixel access to the GWorld (known to be 32 bit)
      Bounding sphere used to optimize the ray/polygon intersection test.
      Time is approximately 2 microseconds per pixel per polygon on an 8500.
*)

interface

   uses
      Types, Quickdraw, QDOffscreen;
      
   const
      kMAXPOINTS = 10;

   const
      BIG_FLOAT = 1000000.0;
         
   type
      float = real;
      
   type
      My2DPoint = record (* point in z==0 plane*)
         x2D: float; (* x coordinate*)
         y2D: float; (* y coordinate*)
      end;
      My3DPoint = record
         x3D: float;                 (* x coordinate*)
         y3D: float;                 (* y coordinate*)
         z3D: float;                 (* z coordinate*)
      end;
      My3DDirection = record
         thetaX:float;              (* angle in radians*)
         thetaY:float;              (* angle in radians*)
         thetaZ:float;              (* angle in radians*)
      end;
      MyPlane = record
         planeNormal: My3DDirection; (* normal vector to plane*)
         planeOrigin: My3DPoint;     (* origin of plane in 3D space*)
      end;
      MyPolygon = record
         numPoints: longint;      (* number of points in polygon*)
         thePoint: array[0..kMAXPOINTS-1] of My2DPoint;
                                  (* polygon in z==0 plane*)
         polyPlane: MyPlane;      (* rotate/translate z==0 plane to this plane*)
         polyColor: RGBColor;     (* the color to draw this polygon*)
      end;
      MyPolygonArray = array[0..0] of MyPolygon;
      
         
   procedure InitProjection(
      const viewPoint: My3DPoint;(* viewpoint from which to project*)
      const illumPoint:My3DPoint;(* viewpoint from which to draw shadow*)
      storage: univ Ptr;         (* auxiliary storage preallocated for your use*)
      storageSize: longint       (* number of bytes of storage*)
   );

   procedure CalcProjection(
      offScreen: GWorldPtr;          (* GWorld to draw projection *)
      const thePolys: MyPolygonArray;(* polygons to project *)
      numPolys: longint;             (* number of polygons to project *)
      const viewPoint: My3DPoint;    (* viewpoint from which to project *)
      const illumPoint: My3DPoint;
                               (* illumination point from which to draw shadow *)
      storage: univ Ptr;       (* auxiliary storage preallocated for your use*)
      storageSize: longint     (* number of bytes of storage*)
   );

implementation

   type
      Ray3D = record
         origin: My3DPoint;
         direction: My3DPoint;
      end;
      PolygonExtra = record
         normal, rotX, rotY, center: My3DPoint;
         radius2: float;
      end;
      PolygonExtraArray = array[0..0] of PolygonExtra;
      StorageRecord = record
         poly_extra: PolygonExtraArray;
                  { must be at the end, since it's an extensible array }
      end;
      StorageRecordPtr = ^StorageRecord;
      
   function DotProduct(const src1, src2 : My3DPoint) : float;
   begin
      DotProduct := src1.x3D*src2.x3D +  
                    src1.y3D*src2.y3D +  
                    src1.z3D*src2.z3D;
   end;
   
CrossProduct
   procedure CrossProduct(src1, src2 : My3DPoint; 
                    var dst : My3DPoint);
   begin
      dst.x3D := src1.y3D*src2.z3D - src1.z3D*src2.y3D;
      dst.y3D := src1.z3D*src2.x3D - src1.x3D*src2.z3D;
      dst.z3D := src1.x3D*src2.y3D - src1.y3D*src2.x3D;
   end;
   
AddVectors
   procedure AddVectors(const src1, src2 : My3DPoint; 
                     var dst : My3DPoint);
   begin
      dst.x3D := src1.x3D + src2.x3D;
      dst.y3D := src1.y3D + src2.y3D;
      dst.z3D := src1.z3D + src2.z3D;
   end;
      
SubtractVectors
   procedure SubtractVectors(const src1, src2 : My3DPoint; 
                      var dst : My3DPoint);
   begin
      dst.x3D := src1.x3D - src2.x3D;
      dst.y3D := src1.y3D - src2.y3D;
      dst.z3D := src1.z3D - src2.z3D;
   end;
   
MidPoint
   procedure MidPoint( const src1, src2 : My3DPoint; 
                      var dst : My3DPoint);
   begin
      dst.x3D := (src1.x3D + src2.x3D) / 2;
      dst.y3D := (src1.y3D + src2.y3D) / 2;
      dst.z3D := (src1.z3D + src2.z3D) / 2;
   end;
   
Distance2
   function Distance2( const src1, src2 : My3DPoint) : float;
   begin
      Distance2 := sqr(src1.x3D - src2.x3D) + 
                      sqr(src1.y3D - src2.y3D) + 
                      sqr(src1.z3D - src2.z3D);
   end;
   
ScaleVector
   procedure ScaleVector(const src : My3DPoint; scale : float; 
                      var dst : My3DPoint);
   begin
      dst.x3D := src.x3D * scale;
      dst.y3D := src.y3D * scale;
      dst.z3D := src.z3D * scale;
   end;
      
NormalizeVector
   procedure NormalizeVector(const src : My3DPoint;
                      var dst : My3DPoint);
      var
         length : float;
   begin
      length := sqrt(DotProduct(src,src));   
      dst.x3D := src.x3D / length;
      dst.y3D := src.y3D / length;
      dst.z3D := src.z3D / length;
   end;
   
MakeViewRay
   procedure MakeViewRay(const eye : My3DPoint;
                      x, y, z: float; var ray : Ray3D);
   begin
      ray.origin.x3D := x;
      ray.origin.y3D := y;
      ray.origin.z3D := z;
      ray.direction.x3D := eye.x3D - x;
      ray.direction.y3D := eye.y3D - y;
      ray.direction.z3D := eye.z3D - z;
      NormalizeVector(ray.direction, ray.direction);
   end;
   
RotateX
   procedure RotateX(src : My3DPoint; sinA, cosA : float; 
                      var dst : My3DPoint);
   begin
      dst.x3D := src.x3D;
      dst.y3D := cosA*src.y3D - sinA*src.z3D;
      dst.z3D := sinA*src.y3D + cosA*src.z3D;
   end;
   
RotateY
   procedure RotateY( src : My3DPoint; sinA, cosA : float; 
                      var dst : My3DPoint);
   begin
      dst.x3D := cosA*src.x3D + sinA*src.z3D;
      dst.y3D := src.y3D;
      dst.z3D := -sinA*src.x3D + cosA*src.z3D;
   end;
   
RotateZ
   procedure RotateZ( src : My3DPoint; sinA, cosA : float; 
                      var dst : My3DPoint);
   begin
      dst.x3D := cosA*src.x3D - sinA*src.y3D;
      dst.y3D := sinA*src.x3D + cosA*src.y3D;
      dst.z3D := src.z3D;
   end;
   
PointInPlaneInPolygon
   function PointInPlaneInPolygon( const pt: My2DPoint; const 
               poly: MyPolygon ): boolean;
      function Quadrant( const pt: My2DPoint; x, y: float ): 
                      longint;
      begin
         if pt.x2D > x then begin
            if pt.y2D > y then begin
               Quadrant := 0;
            end else begin
               Quadrant := 3;
            end;
         end else begin
            if pt.y2D > y then begin
               Quadrant := 1;
            end else begin
               Quadrant := 2;
            end;
         end;
      end;
      
      function x_intercept( const pt1, pt2: My2DPoint;
                      yy: float ): 
                      float;
      begin
         x_intercept := pt2.x2D - 
                     ( (pt2.y2D - yy) * 
                        ((pt1.x2D - pt2.x2D)/(pt1.y2D - pt2.y2D)) );
      end;
      
      var
         i, angle, quad, next_quad, delta: longint;
         last_vertex, next_vertex: My2DPoint;
   begin
      angle := 0;
      last_vertex := poly.thePoint[poly.numPoints-1];
      quad := Quadrant( last_vertex, pt.x2D, pt.y2D );
      for i := 1 to poly.numPoints do begin
         next_vertex := poly.thePoint[i-1];
         next_quad := Quadrant( next_vertex, pt.x2D, pt.y2D );
         delta := next_quad - quad;
         case delta of
            3: delta := -1;
            -3: delta := 1;
            2, -2: begin
               if x_intercept( last_vertex, next_vertex, pt.y2D ) > 
                           pt.x2D then begin
                  delta := -delta;
               end;
            end;
            otherwise begin
            end;
         end;
         angle := angle + delta;
         quad := next_quad;
         last_vertex := next_vertex;
      end;
      PointInPlaneInPolygon := (angle = 4) | (angle = -4);
   end;
   
Intersect
   function Intersect(const ray: Ray3D; const poly: MyPolygon; 
         const poly_extra: PolygonExtra; var distance : float; 
         var intersectionPt: My3DPoint) : boolean;
      var
         tempVector : My3DPoint;
         temp1, temp2 : float;
         intersectionPoint : My3DPoint;
         intersection2D : My2DPoint;
         Ib, Ic, Id: float;
   begin
      Intersect := false;

      { intersect ray with sphere }
      SubtractVectors( ray.origin, poly_extra.center,
                            tempVector );
      Ib := 2 * DotProduct( ray.direction, tempVector );
      Ic := DotProduct( tempVector, tempVector ) - 
                            poly_extra.radius2;
      Id := sqr(Ib) - 4.0*Ic;
      if Id >= 0 then begin { yes, ray intersects sphere }
         temp1 := DotProduct( poly.polyPlane.planeOrigin, 
                            poly_extra.normal ) - 
                     DotProduct( poly_extra.normal, ray.origin );
         temp2 := DotProduct(ray.direction, poly_extra.normal);
         if temp2 <> 0 then begin
            distance := temp1 / temp2;
            if distance > 0 then begin
               ScaleVector(ray.direction, distance, intersectionPoint);
               AddVectors(intersectionPoint, ray.origin, 
                           intersectionPoint);
               
               if Distance2(intersectionPoint, poly_extra.center)
                  <= 
                                          poly_extra.radius2 then begin 
                  { intersection point is whithin sphere.  
                     Find out if it is actually in the polygon }
                  intersectionPt := intersectionPoint;
                  { First translate back to the origin }
                  SubtractVectors(intersectionPoint, 
                     poly.polyPlane.planeOrigin,intersectionPoint);
                  intersection2D.x2D := DotProduct(
                     intersectionPoint,
                        poly_extra.rotX );
                  intersection2D.y2D := DotProduct( 
                     intersectionPoint, 
                        poly_extra.rotY );
                  { Then check if it is whithin the polygon }
                  Intersect := PointInPlaneInPolygon
                                                      (intersection2D,poly);
               end;
            end;
         end;
      end;
   end;

InitProjection
   procedure InitProjection(
      const viewPoint: My3DPoint;(* viewpoint from which to project *)
      const illumPoint:My3DPoint;
                                 (* viewpoint from which to draw shadow *)
      storage: univ Ptr;         (* auxiliary storage preallocated for your use *)
      storageSize: longint       (* number of bytes of storage *)
   );
   begin
{$unused( viewPoint, illumPoint, storage, storageSize )}
   end;

PreparsePolygons
   procedure PreparsePolygons( my_storage: StorageRecordPtr;
   numPolys: longint; const thePolys: MyPolygonArray );
      var
         i, j: longint;
         pt: My3DPoint;
         pts: array[1..kMAXPOINTS] of My3DPoint;
         min_x, min_y, min_z, max_x, max_y, max_z: My3DPoint;
         dist_x, dist_y, dist_z, new_radius2: float;
         radius, new_radius, old_to_new: float;
         sinX, cosX, sinY, cosY, sinZ, cosZ: float;
   begin
      for i := 0 to numPolys-1 do begin
         with my_storage^.poly_extra[i], thePolys[i],
         polyPlane.planeNormal do begin
            sinX := sin(thetaX);
            cosX := cos(thetaX);
            sinY := sin(thetaY);
            cosY := cos(thetaY);
            sinZ := sin(thetaZ);
            cosZ := cos(thetaZ);
            normal.x3d := sinY*cosX;
            normal.y3d := -sinX;
            normal.z3d := cosY*cosX;
            rotX.x3D := 1;
            rotX.y3D := 0;
            rotX.z3D := 0;
            RotateZ(rotX, sinZ, cosZ, rotX);
            RotateX(rotX, sinX, cosX, rotX);
            RotateY(rotX, sinY, cosY, rotX);
            rotY.x3D := 0;
            rotY.y3D := 1;
            rotY.z3D := 0;
            RotateZ(rotY, sinZ, cosZ, rotY);
            RotateX(rotY, sinX, cosX, rotY);
            RotateY(rotY, sinY, cosY, rotY);
            
            for j := 1 to numPoints do begin
               pt.x3D := thePoint[j-1].x2D;
               pt.y3D := thePoint[j-1].y2D;
               pt.z3D := 0;
               RotateZ(pt, sinZ, cosZ, pt);
               RotateX(pt, sinX, cosX, pt);
               RotateY(pt, sinY, cosY, pt);
               pts[j] := pt;
               if j = 1 then begin
                  min_x := pt; min_y := pt; min_z := pt;
                  max_x := pt; max_y := pt; max_z := pt;
               end else begin
                  if pt.x3D < min_x.x3D then begin
                     min_x := pt;
                  end;
                  if pt.y3D < min_y.y3D then begin
                     min_y := pt;
                  end;
                  if pt.z3D < min_z.z3D then begin
                     min_z := pt;
                  end;
                  if pt.x3D > max_x.x3D then begin
                     max_x := pt;
                  end;
                  if pt.y3D > max_y.y3D then begin
                     max_y := pt;
                  end;
                  if pt.z3D > max_z.z3D then begin
                     max_z := pt;
                  end;
               end;
            end;
            
            dist_x := Distance2( min_x, max_x );
            dist_y := Distance2( min_y, max_y );
            dist_z := Distance2( min_z, max_z );
            if dist_x > dist_y then begin
               if dist_x > dist_z then begin
                  radius2 := dist_x/4;
                  MidPoint( min_x, max_x, center );
               end else begin
                  radius2 := dist_z/4;
                  MidPoint( min_z, max_z, center );
               end;
            end else begin
               if dist_y > dist_z then begin
                  radius2 := dist_y/4;
                  MidPoint( min_y, max_y, center );
               end else begin
                  radius2 := dist_z/4;
                  MidPoint( min_z, max_z, center );
               end;
            end;
            
            for j := 1 to numPoints do begin
               new_radius2 := Distance2( center, pts[j] );
               if new_radius2 > radius2 then begin
                  radius := sqrt(radius2);
                  new_radius := sqrt(new_radius2);
                  radius2 := (radius + new_radius)/2;
                  old_to_new := radius2 - radius;
                  center.x3D := (radius2*center.x3D + 
                              old_to_new*pts[j].x3D)/radius;
                  center.y3D := (radius2*center.y3D + 
                              old_to_new*pts[j].y3D)/radius;
                  center.z3D := (radius2*center.z3D + 
                              old_to_new*pts[j].z3D)/radius;
                  radius2 := sqr(radius2);
               end;
            end;
         
            AddVectors( polyPlane.planeOrigin, center, center );

         end;
      end;
   end;
   
CalcProjection
   procedure CalcProjection(
      offScreen: GWorldPtr;          (* GWorld to draw projection *)
      const thePolys: MyPolygonArray;(* polygons to project *)
      numPolys: longint;             (* number of polygons to project *)
      const viewPoint: My3DPoint;    (* viewpoint from which to project *)
      const illumPoint: My3DPoint;   (* illumination point from which to draw shadow *)
      storage: univ Ptr;         (* auxiliary storage preallocated for your use *)
      storageSize: longint      (* number of bytes of storage *)
   );
      var
         bounds: Rect;
         x, y : integer;
         colour : RGBColor;
         viewRay : Ray3D;
         lightRay : Ray3D;
         i : integer;
         closestDistance : float;
         closestIntersectionPt: My3DPoint;
         thisDistance : float;
         intersectionPt: My3DPoint;
         intersect_polygon: longint;
         pm: PixMapHandle;
         junk_boolean: boolean;
         pixels: Ptr;
         rowbytes_add: longint;
         my_storage: StorageRecordPtr;
   begin
{$unused( storage, storageSize )}
      my_storage := StorageRecordPtr(storage);

      PreparsePolygons( my_storage, numPolys, thePolys );

      SetGWorld( offScreen, nil );
      bounds := offScreen^.PortRect;
      pm := GetGWorldPixMap( offScreen );
      junk_boolean := LockPixels( pm );
      pixels := GetPixBaseAddr( pm );
      rowbytes_add := band( pm^^.rowBytes, $3FFF ) - 
                                    4 * (bounds.right - bounds.left);

      for y := bounds.top to bounds.bottom-1 do begin
         for x := bounds.left to bounds.right-1 do begin
            MakeViewRay(viewPoint, x, y, 0, viewRay);
            closestDistance := 0.0;
            intersect_polygon := -1;
            for i:= 1 to numPolys do begin
               if Intersect(viewRay, thePolys[i-1], 
                           my_storage^.poly_extra[i-1], thisDistance, 
                           intersectionPt) then begin
                  if (thisDistance > closestDistance) then begin
                     intersect_polygon := i;
                     closestDistance := thisDistance;
                     closestIntersectionPt := intersectionPt;
                  end;
               end
            end;
            if intersect_polygon > 0 then begin
               colour := thePolys[intersect_polygon-1].polyColor;

               MakeViewRay(illumPoint, closestIntersectionPt.x3D, 
                                 closestIntersectionPt.y3D, 
                                 closestIntersectionPt.z3D, lightRay);

               for i:= 1 to numPolys do begin
                  if (intersect_polygon <> i) & 
                     Intersect(lightRay, thePolys[i-1], 
                     my_storage^.poly_extra[i-1], 
                     thisDistance, intersectionPt) then begin
               colour.red := band(colour.red, $0FFFF) div 2;
               colour.green := band(colour.green, $0FFFF) div 2;
               colour.blue := band(colour.blue, $0FFFF) div 2;
                     leave;
                  end
               end;
               
      LongintPtr(pixels)^ := bsl( band(colour.red, $0FF00), 8 ) 
                     + band(colour.green, $0FF00) + 
                        bsr( band(colour.blue, $0FF00), 8 );
            end else begin
               LongintPtr(pixels)^ := 0;
            end;
            longint(pixels) := longint(pixels) + 4;
         end;
         longint(pixels) := longint(pixels) + rowbytes_add;
      end;
   end;

end.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Recruit two powerful-sounding students t...
I am a fan of anime, and I hear about a lot that comes through, but one that escaped my attention until now is A Certain Scientific Railgun T, and that name is very enticing. If it's new to you too, then players of Blue Archive can get a hands-on... | Read more »
Top Hat Studios unveils a new gameplay t...
There are a lot of big games coming that you might be excited about, but one of those I am most interested in is Athenian Rhapsody because it looks delightfully silly. The developers behind this project, the rather fancy-sounding Top Hat Studios,... | Read more »
Bound through time on the hunt for sneak...
Have you ever sat down and wondered what would happen if Dr Who and Sherlock Holmes went on an adventure? Well, besides probably being the best mash-up of English fiction, you'd get the Hidden Through Time series, and now Rogueside has announced... | Read more »
The secrets of Penacony might soon come...
Version 2.2 of Honkai: Star Rail is on the horizon and brings the culmination of the Penacony adventure after quite the escalation in the latest story quests. To help you through this new expansion is the introduction of two powerful new... | Read more »
The Legend of Heroes: Trails of Cold Ste...
I adore game series that have connecting lore and stories, which of course means the Legend of Heroes is very dear to me, Trails lore has been building for two decades. Excitedly, the next stage is upon us as Userjoy has announced the upcoming... | Read more »
Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »

Price Scanner via MacPrices.net

May 2024 Apple Education discounts on MacBook...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take up to $300 off the purchase of a new MacBook... Read more
Clearance 16-inch M2 Pro MacBook Pros in stoc...
Apple has clearance 16″ M2 Pro MacBook Pros available in their Certified Refurbished store starting at $2049 and ranging up to $450 off original MSRP. Each model features a new outer case, shipping... Read more
Save $300 at Apple on 14-inch M3 MacBook Pros...
Apple has 14″ M3 MacBook Pros with 16GB of RAM, Certified Refurbished, available for $270-$300 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year warranty is... Read more
Apple continues to offer 14-inch M3 MacBook P...
Apple has 14″ M3 MacBook Pros, Certified Refurbished, available starting at only $1359 and ranging up to $270 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year... Read more
Apple AirPods Pro with USB-C return to all-ti...
Amazon has Apple’s AirPods Pro with USB-C in stock and on sale for $179.99 including free shipping. Their price is $70 (28%) off MSRP, and it’s currently the lowest price available for new AirPods... Read more
Apple Magic Keyboards for iPads are on sale f...
Amazon has Apple Magic Keyboards for iPads on sale today for up to $70 off MSRP, shipping included: – Magic Keyboard for 10th-generation Apple iPad: $199, save $50 – Magic Keyboard for 11″ iPad Pro/... Read more
Apple’s 13-inch M2 MacBook Airs return to rec...
Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices currently... Read more
Best Buy is clearing out iPad Airs for up to...
In advance of next week’s probably release of new and updated iPad Airs, Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices for up to $200 off Apple’s MSRP, starting at $399. Sale prices... Read more
Every version of Apple Pencil is on sale toda...
Best Buy has all Apple Pencils on sale today for $79, ranging up to 39% off MSRP for some models. Sale prices for online orders only, in-store prices may vary. Order online and choose free shipping... Read more
Sunday Sale: Apple Studio Display with Standa...
Amazon has the standard-glass Apple Studio Display on sale for $300 off MSRP for a limited time. Shipping is free: – Studio Display (Standard glass): $1299.97 $300 off MSRP For the latest prices and... Read more

Jobs Board

Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
*Apple* App Developer - Datrose (United Stat...
…year experiencein programming and have computer knowledge with SWIFT. Job Responsibilites: Apple App Developer is expected to support essential tasks for the RxASL Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.