Exemple #1
0
static void waves (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal, const TraceThreadData *Thread)
{
	register unsigned int i;
	register DBL length, scalar, index, sinValue ;
	VECTOR point;

	for (i = 0 ; i < Thread->numberOfWaves ; i++)
	{
		VSub (point, EPoint, *Thread->waveSources[i]);

		VLength (length, point);

		if (length == 0.0)
		{
			length = 1.0;
		}

		index = length * Tnormal->Frequency * Thread->waveFrequencies[i] + Tnormal->Phase;

		sinValue = cycloidal(index);

		scalar = sinValue * Tnormal->Amount / Thread->waveFrequencies[i];

		VAddScaledEq(normal, scalar / (length * (DBL)Thread->numberOfWaves), point);
	}
}
Exemple #2
0
static void Do_Average_Normals (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal, Intersection *Inter, const Ray *ray, TraceThreadData *Thread)
{
	int i;
	BLEND_MAP *Map = Tnormal->Blend_Map;
	SNGL Value;
	SNGL Total = 0.0;
	VECTOR V1,V2;

	Make_Vector (V1, 0.0, 0.0, 0.0);

	for (i = 0; i < Map->Number_Of_Entries; i++)
	{
		Value = Map->Blend_Map_Entries[i].value;

		Assign_Vector(V2,normal);

		Perturb_Normal(V2,Map->Blend_Map_Entries[i].Vals.Tnormal,EPoint,Inter,ray,Thread);

		VAddScaledEq(V1,Value,V2);

		Total += Value;
	}

	VInverseScale(normal,V1,Total);
}
Exemple #3
0
static void bumps (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal)
{
	VECTOR bump_turb;

	/* Get normal displacement value. */

	DNoise (bump_turb, EPoint);

	/* Displace "normal". */

	VAddScaledEq(normal, Tnormal->Amount, bump_turb);
}
Exemple #4
0
static void dents (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal, const TraceThreadData *Thread)
{
	DBL noise;
	VECTOR stucco_turb;

	noise = Noise (EPoint, GetNoiseGen((TPATTERN*)Tnormal, Thread));

	noise = noise * noise * noise * Tnormal->Amount;

	/* Get normal displacement value. */

	DNoise(stucco_turb, EPoint);

	/* Displace "normal". */

	VAddScaledEq(normal, noise, stucco_turb);
}
Exemple #5
0
static void quilted (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal)
{
	VECTOR value;
	DBL t;

	value[X] = EPoint[X]-FLOOR(EPoint[X])-0.5;
	value[Y] = EPoint[Y]-FLOOR(EPoint[Y])-0.5;
	value[Z] = EPoint[Z]-FLOOR(EPoint[Z])-0.5;

	t = sqrt(value[X]*value[X]+value[Y]*value[Y]+value[Z]*value[Z]);

	t = quilt_cubic(t, Tnormal->Vals.Quilted.Control0, Tnormal->Vals.Quilted.Control1);

	value[X] *= t;
	value[Y] *= t;
	value[Z] *= t;

	VAddScaledEq (normal, Tnormal->Amount,value);
}
Exemple #6
0
static void wrinkles (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal)
{
	register int i;
	register DBL scale = 1.0;
	VECTOR result, value, value2;

	Make_Vector(result, 0.0, 0.0, 0.0);

	for (i = 0; i < 10; scale *= 2.0, i++)
	{
		VScale(value2,EPoint,scale);
		DNoise(value, value2);

		result[X] += fabs(value[X] / scale);
		result[Y] += fabs(value[Y] / scale);
		result[Z] += fabs(value[Z] / scale);
	}

	/* Displace "normal". */

	VAddScaledEq(normal, Tnormal->Amount, result);
}
Exemple #7
0
void Warp_EPoint (VECTOR TPoint, VECTOR EPoint, TPATTERN *TPat)
{
   VECTOR PTurbulence,RP;
   int Axis,i,temp_rand;
   int blockX = 0, blockY = 0, blockZ = 0 ;
   SNGL BlkNum;
   DBL  Length;
   DBL  Strength;
   WARP *Warp=TPat->Warps;
   TURB *Turb;
   TRANS *Tr;
   REPEAT *Repeat;
   BLACK_HOLE *Black_Hole;
   VECTOR Delta, Center;

   Assign_Vector(TPoint, EPoint);

   while (Warp != NULL)
   {
      switch(Warp->Warp_Type)
      {
        case CLASSIC_TURB_WARP:
          if ((TPat->Type == MARBLE_PATTERN) ||
              (TPat->Type == NO_PATTERN)     ||
              (TPat->Type == WOOD_PATTERN))
          {
             break;
          }
        /* If not a special type, fall through to next case */

        case EXTRA_TURB_WARP:
          Turb=(TURB *)Warp;
          DTurbulence (PTurbulence, TPoint, Turb);
          TPoint[X] += PTurbulence[X] * Turb->Turbulence[X];
          TPoint[Y] += PTurbulence[Y] * Turb->Turbulence[Y];
          TPoint[Z] += PTurbulence[Z] * Turb->Turbulence[Z];
          break;

        case NO_WARP:
          break;

        case TRANSFORM_WARP:
          Tr=(TRANS *)Warp;
          MInvTransPoint(TPoint, TPoint, &(Tr->Trans));
          break;

        case REPEAT_WARP:
          Repeat=(REPEAT *)Warp;
          Assign_Vector(RP,TPoint);
          Axis=Repeat->Axis;
          BlkNum=(SNGL)floor(TPoint[Axis]/Repeat->Width);
          
          RP[Axis]=TPoint[Axis]-BlkNum*Repeat->Width;
          
          if (((int)BlkNum) & 1)
          {          
             VEvaluateEq(RP,Repeat->Flip);
             if ( Repeat->Flip[Axis] < 0 ) 
             {
                RP[Axis] = Repeat->Width+RP[Axis];
             }
          }

          VAddScaledEq(RP,BlkNum,Repeat->Offset);
          Assign_Vector(TPoint,RP);
          break;

        case BLACK_HOLE_WARP:
          Black_Hole = (BLACK_HOLE *) Warp ;
          Assign_Vector (Center, Black_Hole->Center) ;

          if (Black_Hole->Repeat)
          {
            /* first, get the block number we're in for each dimension  */
            /* block numbers are (currently) calculated relative to 0   */
            /* we use floor () since it correctly returns -1 for the
               first block below 0 in each axis                         */
            /* one final point - we could run into overflow problems if
               the repeat vector was small and the scene very large.    */
            if (Black_Hole->Repeat_Vector [X] >= Small_Tolerance)
              blockX = (int) floor (TPoint [X] / Black_Hole->Repeat_Vector [X]) ;

            if (Black_Hole->Repeat_Vector [Y] >= Small_Tolerance)
              blockY = (int) floor (TPoint [Y] / Black_Hole->Repeat_Vector [Y]) ;

            if (Black_Hole->Repeat_Vector [Z] >= Small_Tolerance)
              blockZ = (int) floor (TPoint [Z] / Black_Hole->Repeat_Vector [Z]) ;

            if (Black_Hole->Uncertain)
            {
              /* if the position is uncertain calculate the new one first */
              /* this will allow the same numbers to be returned by frand */
              
              temp_rand = POV_GET_OLD_RAND(); /*protect seed*/
  
              POV_SRAND (Hash3d (blockX, blockY, blockZ)) ;
              Center [X] += FRAND () * Black_Hole->Uncertainty_Vector [X] ;
              Center [Y] += FRAND () * Black_Hole->Uncertainty_Vector [Y] ;
              Center [Z] += FRAND () * Black_Hole->Uncertainty_Vector [Z] ;
              POV_SRAND (temp_rand) ;  /*restore*/
            }

            Center [X] += Black_Hole->Repeat_Vector [X] * blockX ;
            Center [Y] += Black_Hole->Repeat_Vector [Y] * blockY ;
            Center [Z] += Black_Hole->Repeat_Vector [Z] * blockZ ;
          }

          VSub (Delta, TPoint, Center) ;
          VLength (Length, Delta) ;

          /* Length is the distance from the centre of the black hole */
          if (Length >= Black_Hole->Radius) break ;

          if (Black_Hole->Type == 0)
          {
            /* now convert the length to a proportion (0 to 1) that the point
               is from the edge of the black hole. a point on the perimeter
               of the black hole will be 0.0 ; a point at the centre will be
               1.0 ; a point exactly halfway will be 0.5, and so forth. */
            Length = (Black_Hole->Radius - Length) / Black_Hole->Radius ;

            /* Strength is the magnitude of the transformation effect. firstly,
               apply the Power variable to Length. this is meant to provide a
               means of controlling how fast the power of the Black Hole falls
               off from its centre. if Power is 2.0, then the effect is inverse
               square. increasing power will cause the Black Hole to be a lot
               weaker in its effect towards its perimeter. 
               
               finally we multiply Strength with the Black Hole's Strength
               variable. if the resultant value exceeds 1.0 we clip it to 1.0.
               this means a point will never be transformed by more than its
               original distance from the centre. the result of this clipping
               is that you will have an 'exclusion' area near the centre of
               the black hole where all points whose final value exceeded or
               equalled 1.0 were moved by a fixed amount. this only happens
               if the Strength value of the Black Hole was greater than one. */

            Strength = pow (Length, Black_Hole->Power) * Black_Hole->Strength ;
            if (Strength > 1.0) Strength = 1.0 ;
            
            /* if the Black Hole is inverted, it gives the impression of 'push-
               ing' the pattern away from its centre. otherwise it sucks. */
            VScaleEq (Delta, Black_Hole->Inverted ? -Strength : Strength) ;

            /* add the scaled Delta to the input point to end up with TPoint. */
            VAddEq (TPoint, Delta) ;
          }
          break;
          
        /* 10/23/1998 Talious added SPherical Cylindrical and toroidal
        warps */

        case CYLINDRICAL_WARP:
          warp_cylindrical(TPoint, (CYLW *)Warp);
          break;

        case PLANAR_WARP:
          warp_planar(TPoint, (PLANARW *)Warp);
          break;
      
        case SPHERICAL_WARP:
          warp_spherical(TPoint, (SPHEREW *)Warp);
          break;

        case TOROIDAL_WARP:
          warp_toroidal(TPoint, (TOROIDAL *) Warp);
          break;
          
        default:
          Error("Warp type %d not yet implemented",Warp->Warp_Type);
      }
      Warp=Warp->Next_Warp;
   }

   for (i=X; i<=Z; i++)
     if (TPoint[i] > COORDINATE_LIMIT)
       TPoint[i]= COORDINATE_LIMIT;
     else
       if (TPoint[i] < -COORDINATE_LIMIT)
         TPoint[i] = -COORDINATE_LIMIT;

}
Exemple #8
0
void Perturb_Normal(VECTOR Layer_Normal, const TNORMAL *Tnormal, const VECTOR EPoint, Intersection *Intersection, const Ray *ray, TraceThreadData *Thread)
{
	VECTOR TPoint,P1;
	DBL value1,value2,Amount;
	int i;
	BLEND_MAP *Blend_Map;
	BLEND_MAP_ENTRY *Prev, *Cur;

	if (Tnormal==NULL)
	{
		return;
	}

	/* If normal_map present, use it and return */

	if ((Blend_Map=Tnormal->Blend_Map) != NULL)
	{
		if ((Blend_Map->Type == NORMAL_TYPE) && (Tnormal->Type == UV_MAP_PATTERN))
		{
			UV_VECT UV_Coords;

			Cur = &(Tnormal->Blend_Map->Blend_Map_Entries[0]);

			/* Don't bother warping, simply get the UV vect of the intersection */
			Intersection->Object->UVCoord(UV_Coords, Intersection, Thread);
			TPoint[X] = UV_Coords[U];
			TPoint[Y] = UV_Coords[V];
			TPoint[Z] = 0;

			Perturb_Normal(Layer_Normal,Cur->Vals.Tnormal,TPoint,Intersection,ray,Thread);
			VNormalizeEq(Layer_Normal);
			Assign_Vector(Intersection->PNormal, Layer_Normal); /* -hdf- June 98 */

			return;
		}
		else if ((Blend_Map->Type == NORMAL_TYPE) && (Tnormal->Type != AVERAGE_PATTERN))
		{
			/* NK 19 Nov 1999 added Warp_EPoint */
			Warp_EPoint (TPoint, EPoint, (TPATTERN *)Tnormal);
			value1 = Evaluate_TPat((TPATTERN *)Tnormal,TPoint,Intersection,ray,Thread);

			Search_Blend_Map (value1,Blend_Map,&Prev,&Cur);

			Warp_Normal(Layer_Normal,Layer_Normal, (TPATTERN *)Tnormal, Test_Flag(Tnormal,DONT_SCALE_BUMPS_FLAG));
			Assign_Vector(P1,Layer_Normal);

			Warp_EPoint (TPoint, EPoint, (TPATTERN *)Tnormal);

			Perturb_Normal(Layer_Normal,Cur->Vals.Tnormal,TPoint,Intersection,ray,Thread);

			if (Prev != Cur)
			{
				Perturb_Normal(P1,Prev->Vals.Tnormal,TPoint,Intersection,ray,Thread);

				value2 = (value1-Prev->value)/(Cur->value-Prev->value);
				value1 = 1.0-value2;

				VLinComb2(Layer_Normal,value1,P1,value2,Layer_Normal);
			}

			UnWarp_Normal(Layer_Normal,Layer_Normal,(TPATTERN *)Tnormal, Test_Flag(Tnormal,DONT_SCALE_BUMPS_FLAG));

			VNormalizeEq(Layer_Normal);

			Assign_Vector(Intersection->PNormal, Layer_Normal); /* -hdf- June 98 */

			return;
		}
	}

	/* No normal_map. */

	if (Tnormal->Type <= LAST_NORM_ONLY_PATTERN)
	{
		Warp_Normal(Layer_Normal,Layer_Normal, (TPATTERN *)Tnormal,
		            Test_Flag(Tnormal,DONT_SCALE_BUMPS_FLAG));

		Warp_EPoint (TPoint, EPoint, (TPATTERN *)Tnormal);

		switch (Tnormal->Type)
		{
			case BITMAP_PATTERN:    bump_map    (TPoint, Tnormal, Layer_Normal);            break;
			case BUMPS_PATTERN:     bumps       (TPoint, Tnormal, Layer_Normal);            break;
			case DENTS_PATTERN:     dents       (TPoint, Tnormal, Layer_Normal, Thread);    break;
			case RIPPLES_PATTERN:   ripples     (TPoint, Tnormal, Layer_Normal, Thread);    break;
			case WAVES_PATTERN:     waves       (TPoint, Tnormal, Layer_Normal, Thread);    break;
			case WRINKLES_PATTERN:  wrinkles    (TPoint, Tnormal, Layer_Normal);            break;
			case QUILTED_PATTERN:   quilted     (TPoint, Tnormal, Layer_Normal);            break;
			case FACETS_PATTERN:    facets      (TPoint, Tnormal, Layer_Normal, Thread);    break;
			case AVERAGE_PATTERN:   Do_Average_Normals (TPoint, Tnormal, Layer_Normal, Intersection, ray, Thread); break;
			default:
				throw POV_EXCEPTION_STRING("Normal pattern not yet implemented.");
		}

		UnWarp_Normal(Layer_Normal,Layer_Normal, (TPATTERN *)Tnormal,
		              Test_Flag(Tnormal,DONT_SCALE_BUMPS_FLAG));
	}
	else
	{
		Warp_Normal(Layer_Normal,Layer_Normal, (TPATTERN *)Tnormal,
		            Test_Flag(Tnormal,DONT_SCALE_BUMPS_FLAG));

		Amount=Tnormal->Amount * -5.0; /*fudge factor*/
		Amount*=0.02/Tnormal->Delta; /* NK delta */

		/* warp the center point first - this is the last warp */
		Warp_EPoint(TPoint,EPoint,(TPATTERN *)Tnormal);

		for(i=0; i<=3; i++)
		{
			VAddScaled(P1,TPoint,Tnormal->Delta,Pyramid_Vect[i]); /* NK delta */
			value1 = Do_Slope_Map(Evaluate_TPat((TPATTERN *)Tnormal,P1,Intersection,ray,Thread),Blend_Map);
			VAddScaledEq(Layer_Normal,value1*Amount,Pyramid_Vect[i]);
		}

		UnWarp_Normal(Layer_Normal,Layer_Normal,(TPATTERN *)Tnormal,
		              Test_Flag(Tnormal,DONT_SCALE_BUMPS_FLAG));

	}

	if ( Intersection )
		Assign_Vector(Intersection->PNormal, Layer_Normal); /* -hdf- June 98 */
}
Exemple #9
0
static void facets (const VECTOR EPoint, const TNORMAL *Tnormal, VECTOR normal, TraceThreadData *Thread)
{
	int    i;
	int    thisseed;
	DBL    sum, minsum;
	VECTOR sv, tv, dv, t1, add, newnormal, pert;
	DBL    scale;
	int    UseSquare;
	int    UseUnity;
	DBL    Metric;

	VECTOR *cv = Thread->Facets_Cube;
	Metric = Tnormal->Vals.Facets.Metric;

	UseSquare = (Metric == 2 );
	UseUnity  = (Metric == 1 );

	VNormalize( normal, normal );

	if ( Tnormal->Vals.Facets.UseCoords )
	{
		Assign_Vector(tv,EPoint);
	}
	else
	{
		Assign_Vector(tv,normal);
	}

	if ( Tnormal->Vals.Facets.Size < 1e-6 )
	{
		scale = 1e6;
	}
	else
	{
		scale = 1. / Tnormal->Vals.Facets.Size;
	}

	VScaleEq( tv, scale );

	/*
	 * Check to see if the input point is in the same unit cube as the last
	 * call to this function, to use cache of cubelets for speed.
	 */

	thisseed = PickInCube(tv, t1);

	if (thisseed != Thread->Facets_Last_Seed)
	{
		/*
		 * No, not same unit cube.  Calculate the random points for this new
		 * cube and its 80 neighbours which differ in any axis by 1 or 2.
		 * Why distance of 2?  If there is 1 point in each cube, located
		 * randomly, it is possible for the closest random point to be in the
		 * cube 2 over, or the one two over and one up.  It is NOT possible
		 * for it to be two over and two up.  Picture a 3x3x3 cube with 9 more
		 * cubes glued onto each face.
		 */

		/* Now store a points for this cube and each of the 80 neighbour cubes. */

		int cvc = 0;

		for (add[X] = -2.0; add[X] < 2.5; add[X] +=1.0)
		{
			for (add[Y] = -2.0; add[Y] < 2.5; add[Y] += 1.0)
			{
				for (add[Z] = -2.0; add[Z] < 2.5; add[Z] += 1.0)
				{
					/* For each cubelet in a 5x5 cube. */

					if ((fabs(add[X])>1.5)+(fabs(add[Y])>1.5)+(fabs(add[Z])>1.5) <= 1.0)
					{
						/* Yes, it's within a 3d knight move away. */

						VAdd(sv, tv, add);

						PickInCube(sv, t1);

						cv[cvc][X] = t1[X];
						cv[cvc][Y] = t1[Y];
						cv[cvc][Z] = t1[Z];
						cvc++;
					}
				}
			}
		}

		Thread->Facets_Last_Seed = thisseed;
		Thread->Facets_CVC = cvc;
	}

	/*
	 * Find the point with the shortest distance from the input point.
	 */

	VSub(dv, cv[0], tv);
	if ( UseSquare )
	{
		minsum  = VSumSqr(dv);
	}
	else if ( UseUnity )
	{
		minsum = dv[X]+dv[Y]+dv[Z];
	}
	else
	{
		minsum = pow(fabs(dv[X]), Metric)+
		         pow(fabs(dv[Y]), Metric)+
		         pow(fabs(dv[Z]), Metric);
	}

	Assign_Vector( newnormal, cv[0] );

	/* Loop for the 81 cubelets to find closest. */

	for (i = 1; i < Thread->Facets_CVC; i++)
	{
		VSub(dv, cv[i], tv);

		if ( UseSquare )
		{
			sum  = VSumSqr(dv);
		}
		else
		{
			if ( UseUnity )
			{
				sum = dv[X]+dv[Y]+dv[Z];
			}
			else
			{
				sum = pow(fabs(dv[X]), Metric)+
				      pow(fabs(dv[Y]), Metric)+
				      pow(fabs(dv[Z]), Metric);
			}
		}

		if (sum < minsum)
		{
			minsum = sum;
			Assign_Vector( newnormal, cv[i] );
		}
	}

	if ( Tnormal->Vals.Facets.UseCoords )
	{
		DNoise( pert, newnormal );
		VDot( sum, pert, normal );
		VScale( newnormal, normal, sum );
		VSubEq( pert, newnormal );
		VAddScaledEq( normal, Tnormal->Vals.Facets.UseCoords, pert );
	}
	else
	{
		Assign_Vector( normal, newnormal );
	}
}
Exemple #10
0
int Torus::Intersect(const Ray& ray, DBL *Depth, SceneThreadData *Thread) const
{
	int i, n;
	DBL len, R2, Py2, Dy2, PDy2, k1, k2;
	DBL y1, y2, r1, r2;
	DBL c[5];
	DBL r[4];
	VECTOR P, D;
	DBL DistanceP;            // Distance from P to torus center (origo).
	DBL BoundingSphereRadius; // Sphere fully (amply) enclosing torus.
	DBL Closer;               // P is moved Closer*D closer to torus.

	Thread->Stats()[Ray_Torus_Tests]++;

	/* Transform the ray into the torus space. */

	MInvTransPoint(P, ray.Origin, Trans);

	MInvTransDirection(D, ray.Direction, Trans);

	VLength(len, D);

	VInverseScaleEq(D, len);

	i = 0;

	y1 = -MinorRadius;
	y2 =  MinorRadius;
	r1 = Sqr(MajorRadius - MinorRadius);

	if ( MajorRadius < MinorRadius )
		r1 = 0;

	r2 = Sqr(MajorRadius + MinorRadius);

#ifdef TORUS_EXTRA_STATS
	Thread->Stats()[Torus_Bound_Tests]++;
#endif

	if (Test_Thick_Cylinder(P, D, y1, y2, r1, r2))
	{
#ifdef TORUS_EXTRA_STATS
		Thread->Stats()[Torus_Bound_Tests_Succeeded]++;
#endif

		// Move P close to bounding sphere to have more precise root calculation.
		// Bounding sphere radius is R + r, we add r once more to ensure
		// that P is safely outside sphere.
		BoundingSphereRadius = MajorRadius + MinorRadius + MinorRadius;
		DistanceP = VSumSqr(P); // Distance is currently squared.
		Closer = 0.0;
		if (DistanceP > Sqr(BoundingSphereRadius))
		{
			DistanceP = sqrt(DistanceP); // Now real distance.
			Closer = DistanceP - BoundingSphereRadius;
			VAddScaledEq(P, Closer, D);
		}

		R2   = Sqr(MajorRadius);
		r2   = Sqr(MinorRadius);

		Py2  = P[Y] * P[Y];
		Dy2  = D[Y] * D[Y];
		PDy2 = P[Y] * D[Y];

		k1   = P[X] * P[X] + P[Z] * P[Z] + Py2 - R2 - r2;
		k2   = P[X] * D[X] + P[Z] * D[Z] + PDy2;

		c[0] = 1.0;

		c[1] = 4.0 * k2;

		c[2] = 2.0 * (k1 + 2.0 * (k2 * k2 + R2 * Dy2));

		c[3] = 4.0 * (k2 * k1 + 2.0 * R2 * PDy2);

		c[4] = k1 * k1 + 4.0 * R2 * (Py2 - r2);

		n = Solve_Polynomial(4, c, r, Test_Flag(this, STURM_FLAG), ROOT_TOLERANCE, Thread);

		while(n--)
			Depth[i++] = (r[n] + Closer) / len;
	}

	if (i)
		Thread->Stats()[Ray_Torus_Tests_Succeeded]++;

	return(i);
}
Exemple #11
0
static void bezier_value(VECTOR (*Control_Points)[4][4], DBL u0, DBL  v0, VECTOR P, VECTOR  N)
{
  const DBL C[] = { 1.0, 3.0, 3.0, 1.0 };
  int i, j;
  DBL c, t, ut, vt;
  DBL u[4], uu[4], v[4], vv[4];
  DBL du[4], duu[4], dv[4], dvv[4];
  DBL squared_u1, squared_v1;
  VECTOR U1, V1;
  
  /* Calculate binomial coefficients times coordinate positions. */
  
  u[0] = 1.0; uu[0] = 1.0; du[0] = 0.0; duu[0] = 0.0;
  v[0] = 1.0; vv[0] = 1.0; dv[0] = 0.0; dvv[0] = 0.0;

  for (i = 1; i < 4; i++)
  {
    u[i] = u[i - 1] * u0;  uu[i] = uu[i - 1] * (1.0 - u0);
    v[i] = v[i - 1] * v0;  vv[i] = vv[i - 1] * (1.0 - v0);

    du[i] = i * u[i - 1];  duu[i] = -i * uu[i - 1];
    dv[i] = i * v[i - 1];  dvv[i] = -i * vv[i - 1];
  }

  /* Now evaluate position and tangents based on control points. */

  Make_Vector(P, 0, 0, 0);
  Make_Vector(U1, 0, 0, 0);
  Make_Vector(V1, 0, 0, 0);

  for (i = 0; i < 4; i++)
  {
    for (j = 0; j < 4; j++)
    {
      c = C[i] * C[j];
      
      ut = u[i] * uu[3 - i];
      vt = v[j] * vv[3 - j];
      
      t = c * ut * vt;
      
      VAddScaledEq(P, t, (*Control_Points)[i][j]);
      
      t = c * vt * (du[i] * uu[3 - i] + u[i] * duu[3 - i]);
      
      VAddScaledEq(U1, t, (*Control_Points)[i][j]);
      
      t = c * ut * (dv[j] * vv[3 - j] + v[j] * dvv[3 - j]);
      
      VAddScaledEq(V1, t, (*Control_Points)[i][j]);
    }
  }
  
  /* Make the normal from the cross product of the tangents. */
  
  VCross(N, U1, V1);
  
  VDot(t, N, N);
  
  squared_u1 = VSumSqr(U1);
  squared_v1 = VSumSqr(V1);
  if (t > (BEZIER_EPSILON * squared_u1 * squared_v1))
  {
    t = 1.0 / sqrt(t);
    
    VScaleEq(N, t);
  }
  else
  {
    Make_Vector(N, 1, 0, 0);
  }
}