summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dimension/common.nonterminals2
-rw-r--r--dimension/common.rules108
-rw-r--r--dimension/common.terminals6
-rw-r--r--dimension/directives.rules7
-rw-r--r--dimension/grammar.epilogue9
-rw-r--r--dimension/lexer.l3
-rw-r--r--dimension/parse.h3
-rw-r--r--dimension/realize.c124
-rw-r--r--tests/dimension/Makefile.am2
-rwxr-xr-xtests/dimension/csg.sh5
-rwxr-xr-xtests/dimension/demo.sh10
-rw-r--r--tests/dimension/transformations.pov39
-rwxr-xr-xtests/dimension/transformations.sh63
13 files changed, 324 insertions, 57 deletions
diff --git a/dimension/common.nonterminals b/dimension/common.nonterminals
index 1d39424..5f915c5 100644
--- a/dimension/common.nonterminals
+++ b/dimension/common.nonterminals
@@ -25,6 +25,8 @@
/* Transformations */
%type <astnode> TRANSFORMATION
+%type <astnode> TRANSFORMATION_ITEMS
+%type <astnode> TRANSFORMATION_ITEM
/* The camera */
%type <astnode> CAMERA
diff --git a/dimension/common.rules b/dimension/common.rules
index d897eb3..77830a6 100644
--- a/dimension/common.rules
+++ b/dimension/common.rules
@@ -43,14 +43,116 @@ STRING: "string" {
/* Transformations */
TRANSFORMATION: "rotate" VECTOR {
- $$ = dmnsn_new_astnode1(DMNSN_AST_ROTATION, @$, $2);
+ dmnsn_astnode rotation
+ = dmnsn_new_astnode1(DMNSN_AST_ROTATION, @$, $2);
+ $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, rotation);
}
| "scale" VECTOR {
- $$ = dmnsn_new_astnode1(DMNSN_AST_SCALE, @$, $2);
+ dmnsn_astnode scale
+ = dmnsn_new_astnode1(DMNSN_AST_SCALE, @$, $2);
+ $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, scale);
}
| "translate" VECTOR {
- $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSLATION, @$, $2);
+ dmnsn_astnode translation
+ = dmnsn_new_astnode1(DMNSN_AST_TRANSLATION, @$, $2);
+ $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$,
+ translation);
}
+ | "matrix" "<" FLOAT "," FLOAT "," FLOAT ","
+ FLOAT "," FLOAT "," FLOAT ","
+ FLOAT "," FLOAT "," FLOAT ","
+ FLOAT "," FLOAT "," FLOAT ">"
+ {
+ dmnsn_astnode matrix = dmnsn_new_astnode(DMNSN_AST_MATRIX, @$);
+
+ dmnsn_array_push(matrix.children, &$3);
+ dmnsn_array_push(matrix.children, &$5);
+ dmnsn_array_push(matrix.children, &$7);
+
+ dmnsn_array_push(matrix.children, &$9);
+ dmnsn_array_push(matrix.children, &$11);
+ dmnsn_array_push(matrix.children, &$13);
+
+ dmnsn_array_push(matrix.children, &$15);
+ dmnsn_array_push(matrix.children, &$17);
+ dmnsn_array_push(matrix.children, &$19);
+
+ dmnsn_array_push(matrix.children, &$21);
+ dmnsn_array_push(matrix.children, &$23);
+ dmnsn_array_push(matrix.children, &$25);
+
+ $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, matrix);
+ }
+ | "transform" IDENTIFIER {
+ dmnsn_astnode *trans = dmnsn_find_symbol(symtable, $2.ptr);
+ if (!trans) {
+ dmnsn_diagnostic(@2, "unbound identifier '%s'",
+ (const char *)$2.ptr);
+ dmnsn_delete_astnode($2);
+ YYERROR;
+ }
+ if (trans->type != DMNSN_AST_TRANSFORMATION) {
+ dmnsn_diagnostic(
+ @2, "identifier '%s' is a %s; expected a %s",
+ (const char *)$2.ptr,
+ dmnsn_astnode_string(trans->type),
+ dmnsn_astnode_string(DMNSN_AST_TRANSFORMATION)
+ );
+ dmnsn_delete_astnode($2);
+ YYERROR;
+ }
+
+ $$ = dmnsn_new_astnode(DMNSN_AST_TRANSFORMATION, @$);
+ dmnsn_copy_children($$, *trans);
+ dmnsn_delete_astnode($2);
+ }
+ | "transform" "{"
+ TRANSFORMATION_ITEMS
+ "}"
+ {
+ $$ = $3;
+ }
+;
+
+TRANSFORMATION_ITEMS: TRANSFORMATION_ITEM {
+ $$ = dmnsn_new_astnode1(DMNSN_AST_TRANSFORMATION, @$, $1);
+ }
+ | TRANSFORMATION_ITEMS TRANSFORMATION_ITEM {
+ $$ = $1;
+ dmnsn_array_push($$.children, &$2);
+ }
+;
+
+TRANSFORMATION_ITEM: IDENTIFIER {
+ dmnsn_astnode *trans = dmnsn_find_symbol(symtable, $1.ptr);
+ if (!trans) {
+ dmnsn_diagnostic(@1, "unbound identifier '%s'",
+ (const char *)$1.ptr);
+ dmnsn_delete_astnode($1);
+ YYERROR;
+ }
+ if (trans->type != DMNSN_AST_TRANSFORMATION) {
+ dmnsn_diagnostic(
+ @1, "identifier '%s' is a %s; expected a %s",
+ (const char *)$1.ptr,
+ dmnsn_astnode_string(trans->type),
+ dmnsn_astnode_string(DMNSN_AST_TRANSFORMATION)
+ );
+ dmnsn_delete_astnode($1);
+ YYERROR;
+ }
+
+ dmnsn_array_get(trans->children, 0, &$$);
+ ++*$$.refcount;
+ }
+ | TRANSFORMATION {
+ dmnsn_array_get($1.children, 0, &$$);
+ ++*$$.refcount;
+ dmnsn_delete_astnode($1);
+ }
+ | "inverse" {
+ $$ = dmnsn_new_astnode(DMNSN_AST_INVERSE, @$);
+ }
;
/* Cameras */
diff --git a/dimension/common.terminals b/dimension/common.terminals
index fd62320..7ccc8cb 100644
--- a/dimension/common.terminals
+++ b/dimension/common.terminals
@@ -240,7 +240,7 @@
%token DMNSN_T_INTERPOLATE
%token DMNSN_T_INTERSECTION "intersection"
%token DMNSN_T_INTERVALS
-%token DMNSN_T_INVERSE
+%token DMNSN_T_INVERSE "inverse"
%token DMNSN_T_IOR "ior"
%token DMNSN_T_IRID
%token DMNSN_T_IRID_WAVELENGTH
@@ -270,7 +270,7 @@
%token DMNSN_T_MARBLE
%token DMNSN_T_MATERIAL
%token DMNSN_T_MATERIAL_MAP
-%token DMNSN_T_MATRIX
+%token DMNSN_T_MATRIX "matrix"
%token DMNSN_T_MAX "max"
%token DMNSN_T_MAX_EXTENT
%token DMNSN_T_MAX_GRADIENT
@@ -445,7 +445,7 @@
%token DMNSN_T_TOROIDAL
%token DMNSN_T_TORUS
%token DMNSN_T_TRACE
-%token DMNSN_T_TRANSFORM
+%token DMNSN_T_TRANSFORM "transform"
%token DMNSN_T_TRANSLATE "translate"
%token DMNSN_T_TRANSMIT "transmit"
%token DMNSN_T_TRIANGLE
diff --git a/dimension/directives.rules b/dimension/directives.rules
index 84358f6..a3c53d2 100644
--- a/dimension/directives.rules
+++ b/dimension/directives.rules
@@ -115,7 +115,12 @@ RVALUE: ARITH_EXPR ";" %dprec 2 {
| FINISH
| INTERIOR
| CAMERA
- | TRANSFORMATION
+ | "transform" "{"
+ TRANSFORMATION_ITEMS
+ "}"
+ {
+ $$ = $3;
+ }
;
DECL_PARAMS: /* empty */ {
diff --git a/dimension/grammar.epilogue b/dimension/grammar.epilogue
index 7b004e3..073f412 100644
--- a/dimension/grammar.epilogue
+++ b/dimension/grammar.epilogue
@@ -163,9 +163,12 @@ dmnsn_astnode_string(dmnsn_astnode_type astnode_type)
dmnsn_astnode_map(DMNSN_AST_INTERIOR, "interior");
dmnsn_astnode_map(DMNSN_AST_IOR, "ior");
- dmnsn_astnode_map(DMNSN_AST_ROTATION, "rotate");
- dmnsn_astnode_map(DMNSN_AST_SCALE, "scale");
- dmnsn_astnode_map(DMNSN_AST_TRANSLATION, "translate");
+ dmnsn_astnode_map(DMNSN_AST_TRANSFORMATION, "transformation");
+ dmnsn_astnode_map(DMNSN_AST_ROTATION, "rotation");
+ dmnsn_astnode_map(DMNSN_AST_SCALE, "scale");
+ dmnsn_astnode_map(DMNSN_AST_TRANSLATION, "translation");
+ dmnsn_astnode_map(DMNSN_AST_MATRIX, "matrix");
+ dmnsn_astnode_map(DMNSN_AST_INVERSE, "inverse");
dmnsn_astnode_map(DMNSN_AST_FLOAT, "float");
dmnsn_astnode_map(DMNSN_AST_INTEGER, "integer");
diff --git a/dimension/lexer.l b/dimension/lexer.l
index 85c3fda..ea98c07 100644
--- a/dimension/lexer.l
+++ b/dimension/lexer.l
@@ -207,12 +207,14 @@ unsigned long wchar;
"int" RETURN_TOKEN(DMNSN_T_INT);
"interior" RETURN_TOKEN(DMNSN_T_INTERIOR);
"intersection" RETURN_TOKEN(DMNSN_T_INTERSECTION);
+"inverse" RETURN_TOKEN(DMNSN_T_INVERSE);
"ior" RETURN_TOKEN(DMNSN_T_IOR);
"ln" RETURN_TOKEN(DMNSN_T_LN);
"location" RETURN_TOKEN(DMNSN_T_LOCATION);
"log" RETURN_TOKEN(DMNSN_T_LOG);
"look_at" RETURN_TOKEN(DMNSN_T_LOOK_AT);
"light_source" RETURN_TOKEN(DMNSN_T_LIGHT_SOURCE);
+"matrix" RETURN_TOKEN(DMNSN_T_MATRIX);
"max" RETURN_TOKEN(DMNSN_T_MAX);
"max_trace_level" RETURN_TOKEN(DMNSN_T_MAX_TRACE_LEVEL);
"merge" RETURN_TOKEN(DMNSN_T_MERGE);
@@ -251,6 +253,7 @@ unsigned long wchar;
"tan" RETURN_TOKEN(DMNSN_T_TAN);
"tanh" RETURN_TOKEN(DMNSN_T_TANH);
"texture" RETURN_TOKEN(DMNSN_T_TEXTURE);
+"transform" RETURN_TOKEN(DMNSN_T_TRANSFORM);
"translate" RETURN_TOKEN(DMNSN_T_TRANSLATE);
"transmit" RETURN_TOKEN(DMNSN_T_TRANSMIT);
"true" RETURN_TOKEN(DMNSN_T_TRUE);
diff --git a/dimension/parse.h b/dimension/parse.h
index b4bd8fe..fd78665 100644
--- a/dimension/parse.h
+++ b/dimension/parse.h
@@ -77,9 +77,12 @@ typedef enum {
DMNSN_AST_INTERIOR,
DMNSN_AST_IOR,
+ DMNSN_AST_TRANSFORMATION,
DMNSN_AST_ROTATION,
DMNSN_AST_SCALE,
DMNSN_AST_TRANSLATION,
+ DMNSN_AST_MATRIX,
+ DMNSN_AST_INVERSE,
DMNSN_AST_FLOAT,
DMNSN_AST_INTEGER,
diff --git a/dimension/realize.c b/dimension/realize.c
index e558c5b..a9b9c71 100644
--- a/dimension/realize.c
+++ b/dimension/realize.c
@@ -113,6 +113,31 @@ dmnsn_realize_color(dmnsn_astnode astnode)
}
static dmnsn_matrix
+dmnsn_realize_translation(dmnsn_astnode astnode)
+{
+ dmnsn_assert(astnode.type == DMNSN_AST_TRANSLATION,
+ "Expected a translation.");
+
+ dmnsn_astnode trans_node;
+ dmnsn_array_get(astnode.children, 0, &trans_node);
+ dmnsn_vector trans = dmnsn_realize_vector(trans_node);
+
+ return dmnsn_translation_matrix(trans);
+}
+
+static dmnsn_matrix
+dmnsn_realize_scale(dmnsn_astnode astnode)
+{
+ dmnsn_assert(astnode.type == DMNSN_AST_SCALE, "Expected a scale.");
+
+ dmnsn_astnode scale_node;
+ dmnsn_array_get(astnode.children, 0, &scale_node);
+ dmnsn_vector scale = dmnsn_realize_vector(scale_node);
+
+ return dmnsn_scale_matrix(scale);
+}
+
+static dmnsn_matrix
dmnsn_realize_rotation(dmnsn_astnode astnode)
{
dmnsn_assert(astnode.type == DMNSN_AST_ROTATION, "Expected a rotation.");
@@ -143,45 +168,72 @@ dmnsn_realize_rotation(dmnsn_astnode astnode)
}
static dmnsn_matrix
-dmnsn_realize_scale(dmnsn_astnode astnode)
+dmnsn_realize_matrix(dmnsn_astnode astnode)
{
- dmnsn_assert(astnode.type == DMNSN_AST_SCALE, "Expected a scale.");
+ dmnsn_assert(astnode.type == DMNSN_AST_MATRIX, "Expected a matrix.");
- dmnsn_astnode scale_node;
- dmnsn_array_get(astnode.children, 0, &scale_node);
- dmnsn_vector scale = dmnsn_realize_vector(scale_node);
+ dmnsn_astnode *children = dmnsn_array_first(astnode.children);
+ dmnsn_matrix trans;
- return dmnsn_scale_matrix(scale);
-}
+ trans.n[0][0] = dmnsn_realize_float(children[0]);
+ trans.n[0][1] = dmnsn_realize_float(children[1]);
+ trans.n[0][2] = dmnsn_realize_float(children[2]);
+ trans.n[0][3] = dmnsn_realize_float(children[9]);
-static dmnsn_matrix
-dmnsn_realize_translation(dmnsn_astnode astnode)
-{
- dmnsn_assert(astnode.type == DMNSN_AST_TRANSLATION,
- "Expected a translation.");
+ trans.n[1][0] = dmnsn_realize_float(children[3]);
+ trans.n[1][1] = dmnsn_realize_float(children[4]);
+ trans.n[1][2] = dmnsn_realize_float(children[5]);
+ trans.n[1][3] = dmnsn_realize_float(children[10]);
- dmnsn_astnode trans_node;
- dmnsn_array_get(astnode.children, 0, &trans_node);
- dmnsn_vector trans = dmnsn_realize_vector(trans_node);
+ trans.n[2][0] = dmnsn_realize_float(children[6]);
+ trans.n[2][1] = dmnsn_realize_float(children[7]);
+ trans.n[2][2] = dmnsn_realize_float(children[8]);
+ trans.n[2][3] = dmnsn_realize_float(children[11]);
- return dmnsn_translation_matrix(trans);
+ trans.n[3][0] = 0.0;
+ trans.n[3][1] = 0.0;
+ trans.n[3][2] = 0.0;
+ trans.n[3][3] = 1.0;
+
+ return trans;
}
static dmnsn_matrix
dmnsn_realize_transformation(dmnsn_astnode astnode)
{
- switch (astnode.type) {
- case DMNSN_AST_ROTATION:
- return dmnsn_realize_rotation(astnode);
- case DMNSN_AST_SCALE:
- return dmnsn_realize_scale(astnode);
- case DMNSN_AST_TRANSLATION:
- return dmnsn_realize_translation(astnode);
+ dmnsn_assert(astnode.type == DMNSN_AST_TRANSFORMATION,
+ "Expected a transformation.");
- default:
- dmnsn_assert(false, "Expected a transformation.");
- return dmnsn_identity_matrix(); /* Shut up compiler */
+ dmnsn_matrix trans = dmnsn_identity_matrix();
+
+ DMNSN_ARRAY_FOREACH (dmnsn_astnode *, child, astnode.children) {
+ switch (child->type) {
+ case DMNSN_AST_TRANSLATION:
+ trans = dmnsn_matrix_mul(trans, dmnsn_realize_translation(*child));
+ break;
+ case DMNSN_AST_SCALE:
+ trans = dmnsn_matrix_mul(trans, dmnsn_realize_scale(*child));
+ break;
+ case DMNSN_AST_ROTATION:
+ trans = dmnsn_matrix_mul(trans, dmnsn_realize_rotation(*child));
+ break;
+ case DMNSN_AST_MATRIX:
+ trans = dmnsn_matrix_mul(trans, dmnsn_realize_matrix(*child));
+ break;
+ case DMNSN_AST_INVERSE:
+ trans = dmnsn_matrix_inverse(trans);
+ break;
+ case DMNSN_AST_TRANSFORMATION:
+ trans = dmnsn_matrix_mul(trans, dmnsn_realize_transformation(*child));
+ break;
+
+ default:
+ dmnsn_assert(false, "Invalid transformation type.");
+ break;
+ }
}
+
+ return trans;
}
static void
@@ -320,9 +372,7 @@ dmnsn_realize_camera(dmnsn_astnode astnode)
}
/* Transformations */
- case DMNSN_AST_ROTATION:
- case DMNSN_AST_SCALE:
- case DMNSN_AST_TRANSLATION:
+ case DMNSN_AST_TRANSFORMATION:
trans = dmnsn_matrix_mul(dmnsn_realize_transformation(*item), trans);
break;
@@ -408,9 +458,7 @@ dmnsn_realize_pigment_modifiers(dmnsn_astnode astnode, dmnsn_pigment *pigment)
DMNSN_ARRAY_FOREACH(dmnsn_astnode *, modifier, astnode.children) {
switch (modifier->type) {
- case DMNSN_AST_ROTATION:
- case DMNSN_AST_SCALE:
- case DMNSN_AST_TRANSLATION:
+ case DMNSN_AST_TRANSFORMATION:
pigment->trans = dmnsn_matrix_mul(
dmnsn_realize_transformation(*modifier),
pigment->trans
@@ -621,9 +669,7 @@ dmnsn_realize_texture(dmnsn_astnode astnode)
texture->finish = dmnsn_realize_finish(*item);
break;
- case DMNSN_AST_ROTATION:
- case DMNSN_AST_SCALE:
- case DMNSN_AST_TRANSLATION:
+ case DMNSN_AST_TRANSFORMATION:
texture->trans = dmnsn_matrix_mul(
dmnsn_realize_transformation(*item),
texture->trans
@@ -673,9 +719,7 @@ dmnsn_realize_object_modifiers(dmnsn_astnode astnode, dmnsn_object *object)
DMNSN_ARRAY_FOREACH (dmnsn_astnode *, modifier, astnode.children) {
switch (modifier->type) {
- case DMNSN_AST_ROTATION:
- case DMNSN_AST_SCALE:
- case DMNSN_AST_TRANSLATION:
+ case DMNSN_AST_TRANSFORMATION:
object->trans = dmnsn_matrix_mul(
dmnsn_realize_transformation(*modifier),
object->trans
@@ -728,9 +772,7 @@ dmnsn_realize_light_source_modifiers(dmnsn_astnode astnode, dmnsn_light *light)
DMNSN_ARRAY_FOREACH (dmnsn_astnode *, modifier, astnode.children) {
switch (modifier->type) {
- case DMNSN_AST_ROTATION:
- case DMNSN_AST_SCALE:
- case DMNSN_AST_TRANSLATION:
+ case DMNSN_AST_TRANSFORMATION:
light->x0 = dmnsn_transform_vector(
dmnsn_realize_transformation(*modifier),
light->x0
diff --git a/tests/dimension/Makefile.am b/tests/dimension/Makefile.am
index d13b56e..bdb94ed 100644
--- a/tests/dimension/Makefile.am
+++ b/tests/dimension/Makefile.am
@@ -25,6 +25,7 @@ TESTS = punctuation.sh \
labels.sh \
directives.sh \
arithexp.sh \
+ transformations.sh \
csg.sh \
demo.sh
TESTS_ENVIRONMENT = top_builddir=$(top_builddir)
@@ -40,6 +41,7 @@ EXTRA_DIST = $(TESTS) \
directives.inc \
directives.pov \
arithexp.pov \
+ transformations.pov \
csg.pov \
demo.pov
diff --git a/tests/dimension/csg.sh b/tests/dimension/csg.sh
index 7f4732c..360398c 100755
--- a/tests/dimension/csg.sh
+++ b/tests/dimension/csg.sh
@@ -75,8 +75,9 @@ csg_exp="$(echo -n \
(integer 0) (integer 0))
pigment-modifiers))))
(object-modifiers
- (translate (vector (integer 0) (integer 20) (integer 0)
- (integer 0) (integer 0))))))' \
+ (transformation
+ (translation (vector (integer 0) (integer 20) (integer 0)
+ (integer 0) (integer 0)))))))' \
| tr '\n' ' ' | sed -r 's/[[:space:]]+/ /g')"
if [ "$csg" != "$csg_exp" ]; then
diff --git a/tests/dimension/demo.sh b/tests/dimension/demo.sh
index 22308dc..18f1c05 100755
--- a/tests/dimension/demo.sh
+++ b/tests/dimension/demo.sh
@@ -28,8 +28,9 @@ demo_exp=$(echo -n \
(right (vector (float 1.6) (integer 0) (integer 0) (integer 0) (integer 0)))
(look_at (vector (integer 0) (integer 0) (integer 0)
(integer 0) (integer 0)))
- (rotate (vector (integer 0) (integer 53) (integer 0)
- (integer 0) (integer 0))))
+ (transformation
+ (rotation (vector (integer 0) (integer 53) (integer 0)
+ (integer 0) (integer 0)))))
(background
(vector (integer 0) (float 0.1) (float 0.2) (float 0.1) (integer 0)))
(light_source
@@ -42,8 +43,9 @@ demo_exp=$(echo -n \
(vector (integer -1) (integer -1) (integer -1) (integer 0) (integer 0))
(vector (integer 1) (integer 1) (integer 1) (integer 0) (integer 0))
(object-modifiers
- (rotate (vector (integer 45) (integer 0) (integer 0)
- (integer 0) (integer 0)))
+ (transformation
+ (rotation (vector (integer 45) (integer 0) (integer 0)
+ (integer 0) (integer 0))))
(texture
(pigment
(vector (integer 0) (integer 0) (integer 1)
diff --git a/tests/dimension/transformations.pov b/tests/dimension/transformations.pov
new file mode 100644
index 0000000..4b56c3f
--- /dev/null
+++ b/tests/dimension/transformations.pov
@@ -0,0 +1,39 @@
+/*************************************************************************
+ * Copyright (C) 2009-2010 Tavian Barnes <tavianator@gmail.com> *
+ * *
+ * This file is part of The Dimension Test Suite. *
+ * *
+ * The Dimension Test Suite 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 3 of the *
+ * License, or (at your option) any later version. *
+ * *
+ * The Dimension Test Suite 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, see <http://www.gnu.org/licenses/>. *
+ *************************************************************************/
+
+// Test transformations
+
+#declare Trans = transform { translate 1*z }
+
+sphere {
+ 0, 0
+ translate -1*x
+ rotate 90*y
+ scale 2
+ transform Trans
+ matrix <1, 1, 0,
+ 0, 1, 0,
+ 0, 0, 1,
+ 0, 0, 0>
+ transform {
+ Trans
+ rotate 45*z
+ inverse
+ }
+}
diff --git a/tests/dimension/transformations.sh b/tests/dimension/transformations.sh
new file mode 100755
index 0000000..3e6b8e2
--- /dev/null
+++ b/tests/dimension/transformations.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+#########################################################################
+# Copyright (C) 2009-2010 Tavian Barnes <tavianator@gmail.com> #
+# #
+# This file is part of The Dimension Test Suite. #
+# #
+# The Dimension Test Suite 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 3 of the #
+# License, or (at your option) any later version. #
+# #
+# The Dimension Test Suite 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, see <http://www.gnu.org/licenses/>. #
+#########################################################################
+
+transformations=$(${top_builddir}/dimension/dimension --parse ${srcdir}/transformations.pov)
+transformations_exp="$(echo -n \
+'((sphere
+ (vector (integer 0) (integer 0) (integer 0) (integer 0) (integer 0))
+ (integer 0)
+ (object-modifiers
+ (transformation
+ (translation
+ (vector (integer -1) (integer 0) (integer 0)
+ (integer 0) (integer 0))))
+ (transformation
+ (rotation
+ (vector (integer 0) (integer 90) (integer 0)
+ (integer 0) (integer 0))))
+ (transformation
+ (scale
+ (vector (integer 2) (integer 2) (integer 2) (integer 2) (integer 2))))
+ (transformation
+ (translation
+ (vector (integer 0) (integer 0) (integer 1) (integer 0) (integer 0))))
+ (transformation
+ (matrix
+ (integer 1) (integer 1) (integer 0)
+ (integer 0) (integer 1) (integer 0)
+ (integer 0) (integer 0) (integer 1)
+ (integer 0) (integer 0) (integer 0)))
+ (transformation
+ (translation
+ (vector (integer 0) (integer 0) (integer 1) (integer 0) (integer 0)))
+ (rotation
+ (vector (integer 0) (integer 0) (integer 45)
+ (integer 0) (integer 0)))
+ inverse))))' \
+| tr '\n' ' ' | sed -r 's/[[:space:]]+/ /g')"
+
+if [ "$transformations" != "$transformations_exp" ]; then
+ echo "transformations.pov parsed as \"$transformations\"" >&2
+ echo " -- expected \"$transformations_exp\"" >&2
+ exit 1
+fi
+
+${top_builddir}/dimension/dimension -w1 -h1 -o /dev/null ${srcdir}/transformations.pov