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

Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links... | Read more »
Price of Glory unleashes its 1.4 Alpha u...
As much as we all probably dislike Maths as a subject, we do have to hand it to geometry for giving us the good old Hexgrid, home of some of the best strategy games. One such example, Price of Glory, has dropped its 1.4 Alpha update, stocked full... | Read more »
The SLC 2025 kicks off this month to cro...
Ever since the Solo Leveling: Arise Championship 2025 was announced, I have been looking forward to it. The promotional clip they released a month or two back showed crowds going absolutely nuts for the previous competitions, so imagine the... | Read more »
Dive into some early Magicpunk fun as Cr...
Excellent news for fans of steampunk and magic; the Precursor Test for Magicpunk MMORPG Crystal of Atlan opens today. This rather fancy way of saying beta test will remain open until March 5th and is available for PC - boo - and Android devices -... | Read more »
Prepare to get your mind melted as Evang...
If you are a fan of sci-fi shooters and incredibly weird, mind-bending anime series, then you are in for a treat, as Goddess of Victory: Nikke is gearing up for its second collaboration with Evangelion. We were also treated to an upcoming... | Read more »
Square Enix gives with one hand and slap...
We have something of a mixed bag coming over from Square Enix HQ today. Two of their mobile games are revelling in life with new events keeping them alive, whilst another has been thrown onto the ever-growing discard pile Square is building. I... | Read more »
Let the world burn as you have some fest...
It is time to leave the world burning once again as you take a much-needed break from that whole “hero” lark and enjoy some celebrations in Genshin Impact. Version 5.4, Moonlight Amidst Dreams, will see you in Inazuma to attend the Mikawa Flower... | Read more »
Full Moon Over the Abyssal Sea lands on...
Aether Gazer has announced its latest major update, and it is one of the loveliest event names I have ever heard. Full Moon Over the Abyssal Sea is an amazing name, and it comes loaded with two side stories, a new S-grade Modifier, and some fancy... | Read more »
Open your own eatery for all the forest...
Very important question; when you read the title Zoo Restaurant, do you also immediately think of running a restaurant in which you cook Zoo animals as the course? I will just assume yes. Anyway, come June 23rd we will all be able to start up our... | Read more »
Crystal of Atlan opens registration for...
Nuverse was prominently featured in the last month for all the wrong reasons with the USA TikTok debacle, but now it is putting all that behind it and preparing for the Crystal of Atlan beta test. Taking place between February 18th and March 5th,... | Read more »

Price Scanner via MacPrices.net

AT&T is offering a 65% discount on the ne...
AT&T is offering the new iPhone 16e for up to 65% off their monthly finance fee with 36-months of service. No trade-in is required. Discount is applied via monthly bill credits over the 36 month... Read more
Use this code to get a free iPhone 13 at Visi...
For a limited time, use code SWEETDEAL to get a free 128GB iPhone 13 Visible, Verizon’s low-cost wireless cell service, Visible. Deal is valid when you purchase the Visible+ annual plan. Free... Read more
M4 Mac minis on sale for $50-$80 off MSRP at...
B&H Photo has M4 Mac minis in stock and on sale right now for $50 to $80 off Apple’s MSRP, each including free 1-2 day shipping to most US addresses: – M4 Mac mini (16GB/256GB): $549, $50 off... Read more
Buy an iPhone 16 at Boost Mobile and get one...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering one year of free Unlimited service with the purchase of any iPhone 16. Purchase the iPhone at standard MSRP, and then choose... Read more
Get an iPhone 15 for only $299 at Boost Mobil...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering the 128GB iPhone 15 for $299.99 including service with their Unlimited Premium plan (50GB of premium data, $60/month), or $20... Read more
Unreal Mobile is offering $100 off any new iP...
Unreal Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering a $100 discount on any new iPhone with service. This includes new iPhone 16 models as well as iPhone 15, 14, 13, and SE... Read more
Apple drops prices on clearance iPhone 14 mod...
With today’s introduction of the new iPhone 16e, Apple has discontinued the iPhone 14, 14 Pro, and SE. In response, Apple has dropped prices on unlocked, Certified Refurbished, iPhone 14 models to a... Read more
B&H has 16-inch M4 Max MacBook Pros on sa...
B&H Photo is offering a $360-$410 discount on new 16-inch MacBook Pros with M4 Max CPUs right now. B&H offers free 1-2 day shipping to most US addresses: – 16″ M4 Max MacBook Pro (36GB/1TB/... Read more
Amazon is offering a $100 discount on the M4...
Amazon has the M4 Pro Mac mini discounted $100 off MSRP right now. Shipping is free. Their price is the lowest currently available for this popular mini: – Mac mini M4 Pro (24GB/512GB): $1299, $100... Read more
B&H continues to offer $150-$220 discount...
B&H Photo has 14-inch M4 MacBook Pros on sale for $150-$220 off MSRP. B&H offers free 1-2 day shipping to most US addresses: – 14″ M4 MacBook Pro (16GB/512GB): $1449, $150 off MSRP – 14″ M4... Read more

Jobs Board

All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.