Vuo  2.0.2
VuoTransform.c
Go to the documentation of this file.
1 
10 #include <stdlib.h>
11 #include <string.h>
12 #include "type.h"
13 
15 #ifdef VUO_COMPILER
17  "title" : "3D Transform",
18  "description" : "A 3D transformation (scale, rotation, translation).",
19  "keywords" : [ ],
20  "version" : "1.0.0",
21  "dependencies" : [
22  "VuoInteger",
23  "VuoPoint3d",
24  "VuoPoint4d",
25  "VuoText",
26  "VuoTransform2d"
27  ]
28  });
29 #endif
30 
37 void VuoTransform_getMatrix(const VuoTransform value, float *matrix)
38 {
39  matrix[ 0] = value.rotation[0] * value.scale.x;
40  matrix[ 1] = value.rotation[1] * value.scale.x;
41  matrix[ 2] = value.rotation[2] * value.scale.x;
42  matrix[ 3] = 0;
43 
44  matrix[ 4] = value.rotation[3] * value.scale.y;
45  matrix[ 5] = value.rotation[4] * value.scale.y;
46  matrix[ 6] = value.rotation[5] * value.scale.y;
47  matrix[ 7] = 0;
48 
49  matrix[ 8] = value.rotation[6] * value.scale.z;
50  matrix[ 9] = value.rotation[7] * value.scale.z;
51  matrix[10] = value.rotation[8] * value.scale.z;
52  matrix[11] = 0;
53 
54  matrix[12] = value.translation.x;
55  matrix[13] = value.translation.y;
56  matrix[14] = value.translation.z;
57  matrix[15] = 1;
58 }
59 
66 void VuoTransform_invertMatrix4x4(const float *matrix, float *outputInvertedMatrix)
67 {
68  float inverseTranslation[16];
70  VuoPoint3d_make(-matrix[12], -matrix[13], -matrix[14]),
71  VuoPoint3d_make(0,0,0),
72  VuoPoint3d_make(1,1,1)),
73  inverseTranslation);
74 
75  // Transpose the rotation/scale part of the input matrix (which undoes the rotation), and set the last row/column to identity.
76  float inverseRotation[16] = {
77  matrix[0],
78  matrix[4],
79  matrix[8],
80  0,
81 
82  matrix[1],
83  matrix[5],
84  matrix[9],
85  0,
86 
87  matrix[2],
88  matrix[6],
89  matrix[10],
90  0,
91 
92  0,
93  0,
94  0,
95  1
96  };
97 
98  // Avoid propagating NaNs when the scale of one axis is zero.
99  VuoPoint3d inverseScale = (VuoPoint3d){
100  1/VuoPoint3d_magnitude(VuoPoint3d_make(matrix[0], matrix[1], matrix[2])),
101  1/VuoPoint3d_magnitude(VuoPoint3d_make(matrix[4], matrix[5], matrix[6])),
102  1/VuoPoint3d_magnitude(VuoPoint3d_make(matrix[8], matrix[9], matrix[10]))
103  };
104  if (isnan(inverseScale.x) || isinf(inverseScale.x))
105  inverseScale.x = 0;
106  if (isnan(inverseScale.y) || isinf(inverseScale.y))
107  inverseScale.y = 0;
108  if (isnan(inverseScale.z) || isinf(inverseScale.z))
109  inverseScale.z = 0;
110 
111  float inverseScaleMatrix[16];
113  (VuoPoint3d){0,0,0},
114  (VuoPoint3d){0,0,0},
115  inverseScale),
116  inverseScaleMatrix);
117 
118  VuoTransform_multiplyMatrices4x4(inverseTranslation, inverseRotation, outputInvertedMatrix);
119 
120  VuoTransform_multiplyMatrices4x4(outputInvertedMatrix, inverseScaleMatrix, outputInvertedMatrix);
121 
122  // Apply inverseScale a second time, since inverseRotation includes forward scale.
123  VuoTransform_multiplyMatrices4x4(outputInvertedMatrix, inverseScaleMatrix, outputInvertedMatrix);
124 }
125 
130 VuoPoint3d VuoTransform_getEuler(const VuoTransform transform)
131 {
132  if (transform.type == VuoTransformTypeEuler)
133  return transform.rotationSource.euler;
134 
135  return VuoTransform_eulerFromQuaternion(transform.rotationSource.quaternion);
136 }
137 
141 VuoPoint4d VuoTransform_getQuaternion(const VuoTransform transform)
142 {
143  if (transform.type == VuoTransformTypeQuaternion)
144  return transform.rotationSource.quaternion;
145 
146  return VuoTransform_quaternionFromEuler(transform.rotationSource.euler);
147 }
148 
155 VuoPoint3d VuoTransform_getDirection(const VuoTransform transform)
156 {
157  // Make a new transform with only the rotational component.
158  VuoTransform r;
159  if (transform.type == VuoTransformTypeEuler)
160  r = VuoTransform_makeEuler(VuoPoint3d_make(0,0,0), transform.rotationSource.euler, VuoPoint3d_make(1,1,1));
161  else
162  r = VuoTransform_makeQuaternion(VuoPoint3d_make(0,0,0), transform.rotationSource.quaternion, VuoPoint3d_make(1,1,1));
163 
164  float m[16];
167 }
168 
174 {
175  VuoTransform t;
176 
177  t.type = VuoTransformTypeEuler;
178 
179  t.translation = (VuoPoint3d){0,0,0};
180 
181  t.rotationSource.euler = (VuoPoint3d){0,0,0};
182  t.rotation[0] = 1;
183  t.rotation[1] = 0;
184  t.rotation[2] = 0;
185  t.rotation[3] = 0;
186  t.rotation[4] = 1;
187  t.rotation[5] = 0;
188  t.rotation[6] = 0;
189  t.rotation[7] = 0;
190  t.rotation[8] = 1;
191 
192  t.scale = (VuoPoint3d){1,1,1};
193 
194  return t;
195 }
196 
201 VuoTransform VuoTransform_makeEuler(VuoPoint3d translation, VuoPoint3d rotation, VuoPoint3d scale)
202 {
203  VuoTransform t;
204  t.type = VuoTransformTypeEuler;
205  t.translation = translation;
206  t.rotationSource.euler = rotation;
207  t.scale = scale;
209 
210  return t;
211 }
212 
216 void VuoTransform_rotationMatrixFromQuaternion(const VuoPoint4d quaternion, float* matrix)
217 {
218  matrix[0] = 1. - 2. * (quaternion.y * quaternion.y + quaternion.z * quaternion.z);
219  matrix[1] = 2. * (quaternion.x * quaternion.y + quaternion.w * quaternion.z);
220  matrix[2] = 2. * (quaternion.x * quaternion.z - quaternion.w * quaternion.y);
221  matrix[3] = 2. * (quaternion.x * quaternion.y - quaternion.w * quaternion.z);
222  matrix[4] = 1. - 2. * (quaternion.x * quaternion.x + quaternion.z * quaternion.z);
223  matrix[5] = 2. * (quaternion.y * quaternion.z + quaternion.w * quaternion.x);
224  matrix[6] = 2. * (quaternion.x * quaternion.z + quaternion.w * quaternion.y);
225  matrix[7] = 2. * (quaternion.y * quaternion.z - quaternion.w * quaternion.x);
226  matrix[8] = 1. - 2. * (quaternion.x * quaternion.x + quaternion.y * quaternion.y);
227 }
228 
232 void VuoTransform_rotationMatrixFromEuler(const VuoPoint3d euler, float* matrix)
233 {
234  matrix[0] = cos(euler.y)*cos(euler.z);
235  matrix[1] = cos(euler.y)*sin(euler.z);
236  matrix[2] = -sin(euler.y);
237  matrix[3] = cos(euler.z)*sin(euler.x)*sin(euler.y) - cos(euler.x)*sin(euler.z);
238  matrix[4] = cos(euler.x)*cos(euler.z) + sin(euler.x)*sin(euler.y)*sin(euler.z);
239  matrix[5] = cos(euler.y)*sin(euler.x);
240  matrix[6] = cos(euler.x)*cos(euler.z)*sin(euler.y) + sin(euler.x)*sin(euler.z);
241  matrix[7] = -cos(euler.z)*sin(euler.x) + cos(euler.x)*sin(euler.y)*sin(euler.z);
242  matrix[8] = cos(euler.x)*cos(euler.y);
243 }
244 
251 VuoTransform VuoTransform_makeQuaternion(VuoPoint3d translation, VuoPoint4d rotation, VuoPoint3d scale)
252 {
253  VuoTransform t;
254 
255  t.type = VuoTransformTypeQuaternion;
256 
257  t.translation = translation;
258 
259  VuoPoint4d q = VuoPoint4d_normalize(rotation);
260 
261  t.rotationSource.quaternion = q;
262 
264 
265  t.scale = scale;
266 
267  return t;
268 }
269 
274 {
275  VuoPoint3d center3d = VuoPoint3d_make(transform2d.translation.x, transform2d.translation.y, 0);
276  VuoPoint3d rotation3d = VuoPoint3d_make(0, 0, transform2d.rotation);
277  VuoPoint3d scale3d = VuoPoint3d_make(transform2d.scale.x, transform2d.scale.y, 1);
278  return VuoTransform_makeEuler(center3d, rotation3d, scale3d);
279 }
280 
285 {
286  VuoTransform2d t;
287  t.translation = (VuoPoint2d){transform.translation.x, transform.translation.y};
288  t.rotation = VuoTransform_getEuler(transform).z;
289  t.scale = (VuoPoint2d){transform.scale.x, transform.scale.y};
290  return t;
291 }
292 
298 VuoTransform VuoTransform_makeFromTarget(VuoPoint3d position, VuoPoint3d target, VuoPoint3d upDirection)
299 {
300  VuoPoint3d n = VuoPoint3d_normalize(VuoPoint3d_subtract(position, target));
301  VuoPoint3d u = VuoPoint3d_normalize(VuoPoint3d_crossProduct(upDirection, n));
302  VuoPoint3d v = VuoPoint3d_crossProduct(n, u);
303 
304  VuoTransform t;
305 
306  t.type = VuoTransformTypeTargeted;
307 
308  t.translation = position;
309 
310  t.rotationSource.target = target;
311  t.rotationSource.upDirection = upDirection;
312  t.rotation[0] = u.x;
313  t.rotation[3] = v.x;
314  t.rotation[6] = n.x;
315  t.rotation[1] = u.y;
316  t.rotation[4] = v.y;
317  t.rotation[7] = n.y;
318  t.rotation[2] = u.z;
319  t.rotation[5] = v.z;
320  t.rotation[8] = n.z;
321 
322  t.scale = VuoPoint3d_make(1,1,1);
323 
324  return t;
325 }
326 
331 {
332  VuoTransform t;
333 
334  t.scale = VuoTransform_getMatrix4x4Scale(matrix);
335 
336  t.rotation[0] = matrix[ 0] / t.scale.x;
337  t.rotation[1] = matrix[ 1] / t.scale.x;
338  t.rotation[2] = matrix[ 2] / t.scale.x;
339 
340  t.rotation[3] = matrix[ 4] / t.scale.y;
341  t.rotation[4] = matrix[ 5] / t.scale.y;
342  t.rotation[5] = matrix[ 6] / t.scale.y;
343 
344  t.rotation[6] = matrix[ 8] / t.scale.z;
345  t.rotation[7] = matrix[ 9] / t.scale.z;
346  t.rotation[8] = matrix[10] / t.scale.z;
347 
348  t.type = VuoTransformTypeQuaternion;
349 
350  t.rotationSource.quaternion = VuoTransform_quaternionFromMatrix(t.rotation);
351 
352  t.translation = VuoTransform_getMatrix4x4Translation(matrix);
353 
354  return t;
355 }
356 
360 VuoPoint2d VuoTransform_transform_VuoPoint2d(VuoTransform transform, VuoPoint2d point)
361 {
362  return VuoTransform_transform_VuoPoint3d(transform, (VuoPoint3d){point.x, point.y, 0}).xy;
363 }
364 
368 VuoPoint3d VuoTransform_transform_VuoPoint3d(VuoTransform transform, VuoPoint3d point)
369 {
370  float matrix[16];
371  VuoTransform_getMatrix(transform, matrix);
372  return VuoTransform_transformPoint(matrix, point);
373 }
374 
383 {
384  VuoReal left = rectangle.center.x - rectangle.size.x/2.;
385  VuoReal right = rectangle.center.x + rectangle.size.x/2.;
386  VuoReal bottom = rectangle.center.y - rectangle.size.y/2.;
387  VuoReal top = rectangle.center.y + rectangle.size.y/2.;
388 
389  VuoPoint3d topLeft = VuoTransform_transformPoint(matrix, VuoPoint3d_make(left, top, 0.));
390  VuoPoint3d topRight = VuoTransform_transformPoint(matrix, VuoPoint3d_make(right, top, 0.));
391  VuoPoint3d bottomLeft = VuoTransform_transformPoint(matrix, VuoPoint3d_make(left, bottom, 0.));
392  VuoPoint3d bottomRight = VuoTransform_transformPoint(matrix, VuoPoint3d_make(right, bottom, 0.));
393 
394  VuoReal transformedLeft = MIN(MIN(MIN(topLeft.x, topRight.x), bottomLeft.x), bottomRight.x);
395  VuoReal transformedRight = MAX(MAX(MAX(topLeft.x, topRight.x), bottomLeft.x), bottomRight.x);
396  VuoReal transformedBottom = MIN(MIN(MIN(topLeft.y, topRight.y), bottomLeft.y), bottomRight.y);
397  VuoReal transformedTop = MAX(MAX(MAX(topLeft.y, topRight.y), bottomLeft.y), bottomRight.y);
398 
399  VuoRectangle transformedRectangle = VuoRectangle_make(
400  (transformedLeft + transformedRight)/2.,
401  (transformedBottom + transformedTop)/2.,
402  transformedRight - transformedLeft,
403  transformedTop - transformedBottom);
404 
405  return transformedRectangle;
406 }
407 
413 void VuoTransform_getBillboardMatrix(VuoInteger imageWidth, VuoInteger imageHeight, VuoReal imageScaleFactor, VuoBoolean preservePhysicalSize, VuoReal translationX, VuoReal translationY, VuoInteger viewportWidth, VuoInteger viewportHeight, VuoReal backingScaleFactor, VuoPoint2d mesh0, float *billboardMatrix)
414 {
415  VuoReal combinedScaleFactor = 1;
416  if (preservePhysicalSize)
417  combinedScaleFactor = backingScaleFactor / imageScaleFactor;
418 
419 // VLog("%lldx%lld@%gx preservePhysicalSize=%ld on %lldx%lld@%gx = %gx",imageWidth,imageHeight,imageScaleFactor,preservePhysicalSize,viewportWidth,viewportHeight,backingScaleFactor,combinedScaleFactor);
420 
421  imageWidth *= combinedScaleFactor;
422  imageHeight *= combinedScaleFactor;
423 
425 
426  // If we don't know the viewport size, we don't know the image scale,
427  // so zero the scale (but keep the translation).
428  if (viewportWidth <= 0)
429  {
430  billboardMatrix[0] = 0;
431  billboardMatrix[5] = 0;
432  billboardMatrix[12] = translationX;
433  billboardMatrix[13] = translationY;
434  return;
435  }
436 
437  // Apply scale to make the image appear at real size (1:1).
438  billboardMatrix[0] = 2. * imageWidth/viewportWidth;
439  billboardMatrix[5] = billboardMatrix[0] * imageHeight/imageWidth;
440 
441  // Apply 2D translation.
442  // Align the translation to pixel boundaries
443  billboardMatrix[12] = floor((translationX+1.)/2.*viewportWidth) / ((float)viewportWidth) * 2. - 1.;
444  billboardMatrix[13] = floor((translationY+1.)/2.*viewportWidth) / ((float)viewportWidth) * 2. - 1.;
445 
446  // Account for odd-dimensioned image with center anchor.
447  // (We know from VuoSceneText_make() that the mesh's first coordinate
448  // is 0.0, -0.5, or -1.0, depending on whether the anchor is left/top, center, or right/bottom, respectively.)
449  bool meshCenteredX = VuoReal_areEqual(mesh0.x, -.5);
450  if (meshCenteredX)
451  billboardMatrix[12] += (imageWidth % 2 ? (1./viewportWidth) : 0);
452  bool meshCenteredY = VuoReal_areEqual(mesh0.y, -.5);
453  if (meshCenteredY)
454  billboardMatrix[13] -= (imageHeight % 2 ? (1./viewportWidth) : 0);
455 
456  // Account for odd-dimensioned viewport
457  billboardMatrix[13] += (viewportWidth % 2 ? (1./viewportWidth) : 0);
458  billboardMatrix[13] -= (viewportHeight % 2 ? (1./viewportWidth) : 0);
459 }
460 
465 {
466  float aMatrix[16];
467  VuoTransform_getMatrix(a, aMatrix);
468 
469  float bMatrix[16];
470  VuoTransform_getMatrix(b, bMatrix);
471 
472  float compositeMatrix[16];
473  VuoTransform_multiplyMatrices4x4(aMatrix, bMatrix, compositeMatrix);
474 
475  return VuoTransform_makeFromMatrix4x4(compositeMatrix);
476 }
477 
481 VuoPoint4d VuoTransform_quaternionFromBasis(VuoPoint3d basis[3])
482 {
483  // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
484  VuoPoint4d q;
485  q.w = sqrt(1 + basis[0].x + basis[1].y + basis[2].z) / 2;
486  q.x = (basis[2].y - basis[1].z) / (4 * q.w);
487  q.y = (basis[0].z - basis[2].x) / (4 * q.w);
488  q.z = (basis[1].x - basis[0].y) / (4 * q.w);
489  return q;
490 }
491 
513 {
515  json_object *o = NULL;
516 
517  if (json_object_object_get_ex(js, "target", &o))
518  {
519  VuoPoint3d target;
520  target.x = json_object_get_double(json_object_array_get_idx(o,0));
521  target.y = json_object_get_double(json_object_array_get_idx(o,1));
522  target.z = json_object_get_double(json_object_array_get_idx(o,2));
523 
524  VuoPoint3d position = VuoPoint3d_make(0,0,0);
525  if (json_object_object_get_ex(js, "translation", &o))
526  {
527  position.x = json_object_get_double(json_object_array_get_idx(o,0));
528  position.y = json_object_get_double(json_object_array_get_idx(o,1));
529  position.z = json_object_get_double(json_object_array_get_idx(o,2));
530  }
531 
532  VuoPoint3d upDirection = VuoPoint3d_make(0,1,0);
533  if (json_object_object_get_ex(js, "upDirection", &o))
534  {
535  upDirection.x = json_object_get_double(json_object_array_get_idx(o,0));
536  upDirection.y = json_object_get_double(json_object_array_get_idx(o,1));
537  upDirection.z = json_object_get_double(json_object_array_get_idx(o,2));
538  }
539 
540  return VuoTransform_makeFromTarget(position, target, upDirection);
541  }
542 
543  if (json_object_object_get_ex(js, "quaternionRotation", &o))
544  {
545  t.type = VuoTransformTypeQuaternion;
546  VuoPoint4d q;
547  q.x = json_object_get_double(json_object_array_get_idx(o,0));
548  q.y = json_object_get_double(json_object_array_get_idx(o,1));
549  q.z = json_object_get_double(json_object_array_get_idx(o,2));
550  q.w = json_object_get_double(json_object_array_get_idx(o,3));
552  }
553  else if (json_object_object_get_ex(js, "eulerRotation", &o))
554  {
555  t.type = VuoTransformTypeEuler;
556  VuoPoint3d e;
557  e.x = json_object_get_double(json_object_array_get_idx(o,0));
558  e.y = json_object_get_double(json_object_array_get_idx(o,1));
559  e.z = json_object_get_double(json_object_array_get_idx(o,2));
561  }
562 
563  if (json_object_object_get_ex(js, "translation", &o))
564  {
565  t.translation.x = json_object_get_double(json_object_array_get_idx(o,0));
566  t.translation.y = json_object_get_double(json_object_array_get_idx(o,1));
567  t.translation.z = json_object_get_double(json_object_array_get_idx(o,2));
568  }
569 
570  if (json_object_object_get_ex(js, "scale", &o))
571  {
572  t.scale.x = json_object_get_double(json_object_array_get_idx(o,0));
573  t.scale.y = json_object_get_double(json_object_array_get_idx(o,1));
574  t.scale.z = json_object_get_double(json_object_array_get_idx(o,2));
575  }
576 
577  return t;
578 }
579 
583 static inline float cook(float f)
584 {
585  if (fabs(f) < FLT_EPSILON)
586  return 0;
587 
588  return f;
589 }
590 
596 {
597  if (VuoTransform_isIdentity(value))
598  return json_object_new_string("identity");
599 
600  json_object *js = json_object_new_object();
601 
602  {
603  json_object * o = json_object_new_array();
604  json_object_array_add(o,json_object_new_double(cook(value.translation.x)));
605  json_object_array_add(o,json_object_new_double(cook(value.translation.y)));
606  json_object_array_add(o,json_object_new_double(cook(value.translation.z)));
607  json_object_object_add(js, "translation", o);
608  }
609 
610  // Don't store value.rotation, since we can calculate it from the source rotation.
611 
612  if (value.type == VuoTransformTypeQuaternion)
613  {
614  json_object * o = json_object_new_array();
615  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.x)));
616  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.y)));
617  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.z)));
618  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.w)));
619  json_object_object_add(js, "quaternionRotation", o);
620  }
621  else if (value.type == VuoTransformTypeEuler)
622  {
623  json_object * o = json_object_new_array();
624  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.euler.x)));
625  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.euler.y)));
626  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.euler.z)));
627  json_object_object_add(js, "eulerRotation", o);
628  }
629  else
630  {
631  json_object * o = json_object_new_array();
632  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.target.x)));
633  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.target.y)));
634  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.target.z)));
635  json_object_object_add(js, "target", o);
636 
637  o = json_object_new_array();
638  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.upDirection.x)));
639  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.upDirection.y)));
640  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.upDirection.z)));
641  json_object_object_add(js, "upDirection", o);
642  }
643 
644  if (value.type != VuoTransformTypeTargeted)
645  {
646  json_object * o = json_object_new_array();
647  json_object_array_add(o,json_object_new_double(cook(value.scale.x)));
648  json_object_array_add(o,json_object_new_double(cook(value.scale.y)));
649  json_object_array_add(o,json_object_new_double(cook(value.scale.z)));
650  json_object_object_add(js, "scale", o);
651  }
652 
653  return js;
654 }
655 
656 
662 {
663  if (VuoTransform_isIdentity(value))
664  return strdup("identity transform (no change)");
665 
666  if (value.type == VuoTransformTypeTargeted)
667  return VuoText_format("<div>position (%g, %g, %g)</div><div>target (%g, %g, %g)</div><div>up (%g, %g, %g)</div>",
668  value.translation.x, value.translation.y, value.translation.z, value.rotationSource.target.x, value.rotationSource.target.y, value.rotationSource.target.z, value.rotationSource.upDirection.x, value.rotationSource.upDirection.y, value.rotationSource.upDirection.z);
669 
670  char *rotation;
671  if (value.type == VuoTransformTypeQuaternion)
672  rotation = VuoText_format("(%g, %g, %g, %g) quaternion",
673  value.rotationSource.quaternion.x, value.rotationSource.quaternion.y, value.rotationSource.quaternion.z, value.rotationSource.quaternion.w);
674  else
675  {
676  VuoPoint3d r = VuoPoint3d_multiply(value.rotationSource.euler, 180./M_PI);
677  rotation = VuoText_format("(%g°, %g°, %g°) euler",
678  r.x, r.y, r.z);
679  }
680 
681  char *valueAsString = VuoText_format("<div>translation (%g, %g, %g)</div><div>rotation %s</div><div>scale (%g, %g, %g)</div>",
682  value.translation.x, value.translation.y, value.translation.z, rotation, value.scale.x, value.scale.y, value.scale.z);
683  free(rotation);
684  return valueAsString;
685 }