[cogl] math: Adds an experimental euler API



commit df1915d95735f4d13489f955235f5e532161494a
Author: Robert Bragg <robert linux intel com>
Date:   Thu Feb 25 01:40:29 2010 +0000

    math: Adds an experimental euler API
    
    This adds an experimental CoglEuler data type and the following new
    functions:
    
        cogl_euler_init
        cogl_euler_init_from_matrix
        cogl_euler_init_from_quaternion
        cogl_euler_equal
        cogl_euler_copy
        cogl_euler_free
        cogl_quaternion_init_from_euler
    
    Since this is experimental API you need to define
    COGL_ENABLE_EXPERIMENTAL_API before including cogl.h

 cogl/Makefile.am                         |    2 +
 cogl/cogl-euler.c                        |  183 ++++++++++++++++++++++
 cogl/cogl-euler.h                        |  252 ++++++++++++++++++++++++++++++
 cogl/cogl-quaternion.c                   |   37 +++++
 cogl/cogl-quaternion.h                   |    6 +
 cogl/cogl-types.h                        |    8 +
 cogl/cogl.h                              |    1 +
 doc/reference/cogl-2.0/cogl-sections.txt |   11 ++
 doc/reference/cogl/cogl-sections.txt     |   12 ++
 9 files changed, 512 insertions(+), 0 deletions(-)
---
diff --git a/cogl/Makefile.am b/cogl/Makefile.am
index 8e66aa4..ae0d374 100644
--- a/cogl/Makefile.am
+++ b/cogl/Makefile.am
@@ -64,6 +64,7 @@ cogl_public_h = \
 	$(srcdir)/cogl-material-compat.h 	\
 	$(srcdir)/cogl-pipeline.h 		\
 	$(srcdir)/cogl-vector.h 		\
+	$(srcdir)/cogl-euler.h 			\
 	$(srcdir)/cogl-quaternion.h 		\
 	$(srcdir)/cogl-matrix.h 		\
 	$(srcdir)/cogl-offscreen.h 		\
@@ -231,6 +232,7 @@ cogl_sources_c = \
 	$(srcdir)/cogl-primitive.c			\
 	$(srcdir)/cogl-matrix.c				\
 	$(srcdir)/cogl-vector.c				\
+	$(srcdir)/cogl-euler.c				\
 	$(srcdir)/cogl-quaternion.c			\
 	$(srcdir)/cogl-matrix-private.h			\
 	$(srcdir)/cogl-matrix-stack.c			\
diff --git a/cogl/cogl-euler.c b/cogl/cogl-euler.c
new file mode 100644
index 0000000..afe24be
--- /dev/null
+++ b/cogl/cogl-euler.c
@@ -0,0 +1,183 @@
+/*
+ * Cogl
+ *
+ * An object oriented GL/GLES Abstraction/Utility Layer
+ *
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *   Robert Bragg <robert linux intel com>
+ */
+
+#include <cogl.h>
+#include <cogl-euler.h>
+
+#include <math.h>
+#include <string.h>
+
+void
+cogl_euler_init (CoglEuler *euler,
+                 float heading,
+                 float pitch,
+                 float roll)
+{
+  euler->heading = heading;
+  euler->pitch = pitch;
+  euler->roll = roll;
+}
+
+void
+cogl_euler_init_from_matrix (CoglEuler *euler,
+                             const CoglMatrix *matrix)
+{
+  /*
+   * Extracting a canonical Euler angle from a matrix:
+   * (where it is assumed the matrix contains no scaling, mirroring or
+   *  skewing)
+   *
+   * A Euler angle is a combination of three rotations around mutually
+   * perpendicular axis. For this algorithm they are:
+   *
+   * Heading: A rotation about the Y axis by an angle H:
+   * | cosH  0  sinH|
+   * |    0  1     0|
+   * |-sinH  0  cosH|
+   *
+   * Pitch: A rotation around the X axis by an angle P:
+   * |1     0      0|
+   * |0  cosP  -sinP|
+   * |0  sinP   cosP|
+   *
+   * Roll: A rotation about the Z axis by an angle R:
+   * |cosR -sinR  0|
+   * |sinR  cosR  0|
+   * |   0     0  1|
+   *
+   * When multiplied as matrices this gives:
+   *     | cosHcosR+sinHsinPsinR   sinRcosP  -sinHcosR+cosHsinPsinR|
+   * M = |-cosHsinR+sinHsinPcosR   cosRcosP   sinRsinH+cosHsinPcosB|
+   *     | sinHcosP               -sinP       cosHcosP             |
+   *
+   * Given that there are an infinite number of ways to represent
+   * a given orientation, the "canonical" Euler angle is any such that:
+   *  -180 < H < 180,
+   *  -180 < R < 180 and
+   *   -90 < P < 90
+   *
+   * M[3][2] = -sinP lets us immediately solve for P = asin(-M[3][2])
+   *   (Note: asin has a range of +-90)
+   * This gives cosP
+   * This means we can use M[3][1] to calculate sinH:
+   *   sinH = M[3][1]/cosP
+   * And use M[3][3] to calculate cosH:
+   *   cosH = M[3][3]/cosP
+   * This lets us calculate H = atan2(sinH,cosH), but we optimise this:
+   *   1st note: atan2(x, y) does: atan(x/y) and uses the sign of x and y to
+   *   determine the quadrant of the final angle.
+   *   2nd note: we know cosP is > 0 (ignoring cosP == 0)
+   *   Therefore H = atan2((M[3][1]/cosP) / (M[3][3]/cosP)) can be simplified
+   *   by skipping the division by cosP since it won't change the x/y ratio
+   *   nor will it change their sign. This gives:
+   *     H = atan2(M[3][1], M[3][3])
+   * R is computed in the same way as H from M[1][2] and M[2][2] so:
+   *     R = atan2(M[1][2], M[2][2])
+   * Note: If cosP were == 0 then H and R could not be calculated as above
+   * because all the necessary matrix values would == 0. In other words we are
+   * pitched vertically and so H and R would now effectively rotate around the
+   * same axis - known as "Gimbal lock". In this situation we will set all the
+   * rotation on H and set R = 0.
+   *   So with P = R = 0 we have cosP = 0, sinR = 0 and cosR = 1
+   *   We can substitute those into the above equation for M giving:
+   *   |    cosH      0     -sinH|
+   *   |sinHsinP      0  cosHsinP|
+   *   |       0  -sinP         0|
+   *   And calculate H as atan2 (-M[3][2], M[1][1])
+   */
+
+  float sinP;
+  float H; /* heading */
+  float P; /* pitch */
+  float R; /* roll */
+
+  /* NB: CoglMatrix provides struct members named according to the
+   * [row][column] indexed. So matrix->zx is row 3 column 1. */
+  sinP = -matrix->zy;
+
+  /* Determine the Pitch, avoiding domain errors with asin () which
+   * might occur due to previous imprecision in manipulating the
+   * matrix. */
+  if (sinP <= -1.0f)
+    P = -G_PI_2;
+  else if (sinP >= 1.0f)
+    P = G_PI_2;
+  else
+    P = asinf (sinP);
+
+  /* If P is too close to 0 then we have hit Gimbal lock */
+  if (sinP > 0.999f)
+    {
+      H = atan2f (-matrix->zy, matrix->xx);
+      R = 0;
+    }
+  else
+    {
+      H = atan2f (matrix->zx, matrix->zz);
+      R = atan2f (matrix->xy, matrix->yy);
+    }
+
+  euler->heading = H;
+  euler->pitch = P;
+  euler->roll = R;
+}
+
+gboolean
+cogl_euler_equal (gconstpointer v1, gconstpointer v2)
+{
+  const CoglEuler *a = v1;
+  const CoglEuler *b = v2;
+
+  g_return_val_if_fail (v1 != NULL, FALSE);
+  g_return_val_if_fail (v2 != NULL, FALSE);
+
+  if (v1 == v2)
+    return TRUE;
+
+  return (a->heading == b->heading &&
+          a->pitch == b->pitch &&
+          a->roll == b->roll);
+}
+
+CoglEuler *
+cogl_euler_copy (const CoglEuler *src)
+{
+  if (G_LIKELY (src))
+    {
+      CoglEuler *new = g_slice_new (CoglEuler);
+      memcpy (new, src, sizeof (float) * 3);
+      return new;
+    }
+  else
+    return NULL;
+}
+
+void
+cogl_euler_free (CoglEuler *euler)
+{
+  g_slice_free (CoglEuler, euler);
+}
+
diff --git a/cogl/cogl-euler.h b/cogl/cogl-euler.h
new file mode 100644
index 0000000..ca623e8
--- /dev/null
+++ b/cogl/cogl-euler.h
@@ -0,0 +1,252 @@
+/*
+ * Cogl
+ *
+ * An object oriented GL/GLES Abstraction/Utility Layer
+ *
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors:
+ *   Robert Bragg <robert linux intel com>
+ */
+
+#if !defined(__COGL_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <cogl/cogl.h> can be included directly."
+#endif
+
+#ifndef __COGL_EULER_H
+#define __COGL_EULER_H
+
+#include <cogl/cogl-types.h>
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:cogl-euler
+ * @short_description: Functions for initializing and manipulating
+ * euler angles.
+ *
+ * Euler angles are a simple representation of a 3 dimensional
+ * rotation; comprised of 3 ordered heading, pitch and roll rotations.
+ * An important thing to understand is that the axis of rotation
+ * belong to the object being rotated and so they also rotate as each
+ * of the heading, pitch and roll rotations are applied.
+ *
+ * One way to consider euler angles is to imagine controlling an
+ * aeroplane, where you first choose a heading (Such as flying south
+ * east), then you set the pitch (such as 30 degrees to take off) and
+ * then you might set a roll, by dipping the left, wing as you prepare
+ * to turn.
+ *
+ * They have some advantages and limitations that it helps to be
+ * aware of:
+ *
+ * Advantages:
+ * <itemizedlist>
+ * <listitem>
+ * Easy to understand and use, compared to quaternions and matrices,
+ * so may be a good choice for a user interface.
+ * <listitem>
+ * <listitem>
+ * Efficient storage, needing only 3 components any rotation can be
+ * represented.
+ * <note>Actually the #CoglEuler type isn't optimized for size because
+ * we may cache the equivalent #CoglQuaternion along with a euler
+ * rotation, but it would be trivial for an application to track the
+ * components of euler rotations in a packed float array if optimizing
+ * for size was important. The values could be passed to Cogl only when
+ * manipulation is necessary.</note>
+ * </listitem>
+ * </itemizedlist>
+ *
+ * Disadvantages:
+ * <itemizedlist>
+ * <listitem>
+ * Aliasing: it's possible to represent some rotations with multiple
+ * different heading, pitch and roll rotations.
+ * </listitem>
+ * <listitem>
+ * They can suffer from a problem called Gimbal Lock. A good
+ * explanation of this can be seen on wikipedia here:
+ * http://en.wikipedia.org/wiki/Gimbal_lock but basically two
+ * of the axis of rotation may become aligned and so you loose a
+ * degree of freedom. For example a pitch of +-90° would mean that
+ * heading and bank rotate around the same axis.
+ * </listitem>
+ * <listitem>
+ * If you use euler angles to orient something in 3D space and try to
+ * transition between orientations by interpolating the component
+ * angles you probably wont get the transitions you expect as they may
+ * not follow the shortest path between the two orientations.
+ * </listitem>
+ * <listitem>
+ * There's no standard to what order the component axis rotations are
+ * applied. The most common convention seems to be what we do in Cogl
+ * with heading (y-axis), pitch (x-axis) and then roll (z-axis), but
+ * other software might apply x-axis, y-axis then z-axis or any other
+ * order so you need to consider this if you are accepting euler
+ * rotations from some other software. Other software may also use
+ * slightly different aeronautical terms, such as "yaw" instead of
+ * "heading" or "bank" instead of "roll".
+ * </listitem>
+ * </itemlist>
+ *
+ * To minimize the aliasing issue we may refer to "Canonical Euler"
+ * angles where heading and roll are restricted to +- 180° and pitch is
+ * restricted to +- 90°. If pitch is +- 90° bank is set to 0°.
+ *
+ * Quaternions don't suffer from Gimbal Lock and they can be nicely
+ * interpolated between, their disadvantage is that they don't have an
+ * intuitive representation.
+ *
+ * A common practice is to accept angles in the intuitive Euler form
+ * and convert them to quaternions internally to avoid Gimbal Lock and
+ * handle interpolations. See cogl_quaternion_init_from_euler().
+ */
+
+/**
+ * CoglEuler:
+ * @heading: Angle to rotate around an object's y axis
+ * @pitch: Angle to rotate around an object's x axis
+ * @roll: Angle to rotate around an object's z axis
+ *
+ * Represents an ordered rotation first of @heading degrees around an
+ * object's y axis, then @pitch degrees around an object's x axis and
+ * finally @roll degrees around an object's z axis.
+ *
+ * <note>It's important to understand the that axis are associated
+ * with the object being rotated, so the axis also rotate in sequence
+ * with the rotations being applied.</note>
+ *
+ * The members of a #CoglEuler can be initialized, for example, with
+ * cogl_euler_init() and cogl_euler_init_from_quaternion ().
+ *
+ * You may also want to look at cogl_quaternion_init_from_euler() if
+ * you want to do interpolation between 3d rotations.
+ *
+ * Since: 2.0
+ */
+struct _CoglEuler
+{
+  /*< public > */
+  float heading;
+  float pitch;
+  float roll;
+
+  /*< private > */
+  /* May cached a quaternion here in the future */
+  float padding0;
+  float padding1;
+  float padding2;
+  float padding3;
+  float padding4;
+};
+
+/**
+ * cogl_euler_init:
+ * @euler: The #CoglEuler angle to initialize
+ * @heading: Angle to rotate around an object's y axis
+ * @pitch: Angle to rotate around an object's x axis
+ * @roll: Angle to rotate around an object's z axis
+ *
+ * Initializes @euler to represent a rotation of @x_angle degrees
+ * around the x axis, then @y_angle degrees around the y_axis and
+ * @z_angle degrees around the z axis.
+ *
+ * Since: 2.0
+ */
+void
+cogl_euler_init (CoglEuler *euler,
+                 float heading,
+                 float pitch,
+                 float roll);
+
+/**
+ * cogl_euler_init_from_matrix:
+ * @euler: The #CoglEuler angle to initialize
+ * @matrix: A #CoglMatrix containing a rotation, but no scaling,
+ *          mirroring or skewing.
+ *
+ * Extracts a euler rotation from the given @matrix and
+ * initializses @euler with the component x, y and z rotation angles.
+ */
+void
+cogl_euler_init_from_matrix (CoglEuler *euler,
+                             const CoglMatrix *matrix);
+
+/**
+ * cogl_euler_init_from_quaternion:
+ * @euler: The #CoglEuler angle to initialize
+ * @quaternion: A #CoglEuler with the rotation to initialize with
+ *
+ * Initializes a @euler rotation with the equivalent rotation
+ * represented by the given @quaternion.
+ */
+void
+cogl_euler_init_from_quaternion (CoglEuler *euler,
+                                 const CoglQuaternion *quaternion);
+
+/**
+ * cogl_euler_equal:
+ * @v1: The first euler angle to compare
+ * @v1: The second euler angle to compare
+ *
+ * Compares the two given euler angles @v1 and @v1 and it they are
+ * equal returns %TRUE else %FALSE.
+ *
+ * <note>This function only checks that all three components rotations
+ * are numerically equal, it does not consider that some rotations
+ * can be represented with different component rotations</note>
+ *
+ * Returns: %TRUE if @v1 and @v2 are equal else %FALSE.
+ * Since: 2.0
+ */
+gboolean
+cogl_euler_equal (gconstpointer v1, gconstpointer v2);
+
+/**
+ * cogl_euler_copy:
+ * @src: A #CoglEuler to copy
+ *
+ * Allocates a new #CoglEuler and initilizes it with the component
+ * angles of @src. The newly allocated euler should be freed using
+ * cogl_euler_free().
+ *
+ * Returns: A newly allocated #CoglEuler
+ * Since: 2.0
+ */
+CoglEuler *
+cogl_euler_copy (const CoglEuler *src);
+
+/**
+ * cogl_euler_free:
+ * @euler: A #CoglEuler allocated via cogl_euler_copy()
+ *
+ * Frees a #CoglEuler that was previously allocated using
+ * cogl_euler_copy().
+ *
+ * Since: 2.0
+ */
+void
+cogl_euler_free (CoglEuler *euler);
+
+G_END_DECLS
+
+#endif /* __COGL_EULER_H */
+
diff --git a/cogl/cogl-quaternion.c b/cogl/cogl-quaternion.c
index ae5c798..ff59720 100644
--- a/cogl/cogl-quaternion.c
+++ b/cogl/cogl-quaternion.c
@@ -39,6 +39,7 @@
 #include <cogl-quaternion-private.h>
 #include <cogl-matrix.h>
 #include <cogl-vector.h>
+#include <cogl-euler.h>
 
 #include <string.h>
 #include <math.h>
@@ -175,6 +176,42 @@ cogl_quaternion_init_from_z_rotation (CoglQuaternion *quaternion,
 }
 
 void
+cogl_quaternion_init_from_euler (CoglQuaternion *quaternion,
+                                 const CoglEuler *euler)
+{
+  /* NB: We are using quaternions to represent an axis (a), angle (ð???) pair
+   * in this form:
+   * [w=cos(ð???/2) ( x=sin(ð???/2)*a.x, y=sin(ð???/2)*a.y, z=sin(ð???/2)*a.x )]
+   */
+  float sin_heading =
+    sinf (euler->heading * _COGL_QUATERNION_DEGREES_TO_RADIANS * 0.5f);
+  float sin_pitch =
+    sinf (euler->pitch * _COGL_QUATERNION_DEGREES_TO_RADIANS * 0.5f);
+  float sin_roll =
+    sinf (euler->roll * _COGL_QUATERNION_DEGREES_TO_RADIANS * 0.5f);
+  float cos_heading =
+    cosf (euler->heading * _COGL_QUATERNION_DEGREES_TO_RADIANS * 0.5f);
+  float cos_pitch =
+    cosf (euler->pitch * _COGL_QUATERNION_DEGREES_TO_RADIANS * 0.5f);
+  float cos_roll =
+    cosf (euler->roll * _COGL_QUATERNION_DEGREES_TO_RADIANS * 0.5f);
+
+  quaternion->w =
+    cos_heading * cos_pitch * cos_roll +
+    sin_heading * sin_pitch * sin_roll;
+
+  quaternion->x =
+    cos_heading * sin_pitch * cos_roll +
+    sin_heading * cos_pitch * sin_roll;
+  quaternion->y =
+    sin_heading * cos_pitch * cos_roll -
+    cos_heading * sin_pitch * sin_roll;
+  quaternion->z =
+    cos_heading * cos_pitch * sin_roll -
+    sin_heading * sin_pitch * cos_roll;
+}
+
+void
 cogl_quaternion_init_from_quaternion (CoglQuaternion *quaternion,
                                       CoglQuaternion *src)
 {
diff --git a/cogl/cogl-quaternion.h b/cogl/cogl-quaternion.h
index da4e765..1b83bac 100644
--- a/cogl/cogl-quaternion.h
+++ b/cogl/cogl-quaternion.h
@@ -49,6 +49,8 @@ G_BEGIN_DECLS
  * rotation may become aligned and you loose a degree of freedom.
  * (<ulink url="http://en.wikipedia.org/wiki/Gimbal_lock"/>).
  */
+#include <cogl/cogl-vector.h>
+#include <cogl/cogl-euler.h>
 
 /**
  * CoglQuaternion:
@@ -244,6 +246,10 @@ void
 cogl_quaternion_init_from_z_rotation (CoglQuaternion *quaternion,
                                       float angle);
 
+void
+cogl_quaternion_init_from_euler (CoglQuaternion *quaternion,
+                                 const CoglEuler *euler);
+
 /**
  * cogl_quaternion_equal:
  * @a: A #CoglQuaternion
diff --git a/cogl/cogl-types.h b/cogl/cogl-types.h
index 28644a9..36f5269 100644
--- a/cogl/cogl-types.h
+++ b/cogl/cogl-types.h
@@ -122,6 +122,14 @@ typedef void (* CoglFuncPtr) (void);
  * between cogl-matrix.h, cogl-euler.h and cogl-quaterion.h */
 typedef struct _CoglMatrix      CoglMatrix;
 
+/* Same as above we forward declared CoglQuaternion to avoid
+ * circular dependencies. */
+typedef struct _CoglQuaternion CoglQuaternion;
+
+/* Same as above we forward declared CoglEuler to avoid
+ * circular dependencies. */
+typedef struct _CoglEuler CoglEuler;
+
 /**
  * CoglFixed:
  *
diff --git a/cogl/cogl.h b/cogl/cogl.h
index 21a453d..52c2940 100644
--- a/cogl/cogl.h
+++ b/cogl/cogl.h
@@ -77,6 +77,7 @@ typedef struct _CoglFramebuffer CoglFramebuffer;
 #include <cogl/cogl-buffer.h>
 #include <cogl/cogl-pixel-array.h>
 #include <cogl/cogl-vector.h>
+#include <cogl/cogl-euler.h>
 #include <cogl/cogl-quaternion.h>
 #include <cogl/cogl-texture-3d.h>
 #include <cogl/cogl-index-array.h>
diff --git a/doc/reference/cogl-2.0/cogl-sections.txt b/doc/reference/cogl-2.0/cogl-sections.txt
index 883135e..3a0d0d2 100644
--- a/doc/reference/cogl-2.0/cogl-sections.txt
+++ b/doc/reference/cogl-2.0/cogl-sections.txt
@@ -355,6 +355,17 @@ cogl_matrix_get_array
 cogl_matrix_get_inverse
 </SECTION>
 
+<FILE>cogl-euler</FILE>
+<TITLE>Eulers (Rotations)</TITLE>
+CoglEuler
+cogl_euler_init
+cogl_euler_init_from_matrix
+cogl_euler_init_from_quaternion
+cogl_euler_equal
+cogl_euler_copy
+cogl_euler_free
+</SECTION>
+
 <SECTION>
 <FILE>cogl-quaternion</FILE>
 <TITLE>Quaternions (Rotations)</TITLE>
diff --git a/doc/reference/cogl/cogl-sections.txt b/doc/reference/cogl/cogl-sections.txt
index 10423e0..4e521a5 100644
--- a/doc/reference/cogl/cogl-sections.txt
+++ b/doc/reference/cogl/cogl-sections.txt
@@ -660,6 +660,18 @@ cogl_offscreen_unref
 </SECTION>
 
 <SECTION>
+<FILE>cogl-euler</FILE>
+<TITLE>Eulers (Rotations)</TITLE>
+CoglEuler
+cogl_euler_init
+cogl_euler_init_from_matrix
+cogl_euler_init_from_quaternion
+cogl_euler_equal
+cogl_euler_copy
+cogl_euler_free
+</SECTION>
+
+<SECTION>
 <FILE>cogl-quaternion</FILE>
 <TITLE>Quaternions (Rotations)</TITLE>
 CoglQuaternion



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