diff src/paranormal-ng/xform.c @ 2078:1fa3c8cd366a

paranormal-ng: a GL visualiser. lots to do, most stuff won't work, but hey, this will do cool stuff too soon
author William Pitcock <nenolod@atheme.org>
date Mon, 15 Oct 2007 06:20:13 -0500
parents
children 9084e2e05f4a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal-ng/xform.c	Mon Oct 15 06:20:13 2007 -0500
@@ -0,0 +1,781 @@
+/*
+ * paranormal: iterated pipeline-driven visualization plugin
+ * Copyright (c) 2006, 2007 William Pitcock <nenolod@dereferenced.org>
+ * Portions copyright (c) 2001 Jamie Gennis <jgennis@mindspring.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; under version 2 of the License.
+ *
+ * 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
+ */
+
+/* FIXME: allow for only using an xform on part of the img? */
+/* FIXME: perhaps combine these into a single vector field
+   so that only 1 apply_xform needs to be done for as many
+   of these as someone wants to use */
+
+#include <config.h>
+
+#include <math.h>
+
+#include <glib.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "pn_utils.h"
+
+#include "libcalc/calc.h"
+
+struct xform_vector
+{
+  gint32 offset; /* the offset of the top left pixel */
+  guint16 w; /* 4:4:4:4 NE, NW, SE, SW pixel weights
+		  The total should be 16 */
+
+  /* if offset < 0 then w is the color index to
+     which the pixel will be set */
+};
+
+static void
+xfvec (float x, float y, struct xform_vector *v)
+{
+  float xd, yd;
+  int weight[4];
+
+  if (x >= pn_image_data->width-1 || y >= pn_image_data->height-1
+      || x < 0 || y < 0)
+    {
+      v->offset = -1;
+      v->w = 0;
+      return;
+    }
+
+  v->offset = PN_IMG_INDEX (floor(x), floor(y));
+
+  xd = x - floor (x);
+  yd = y - floor (y);
+
+  weight[3] = xd * yd * 16;
+  weight[2] = (1-xd) * yd * 16;
+  weight[1] = xd * (1-yd) * 16;
+  weight[0] = 16 - weight[3] - weight[2] - weight[1]; /* just in case */
+
+  v->w = (weight[0]<<12) | (weight[1]<<8) | (weight[2]<<4) | weight[3];
+}
+
+static void
+apply_xform (struct xform_vector *vfield)
+{
+  int i;
+  struct xform_vector *v;
+  register guchar *srcptr;
+  register guchar *destptr;
+  register int color;
+
+  if (vfield == NULL)
+      return;
+
+  for (i=0, v=vfield, destptr=pn_image_data->surface[1];
+       i<pn_image_data->width*pn_image_data->height;
+       i++, v++, destptr++)
+    {
+      /* off the screen */
+      if (v->offset < 0)
+	{
+	  *destptr = (guchar)v->w;
+	  continue;
+	}
+
+      srcptr = pn_image_data->surface[0] + v->offset;
+
+      /* exactly on the pixel */
+      if (v->w == 0)
+	  *destptr = *srcptr;
+      
+      /* gotta blend the points */
+      else
+	{
+	  color =  *srcptr * (v->w>>12);
+	  color += *++srcptr * ((v->w>>8) & 0x0f);
+	  color += *(srcptr+=pn_image_data->width) * (v->w & 0x0f);
+	  color += *(--srcptr) * ((v->w>>4) & 0x0f);
+	  color >>= 4;
+	  *destptr = (guchar)color;
+	}
+    }
+}
+
+/* **************** xform_spin **************** */
+/* FIXME: Describe these better, how they are backwards */
+/* FIXME: better name? */
+struct pn_actuator_option_desc xform_spin_opts[] =
+{
+  { "angle", "The angle of rotation", OPT_TYPE_FLOAT, { fval: -8.0 } },
+  { "r_add", "The number of pixels by which the r coordinate will be "
+    "increased (before scaling)", OPT_TYPE_FLOAT, { fval: 0.0 } },
+  { "r_scale", "The amount by which the r coordinate of each pixel will "
+    "be scaled", OPT_TYPE_FLOAT, { fval: 1.0 } },
+  { NULL }
+};
+
+struct xform_spin_data
+{
+  int width, height;
+  struct xform_vector *vfield;
+};
+
+static void
+xform_spin_init (gpointer *data)
+{
+  *data = g_new0 (struct xform_spin_data, 1);
+}
+
+static void
+xform_spin_cleanup (gpointer data)
+{
+  struct xform_spin_data *d = (struct xform_spin_data *) data;
+
+  
+  if (d)
+    {
+      if (d->vfield)
+	g_free (d->vfield);
+      g_free (d);
+    }
+}
+
+static void
+xform_spin_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  struct xform_spin_data *d = (struct xform_spin_data*)data;
+  float i, j;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+	g_free (d->vfield);
+
+      d->vfield = g_malloc0 (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j=-(pn_image_data->height>>1)+1; j<=pn_image_data->height>>1; j++)
+	for (i=-(pn_image_data->width>>1); i<pn_image_data->width>>1; i++)
+	  {
+	    float r, t = 0;
+	    float x, y;
+
+	    r = sqrt (i*i + j*j);
+	    if (r)
+	      t = asin (j/r);
+	    if (i < 0)
+	      t = M_PI - t;
+
+	    t += opts[0].val.fval * M_PI/180.0; 
+	    r += opts[1].val.fval;
+	    r *= opts[2].val.fval;
+
+	    x = (r * cos (t)) + (pn_image_data->width>>1);
+	    y = (pn_image_data->height>>1) - (r * sin (t));
+
+	    xfvec (x, y, &d->vfield
+		   [PN_IMG_INDEX ((pn_image_data->width>>1)+(int)rint(i),
+				  ((pn_image_data->height>>1)-(int)rint(j)))]);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_spin =
+{
+  "xform_spin", "Spin Transform", 
+  "Rotates and radially scales the image",
+  0, xform_spin_opts,
+  xform_spin_init, xform_spin_cleanup, xform_spin_exec
+};
+
+/* **************** xform_ripple **************** */
+struct pn_actuator_option_desc xform_ripple_opts[] =
+{
+  { "angle", "The angle of rotation", OPT_TYPE_FLOAT, { fval: 0 } },
+  { "ripples", "The number of ripples that fit on the screen "
+    "(horizontally)", OPT_TYPE_FLOAT, { fval: 8 } },
+  { "base_speed", "The minimum number of pixels to move each pixel",
+    OPT_TYPE_FLOAT, { fval: 1 } },
+  { "mod_speed", "The maximum number of pixels by which base_speed"
+    " will be modified", OPT_TYPE_FLOAT, { fval: 1 } },
+  { NULL }
+};
+
+struct xform_ripple_data
+{
+  int width, height;
+  struct xform_vector *vfield;
+};
+
+static void
+xform_ripple_init (gpointer *data)
+{
+  *data = g_new0 (struct xform_ripple_data, 1);
+}
+
+static void
+xform_ripple_cleanup (gpointer data)
+{
+  struct xform_ripple_data *d = (struct xform_ripple_data*) data;
+
+  if (d)
+    {
+      if (d->vfield)
+	g_free (d->vfield);
+      g_free (d);
+    }
+}
+
+static void
+xform_ripple_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  struct xform_ripple_data *d = (struct xform_ripple_data*)data;
+  float i, j;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+	g_free (d->vfield);
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j=-(pn_image_data->height>>1)+1; j<=pn_image_data->height>>1; j++)
+	for (i=-(pn_image_data->width>>1); i<pn_image_data->width>>1; i++)
+	  {
+	    float r, t = 0;
+	    float x, y;
+
+	    r = sqrt (i*i + j*j);
+	    if (r)
+	      t = asin (j/r);
+	    if (i < 0)
+	      t = M_PI - t;
+
+	    t += opts[0].val.fval * M_PI/180.0; 
+
+	    if (r > 4)//(pn_image_data->width/(2*opts[1].val.fval)))
+	      r -=  opts[2].val.fval + (opts[3].val.fval/2) *
+		(1 + sin ((r/(pn_image_data->width/(2*opts[1].val.fval)))*M_PI));
+/*  	    else if (r > 4) */
+/*  	      r *= r/(pn_image_data->width/opts[1].val.fval); */
+	    else /* don't let it explode */
+	      r = 1000000;
+
+
+	    x = (r * cos (t)) + (pn_image_data->width>>1);
+	    y = (pn_image_data->height>>1) - (r * sin (t));
+
+	    xfvec (x, y, &d->vfield
+		   [PN_IMG_INDEX ((pn_image_data->width>>1)+(int)rint(i),
+				  ((pn_image_data->height>>1)-(int)rint(j)))]);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_ripple =
+{
+  "xform_ripple", "Ripple Transform", "Creates an ripple effect",
+  0, xform_ripple_opts,
+  xform_ripple_init, xform_ripple_cleanup, xform_ripple_exec
+};
+
+/* **************** xform_bump_spin **************** */
+struct pn_actuator_option_desc xform_bump_spin_opts[] =
+{
+  { "angle", "The angle of rotation", OPT_TYPE_FLOAT, { fval: 0 } },
+  { "bumps", "The number of bumps that on the image",
+    OPT_TYPE_FLOAT, { fval: 8 } },
+  { "base_scale", "The base radial scale",
+    OPT_TYPE_FLOAT, { fval: 0.95 } },
+  { "mod_scale", "The maximum amount that should be "
+    "added to the base_scale to create the 'bump' effect",
+    OPT_TYPE_FLOAT, { fval: .1 } },
+  { NULL }
+};
+
+struct xform_bump_spin_data
+{
+  int width, height;
+  struct xform_vector *vfield;
+};
+
+static void
+xform_bump_spin_init (gpointer *data)
+{
+  *data = g_new0 (struct xform_bump_spin_data, 1);
+}
+
+static void
+xform_bump_spin_cleanup (gpointer data)
+{
+  struct xform_bump_spin_data *d = (struct xform_bump_spin_data*) data;
+
+  if (d)
+    {
+      if (d->vfield)
+	g_free (d->vfield);
+      g_free (d);
+    }
+}
+
+static void
+xform_bump_spin_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  struct xform_bump_spin_data *d = (struct xform_bump_spin_data*)data;
+  float i, j;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+	g_free (d->vfield);
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j=-(pn_image_data->height>>1)+1; j<=pn_image_data->height>>1; j++)
+	for (i=-(pn_image_data->width>>1); i<pn_image_data->width>>1; i++)
+	  {
+	    float r, t = 0;
+	    float x, y;
+
+	    r = sqrt (i*i + j*j);
+	    if (r)
+	      t = asin (j/r);
+	    if (i < 0)
+	      t = M_PI - t;
+
+	    t += opts[0].val.fval * M_PI/180.0;
+
+	    r *=  opts[2].val.fval + opts[3].val.fval
+	      * (1 + sin (t*opts[1].val.fval));
+
+	    x = (r * cos (t)) + (pn_image_data->width>>1);
+	    y = (pn_image_data->height>>1) - (r * sin (t));
+
+	    xfvec (x, y, &d->vfield
+		   [PN_IMG_INDEX ((pn_image_data->width>>1)+(int)rint(i),
+				  ((pn_image_data->height>>1)-(int)rint(j)))]);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_bump_spin =
+{
+  "xform_bump_spin", "Bump Transform", 
+  "Rotate the image at a varying speed to create "
+  "the illusion of bumps",
+  0, xform_bump_spin_opts,
+  xform_bump_spin_init, xform_bump_spin_cleanup, xform_bump_spin_exec
+};
+
+/* **************** xform_halfrender **************** */
+struct pn_actuator_option_desc xform_halfrender_opts[] =
+{
+  { "direction", "Negative is horizontal, positive is vertical.", 
+    OPT_TYPE_INT, { ival: 1 } },
+  { "render_twice", "Render the second image.",
+    OPT_TYPE_BOOLEAN, { bval: TRUE } },
+  { NULL }
+};
+
+static void
+xform_halfrender_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  gint x, y;
+
+  if (opts[0].val.ival < 0)
+    {
+       for (y = 0; y < pn_image_data->height; y += 2)
+         {
+            for (x = 0; x < pn_image_data->width; x++)
+              {
+                 pn_image_data->surface[1][PN_IMG_INDEX(x, y / 2)] =
+                   pn_image_data->surface[0][PN_IMG_INDEX(x, y)];
+	         if (opts[1].val.bval)
+                   {
+                     pn_image_data->surface[1][PN_IMG_INDEX(x, (y / 2) + (pn_image_data->height / 2))] =
+                       pn_image_data->surface[0][PN_IMG_INDEX(x, y)];
+                   }
+              }
+         }
+    }
+  else
+    {
+       for (y = 0; y < pn_image_data->height; y++)
+         {
+            for (x = 0; x < pn_image_data->width; x += 2)
+              {
+                 pn_image_data->surface[1][PN_IMG_INDEX(x / 2, y)] =
+                   pn_image_data->surface[0][PN_IMG_INDEX(x, y)];
+                 if (opts[1].val.bval)
+                   {
+                     pn_image_data->surface[1][PN_IMG_INDEX((x / 2) + (pn_image_data->width / 2), y)] =
+                       pn_image_data->surface[0][PN_IMG_INDEX(x, y)];
+                   }
+              }
+         }
+    }
+
+    pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_halfrender =
+{
+  "xform_halfrender", "Halfrender Transform",
+  "Divides the surface in half and renders it twice.",
+  0, xform_halfrender_opts,
+  NULL, NULL, xform_halfrender_exec
+};
+
+/* **************** xform_movement **************** */
+struct pn_actuator_option_desc xform_movement_opts[] =
+{
+  { "formula", "The formula to evaluate.",
+    OPT_TYPE_STRING, { sval: "r = r * cos(r); d = sin(d);" } },
+  { "polar", "Whether the coordinates are polar or not.",
+    OPT_TYPE_BOOLEAN, { bval: TRUE } },
+  { NULL }
+};
+
+typedef struct {
+  int width, height;                 /* Previous width and height. */
+  struct xform_vector *vfield;
+} PnMovementData;
+
+static void
+xform_movement_init (gpointer *data)
+{
+    *data = g_new0(PnMovementData, 1);
+}
+
+static void
+xform_movement_cleanup (gpointer data)
+{
+    PnMovementData *d = (PnMovementData *) data;
+
+    if (d)
+      {
+         if (d->vfield)
+ 	     g_free (d->vfield);
+         g_free (d);
+      }
+}
+
+inline void
+xform_trans_polar (struct xform_vector *vfield, gint x, gint y,
+	expression_t *expr, symbol_dict_t *dict)
+{
+  gdouble *rf, *df;
+  gdouble xf, yf;
+  gint xn, yn;
+
+  rf = dict_variable(dict, "r");
+  df = dict_variable(dict, "d");
+
+  /* Points (xf, yf) must be in a (-1..1) square. */
+  xf = 2.0 * x / (pn_image_data->width - 1) - 1.0;
+  yf = 2.0 * y / (pn_image_data->height - 1) - 1.0;
+
+  /* Now, convert to polar coordinates r and d. */
+  *rf = hypot(xf, yf);
+  *df = atan2(yf, xf);
+
+  /* Run the script. */
+  expr_execute(expr, dict);
+
+  /* Back to (-1..1) square. */
+  xf = (*rf) * cos ((*df));
+  yf = (*rf) * sin ((*df));
+
+  /* Convert back to physical coordinates. */
+  xn = (int)(((xf + 1.0) * (pn_image_data->width - 1) / 2) + 0.5);
+  yn = (int)(((yf + 1.0) * (pn_image_data->height - 1) / 2) + 0.5);
+
+  if (xn < 0 || xn >= pn_image_data->width || yn < 0 || yn >= pn_image_data->height)
+    {
+      xn = x; yn = y;
+    }
+
+  xfvec (xn, yn, &vfield[PN_IMG_INDEX (x, y)]);
+}
+
+inline void
+xform_trans_literal (struct xform_vector *vfield, gint x, gint y,
+	expression_t *expr, symbol_dict_t *dict)
+{
+  gdouble rf, df;
+  gdouble *xf, *yf;
+  gint xn, yn;
+
+  xf = dict_variable(dict, "x");
+  yf = dict_variable(dict, "y");
+
+  /* Points (xf, yf) must be in a (-1..1) square. */
+  *xf = 2.0 * x / (pn_image_data->width - 1) - 1.0;
+  *yf = 2.0 * y / (pn_image_data->height - 1) - 1.0;
+
+  /* Run the script. */
+  expr_execute(expr, dict);
+
+  /* Convert back to physical coordinates. */
+  xn = (int)(((*xf + 1.0) * (pn_image_data->width - 1) / 2) + 0.5);
+  yn = (int)(((*yf + 1.0) * (pn_image_data->height - 1) / 2) + 0.5);
+
+  if (xn < 0 || xn >= pn_image_data->width || yn < 0 || yn >= pn_image_data->height)
+    {
+      xn = x; yn = y;
+    }
+
+  xfvec (xn, yn, &vfield[PN_IMG_INDEX (x, y)]);
+}
+
+static void
+xform_movement_exec (const struct pn_actuator_option *opts,
+		 gpointer odata)
+{
+  PnMovementData *d = (PnMovementData *) odata;
+  void (*transform_func)(struct xform_vector *, gint, gint, expression_t *, symbol_dict_t *) = 
+        opts[1].val.bval == TRUE ? xform_trans_polar : xform_trans_literal;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      gint i, j;
+      gdouble *rf, *df;
+      gdouble xf, yf;
+      gint xn, yn;
+      expression_t *expr;
+      symbol_dict_t *dict;
+
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+        {
+  	  g_free (d->vfield);
+          d->vfield = NULL;
+        }
+
+      if (opts[0].val.sval == NULL)
+        return;
+
+      dict = dict_new();
+      expr = expr_compile_string(opts[0].val.sval, dict);
+      if (!expr)
+        {
+           dict_free(dict);
+           return;
+        }
+
+      rf = dict_variable(dict, "r");
+      df = dict_variable(dict, "d");
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j = 0; j < pn_image_data->height; j++)
+	for (i = 0; i < pn_image_data->width; i++)
+	  {
+            transform_func(d->vfield, i, j, expr, dict);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_movement =
+{
+  "xform_movement", "Movement Transform",
+  "A customizable blitter.",
+  0, xform_movement_opts,
+  xform_movement_init, xform_movement_cleanup, xform_movement_exec
+};
+
+/* **************** xform_dynmovement **************** */
+/* FIXME: really slow */
+struct pn_actuator_option_desc xform_dynmovement_opts[] =
+{
+  { "init_script", "The formula to evaluate on init.",
+    OPT_TYPE_STRING, { sval: "" } },
+  { "beat_script", "The formula to evaluate on each beat.",
+    OPT_TYPE_STRING, { sval: "" } },
+  { "frame_script", "The formula to evaluate on each frame.",
+    OPT_TYPE_STRING, { sval: "" } },
+  { "point_script", "The formula to evaluate.",
+    OPT_TYPE_STRING, { sval: "d = 0.15;" } },
+  { "polar", "Whether or not the coordinates to use are polar.",
+    OPT_TYPE_BOOLEAN, { bval: TRUE } },
+  { NULL }
+};
+
+typedef struct {
+  int width, height;                 /* Previous width and height. */
+  expression_t *expr_init;
+  expression_t *expr_frame;
+  expression_t *expr_beat;
+  expression_t *expr_point;
+  symbol_dict_t *dict;
+  struct xform_vector *vfield;
+} PnDynMovementData;
+
+static void
+xform_dynmovement_init (gpointer *data)
+{
+    *data = g_new0(PnDynMovementData, 1);
+}
+
+static void
+xform_dynmovement_cleanup (gpointer data)
+{
+    PnDynMovementData *d = (PnDynMovementData *) data;
+
+    if (d)
+      {
+         if (d->expr_init)
+             expr_free (d->expr_init);
+         if (d->expr_beat)
+             expr_free (d->expr_beat);
+         if (d->expr_frame)
+             expr_free (d->expr_frame);
+         if (d->expr_point)
+             expr_free (d->expr_point);
+         if (d->dict)
+             dict_free (d->dict);
+         if (d->vfield)
+ 	     g_free (d->vfield);
+         g_free (d);
+      }
+}
+
+static void
+xform_dynmovement_exec (const struct pn_actuator_option *opts,
+		 gpointer odata)
+{
+  PnDynMovementData *d = (PnDynMovementData *) odata;
+  gint i, j;
+  gdouble *rf, *df;
+  gdouble xf, yf;
+  gint xn, yn;
+  void (*transform_func)(struct xform_vector *, gint, gint, expression_t *, symbol_dict_t *) = 
+        opts[4].val.bval == TRUE ? xform_trans_polar : xform_trans_literal;
+  gboolean make_table = FALSE;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+        {
+  	  g_free (d->vfield);
+          d->vfield = NULL;
+        }
+
+      if (opts[3].val.sval == NULL)
+          return;
+
+      if (!d->dict)
+          d->dict = dict_new();
+      else
+        {
+          dict_free(d->dict);
+          d->dict = dict_new();
+        }
+
+      if (d->expr_init)
+        {
+          expr_free(d->expr_init);
+          d->expr_init = NULL;
+        }
+
+      /* initialize */
+      d->expr_init = expr_compile_string(opts[0].val.sval, d->dict);
+
+      if (d->expr_init != NULL)
+        {
+           expr_execute(d->expr_init, d->dict);
+        }
+
+      d->expr_beat = expr_compile_string(opts[1].val.sval, d->dict);
+      d->expr_frame = expr_compile_string(opts[2].val.sval, d->dict);
+      d->expr_point = expr_compile_string(opts[3].val.sval, d->dict);
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      make_table = TRUE;
+   }
+
+   rf = dict_variable(d->dict, "r");
+   df = dict_variable(d->dict, "d");
+
+   if (*opts[2].val.sval != '\0' || pn_new_beat)
+       make_table = TRUE;
+
+   /* run the on-frame script. */
+   if (make_table == TRUE)
+     {
+       if (d->expr_beat != NULL)
+         expr_execute(d->expr_beat, d->dict);
+
+       if (d->expr_frame != NULL)
+         expr_execute(d->expr_frame, d->dict);
+
+       for (j = 0; j < pn_image_data->height; j++)
+         for (i = 0; i < pn_image_data->width; i++)
+           {
+             transform_func(d->vfield, i, j, d->expr_point, d->dict);
+           }
+     }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_dynmovement =
+{
+  "xform_dynmovement", "Dynamic Movement Transform",
+  "A customizable blitter.",
+  0, xform_dynmovement_opts,
+  xform_dynmovement_init, xform_dynmovement_cleanup, xform_dynmovement_exec
+};