[clutter] Migrated internal ClutterBezier to float.



commit 7c6d0251dd19328c911a02c3cafdac55859257ee
Author: Erick Pérez Castellanos <erick red gmail com>
Date:   Sat Apr 13 12:50:33 2013 -0400

    Migrated internal ClutterBezier to float.
    
    Initial stage of ClutterPath migration to using float.
    Naive implementation of ClutterBezier, for now the points along
    the curve doesn't move at a regular speed. It is required a more
    precise calculation of the length of the curve for that to happen.
    Anyway the old implementation worked like this.

 clutter/clutter-bezier.c |  481 ++++++++++++++++------------------------------
 clutter/clutter-bezier.h |   42 ++--
 clutter/clutter-path.c   |    3 +-
 3 files changed, 185 insertions(+), 341 deletions(-)
---
diff --git a/clutter/clutter-bezier.c b/clutter/clutter-bezier.c
index 7b6ba3c..304d051 100644
--- a/clutter/clutter-bezier.c
+++ b/clutter/clutter-bezier.c
@@ -6,6 +6,7 @@
  * Authored By Tomas Frydrych  <tf openedhand com>
  *
  * Copyright (C) 2007 OpenedHand
+ * Copyright (C) 2013 Erick Pérez Castellanos <erick red gmail com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,97 +22,105 @@
  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <glib.h>
 #include <string.h>
+#include <math.h>
+
+#include <glib.h>
 #include "clutter-bezier.h"
 #include "clutter-debug.h"
 
-/*
- * We have some experimental code here to allow for constant velocity
- * movement of actors along the bezier path, this macro enables it.
- */
-#undef CBZ_L2T_INTERPOLATION
-
 /****************************************************************************
- * ClutterBezier -- represenation of a cubic bezier curve                   *
+ * ClutterBezier -- representation of a cubic bezier curve                  *
  * (private; a building block for the public bspline object)                *
  ****************************************************************************/
 
 /*
- * The t parameter of the bezier is from interval <0,1>, so we can use
- * 14.18 format and special multiplication functions that preserve
- * more of the least significant bits but would overflow if the value
- * is > 1
+ * Constants for sampling of the bezier. Float point.
  */
-#define CBZ_T_Q 18
-#define CBZ_T_ONE (1 << CBZ_T_Q)
-#define CBZ_T_MUL(x,y) ((((x) >> 3) * ((y) >> 3)) >> 12)
-#define CBZ_T_POW2(x) CBZ_T_MUL (x, x)
-#define CBZ_T_POW3(x) CBZ_T_MUL (CBZ_T_POW2 (x), x)
-#define CBZ_T_DIV(x,y) ((((x) << 9)/(y)) << 9)
-
-/*
- * Constants for sampling of the bezier
- */
-#define CBZ_T_SAMPLES 128
-#define CBZ_T_STEP (CBZ_T_ONE / CBZ_T_SAMPLES)
-#define CBZ_L_STEP (CBZ_T_ONE / CBZ_T_SAMPLES)
-
-typedef gint32 _FixedT;
+#define CBZ_T_SAMPLES 128.0
+#define CBZ_T_STEP (1.0 / CBZ_T_SAMPLES)
 
 /*
  * This is a private type representing a single cubic bezier
  */
 struct _ClutterBezier
 {
-  /*
-   * bezier coefficients -- these are calculated using multiplication and
-   * addition from integer input, so these are also integers
-   */
-  gint ax;
-  gint bx;
-  gint cx;
-  gint dx;
-
-  gint ay;
-  gint by;
-  gint cy;
-  gint dy;
-    
+  /* bezier coefficients */
+  gfloat ax;
+  gfloat bx;
+  gfloat cx;
+  gfloat dx;
+
+  gfloat ay;
+  gfloat by;
+  gfloat cy;
+  gfloat dy;
+
   /* length of the bezier */
-  guint length;
-
-#ifdef CBZ_L2T_INTERPOLATION
-  /*
-   * coefficients for the L -> t bezier; these are calculated from fixed
-   * point input, and more specifically numbers that have been normalised
-   * to fit <0,1>, so these are also fixed point, and we can used the
-   * _FixedT type here.
-   */
-  _FixedT La;
-  _FixedT Lb;
-  _FixedT Lc;
-  /*  _FixedT Ld; == 0 */
-#endif
+  gfloat length;
 };
 
+static gfloat
+_clutter_bezier_t2x (const ClutterBezier *b,
+                    gfloat               t)
+{
+  return b->ax * (1 - t) * (1 - t) * (1 - t) +
+    b->bx * (1 - t) * (1 - t) * t +
+    b->cx * (1 - t) * t * t +
+    b->dx * t * t * t;
+}
+
+static gfloat
+_clutter_bezier_t2y (const ClutterBezier *b,
+                    gfloat               t)
+{
+  return b->ay * (1 - t) * (1 - t) * (1 - t) +
+    b->by * (1 - t) * (1 - t) * t +
+    b->cy * (1 - t) * t * t +
+    b->dy * t * t * t;
+}
+
+/*
+ * _clutter_bezier_new:
+ *
+ * Allocate the bezier
+ *
+ * Return value: The new bezier object.
+ */
 ClutterBezier *
 _clutter_bezier_new (void)
 {
   return g_slice_new0 (ClutterBezier);
 }
 
+/*
+ * _clutter_bezier_free:
+ * @b: The bezier to free
+ *
+ * Free the object
+ */
 void
-_clutter_bezier_free (ClutterBezier * b)
+_clutter_bezier_free (ClutterBezier *b)
 {
   if (G_LIKELY (b))
-    {
-      g_slice_free (ClutterBezier, b);
-    }
+    g_slice_free (ClutterBezier, b);
 }
 
+/*
+ * _clutter_bezier_clone_and_move:
+ * @b: A #ClutterBezier for cloning
+ * @x: The x coordinates of the new end of the bezier
+ * @y: The y coordinates of the new end of the bezier
+ *
+ * Clone the bezier and move th end-point, leaving both control
+ * points in place.
+ *
+ * Return value: The new bezier object.
+ */
 ClutterBezier *
-_clutter_bezier_clone_and_move (const ClutterBezier *b, gint x, gint y)
+_clutter_bezier_clone_and_move (const ClutterBezier *b,
+                                gfloat               x,
+                               gfloat               y)
 {
   ClutterBezier * b2 = _clutter_bezier_new ();
   memcpy (b2, b, sizeof (ClutterBezier));
@@ -122,294 +131,124 @@ _clutter_bezier_clone_and_move (const ClutterBezier *b, gint x, gint y)
   return b2;
 }
 
-#ifdef CBZ_L2T_INTERPOLATION
 /*
- * L is relative advance along the bezier curve from interval <0,1>
+ * _clutter_bezier_advance:
+ * @b: A #ClutterBezier
+ * @L: A relative length
+ * @knot: The point whith the calculated position
+ *
+ * Advances along the bezier @b to relative length @L and returns the coordinances
+ * in @knot
  */
-static _FixedT
-_clutter_bezier_L2t (const ClutterBezier *b, _FixedT L)
+void
+_clutter_bezier_advance (const ClutterBezier *b,
+                         gfloat               L,
+                         ClutterKnot         *knot)
 {
-  _FixedT t = CBZ_T_MUL (b->La, CBZ_T_POW3(L))
-    +  CBZ_T_MUL (b->Lb, CBZ_T_POW2(L))
-    +  CBZ_T_MUL (b->Lc, L);
-  
-  if (t > CBZ_T_ONE)
-    t = CBZ_T_ONE;
-  else if (t < 0)
-    t = 0;
-  
-  return t;
-}
-#endif
+  gfloat t;
+  t = L;
 
-static gint
-_clutter_bezier_t2x (const ClutterBezier * b, _FixedT t)
-{
-  /*
-   * NB -- the int coefficients can be at most 8192 for the multiplication
-   * to work in this fashion due to the limits of the 14.18 fixed.
-   */
-  return ((b->ax*CBZ_T_POW3(t) + b->bx*CBZ_T_POW2(t) + b->cx*t) >> CBZ_T_Q)
-    + b->dx;
-}
+  knot->x = _clutter_bezier_t2x (b, t);
+  knot->y = _clutter_bezier_t2y (b, t);
 
-static gint
-_clutter_bezier_t2y (const ClutterBezier * b, _FixedT t)
-{
-  /*
-   * NB -- the int coefficients can be at most 8192 for the multiplication
-   * to work in this fashion due to the limits of the 14.18 fixed.
-   */
-  return ((b->ay*CBZ_T_POW3(t) + b->by*CBZ_T_POW2(t) + b->cy*t) >> CBZ_T_Q)
-    + b->dy;
+  CLUTTER_NOTE (MISC,
+               "advancing to relative point {%d,%d} by moving a distance equals to: %f, with t: %f",
+               knot->x, knot->y, L, t);
 }
 
 /*
- * Advances along the bezier to relative length L and returns the coordinances
- * in knot
+ * _clutter_bezier_init:
+ * @b: A #ClutterBezier
+ * @x_0: x coordinates of the start point of the cubic bezier
+ * @y_0: y coordinates of the start point of the cubic bezier
+ * @x_1: x coordinates of the first control point
+ * @y_1: y coordinates of the first control point
+ * @x_2: x coordinates of the second control point
+ * @y_2: y coordinates of the second control point
+ * @x_3: x coordinates of the end point of the cubic bezier
+ * @y_3: y coordinates of the end point of the cubic bezier
+ *
+ * Initialize the data of the bezier object @b.
  */
 void
-_clutter_bezier_advance (const ClutterBezier *b, gint L, ClutterKnot * knot)
-{
-#ifdef CBZ_L2T_INTERPOLATION
-  _FixedT t = clutter_bezier_L2t (b, L);
-#else
-  _FixedT t = L;
-#endif
-  
-  knot->x = _clutter_bezier_t2x (b, t);
-  knot->y = _clutter_bezier_t2y (b, t);
-  
-  CLUTTER_NOTE (MISC, "advancing to relative pt %f: t %f, {%d,%d}",
-                (double) L / (double) CBZ_T_ONE,
-                (double) t / (double) CBZ_T_ONE,
-                knot->x, knot->y);
-}
-
-void
 _clutter_bezier_init (ClutterBezier *b,
-                    gint x_0, gint y_0,
-                    gint x_1, gint y_1,
-                    gint x_2, gint y_2,
-                    gint x_3, gint y_3)
+                      gfloat         x_0,
+                      gfloat         y_0,
+                      gfloat         x_1,
+                      gfloat         y_1,
+                      gfloat         x_2,
+                      gfloat         y_2,
+                      gfloat         x_3,
+                      gfloat         y_3)
 {
-  _FixedT t;
-  int i;
-  int xp = x_0;
-  int yp = y_0;
-  _FixedT length [CBZ_T_SAMPLES + 1];
-
-#ifdef CBZ_L2T_INTERPOLATION
-  int j, k;
-  _FixedT L;
-  _FixedT t_equalized [CBZ_T_SAMPLES + 1];
-#endif
-
-#if 0
-  g_debug ("Initializing bezier at {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
-           x0, y0, x1, y1, x2, y2, x3, y3);
-#endif
-  
-  b->dx = x_0;
-  b->dy = y_0;
-
-  b->cx = 3 * (x_1 - x_0);
-  b->cy = 3 * (y_1 - y_0);
-
-  b->bx = 3 * (x_2 - x_1) - b->cx;
-  b->by = 3 * (y_2 - y_1) - b->cy;
-
-  b->ax = x_3 - 3 * x_2 + 3 * x_1 - x_0;
-  b->ay = y_3 - 3 * y_2 + 3 * y_1 - y_0;
-
-#if 0
-  g_debug ("Cooeficients {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}",
-           b->ax, b->ay, b->bx, b->by, b->cx, b->cy, b->dx, b->dy);
-#endif
-  
-  /*
-   * Because of the way we do the multiplication in bezeir_t2x,y
-   * these coefficients need to be at most 0x1fff; this should be the case,
-   * I think, but have added this warning to catch any problems -- if it
-   * triggers, we need to change those two functions a bit.
-   */
-  if (b->ax > 0x1fff || b->bx > 0x1fff || b->cx > 0x1fff)
-    g_warning ("Calculated coefficents will result in multiplication "
-               "overflow in clutter_bezier_t2x and clutter_bezier_t2y.");
-
-  /*
-   * Sample the bezier with CBZ_T_SAMPLES and calculate length at
-   * each point.
-   *
-   * We are working with integers here, so we use the fast sqrti function.
-   */
-  length[0] = 0;
-    
+  gfloat t;
+  gint i;
+
+  gfloat xp;
+  gfloat yp;
+
+  CLUTTER_NOTE (MISC,
+               "Initializing bezier at {{%f,%f},{%f,%f},{%f,%f},{%f,%f}}",
+               x_0, y_0, x_1, y_1, x_2, y_2, x_3, y_3);
+
+  b->ax = x_0;
+  b->ay = y_0;
+  b->bx = 3 * x_1;
+  b->by = 3 * y_1;
+  b->cx = 3 * x_2;
+  b->cy = 3 * y_2;
+  b->dx = x_3;
+  b->dy = y_3;
+  b->length = 0.0;
+
+  CLUTTER_NOTE (MISC,
+               "Coefficients {{%f,%f},{%f,%f},{%f,%f},{%f,%f}}",
+               b->ax, b->ay, b->bx, b->by, b->cx, b->cy, b->dx, b->dy);
+
+  xp = b->ax;
+  yp = b->ay;
   for (t = CBZ_T_STEP, i = 1; i <= CBZ_T_SAMPLES; ++i, t += CBZ_T_STEP)
     {
-      int x = _clutter_bezier_t2x (b, t);
-      int y = _clutter_bezier_t2y (b, t);
-       
-      guint l = cogl_sqrti ((y - yp)*(y - yp) + (x - xp)*(x - xp));
-
-      l += length[i-1];
+      gfloat x = _clutter_bezier_t2x (b, t);
+      gfloat y = _clutter_bezier_t2y (b, t);
 
-      length[i] = l;
+      b->length += sqrtf ((y - yp)*(y - yp) + (x - xp)*(x - xp));
 
       xp = x;
       yp = y;
     }
 
-  b->length = length[CBZ_T_SAMPLES];
-
-#if 0
-  g_debug ("length %d", b->length);
-#endif
-  
-#ifdef CBZ_L2T_INTERPOLATION
-  /*
-   * Now normalize the length values, converting them into _FixedT
-   */
-  for (i = 0; i <= CBZ_T_SAMPLES; ++i)
-    {
-      length[i] = (length[i] << CBZ_T_Q) / b->length;
-    }
-
-  /*
-   * Now generate a L -> t table such that the L will equidistant
-   * over <0,1>
-   */
-  t_equalized[0] = 0;
-    
-  for (i = 1, j = 1, L = CBZ_L_STEP; i < CBZ_T_SAMPLES; ++i, L += CBZ_L_STEP)
-    {
-      _FixedT l1, l2;
-      _FixedT d1, d2, d;
-      _FixedT t1, t2;
-       
-      /* find the band for our L */
-      for (k = j; k < CBZ_T_SAMPLES; ++k)
-       {
-          if (L < length[k])
-            break;
-       }
-
-      /*
-       * Now we know that L is from (length[k-1],length[k]>
-       * We remember k-1 in order not to have to iterate over the
-       * whole length array in the next iteration of the main loop
-       */
-      j = k - 1;
-
-      /*
-       * Now interpolate equlised t as a weighted average
-       */
-      l1 = length[k-1];
-      l2 = length[k];
-      d1 = l2 - L;
-      d2 = L - l1;
-      d = l2 - l1;
-      t1 = (k - 1) * CBZ_T_STEP;
-      t2 = k * CBZ_T_STEP;
-       
-      t_equalized[i] = (t1*d1 + t2*d2)/d;
-
-      if (t_equalized[i] < t_equalized[i-1])
-        g_debug ("wrong t: L %f, l1 %f, l2 %f, t1 %f, t2 %f",
-                 (double) (L)/(double)CBZ_T_ONE,
-                 (double) (l1)/(double)CBZ_T_ONE,
-                 (double) (l2)/(double)CBZ_T_ONE,
-                 (double) (t1)/(double)CBZ_T_ONE,                 
-                 (double) (t2)/(double)CBZ_T_ONE);
-      
-    }
-
-  t_equalized[CBZ_T_SAMPLES] = CBZ_T_ONE;
-
-  /* We now fit a bezier -- at this stage, do a single fit through our values
-   * at 0, 1/3, 2/3 and 1
-   *
-   * FIXME -- do we need to  use a better fitting approach to choose the best
-   * beziere. The actual curve we acquire this way is not too bad shapwise,
-   * but (probably due to rounding errors) the resulting curve no longer
-   * satisfies the necessary condition that for L2 > L1, t2 > t1, which 
-   * causes oscilation.
-   */
-
-#if 0
-  /*
-   * These are the control points we use to calculate the curve coefficients
-   * for bezier t(L); these are not needed directly, but are implied in the
-   * calculations below.
-   *
-   * (p0 is 0,0, and p3 is 1,1)
-   */
-  p1 = (18 * t_equalized[CBZ_T_SAMPLES/3] -
-        9 * t_equalized[2*CBZ_T_SAMPLES/3] +
-        2 << CBZ_T_Q) / 6;
-
-  p2 = (18 * t_equalized[2*CBZ_T_SAMPLES/3] -
-        9 * t_equalized[CBZ_T_SAMPLES/3] -
-        (5 << CBZ_T_Q)) / 6;
-#endif
-    
-  b->Lc = (18 * t_equalized[CBZ_T_SAMPLES/3] -
-           9 * t_equalized[2*CBZ_T_SAMPLES/3] +
-           (2 << CBZ_T_Q)) >> 1;
-    
-  b->Lb = (36 * t_equalized[2*CBZ_T_SAMPLES/3] -
-           45 * t_equalized[CBZ_T_SAMPLES/3] -
-           (9 << CBZ_T_Q)) >> 1;
-
-  b->La = ((27 * (t_equalized[CBZ_T_SAMPLES/3] -
-                 t_equalized[2*CBZ_T_SAMPLES/3]) +
-            (7 << CBZ_T_Q)) >> 1) + CBZ_T_ONE;
-
-  g_debug ("t(1/3) %f, t(2/3) %f",
-           (double)t_equalized[CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE,
-           (double)t_equalized[2*CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE);
-
-  g_debug ("L -> t coefficients: %f, %f, %f",
-           (double)b->La/(double)CBZ_T_ONE,
-           (double)b->Lb/(double)CBZ_T_ONE,
-           (double)b->Lc/(double)CBZ_T_ONE);
-
-
-  /*
-   * For debugging, you can load these values into a spreadsheet and graph
-   * them to see how well the approximation matches the data
-   */
-  for (i = 0; i < CBZ_T_SAMPLES; ++i)
-    {
-      g_print ("%f, %f, %f\n",
-               (double)(i*CBZ_T_STEP)/(double)CBZ_T_ONE,
-               (double)(t_equalized[i])/(double)CBZ_T_ONE,
-               (double)(clutter_bezier_L2t(b,i*CBZ_T_STEP))/(double)CBZ_T_ONE);
-    }
-#endif
+  CLUTTER_NOTE (MISC, "length %f", b->length);
 }
 
 /*
- * Moves a control point at indx to location represented by knot
+ * _clutter_bezier_adjust:
+ * @b: A #ClutterBezier object
+ * @knot: The new position of the control point
+ @ @indx: The index of the control point you want to move.
+ *
+ * Moves a control point at index @indx to location represented by @knot
  */
 void
-_clutter_bezier_adjust (ClutterBezier * b, ClutterKnot * knot, guint indx)
+_clutter_bezier_adjust (ClutterBezier *b,
+                       ClutterKnot   *knot,
+                       guint          indx)
 {
-  guint x[4], y[4];
+  gfloat x[4], y[4];
 
   g_assert (indx < 4);
-    
-  x[0] = b->dx;
-  y[0] = b->dy;
 
-  x[1] = b->cx / 3 + x[0];
-  y[1] = b->cy / 3 + y[0];
+  x[0] = b->ax;
+  y[0] = b->ay;
+
+  x[1] = b->bx;
+  y[1] = b->by;
 
-  x[2] = b->bx / 3 + b->cx + x[1];
-  y[2] = b->by / 3 + b->cy + y[1];
+  x[2] = b->cx;
+  y[2] = b->cy;
 
-  x[3] = b->ax + x[0] + b->cx + b->bx;
-  y[3] = b->ay + y[0] + b->cy + b->by;
+  x[3] = b->dx;
+  y[3] = b->dy;
 
   x[indx] = knot->x;
   y[indx] = knot->y;
@@ -417,7 +256,13 @@ _clutter_bezier_adjust (ClutterBezier * b, ClutterKnot * knot, guint indx)
   _clutter_bezier_init (b, x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3]);
 }
 
-guint
+/*
+ * _clutter_bezier_get_length:
+ * @b: A #ClutterBezier
+ *
+ * Return value: Returns the length of the bezier
+ */
+gfloat
 _clutter_bezier_get_length (const ClutterBezier *b)
 {
   return b->length;
diff --git a/clutter/clutter-bezier.h b/clutter/clutter-bezier.h
index df9f617..885372f 100644
--- a/clutter/clutter-bezier.h
+++ b/clutter/clutter-bezier.h
@@ -6,6 +6,7 @@
  * Authored By Tomas Frydrych  <tf openedhand com>
  *
  * Copyright (C) 2006, 2007 OpenedHand
+ * Copyright (C) 2013 Erick Pérez Castellanos <erick red gmail com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -29,36 +30,35 @@
 
 G_BEGIN_DECLS
 
-/* This is used in _clutter_bezier_advance to represent the full
-   length of the bezier curve. Anything less than that represents a
-   fraction of the length */
-#define CLUTTER_BEZIER_MAX_LENGTH (1 << 18)
-
 typedef struct _ClutterBezier ClutterBezier;
 
-ClutterBezier *_clutter_bezier_new ();
+ClutterBezier *_clutter_bezier_new            ();
 
-void           _clutter_bezier_free (ClutterBezier * b);
+void           _clutter_bezier_free           (ClutterBezier       *b);
 
 ClutterBezier *_clutter_bezier_clone_and_move (const ClutterBezier *b,
-                                               gint           x,
-                                               gint           y);
+                                               gfloat               x,
+                                               gfloat               y);
 
-void           _clutter_bezier_advance (const ClutterBezier *b,
-                                        gint           L,
-                                        ClutterKnot   *knot);
+void           _clutter_bezier_advance        (const ClutterBezier *b,
+                                               gfloat               L,
+                                               ClutterKnot         *knot);
 
-void           _clutter_bezier_init (ClutterBezier *b,
-                                     gint x_0, gint y_0,
-                                     gint x_1, gint y_1,
-                                     gint x_2, gint y_2,
-                                     gint x_3, gint y_3);
+void           _clutter_bezier_init           (ClutterBezier       *b,
+                                               gfloat               x_0,
+                                               gfloat               y_0,
+                                               gfloat               x_1,
+                                               gfloat               y_1,
+                                               gfloat               x_2,
+                                               gfloat               y_2,
+                                               gfloat               x_3,
+                                               gfloat               y_3);
 
-void           _clutter_bezier_adjust (ClutterBezier *b,
-                                       ClutterKnot   *knot,
-                                       guint          indx);
+void           _clutter_bezier_adjust         (ClutterBezier       *b,
+                                               ClutterKnot         *knot,
+                                               guint                indx);
 
-guint          _clutter_bezier_get_length (const ClutterBezier *b);
+gfloat         _clutter_bezier_get_length     (const ClutterBezier *b);
 
 G_END_DECLS
 
diff --git a/clutter/clutter-path.c b/clutter/clutter-path.c
index 85c7b13..86aea54 100644
--- a/clutter/clutter-path.c
+++ b/clutter/clutter-path.c
@@ -1458,8 +1458,7 @@ clutter_path_get_position (ClutterPath *path,
       else
         {
           _clutter_bezier_advance (node->bezier,
-                                   point_distance * CLUTTER_BEZIER_MAX_LENGTH
-                                   / node->length,
+                                   (gfloat) point_distance / (gfloat) node->length,
                                    position);
         }
       break;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]