/* This is -*- C -*- */
/* $Id: guppi-curve.c,v 1.12 2001/08/21 02:29:57 trow Exp $ */

/*
 * guppi-curve.c
 *
 * Copyright (C) 2000 EMC Capital Management, Inc.
 *
 * Developed by Jon Trowbridge <trow@gnu.org> and
 * Havoc Pennington <hp@pobox.com>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <config.h>
#include "guppi-curve.h"

#include <gnan.h>
#include <guppi-convenient.h>

static GtkObjectClass *parent_class = NULL;

static void
guppi_curve_finalize (GtkObject *obj)
{
  if (parent_class->finalize)
    parent_class->finalize (obj);
}

static void
guppi_curve_class_init (GuppiCurveClass *klass)
{
  GtkObjectClass *object_class = (GtkObjectClass *) klass;

  parent_class = gtk_type_class (GUPPI_TYPE_DATA);

  object_class->finalize = guppi_curve_finalize;
}

static void
guppi_curve_init (GuppiCurve *obj)
{

}

GtkType guppi_curve_get_type (void)
{
  static GtkType guppi_curve_type = 0;
  if (!guppi_curve_type) {
    static const GtkTypeInfo guppi_curve_info = {
      "GuppiCurve",
      sizeof (GuppiCurve),
      sizeof (GuppiCurveClass),
      (GtkClassInitFunc) guppi_curve_class_init,
      (GtkObjectInitFunc) guppi_curve_init,
      NULL, NULL, (GtkClassInitFunc) NULL
    };
    guppi_curve_type = gtk_type_unique (GUPPI_TYPE_DATA, &guppi_curve_info);
  }
  return guppi_curve_type;
}

void
guppi_curve_parameter_bounds (GuppiCurve *curve, double *a, double *b)
{
  GuppiCurveClass *klass;

  g_return_if_fail (GUPPI_IS_CURVE (curve));

  if (a == NULL && b == NULL)
    return;

  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  if (klass->bounds) {
    klass->bounds (curve, a, b);
  } else {
    if (a) *a = -G_INFINITY;
    if (b) *b =  G_INFINITY;
  }
}

double
guppi_curve_parameter_lower_bound (GuppiCurve *curve)
{
  double t;
  guppi_curve_parameter_bounds (curve, &t, NULL);
  return t;
}

double
guppi_curve_parameter_upper_bound (GuppiCurve *curve)
{
  double t;
  guppi_curve_parameter_bounds (curve, NULL, &t);
  return t;
}

gboolean
guppi_curve_parameter_in_bounds (GuppiCurve *curve, double t)
{
  double a, b;

  g_return_val_if_fail (GUPPI_IS_CURVE (curve), FALSE);

  guppi_curve_parameter_bounds (curve, &a, &b);

  return a <= t && t <= b;
}

double
guppi_curve_clamp_parameter (GuppiCurve *curve, double t)
{
  double a, b;

  g_return_val_if_fail (GUPPI_IS_CURVE (curve), FALSE);

  guppi_curve_parameter_bounds (curve, &a, &b);

  return CLAMP (t, a, b);
}

void
guppi_curve_get (GuppiCurve *curve, double t, double *x, double *y)
{
  GuppiCurveClass *klass;
  double a, b;

  g_return_if_fail (GUPPI_IS_CURVE (curve));

  if (x == NULL && y == NULL)
    return;

  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  if (klass->bounds) {
    klass->bounds (curve, &a, &b);
    t = CLAMP (t, a, b);
  }

  g_assert (klass->get);
  klass->get (curve, t, x, y);
}

double
guppi_curve_get_x (GuppiCurve *curve, double t)
{
  double x;

  g_return_val_if_fail (GUPPI_IS_CURVE (curve), 0);

  guppi_curve_get (curve, t, &x, NULL);
  return x;
}

double
guppi_curve_get_y (GuppiCurve *curve, double t)
{
  double y;

  g_return_val_if_fail (curve != NULL, 0);
  g_return_val_if_fail (GUPPI_IS_CURVE (curve), 0);

  guppi_curve_get (curve, t, NULL, &y);
  return y;
}

void
guppi_curve_get_bbox (GuppiCurve *curve, double t0, double t1,
		      double *x0, double *y0, double *x1, double *y1)
{
  GuppiCurveClass *klass;
  double a, b;

  g_return_if_fail (GUPPI_IS_CURVE (curve));

  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  guppi_2sort (&t0, &t1);

  if (klass->bounds) {
    klass->bounds (curve, &a, &b);
    t0 = CLAMP (t0, a, b);
    t1 = CLAMP (t1, a, b);
  }

  if (klass->bbox) {

    klass->bbox (curve, t0, t1, x0, y0, x1, y1);

  } else {
    double x, y, bbx0 = 0, bbx1 = 0, bby0 = 0, bby1 = 0;
    const gint N = 200;
    gint i;

    g_assert (klass->get);

    /* An extremely ineffecient and hacky approximation */
    for (i = 0; i <= N; ++i) {
      double t = t0 + i * (t1 - t0) / N;

      klass->get (curve, t, &x, &y);
      if (i == 0) {
	bbx0 = bbx1 = x;
	bby0 = bby1 = y;
      } else {
	if (x < bbx0)
	  bbx0 = x;
	if (x > bbx1)
	  bbx1 = x;
	if (y < bby0)
	  bby0 = y;
	if (y > bby1)
	  bby1 = y;
      }
    }

    if (x0)
      *x0 = bbx0;
    if (x1)
      *x1 = bbx1;
    if (y0)
      *y0 = bby0;
    if (y1)
      *y1 = bby1;

  }
}

void
guppi_curve_clamp_to_bbox (GuppiCurve *curve,
			   double *t0, double *t1,
			   double x0, double y0, double x1, double y1)
{
  GuppiCurveClass *klass;
  double a, b, bt0=0, bt1=1, small_step;
  double x, y;
  gint i, total_steps;
  gboolean overall_done;

  g_return_if_fail (GUPPI_IS_CURVE (curve));
  
  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  guppi_2sort (&x0, &x1);
  guppi_2sort (&y0, &y1);
  
  guppi_curve_parameter_bounds (curve, &a, &b);

  if (t0) bt0 = *t0;
  if (t1) bt1 = *t1;
  
  if (! (klass->clamp && klass->clamp (curve, t0, t1, x0, y0, x1, y1))) {

    if (t0) *t0 = bt0;
    if (t1) *t1 = bt1;

    bt0 = a;
    bt1 = b;

    overall_done = FALSE;

    for (i = 0; i < 3 && !overall_done; ++i) {
      
      small_step = (bt1 - bt0)/20;
      total_steps = 0;
      
      if (t0) {
	gboolean done = FALSE, stepped = FALSE;
	while (!done && *t0 < bt1) {
	  guppi_curve_get (curve, *t0, &x, &y);
	  if (! (guppi_between (x0, x, x1) && guppi_between (y0, y, y1))) {
	    *t0 += small_step;
	    stepped = TRUE;
	    ++total_steps;
	  } else
	    done = TRUE;
	}
	if (stepped) {
	  *t0 -= small_step;
	  --total_steps;
	}
      }
      
      if (t1) {
	gboolean done = FALSE, stepped = FALSE;
	while (!done && *t1 > bt0) {
	  guppi_curve_get (curve, *t1, &x, &y);
	  if (! (guppi_between (x0, x, x1) && guppi_between (y0, y, y1))) {
	    *t1 -= small_step;
	    stepped = TRUE;
	    ++total_steps;
	  } else
	    done = TRUE;
	}
	if (stepped) {
	  *t1 += small_step;
	  --total_steps;
	}
      }
      
      if (t0 && t1 && total_steps > 0) {
	bt0 = *t0;
	bt1 = *t1;
      } else 
	overall_done = TRUE;
      
    }
  }

  if (t0) 
    *t0 = CLAMP (*t0, a, b);

  if (t1)
    *t1 = CLAMP (*t1, a, b);

}

void
guppi_curve_sample (GuppiCurve *curve,
		    const double *t_vec, gint t_stride, gsize N,
		    double *x_vec, gint x_stride,
		    double *y_vec, gint y_stride)
{
  GuppiCurveClass *klass;

  g_return_if_fail (GUPPI_IS_CURVE (curve));
  g_return_if_fail (t_vec != NULL);

  if (N == 0 || (x_vec == NULL && y_vec == NULL))
    return;

  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  if (klass->sample) {
    klass->sample (curve, t_vec, t_stride, N,
		   x_vec, x_stride, y_vec, y_stride);
  } else {
    const guint8 *t_ptr = (const guint8 *) t_vec;
    guint8 *x_ptr = (guint8 *) x_vec;
    guint8 *y_ptr = (guint8 *) y_vec;
    double a, b, t;

    guppi_curve_parameter_bounds (curve, &a, &b);
    
    g_assert (klass->get);

    while (N) {

      t = *(const double *) t_ptr;
      t = CLAMP (t, a, b);

      klass->get (curve, t, (double *) x_ptr, (double *) y_ptr);

      t_ptr += t_stride;
      if (x_ptr)
	x_ptr += x_stride;
      if (y_ptr)
	y_ptr += y_stride;

      --N;
    }

  }
}

void
guppi_curve_sample_uniformly (GuppiCurve *curve,
			      double t0, double t1, gsize N,
			      double *x_vec, gint x_stride,
			      double *y_vec, gint y_stride)
{
  GuppiCurveClass *klass;
  double a, b;

  g_return_if_fail (GUPPI_IS_CURVE (curve));

  if (N == 0 || (x_vec == NULL && y_vec == NULL))
    return;

  guppi_curve_parameter_bounds (curve, &a, &b);

  t0 = CLAMP (t0, a, b);
  t1 = CLAMP (t1, a, b);

  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  if (N == 1) {
    klass->get (curve, (t0 + t1) / 2, x_vec, y_vec);
    return;
  }

  if (klass->sample_uniformly) {

    klass->sample_uniformly (curve, t0, t1, N,
			     x_vec, x_stride, y_vec, y_stride);

  } else {
    double *t_vec = guppi_new (double, N);
    gsize i;

    for (i = 0; i < N; ++i)
      t_vec[i] = t0 + i * (t1 - t0) / (N - 1);

    guppi_curve_sample (curve, t_vec, sizeof (double), N,
			x_vec, x_stride, y_vec, y_stride);

    guppi_free (t_vec);

  }
}

void
guppi_curve_sample_uniformly_to_path (GuppiCurve *curve,
				      double t0, double t1, gsize N,
				      ArtVpath *path)
{
  gint i;

  g_return_if_fail (curve != NULL);
  g_return_if_fail (GUPPI_IS_CURVE (curve));
  g_return_if_fail (path != NULL);

  if (N == 0)
    return;

  guppi_curve_sample_uniformly (curve, t0, t1, N,
				&path[0].x, sizeof (ArtVpath),
				&path[0].y, sizeof (ArtVpath));

  path[0].code = ART_MOVETO_OPEN;
  for (i = 1; i < N; ++i)
    path[i].code = ART_LINETO;
  path[N].code = ART_END;

}

ArtVpath *
guppi_curve_approximate_to_path (GuppiCurve *curve,
				 double t0, double t1, 
				 double x_error, double y_error,
				 double x0, double y0, double x1, double y1,
				 double scale_x, double scale_y)
{
  GuppiCurveClass *klass;
  ArtVpath *path;

  g_return_val_if_fail (GUPPI_IS_CURVE (curve), NULL);
  g_return_val_if_fail (x_error > 0 && y_error > 0, NULL);

  guppi_2sort (&t0, &t1);

  klass = GUPPI_CURVE_CLASS (GTK_OBJECT (curve)->klass);

  if (klass->approx_to_path 
      && (path = klass->approx_to_path (curve, t0, t1, x_error, y_error,
					x0, y0, x1, y1,
					scale_x, scale_y)))
    return path;

  /* A stupid hack, for now. */

  path = guppi_new0 (ArtVpath, 21);
  guppi_curve_sample_uniformly_to_path (curve, t0, t1, 20, path);
  
  return path;
}

/* $Id: guppi-curve.c,v 1.12 2001/08/21 02:29:57 trow Exp $ */


syntax highlighted by Code2HTML, v. 0.9.1