/* $Id: nurbssrf.c,v 1.2 2000/07/11 14:11:04 brianp Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  3.3
 * Copyright (C) 1995-2000  Brian Paul
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


/*
 * NURBS implementation written by Bogdan Sikorski (bogdan@cira.it)
 * See README2 for more info.
 */


#ifdef PC_HEADER
#include "all.h"
#else
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "gluP.h"
#include "nurbs.h"
#endif


static int
get_surface_dim(GLenum type)
{
   switch (type) {
   case GL_MAP2_VERTEX_3:
      return 3;
   case GL_MAP2_VERTEX_4:
      return 4;
   case GL_MAP2_INDEX:
      return 1;
   case GL_MAP2_COLOR_4:
      return 4;
   case GL_MAP2_NORMAL:
      return 3;
   case GL_MAP2_TEXTURE_COORD_1:
      return 1;
   case GL_MAP2_TEXTURE_COORD_2:
      return 2;
   case GL_MAP2_TEXTURE_COORD_3:
      return 3;
   case GL_MAP2_TEXTURE_COORD_4:
      return 4;
   default:
      abort();			/* TODO: is this OK? */
   }
   return 0;			/*never get here */
}

static GLenum
test_nurbs_surface(GLUnurbsObj * nobj, surface_attribs * attrib)
{
   GLenum err;
   GLint tmp_int;

   if (attrib->sorder < 0 || attrib->torder < 0) {
      call_user_error(nobj, GLU_INVALID_VALUE);
      return GLU_ERROR;
   }
   glGetIntegerv(GL_MAX_EVAL_ORDER, &tmp_int);
   if (attrib->sorder > tmp_int || attrib->sorder < 2) {
      call_user_error(nobj, GLU_NURBS_ERROR1);
      return GLU_ERROR;
   }
   if (attrib->torder > tmp_int || attrib->torder < 2) {
      call_user_error(nobj, GLU_NURBS_ERROR1);
      return GLU_ERROR;
   }
   if (attrib->sknot_count < attrib->sorder + 2) {
      call_user_error(nobj, GLU_NURBS_ERROR2);
      return GLU_ERROR;
   }
   if (attrib->tknot_count < attrib->torder + 2) {
      call_user_error(nobj, GLU_NURBS_ERROR2);
      return GLU_ERROR;
   }
   if (attrib->s_stride < 0 || attrib->t_stride < 0) {
      call_user_error(nobj, GLU_NURBS_ERROR34);
      return GLU_ERROR;
   }
   if (attrib->sknot == NULL || attrib->tknot == NULL
       || attrib->ctrlarray == NULL) {
      call_user_error(nobj, GLU_NURBS_ERROR36);
      return GLU_ERROR;
   }
   if ((err = test_knot(attrib->tknot_count, attrib->tknot, attrib->torder))
       != GLU_NO_ERROR) {
      call_user_error(nobj, err);
      return GLU_ERROR;
   }
   if ((err = test_knot(attrib->sknot_count, attrib->sknot, attrib->sorder))
       != GLU_NO_ERROR) {
      call_user_error(nobj, err);
      return GLU_ERROR;
   }
   return GLU_NO_ERROR;
}

static GLenum
test_nurbs_surfaces(GLUnurbsObj * nobj)
{
   /* test the geometric data */
   if (test_nurbs_surface(nobj, &(nobj->surface.geom)) != GLU_NO_ERROR)
      return GLU_ERROR;
   /* now test the attributive data */
   /* color */
   if (nobj->surface.color.type != GLU_INVALID_ENUM)
      if (test_nurbs_surface(nobj, &(nobj->surface.color)) != GLU_NO_ERROR)
	 return GLU_ERROR;
   /* normal */
   if (nobj->surface.normal.type != GLU_INVALID_ENUM)
      if (test_nurbs_surface(nobj, &(nobj->surface.normal)) != GLU_NO_ERROR)
	 return GLU_ERROR;
   /* texture */
   if (nobj->surface.texture.type != GLU_INVALID_ENUM)
      if (test_nurbs_surface(nobj, &(nobj->surface.texture)) != GLU_NO_ERROR)
	 return GLU_ERROR;
   return GLU_NO_ERROR;
}

static GLenum
convert_surf(knot_str_type * s_knot, knot_str_type * t_knot,
	     surface_attribs * attrib, GLfloat ** new_ctrl,
	     GLint * s_n_ctrl, GLint * t_n_ctrl)
{
   GLfloat **tmp_ctrl;
   GLfloat *ctrl_offset;
   GLint tmp_n_control;
   GLint i, j, t_cnt, s_cnt;
   GLint tmp_stride;
   GLint dim;
   GLenum err;

   /* valid range is empty? */
   if ((s_knot->unified_knot != NULL && s_knot->unified_nknots == 0) ||
       (t_knot->unified_knot != NULL && t_knot->unified_nknots == 0)) {
      if (s_knot->unified_knot) {
	 free(s_knot->unified_knot);
	 s_knot->unified_knot = NULL;
      }
      if (t_knot->unified_knot) {
	 free(t_knot->unified_knot);
	 t_knot->unified_knot = NULL;
      }
      *s_n_ctrl = 0;
      *t_n_ctrl = 0;
      return GLU_NO_ERROR;
   }
   t_cnt = attrib->tknot_count - attrib->torder;
   s_cnt = attrib->sknot_count - attrib->sorder;
   if ((tmp_ctrl = (GLfloat **) malloc(sizeof(GLfloat *) * t_cnt)) == NULL)
      return GLU_OUT_OF_MEMORY;
   if ((err = explode_knot(s_knot)) != GLU_NO_ERROR) {
      free(tmp_ctrl);
      if (s_knot->unified_knot) {
	 free(s_knot->unified_knot);
	 s_knot->unified_knot = NULL;
      }
      return err;
   }
   if (s_knot->unified_knot) {
      free(s_knot->unified_knot);
      s_knot->unified_knot = NULL;
   }
   if ((err = calc_alphas(s_knot)) != GLU_NO_ERROR) {
      free(tmp_ctrl);
      free(s_knot->new_knot);
      return err;
   }
   free(s_knot->new_knot);
   ctrl_offset = attrib->ctrlarray;
   dim = attrib->dim;
   for (i = 0; i < t_cnt; i++) {
      if ((err = calc_new_ctrl_pts(ctrl_offset, attrib->s_stride, s_knot,
				   dim, &(tmp_ctrl[i]),
				   &tmp_n_control)) != GLU_NO_ERROR) {
	 for (--i; i <= 0; i--)
	    free(tmp_ctrl[i]);
	 free(tmp_ctrl);
	 free(s_knot->alpha);
	 return err;
      }
      ctrl_offset += attrib->t_stride;
   }
   free(s_knot->alpha);
   tmp_stride = dim * tmp_n_control;
   if ((*new_ctrl = (GLfloat *) malloc(sizeof(GLfloat) * tmp_stride * t_cnt))
       == NULL) {
      for (i = 0; i < t_cnt; i++)
	 free(tmp_ctrl[i]);
      free(tmp_ctrl);
      return GLU_OUT_OF_MEMORY;
   }
   for (i = 0; i < tmp_n_control; i++)
      for (j = 0; j < t_cnt; j++)
	 MEMCPY(*new_ctrl + j * dim + i * dim * t_cnt, tmp_ctrl[j] + dim * i,
		sizeof(GLfloat) * dim);
   for (i = 0; i < t_cnt; i++)
      free(tmp_ctrl[i]);
   free(tmp_ctrl);
   *s_n_ctrl = tmp_n_control;

   if ((tmp_ctrl = (GLfloat **) malloc(sizeof(GLfloat *) * (*s_n_ctrl))) ==
       NULL) {
      return GLU_OUT_OF_MEMORY;
   }
   if ((err = explode_knot(t_knot)) != GLU_NO_ERROR) {
      free(tmp_ctrl);
      if (t_knot->unified_knot) {
	 free(t_knot->unified_knot);
	 t_knot->unified_knot = NULL;
      }
      return err;
   }
   if (t_knot->unified_knot) {
      free(t_knot->unified_knot);
      t_knot->unified_knot = NULL;
   }
   if ((err = calc_alphas(t_knot)) != GLU_NO_ERROR) {
      free(tmp_ctrl);
      free(t_knot->new_knot);
      return err;
   }
   free(t_knot->new_knot);
   ctrl_offset = *new_ctrl;
   for (i = 0; i < (*s_n_ctrl); i++) {
      if ((err = calc_new_ctrl_pts(ctrl_offset, dim, t_knot,
				   dim, &(tmp_ctrl[i]),
				   &tmp_n_control)) != GLU_NO_ERROR) {
	 for (--i; i <= 0; i--)
	    free(tmp_ctrl[i]);
	 free(tmp_ctrl);
	 free(t_knot->alpha);
	 return err;
      }
      ctrl_offset += dim * t_cnt;
   }
   free(t_knot->alpha);
   free(*new_ctrl);
   tmp_stride = dim * tmp_n_control;
   if (
       (*new_ctrl =
	(GLfloat *) malloc(sizeof(GLfloat) * tmp_stride * (*s_n_ctrl))) ==
       NULL) {
      for (i = 0; i < (*s_n_ctrl); i++)
	 free(tmp_ctrl[i]);
      free(tmp_ctrl);
      return GLU_OUT_OF_MEMORY;
   }
   for (i = 0; i < (*s_n_ctrl); i++) {
      MEMCPY(*new_ctrl + i * tmp_stride, tmp_ctrl[i],
	     sizeof(GLfloat) * tmp_stride);
      free(tmp_ctrl[i]);
   }
   free(tmp_ctrl);
   *t_n_ctrl = tmp_n_control;
   return GLU_NO_ERROR;
}

/* prepare the knot information structures */
static GLenum
fill_knot_structures(GLUnurbsObj * nobj,
		     knot_str_type * geom_s_knot, knot_str_type * geom_t_knot,
		     knot_str_type * color_s_knot,
		     knot_str_type * color_t_knot,
		     knot_str_type * normal_s_knot,
		     knot_str_type * normal_t_knot,
		     knot_str_type * texture_s_knot,
		     knot_str_type * texture_t_knot)
{
   GLint order;
   GLfloat *knot;
   GLint nknots;
   GLint t_min, t_max;

   geom_s_knot->unified_knot = NULL;
   knot = geom_s_knot->knot = nobj->surface.geom.sknot;
   nknots = geom_s_knot->nknots = nobj->surface.geom.sknot_count;
   order = geom_s_knot->order = nobj->surface.geom.sorder;
   geom_s_knot->delta_nknots = 0;
   t_min = geom_s_knot->t_min = order - 1;
   t_max = geom_s_knot->t_max = nknots - order;
   if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
      call_user_error(nobj, GLU_NURBS_ERROR3);
      return GLU_ERROR;
   }
   if (fabs(knot[0] - knot[t_min]) < EPSILON) {
      /* knot open at beggining */
      geom_s_knot->open_at_begin = GL_TRUE;
   }
   else
      geom_s_knot->open_at_begin = GL_FALSE;
   if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
      /* knot open at end */
      geom_s_knot->open_at_end = GL_TRUE;
   }
   else
      geom_s_knot->open_at_end = GL_FALSE;
   geom_t_knot->unified_knot = NULL;
   knot = geom_t_knot->knot = nobj->surface.geom.tknot;
   nknots = geom_t_knot->nknots = nobj->surface.geom.tknot_count;
   order = geom_t_knot->order = nobj->surface.geom.torder;
   geom_t_knot->delta_nknots = 0;
   t_min = geom_t_knot->t_min = order - 1;
   t_max = geom_t_knot->t_max = nknots - order;
   if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
      call_user_error(nobj, GLU_NURBS_ERROR3);
      return GLU_ERROR;
   }
   if (fabs(knot[0] - knot[t_min]) < EPSILON) {
      /* knot open at beggining */
      geom_t_knot->open_at_begin = GL_TRUE;
   }
   else
      geom_t_knot->open_at_begin = GL_FALSE;
   if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
      /* knot open at end */
      geom_t_knot->open_at_end = GL_TRUE;
   }
   else
      geom_t_knot->open_at_end = GL_FALSE;

   if (nobj->surface.color.type != GLU_INVALID_ENUM) {
      color_s_knot->unified_knot = (GLfloat *) 1;
      knot = color_s_knot->knot = nobj->surface.color.sknot;
      nknots = color_s_knot->nknots = nobj->surface.color.sknot_count;
      order = color_s_knot->order = nobj->surface.color.sorder;
      color_s_knot->delta_nknots = 0;
      t_min = color_s_knot->t_min = order - 1;
      t_max = color_s_knot->t_max = nknots - order;
      if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
	 call_user_error(nobj, GLU_NURBS_ERROR3);
	 return GLU_ERROR;
      }
      if (fabs(knot[0] - knot[t_min]) < EPSILON) {
	 /* knot open at beggining */
	 color_s_knot->open_at_begin = GL_TRUE;
      }
      else
	 color_s_knot->open_at_begin = GL_FALSE;
      if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
	 /* knot open at end */
	 color_s_knot->open_at_end = GL_TRUE;
      }
      else
	 color_s_knot->open_at_end = GL_FALSE;
      color_t_knot->unified_knot = (GLfloat *) 1;
      knot = color_t_knot->knot = nobj->surface.color.tknot;
      nknots = color_t_knot->nknots = nobj->surface.color.tknot_count;
      order = color_t_knot->order = nobj->surface.color.torder;
      color_t_knot->delta_nknots = 0;
      t_min = color_t_knot->t_min = order - 1;
      t_max = color_t_knot->t_max = nknots - order;
      if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
	 call_user_error(nobj, GLU_NURBS_ERROR3);
	 return GLU_ERROR;
      }
      if (fabs(knot[0] - knot[t_min]) < EPSILON) {
	 /* knot open at beggining */
	 color_t_knot->open_at_begin = GL_TRUE;
      }
      else
	 color_t_knot->open_at_begin = GL_FALSE;
      if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
	 /* knot open at end */
	 color_t_knot->open_at_end = GL_TRUE;
      }
      else
	 color_t_knot->open_at_end = GL_FALSE;
   }
   else {
      color_s_knot->unified_knot = NULL;
      color_t_knot->unified_knot = NULL;
   }

   if (nobj->surface.normal.type != GLU_INVALID_ENUM) {
      normal_s_knot->unified_knot = (GLfloat *) 1;
      knot = normal_s_knot->knot = nobj->surface.normal.sknot;
      nknots = normal_s_knot->nknots = nobj->surface.normal.sknot_count;
      order = normal_s_knot->order = nobj->surface.normal.sorder;
      normal_s_knot->delta_nknots = 0;
      t_min = normal_s_knot->t_min = order - 1;
      t_max = normal_s_knot->t_max = nknots - order;
      if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
	 call_user_error(nobj, GLU_NURBS_ERROR3);
	 return GLU_ERROR;
      }
      if (fabs(knot[0] - knot[t_min]) < EPSILON) {
	 /* knot open at beggining */
	 normal_s_knot->open_at_begin = GL_TRUE;
      }
      else
	 normal_s_knot->open_at_begin = GL_FALSE;
      if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
	 /* knot open at end */
	 normal_s_knot->open_at_end = GL_TRUE;
      }
      else
	 normal_s_knot->open_at_end = GL_FALSE;
      normal_t_knot->unified_knot = (GLfloat *) 1;
      knot = normal_t_knot->knot = nobj->surface.normal.tknot;
      nknots = normal_t_knot->nknots = nobj->surface.normal.tknot_count;
      order = normal_t_knot->order = nobj->surface.normal.torder;
      normal_t_knot->delta_nknots = 0;
      t_min = normal_t_knot->t_min = order - 1;
      t_max = normal_t_knot->t_max = nknots - order;
      if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
	 call_user_error(nobj, GLU_NURBS_ERROR3);
	 return GLU_ERROR;
      }
      if (fabs(knot[0] - knot[t_min]) < EPSILON) {
	 /* knot open at beggining */
	 normal_t_knot->open_at_begin = GL_TRUE;
      }
      else
	 normal_t_knot->open_at_begin = GL_FALSE;
      if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
	 /* knot open at end */
	 normal_t_knot->open_at_end = GL_TRUE;
      }
      else
	 normal_t_knot->open_at_end = GL_FALSE;
   }
   else {
      normal_s_knot->unified_knot = NULL;
      normal_t_knot->unified_knot = NULL;
   }

   if (nobj->surface.texture.type != GLU_INVALID_ENUM) {
      texture_s_knot->unified_knot = (GLfloat *) 1;
      knot = texture_s_knot->knot = nobj->surface.texture.sknot;
      nknots = texture_s_knot->nknots = nobj->surface.texture.sknot_count;
      order = texture_s_knot->order = nobj->surface.texture.sorder;
      texture_s_knot->delta_nknots = 0;
      t_min = texture_s_knot->t_min = order - 1;
      t_max = texture_s_knot->t_max = nknots - order;
      if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
	 call_user_error(nobj, GLU_NURBS_ERROR3);
	 return GLU_ERROR;
      }
      if (fabs(knot[0] - knot[t_min]) < EPSILON) {
	 /* knot open at beggining */
	 texture_s_knot->open_at_begin = GL_TRUE;
      }
      else
	 texture_s_knot->open_at_begin = GL_FALSE;
      if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
	 /* knot open at end */
	 texture_s_knot->open_at_end = GL_TRUE;
      }
      else
	 texture_s_knot->open_at_end = GL_FALSE;
      texture_t_knot->unified_knot = (GLfloat *) 1;
      knot = texture_t_knot->knot = nobj->surface.texture.tknot;
      nknots = texture_t_knot->nknots = nobj->surface.texture.tknot_count;
      order = texture_t_knot->order = nobj->surface.texture.torder;
      texture_t_knot->delta_nknots = 0;
      t_min = texture_t_knot->t_min = order - 1;
      t_max = texture_t_knot->t_max = nknots - order;
      if (fabs(knot[t_min] - knot[t_max]) < EPSILON) {
	 call_user_error(nobj, GLU_NURBS_ERROR3);
	 return GLU_ERROR;
      }
      if (fabs(knot[0] - knot[t_min]) < EPSILON) {
	 /* knot open at beggining */
	 texture_t_knot->open_at_begin = GL_TRUE;
      }
      else
	 texture_t_knot->open_at_begin = GL_FALSE;
      if (fabs(knot[t_max] - knot[nknots - 1]) < EPSILON) {
	 /* knot open at end */
	 texture_t_knot->open_at_end = GL_TRUE;
      }
      else
	 texture_t_knot->open_at_end = GL_FALSE;
   }
   else {
      texture_s_knot->unified_knot = NULL;
      texture_t_knot->unified_knot = NULL;
   }
   return GLU_NO_ERROR;
}


static void
free_new_ctrl(new_ctrl_type * p)
{
   if (p->geom_ctrl)
      free(p->geom_ctrl);
   if (p->geom_offsets)
      free(p->geom_offsets);
   if (p->color_ctrl) {
      free(p->color_ctrl);
      if (p->color_offsets)
	 free(p->color_offsets);
   }
   if (p->normal_ctrl) {
      free(p->normal_ctrl);
      if (p->normal_offsets)
	 free(p->normal_offsets);
   }
   if (p->texture_ctrl) {
      free(p->texture_ctrl);
      if (p->texture_offsets)
	 free(p->texture_offsets);
   }
}

/* convert surfaces - geometry and possible attribute ones into equivalent */
/* sequence of adjacent Bezier patches */
static GLenum
convert_surfs(GLUnurbsObj * nobj, new_ctrl_type * new_ctrl)
{
   knot_str_type geom_s_knot, color_s_knot, normal_s_knot, texture_s_knot;
   knot_str_type geom_t_knot, color_t_knot, normal_t_knot, texture_t_knot;
   GLenum err;

   if ((err = fill_knot_structures(nobj, &geom_s_knot, &geom_t_knot,
				   &color_s_knot, &color_t_knot,
				   &normal_s_knot, &normal_t_knot,
				   &texture_s_knot,
				   &texture_t_knot)) != GLU_NO_ERROR) {
      return err;
   }
   /* unify knots - all knots should have the same working range */
   if ((err = select_knot_working_range(nobj, &geom_s_knot, &color_s_knot,
					&normal_s_knot,
					&texture_s_knot)) != GLU_NO_ERROR) {
      call_user_error(nobj, err);
      return err;
   }
   if ((err = select_knot_working_range(nobj, &geom_t_knot, &color_t_knot,
					&normal_t_knot,
					&texture_t_knot)) != GLU_NO_ERROR) {
      free_unified_knots(&geom_s_knot, &color_s_knot, &normal_s_knot,
			 &texture_s_knot);
      call_user_error(nobj, err);
      return err;
   }

   /* convert the geometry surface */
   nobj->surface.geom.dim = get_surface_dim(nobj->surface.geom.type);
   if ((err = convert_surf(&geom_s_knot, &geom_t_knot, &(nobj->surface.geom),
			   &(new_ctrl->geom_ctrl), &(new_ctrl->geom_s_pt_cnt),
			   &(new_ctrl->geom_t_pt_cnt))) != GLU_NO_ERROR) {
      free_unified_knots(&geom_s_knot, &color_s_knot, &normal_s_knot,
			 &texture_s_knot);
      free_unified_knots(&geom_t_knot, &color_t_knot, &normal_t_knot,
			 &texture_t_knot);
      call_user_error(nobj, err);
      return err;
   }
   /* if additional attributive surfaces are given convert them as well */
   if (color_s_knot.unified_knot) {
      nobj->surface.color.dim = get_surface_dim(nobj->surface.color.type);
      if (
	  (err =
	   convert_surf(&color_s_knot, &color_t_knot, &(nobj->surface.color),
			&(new_ctrl->color_ctrl), &(new_ctrl->color_s_pt_cnt),
			&(new_ctrl->color_t_pt_cnt))) != GLU_NO_ERROR) {
	 free_unified_knots(&color_s_knot, &color_s_knot, &normal_s_knot,
			    &texture_s_knot);
	 free_unified_knots(&color_t_knot, &color_t_knot, &normal_t_knot,
			    &texture_t_knot);
	 free_new_ctrl(new_ctrl);
	 call_user_error(nobj, err);
	 return err;
      }
   }
   if (normal_s_knot.unified_knot) {
      nobj->surface.normal.dim = get_surface_dim(nobj->surface.normal.type);
      if ((err = convert_surf(&normal_s_knot, &normal_t_knot,
			      &(nobj->surface.normal),
			      &(new_ctrl->normal_ctrl),
			      &(new_ctrl->normal_s_pt_cnt),
			      &(new_ctrl->normal_t_pt_cnt))) !=
	  GLU_NO_ERROR) {
	 free_unified_knots(&normal_s_knot, &normal_s_knot, &normal_s_knot,
			    &texture_s_knot);
	 free_unified_knots(&normal_t_knot, &normal_t_knot, &normal_t_knot,
			    &texture_t_knot);
	 free_new_ctrl(new_ctrl);
	 call_user_error(nobj, err);
	 return err;
      }
   }
   if (texture_s_knot.unified_knot) {
      nobj->surface.texture.dim = get_surface_dim(nobj->surface.texture.type);
      if ((err = convert_surf(&texture_s_knot, &texture_t_knot,
			      &(nobj->surface.texture),
			      &(new_ctrl->texture_ctrl),
			      &(new_ctrl->texture_s_pt_cnt),
			      &(new_ctrl->texture_t_pt_cnt))) !=
	  GLU_NO_ERROR) {
	 free_unified_knots(&texture_s_knot, &texture_s_knot, &texture_s_knot,
			    &texture_s_knot);
	 free_unified_knots(&texture_t_knot, &texture_t_knot, &texture_t_knot,
			    &texture_t_knot);
	 free_new_ctrl(new_ctrl);
	 call_user_error(nobj, err);
	 return err;
      }
   }
   return GLU_NO_ERROR;
}

/* tesselate the "boundary" Bezier edge strips */
static void
tesselate_strip_t_line(GLint top_start, GLint top_end, GLint top_z,
		       GLint bottom_start, GLint bottom_end, GLint bottom_z,
		       GLint bottom_domain)
{
   GLint top_cnt, bottom_cnt, tri_cnt, k;
   GLint direction;

   top_cnt = top_end - top_start;
   direction = (top_cnt >= 0 ? 1 : -1);
   bottom_cnt = bottom_end - bottom_start;
   glBegin(GL_LINES);
   while (top_cnt) {
      if (bottom_cnt)
	 tri_cnt = top_cnt / bottom_cnt;
      else
	 tri_cnt = abs(top_cnt);
      for (k = 0; k <= tri_cnt; k++, top_start += direction) {
	 glEvalCoord2f((GLfloat) bottom_z / bottom_domain,
		       (GLfloat) bottom_start / bottom_domain);
	 glEvalPoint2(top_z, top_start);
      }
      if (bottom_cnt) {
	 glEvalCoord2f((GLfloat) bottom_z / bottom_domain,
		       (GLfloat) bottom_start / bottom_domain);
	 bottom_start += direction;
	 top_start -= direction;
	 glEvalCoord2f((GLfloat) bottom_z / bottom_domain,
		       (GLfloat) bottom_start / bottom_domain);
	 glEvalCoord2f((GLfloat) bottom_z / bottom_domain,
		       (GLfloat) bottom_start / bottom_domain);
	 glEvalPoint2(top_z, top_start);
      }
      top_cnt -= direction * tri_cnt;
      bottom_cnt -= direction;
   }
   glEnd();
}


static void
tesselate_strip_t_fill(GLint top_start, GLint top_end, GLint top_z,
		       GLint bottom_start, GLint bottom_end, GLint bottom_z,
		       GLint bottom_domain)
{
   GLint top_cnt, bottom_cnt, tri_cnt, k;
   GLint direction;

   top_cnt = top_end - top_start;
   direction = (top_cnt >= 0 ? 1 : -1);
   bottom_cnt = bottom_end - bottom_start;
   while (top_cnt) {
      if (bottom_cnt)
	 tri_cnt = top_cnt / bottom_cnt;
      else
	 tri_cnt = abs(top_cnt);
      glBegin(GL_TRIANGLE_FAN);
      glEvalCoord2f((GLfloat) bottom_z / bottom_domain,
		    (GLfloat) bottom_start / bottom_domain);
      for (k = 0; k <= tri_cnt; k++, top_start += direction)
	 glEvalPoint2(top_z, top_start);
      if (bottom_cnt) {
	 bottom_start += direction;
	 top_start -= direction;
	 glEvalCoord2f((GLfloat) bottom_z / bottom_domain,
		       (GLfloat) bottom_start / bottom_domain);
      }
      glEnd();
      top_cnt -= direction * tri_cnt;
      bottom_cnt -= direction;
   }
}


static void
tesselate_strip_t(GLenum display_mode, GLint top_start, GLint top_end,
		  GLint top_z, GLint bottom_start, GLint bottom_end,
		  GLint bottom_z, GLint bottom_domain)
{
   if (display_mode == GL_FILL)
      tesselate_strip_t_fill(top_start, top_end, top_z, bottom_start,
			     bottom_end, bottom_z, bottom_domain);
   else
      tesselate_strip_t_line(top_start, top_end, top_z, bottom_start,
			     bottom_end, bottom_z, bottom_domain);
}


static void
tesselate_strip_s_fill(GLint top_start, GLint top_end, GLint top_z,
		       GLint bottom_start, GLint bottom_end, GLint bottom_z,
		       GLfloat bottom_domain)
{
   GLint top_cnt, bottom_cnt, tri_cnt, k;
   GLint direction;

   top_cnt = top_end - top_start;
   direction = (top_cnt >= 0 ? 1 : -1);
   bottom_cnt = bottom_end - bottom_start;
   while (top_cnt) {
      if (bottom_cnt)
	 tri_cnt = top_cnt / bottom_cnt;
      else
	 tri_cnt = abs(top_cnt);
      glBegin(GL_TRIANGLE_FAN);
      glEvalCoord2f((GLfloat) bottom_start / bottom_domain,
		    (GLfloat) bottom_z / bottom_domain);
      for (k = 0; k <= tri_cnt; k++, top_start += direction)
	 glEvalPoint2(top_start, top_z);
      if (bottom_cnt) {
	 bottom_start += direction;
	 top_start -= direction;
	 glEvalCoord2f((GLfloat) bottom_start / bottom_domain,
		       (GLfloat) bottom_z / bottom_domain);
      }
      glEnd();
      top_cnt -= direction * tri_cnt;
      bottom_cnt -= direction;
   }
}


static void
tesselate_strip_s_line(GLint top_start, GLint top_end, GLint top_z,
		       GLint bottom_start, GLint bottom_end, GLint bottom_z,
		       GLfloat bottom_domain)
{
   GLint top_cnt, bottom_cnt, tri_cnt, k;
   GLint direction;

   top_cnt = top_end - top_start;
   direction = (top_cnt >= 0 ? 1 : -1);
   bottom_cnt = bottom_end - bottom_start;
   glBegin(GL_LINES);
   while (top_cnt) {
      if (bottom_cnt)
	 tri_cnt = top_cnt / bottom_cnt;
      else
	 tri_cnt = abs(top_cnt);
      for (k = 0; k <= tri_cnt; k++, top_start += direction) {
	 glEvalCoord2f((GLfloat) bottom_start / bottom_domain,
		       (GLfloat) bottom_z / bottom_domain);
	 glEvalPoint2(top_start, top_z);
      }
      if (bottom_cnt) {
	 glEvalCoord2f((GLfloat) bottom_start / bottom_domain,
		       (GLfloat) bottom_z / bottom_domain);
	 bottom_start += direction;
	 top_start -= direction;
	 glEvalCoord2f((GLfloat) bottom_start / bottom_domain,
		       (GLfloat) bottom_z / bottom_domain);
	 glEvalPoint2(top_start, top_z);
	 glEvalCoord2f((GLfloat) bottom_start / bottom_domain,
		       (GLfloat) bottom_z / bottom_domain);
      }
      top_cnt -= direction * tri_cnt;
      bottom_cnt -= direction;
   }
   glEnd();
}


static void
tesselate_strip_s(GLenum display_mode, GLint top_start, GLint top_end,
		  GLint top_z, GLint bottom_start, GLint bottom_end,
		  GLint bottom_z, GLfloat bottom_domain)
{
   if (display_mode == GL_FILL)
      tesselate_strip_s_fill(top_start, top_end, top_z, bottom_start,
			     bottom_end, bottom_z, bottom_domain);
   else
      tesselate_strip_s_line(top_start, top_end, top_z, bottom_start,
			     bottom_end, bottom_z, bottom_domain);
}

static void
tesselate_bottom_left_corner(GLenum display_mode, GLfloat s_1, GLfloat t_1)
{
   if (display_mode == GL_FILL) {
      glBegin(GL_TRIANGLE_FAN);
      glEvalPoint2(1, 1);
      glEvalCoord2f(s_1, 0.0);
      glEvalCoord2f(0.0, 0.0);
      glEvalCoord2f(0.0, t_1);
   }
   else {
      glBegin(GL_LINES);
      glEvalCoord2f(0.0, 0.0);
      glEvalCoord2f(0.0, t_1);
      glEvalCoord2f(0.0, 0.0);
      glEvalPoint2(1, 1);
      glEvalCoord2f(0.0, 0.0);
      glEvalCoord2f(s_1, 0.0);
   }
   glEnd();
}

static void
tesselate_bottom_right_corner(GLenum display_mode, GLint v_top,
			      GLint v_bottom, GLfloat s_1, GLfloat t_1)
{
   if (display_mode == GL_FILL) {
      glBegin(GL_TRIANGLE_FAN);
      glEvalPoint2(1, v_top);
      glEvalCoord2f(0.0, v_bottom * t_1);
      glEvalCoord2f(0.0, (v_bottom + 1) * t_1);
      glEvalCoord2f(s_1, (v_bottom + 1) * t_1);
   }
   else {
      glBegin(GL_LINES);
      glEvalCoord2f(0.0, (v_bottom + 1) * t_1);
      glEvalPoint2(1, v_top);
      glEvalCoord2f(0.0, (v_bottom + 1) * t_1);
      glEvalCoord2f(0.0, v_bottom * t_1);
      glEvalCoord2f(0.0, (v_bottom + 1) * t_1);
      glEvalCoord2f(s_1, (v_bottom + 1) * t_1);
   }
   glEnd();
}

static void
tesselate_top_left_corner(GLenum display_mode, GLint u_right, GLint u_left,
			  GLfloat s_1, GLfloat t_1)
{
   if (display_mode == GL_FILL) {
      glBegin(GL_TRIANGLE_FAN);
      glEvalPoint2(u_right, 1);
      glEvalCoord2f((u_left + 1) * s_1, t_1);
      glEvalCoord2f((u_left + 1) * s_1, 0.0);
      glEvalCoord2f(u_left * s_1, 0.0);
   }
   else {
      glBegin(GL_LINES);
      glEvalCoord2f((u_left + 1) * s_1, 0.0);
      glEvalPoint2(u_right, 1);
      glEvalCoord2f((u_left + 1) * s_1, 0.0);
      glEvalCoord2f(u_left * s_1, 0.0);
      glEvalCoord2f((u_left + 1) * s_1, 0.0);
      glEvalCoord2f((u_left + 1) * s_1, t_1);
   }
   glEnd();
}

static void
tesselate_top_right_corner(GLenum display_mode, GLint u_left, GLint v_bottom,
			   GLint u_right, GLint v_top, GLfloat s_1,
			   GLfloat t_1)
{
   if (display_mode == GL_FILL) {
      glBegin(GL_TRIANGLE_FAN);
      glEvalPoint2(u_left, v_bottom);
      glEvalCoord2f((u_right - 1) * s_1, v_top * t_1);
      glEvalCoord2f(u_right * s_1, v_top * t_1);
      glEvalCoord2f(u_right * s_1, (v_top - 1) * t_1);
   }
   else {
      glBegin(GL_LINES);
      glEvalCoord2f(u_right * s_1, v_top * t_1);
      glEvalPoint2(u_left, v_bottom);
      glEvalCoord2f(u_right * s_1, v_top * t_1);
      glEvalCoord2f(u_right * s_1, (v_top - 1) * t_1);
      glEvalCoord2f(u_right * s_1, v_top * t_1);
      glEvalCoord2f((u_right - 1) * s_1, v_top * t_1);
   }
   glEnd();
}

/* do mesh mapping of Bezier */
static void
nurbs_map_bezier(GLenum display_mode, GLint * sfactors, GLint * tfactors,
		 GLint s_bezier_cnt, GLint t_bezier_cnt, GLint s, GLint t)
{
   GLint top, bottom, right, left;


   if (s == 0) {
      top = *(tfactors + t * 3);
      bottom = *(tfactors + t * 3 + 1);
   }
   else if (s == s_bezier_cnt - 1) {
      top = *(tfactors + t * 3 + 2);
      bottom = *(tfactors + t * 3);
   }
   else {
      top = bottom = *(tfactors + t * 3);
   }
   if (t == 0) {
      left = *(sfactors + s * 3 + 1);
      right = *(sfactors + s * 3);
   }
   else if (t == t_bezier_cnt - 1) {
      left = *(sfactors + s * 3);
      right = *(sfactors + s * 3 + 2);
   }
   else {
      left = right = *(sfactors + s * 3);
   }

   if (top > bottom) {
      if (left < right) {
	 glMapGrid2f(right, 0.0, 1.0, top, 0.0, 1.0);
	 glEvalMesh2(display_mode, 1, right, 1, top);
	 tesselate_strip_s(display_mode, 1, right, 1, 1, left, 0,
			   (GLfloat) left);
	 tesselate_bottom_left_corner(display_mode, (GLfloat) (1.0 / left),
				      (GLfloat) (1.0 / bottom));
/*			tesselate_strip_t(display_mode,1,top,1,1,bottom,0,(GLfloat)bottom);*/
	 tesselate_strip_t(display_mode, top, 1, 1, bottom, 1, 0,
			   (GLfloat) bottom);
      }
      else if (left == right) {
	 glMapGrid2f(right, 0.0, 1.0, top, 0.0, 1.0);
	 glEvalMesh2(display_mode, 1, right, 0, top);
/*			tesselate_strip_t(display_mode,0,top,1,0,bottom,0,(GLfloat)bottom);*/
	 tesselate_strip_t(display_mode, top, 0, 1, bottom, 0, 0,
			   (GLfloat) bottom);
      }
      else {
	 glMapGrid2f(left, 0.0, 1.0, top, 0.0, 1.0);
	 glEvalMesh2(display_mode, 1, left, 0, top - 1);
/*			tesselate_strip_t(display_mode,0,top-1,1,0,bottom-1,0,
				(GLfloat)bottom);*/
	 tesselate_strip_t(display_mode, top - 1, 0, 1, bottom - 1, 0, 0,
			   (GLfloat) bottom);
	 tesselate_bottom_right_corner(display_mode, top - 1, bottom - 1,
				       (GLfloat) (1.0 / right),
				       (GLfloat) (1.0 / bottom));
/*			tesselate_strip_s(display_mode,1,left,top-1,1,right,right,
				(GLfloat)right);*/
	 tesselate_strip_s(display_mode, left, 1, top - 1, right, 1, right,
			   (GLfloat) right);
      }
   }
   else if (top == bottom) {
      if (left < right) {
	 glMapGrid2f(right, 0.0, 1.0, top, 0.0, 1.0);
	 glEvalMesh2(display_mode, 0, right, 1, top);
	 tesselate_strip_s(display_mode, 0, right, 1, 0, left, 0,
			   (GLfloat) left);
      }
      else if (left == right) {
	 glMapGrid2f(right, 0.0, 1.0, top, 0.0, 1.0);
	 glEvalMesh2(display_mode, 0, right, 0, top);
      }
      else {
	 glMapGrid2f(left, 0.0, 1.0, top, 0.0, 1.0);
	 glEvalMesh2(display_mode, 0, left, 0, top - 1);
/*			tesselate_strip_s(display_mode,0,left,top-1,0,right,right,
				(GLfloat)right);*/
	 tesselate_strip_s(display_mode, left, 0, top - 1, right, 0, right,
			   (GLfloat) right);
      }
   }
   else {
      if (left < right) {
	 glMapGrid2f(right, 0.0, 1.0, bottom, 0.0, 1.0);
	 glEvalMesh2(display_mode, 0, right - 1, 1, bottom);
	 tesselate_strip_s(display_mode, 0, right - 1, 1, 0, left - 1, 0,
			   (GLfloat) left);
	 tesselate_top_left_corner(display_mode, right - 1, left - 1,
				   (GLfloat) (1.0 / left),
				   (GLfloat) (1.0 / top));
	 tesselate_strip_t(display_mode, 1, bottom, right - 1, 1, top, top,
			   (GLfloat) top);
      }
      else if (left == right) {
	 glMapGrid2f(right, 0.0, 1.0, bottom, 0.0, 1.0);
	 glEvalMesh2(display_mode, 0, right - 1, 0, bottom);
	 tesselate_strip_t(display_mode, 0, bottom, right - 1, 0, top, top,
			   (GLfloat) top);
      }
      else {
	 glMapGrid2f(left, 0.0, 1.0, bottom, 0.0, 1.0);
	 glEvalMesh2(display_mode, 0, left - 1, 0, bottom - 1);
	 tesselate_strip_t(display_mode, 0, bottom - 1, left - 1, 0, top - 1,
			   top, (GLfloat) top);
	 tesselate_top_right_corner(display_mode, left - 1, bottom - 1, right,
				    top, (GLfloat) (1.0 / right),
				    (GLfloat) (1.0 / top));
/*			tesselate_strip_s(display_mode,0,left-1,bottom-1,0,right-1,right,
				(GLfloat)right);*/
	 tesselate_strip_s(display_mode, left - 1, 0, bottom - 1, right - 1,
			   0, right, (GLfloat) right);
      }
   }
}

/* draw NURBS surface in OUTLINE POLYGON mode */
static void
draw_polygon_mode(GLenum display_mode, GLUnurbsObj * nobj,
		  new_ctrl_type * new_ctrl, GLint * sfactors,
		  GLint * tfactors)
{
   GLsizei offset;
   GLint t_bezier_cnt, s_bezier_cnt;
   GLboolean do_color, do_normal, do_texture;
   GLint i, j;

   t_bezier_cnt = new_ctrl->t_bezier_cnt;
   s_bezier_cnt = new_ctrl->s_bezier_cnt;
   glEnable(nobj->surface.geom.type);
   if (new_ctrl->color_ctrl) {
      glEnable(nobj->surface.color.type);
      do_color = GL_TRUE;
   }
   else
      do_color = GL_FALSE;
   if (new_ctrl->normal_ctrl) {
      glEnable(nobj->surface.normal.type);
      do_normal = GL_TRUE;
   }
   else
      do_normal = GL_FALSE;
   if (new_ctrl->texture_ctrl) {
      glEnable(nobj->surface.texture.type);
      do_texture = GL_TRUE;
   }
   else
      do_texture = GL_FALSE;
   for (j = 0; j < s_bezier_cnt; j++) {
      for (i = 0; i < t_bezier_cnt; i++) {
	 offset = j * t_bezier_cnt + i;
	 if (fine_culling_test_3D(nobj, *(new_ctrl->geom_offsets + offset),
				  nobj->surface.geom.sorder,
				  nobj->surface.geom.torder,
				  new_ctrl->geom_s_stride,
				  new_ctrl->geom_t_stride,
				  nobj->surface.geom.dim)) continue;
	 glMap2f(nobj->surface.geom.type, 0.0, 1.0, new_ctrl->geom_s_stride,
		 nobj->surface.geom.sorder, 0.0, 1.0, new_ctrl->geom_t_stride,
		 nobj->surface.geom.torder,
		 *(new_ctrl->geom_offsets + offset));
	 if (do_color) {
	    glMap2f(nobj->surface.color.type, 0.0, 1.0,
		    new_ctrl->color_s_stride, nobj->surface.color.sorder,
		    0.0, 1.0, new_ctrl->color_t_stride,
		    nobj->surface.color.torder,
		    *(new_ctrl->color_offsets + offset));
	 }
	 if (do_normal) {
	    glMap2f(nobj->surface.normal.type, 0.0, 1.0,
		    new_ctrl->normal_s_stride, nobj->surface.normal.sorder,
		    0.0, 1.0, new_ctrl->normal_t_stride,
		    nobj->surface.normal.torder,
		    *(new_ctrl->normal_offsets + offset));
	 }
	 if (do_texture) {
	    glMap2f(nobj->surface.texture.type, 0.0, 1.0,
		    new_ctrl->texture_s_stride, nobj->surface.texture.sorder,
		    0.0, 1.0, new_ctrl->texture_t_stride,
		    nobj->surface.texture.torder,
		    *(new_ctrl->texture_offsets + offset));
	 }
/*			glMapGrid2f(sfactors[j*3+0],0.0,1.0,tfactors[i*3+0],0.0,1.0);
			glEvalMesh2(display_mode,0,sfactors[j*3+0],0,tfactors[i*3+0]);*/
	 nurbs_map_bezier(display_mode, sfactors, tfactors, s_bezier_cnt,
			  t_bezier_cnt, j, i);
      }
   }
}



/* draw NURBS surface in OUTLINE POLYGON mode */
#if 0
static void
draw_patch_mode(GLenum display_mode, GLUnurbsObj * nobj,
		new_ctrl_type * new_ctrl, GLint * sfactors, GLint * tfactors)
{
   GLsizei offset;
   GLint t_bezier_cnt, s_bezier_cnt;
   GLboolean do_color, do_normal, do_texture;
   GLint i, j;

   t_bezier_cnt = new_ctrl->t_bezier_cnt;
   s_bezier_cnt = new_ctrl->s_bezier_cnt;
   glEnable(nobj->surface.geom.type);
   if (new_ctrl->color_ctrl) {
      glEnable(nobj->surface.color.type);
      do_color = GL_TRUE;
   }
   else
      do_color = GL_FALSE;
   if (new_ctrl->normal_ctrl) {
      glEnable(nobj->surface.normal.type);
      do_normal = GL_TRUE;
   }
   else
      do_normal = GL_FALSE;
   if (new_ctrl->texture_ctrl) {
      glEnable(nobj->surface.texture.type);
      do_texture = GL_TRUE;
   }
   else
      do_texture = GL_FALSE;
   for (j = 0; j < s_bezier_cnt; j++) {
      for (i = 0; i < t_bezier_cnt; i++) {
	 offset = j * t_bezier_cnt + i;
	 if (fine_culling_test_3D(nobj, *(new_ctrl->geom_offsets + offset),
				  nobj->surface.geom.sorder,
				  nobj->surface.geom.torder,
				  new_ctrl->geom_s_stride,
				  new_ctrl->geom_t_stride,
				  nobj->surface.geom.dim)) continue;
	 glMap2f(nobj->surface.geom.type, 0.0, 1.0, new_ctrl->geom_s_stride,
		 nobj->surface.geom.sorder, 0.0, 1.0, new_ctrl->geom_t_stride,
		 nobj->surface.geom.torder,
		 *(new_ctrl->geom_offsets + offset));
	 if (do_color) {
	    glMap2f(nobj->surface.color.type, 0.0, 1.0,
		    new_ctrl->color_s_stride, nobj->surface.color.sorder,
		    0.0, 1.0, new_ctrl->color_t_stride,
		    nobj->surface.color.torder,
		    *(new_ctrl->color_offsets + offset));
	 }
	 if (do_normal) {
	    glMap2f(nobj->surface.normal.type, 0.0, 1.0,
		    new_ctrl->normal_s_stride, nobj->surface.normal.sorder,
		    0.0, 1.0, new_ctrl->normal_t_stride,
		    nobj->surface.normal.torder,
		    *(new_ctrl->normal_offsets + offset));
	 }
	 if (do_texture) {
	    glMap2f(nobj->surface.texture.type, 0.0, 1.0,
		    new_ctrl->texture_s_stride, nobj->surface.texture.sorder,
		    0.0, 1.0, new_ctrl->texture_t_stride,
		    nobj->surface.texture.torder,
		    *(new_ctrl->texture_offsets + offset));
	 }
	 nurbs_map_bezier(display_mode, sfactors, tfactors, s_bezier_cnt,
			  t_bezier_cnt, i, j);
/*			glMapGrid2f(sfactors[j],0.0,1.0,tfactors[i],0.0,1.0);
			glEvalMesh2(display_mode,0,sfactors[j],0,tfactors[i]);*/
      }
   }
}
#endif



static void
init_new_ctrl(new_ctrl_type * p)
{
   p->geom_ctrl = p->color_ctrl = p->normal_ctrl = p->texture_ctrl = NULL;
   p->geom_offsets = p->color_offsets = p->normal_offsets =
      p->texture_offsets = NULL;
   p->s_bezier_cnt = p->t_bezier_cnt = 0;
}


static GLenum
augment_new_ctrl(GLUnurbsObj * nobj, new_ctrl_type * p)
{
   GLsizei offset_size;
   GLint i, j;

   p->s_bezier_cnt = (p->geom_s_pt_cnt) / (nobj->surface.geom.sorder);
   p->t_bezier_cnt = (p->geom_t_pt_cnt) / (nobj->surface.geom.torder);
   offset_size = (p->s_bezier_cnt) * (p->t_bezier_cnt);
   p->geom_t_stride = nobj->surface.geom.dim;
   p->geom_s_stride = (p->geom_t_pt_cnt) * (nobj->surface.geom.dim);
   p->color_t_stride = nobj->surface.color.dim;
   p->color_s_stride = (p->color_t_pt_cnt) * (nobj->surface.color.dim);
   p->normal_t_stride = nobj->surface.normal.dim;
   p->normal_s_stride = (p->normal_t_pt_cnt) * (nobj->surface.normal.dim);
   p->texture_t_stride = nobj->surface.texture.dim;
   p->texture_s_stride = (p->texture_t_pt_cnt) * (nobj->surface.texture.dim);
   if (
       (p->geom_offsets =
	(GLfloat **) malloc(sizeof(GLfloat *) * offset_size)) == NULL) {
      call_user_error(nobj, GLU_OUT_OF_MEMORY);
      return GLU_ERROR;
   }
   if (p->color_ctrl)
      if (
	  (p->color_offsets =
	   (GLfloat **) malloc(sizeof(GLfloat *) * offset_size)) == NULL) {
	 free_new_ctrl(p);
	 call_user_error(nobj, GLU_OUT_OF_MEMORY);
	 return GLU_ERROR;
      }
   if (p->normal_ctrl)
      if (
	  (p->normal_offsets =
	   (GLfloat **) malloc(sizeof(GLfloat *) * offset_size)) == NULL) {
	 free_new_ctrl(p);
	 call_user_error(nobj, GLU_OUT_OF_MEMORY);
	 return GLU_ERROR;
      }
   if (p->texture_ctrl)
      if (
	  (p->texture_offsets =
	   (GLfloat **) malloc(sizeof(GLfloat *) * offset_size)) == NULL) {
	 free_new_ctrl(p);
	 call_user_error(nobj, GLU_OUT_OF_MEMORY);
	 return GLU_ERROR;
      }
   for (i = 0; i < p->s_bezier_cnt; i++)
      for (j = 0; j < p->t_bezier_cnt; j++)
	 *(p->geom_offsets + i * (p->t_bezier_cnt) + j) =
	    p->geom_ctrl + i * (nobj->surface.geom.sorder) *
	    (nobj->surface.geom.dim) * (p->geom_t_pt_cnt) +
	    j * (nobj->surface.geom.dim) * (nobj->surface.geom.torder);
   if (p->color_ctrl)
      for (i = 0; i < p->s_bezier_cnt; i++)
	 for (j = 0; j < p->t_bezier_cnt; j++)
	    *(p->color_offsets + i * (p->t_bezier_cnt) + j) =
	       p->color_ctrl + i * (nobj->surface.color.sorder) *
	       (nobj->surface.color.dim) * (p->color_t_pt_cnt) +
	       j * (nobj->surface.color.dim) * (nobj->surface.color.torder);
   if (p->normal_ctrl)
      for (i = 0; i < p->s_bezier_cnt; i++)
	 for (j = 0; j < p->t_bezier_cnt; j++)
	    *(p->normal_offsets + i * (p->t_bezier_cnt) + j) =
	       p->normal_ctrl + i * (nobj->surface.normal.sorder) *
	       (nobj->surface.normal.dim) * (p->normal_t_pt_cnt) +
	       j * (nobj->surface.normal.dim) * (nobj->surface.normal.torder);
   if (p->texture_ctrl)
      for (i = 0; i < p->s_bezier_cnt; i++)
	 for (j = 0; j < p->t_bezier_cnt; j++)
	    *(p->texture_offsets + i * (p->t_bezier_cnt) + j) =
	       p->texture_ctrl + i * (nobj->surface.texture.sorder) *
	       (nobj->surface.texture.dim) * (p->texture_t_pt_cnt) +
	       j * (nobj->surface.texture.dim) *
	       (nobj->surface.texture.torder);
   return GLU_NO_ERROR;
}

/* main NURBS surface procedure */
void
do_nurbs_surface(GLUnurbsObj * nobj)
{
   GLint *sfactors, *tfactors;
   new_ctrl_type new_ctrl;

   /* test user supplied data */
   if (test_nurbs_surfaces(nobj) != GLU_NO_ERROR)
      return;

   init_new_ctrl(&new_ctrl);

   if (convert_surfs(nobj, &new_ctrl) != GLU_NO_ERROR)
      return;
   if (augment_new_ctrl(nobj, &new_ctrl) != GLU_NO_ERROR)
      return;
   switch (nobj->sampling_method) {
   case GLU_PATH_LENGTH:
      if (glu_do_sampling_3D(nobj, &new_ctrl, &sfactors, &tfactors) !=
	  GLU_NO_ERROR) {
	 free_new_ctrl(&new_ctrl);
	 return;
      }
      break;
   case GLU_DOMAIN_DISTANCE:
      if (glu_do_sampling_uv(nobj, &new_ctrl, &sfactors, &tfactors) !=
	  GLU_NO_ERROR) {
	 free_new_ctrl(&new_ctrl);
	 return;
      }
      break;
   case GLU_PARAMETRIC_ERROR:
      if (glu_do_sampling_param_3D(nobj, &new_ctrl, &sfactors, &tfactors) !=
	  GLU_NO_ERROR) {
	 free_new_ctrl(&new_ctrl);
	 return;
      }
      break;
   default:
      abort();
   }
   glFrontFace(GL_CW);
   switch (nobj->display_mode) {
   case GLU_FILL:
/*			if(polygon_trimming(nobj,&new_ctrl,sfactors,tfactors)==GLU_NO_ERROR)*/
      draw_polygon_mode(GL_FILL, nobj, &new_ctrl, sfactors, tfactors);
      break;
   case GLU_OUTLINE_POLYGON:
      /* TODO - missing trimming handeling */
/* just for now - no OUTLINE_PATCH mode 
			draw_patch_mode(GL_LINE,nobj,&new_ctrl,sfactors,tfactors);
			break; */
   case GLU_OUTLINE_PATCH:
/*			if(polygon_trimming(nobj,&new_ctrl,sfactors,tfactors)==GLU_NO_ERROR)*/
      draw_polygon_mode(GL_LINE, nobj, &new_ctrl, sfactors, tfactors);
      break;
   default:
      abort();			/* TODO: is this OK? */
   }
   free(sfactors);
   free(tfactors);
   free_new_ctrl(&new_ctrl);
}
