Vuo  2.0.0
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 
365 {
366  VuoReal left = rectangle.center.x - rectangle.size.x/2.;
367  VuoReal right = rectangle.center.x + rectangle.size.x/2.;
368  VuoReal bottom = rectangle.center.y - rectangle.size.y/2.;
369  VuoReal top = rectangle.center.y + rectangle.size.y/2.;
370 
371  VuoPoint3d topLeft = VuoTransform_transformPoint(matrix, VuoPoint3d_make(left, top, 0.));
372  VuoPoint3d topRight = VuoTransform_transformPoint(matrix, VuoPoint3d_make(right, top, 0.));
373  VuoPoint3d bottomLeft = VuoTransform_transformPoint(matrix, VuoPoint3d_make(left, bottom, 0.));
374  VuoPoint3d bottomRight = VuoTransform_transformPoint(matrix, VuoPoint3d_make(right, bottom, 0.));
375 
376  VuoReal transformedLeft = MIN(MIN(MIN(topLeft.x, topRight.x), bottomLeft.x), bottomRight.x);
377  VuoReal transformedRight = MAX(MAX(MAX(topLeft.x, topRight.x), bottomLeft.x), bottomRight.x);
378  VuoReal transformedBottom = MIN(MIN(MIN(topLeft.y, topRight.y), bottomLeft.y), bottomRight.y);
379  VuoReal transformedTop = MAX(MAX(MAX(topLeft.y, topRight.y), bottomLeft.y), bottomRight.y);
380 
381  VuoRectangle transformedRectangle = VuoRectangle_make(
382  (transformedLeft + transformedRight)/2.,
383  (transformedBottom + transformedTop)/2.,
384  transformedRight - transformedLeft,
385  transformedTop - transformedBottom);
386 
387  return transformedRectangle;
388 }
389 
395 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)
396 {
397  VuoReal combinedScaleFactor = 1;
398  if (preservePhysicalSize)
399  combinedScaleFactor = backingScaleFactor / imageScaleFactor;
400 
401 // VLog("%lldx%lld@%gx preservePhysicalSize=%ld on %lldx%lld@%gx = %gx",imageWidth,imageHeight,imageScaleFactor,preservePhysicalSize,viewportWidth,viewportHeight,backingScaleFactor,combinedScaleFactor);
402 
403  imageWidth *= combinedScaleFactor;
404  imageHeight *= combinedScaleFactor;
405 
407 
408  // If we don't know the viewport size, we don't know the image scale,
409  // so zero the scale (but keep the translation).
410  if (viewportWidth <= 0)
411  {
412  billboardMatrix[0] = 0;
413  billboardMatrix[5] = 0;
414  billboardMatrix[12] = translationX;
415  billboardMatrix[13] = translationY;
416  return;
417  }
418 
419  // Apply scale to make the image appear at real size (1:1).
420  billboardMatrix[0] = 2. * imageWidth/viewportWidth;
421  billboardMatrix[5] = billboardMatrix[0] * imageHeight/imageWidth;
422 
423  // Apply 2D translation.
424  // Align the translation to pixel boundaries
425  billboardMatrix[12] = floor((translationX+1.)/2.*viewportWidth) / ((float)viewportWidth) * 2. - 1.;
426  billboardMatrix[13] = floor((translationY+1.)/2.*viewportWidth) / ((float)viewportWidth) * 2. - 1.;
427 
428  // Account for odd-dimensioned image with center anchor.
429  // (We know from VuoSceneText_make() that the mesh's first coordinate
430  // is 0.0, -0.5, or -1.0, depending on whether the anchor is left/top, center, or right/bottom, respectively.)
431  bool meshCenteredX = VuoReal_areEqual(mesh0.x, -.5);
432  if (meshCenteredX)
433  billboardMatrix[12] += (imageWidth % 2 ? (1./viewportWidth) : 0);
434  bool meshCenteredY = VuoReal_areEqual(mesh0.y, -.5);
435  if (meshCenteredY)
436  billboardMatrix[13] -= (imageHeight % 2 ? (1./viewportWidth) : 0);
437 
438  // Account for odd-dimensioned viewport
439  billboardMatrix[13] += (viewportWidth % 2 ? (1./viewportWidth) : 0);
440  billboardMatrix[13] -= (viewportHeight % 2 ? (1./viewportWidth) : 0);
441 }
442 
447 {
448  float aMatrix[16];
449  VuoTransform_getMatrix(a, aMatrix);
450 
451  float bMatrix[16];
452  VuoTransform_getMatrix(b, bMatrix);
453 
454  float compositeMatrix[16];
455  VuoTransform_multiplyMatrices4x4(aMatrix, bMatrix, compositeMatrix);
456 
457  return VuoTransform_makeFromMatrix4x4(compositeMatrix);
458 }
459 
463 VuoPoint4d VuoTransform_quaternionFromBasis(VuoPoint3d basis[3])
464 {
465  // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
466  VuoPoint4d q;
467  q.w = sqrt(1 + basis[0].x + basis[1].y + basis[2].z) / 2;
468  q.x = (basis[2].y - basis[1].z) / (4 * q.w);
469  q.y = (basis[0].z - basis[2].x) / (4 * q.w);
470  q.z = (basis[1].x - basis[0].y) / (4 * q.w);
471  return q;
472 }
473 
495 {
497  json_object *o = NULL;
498 
499  if (json_object_object_get_ex(js, "target", &o))
500  {
501  VuoPoint3d target;
502  target.x = json_object_get_double(json_object_array_get_idx(o,0));
503  target.y = json_object_get_double(json_object_array_get_idx(o,1));
504  target.z = json_object_get_double(json_object_array_get_idx(o,2));
505 
506  VuoPoint3d position = VuoPoint3d_make(0,0,0);
507  if (json_object_object_get_ex(js, "translation", &o))
508  {
509  position.x = json_object_get_double(json_object_array_get_idx(o,0));
510  position.y = json_object_get_double(json_object_array_get_idx(o,1));
511  position.z = json_object_get_double(json_object_array_get_idx(o,2));
512  }
513 
514  VuoPoint3d upDirection = VuoPoint3d_make(0,1,0);
515  if (json_object_object_get_ex(js, "upDirection", &o))
516  {
517  upDirection.x = json_object_get_double(json_object_array_get_idx(o,0));
518  upDirection.y = json_object_get_double(json_object_array_get_idx(o,1));
519  upDirection.z = json_object_get_double(json_object_array_get_idx(o,2));
520  }
521 
522  return VuoTransform_makeFromTarget(position, target, upDirection);
523  }
524 
525  if (json_object_object_get_ex(js, "quaternionRotation", &o))
526  {
527  t.type = VuoTransformTypeQuaternion;
528  VuoPoint4d q;
529  q.x = json_object_get_double(json_object_array_get_idx(o,0));
530  q.y = json_object_get_double(json_object_array_get_idx(o,1));
531  q.z = json_object_get_double(json_object_array_get_idx(o,2));
532  q.w = json_object_get_double(json_object_array_get_idx(o,3));
534  }
535  else if (json_object_object_get_ex(js, "eulerRotation", &o))
536  {
537  t.type = VuoTransformTypeEuler;
538  VuoPoint3d e;
539  e.x = json_object_get_double(json_object_array_get_idx(o,0));
540  e.y = json_object_get_double(json_object_array_get_idx(o,1));
541  e.z = json_object_get_double(json_object_array_get_idx(o,2));
543  }
544 
545  if (json_object_object_get_ex(js, "translation", &o))
546  {
547  t.translation.x = json_object_get_double(json_object_array_get_idx(o,0));
548  t.translation.y = json_object_get_double(json_object_array_get_idx(o,1));
549  t.translation.z = json_object_get_double(json_object_array_get_idx(o,2));
550  }
551 
552  if (json_object_object_get_ex(js, "scale", &o))
553  {
554  t.scale.x = json_object_get_double(json_object_array_get_idx(o,0));
555  t.scale.y = json_object_get_double(json_object_array_get_idx(o,1));
556  t.scale.z = json_object_get_double(json_object_array_get_idx(o,2));
557  }
558 
559  return t;
560 }
561 
565 static inline float cook(float f)
566 {
567  if (fabs(f) < FLT_EPSILON)
568  return 0;
569 
570  return f;
571 }
572 
578 {
579  if (VuoTransform_isIdentity(value))
580  return json_object_new_string("identity");
581 
582  json_object *js = json_object_new_object();
583 
584  {
585  json_object * o = json_object_new_array();
586  json_object_array_add(o,json_object_new_double(cook(value.translation.x)));
587  json_object_array_add(o,json_object_new_double(cook(value.translation.y)));
588  json_object_array_add(o,json_object_new_double(cook(value.translation.z)));
589  json_object_object_add(js, "translation", o);
590  }
591 
592  // Don't store value.rotation, since we can calculate it from the source rotation.
593 
594  if (value.type == VuoTransformTypeQuaternion)
595  {
596  json_object * o = json_object_new_array();
597  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.x)));
598  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.y)));
599  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.z)));
600  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.quaternion.w)));
601  json_object_object_add(js, "quaternionRotation", o);
602  }
603  else if (value.type == VuoTransformTypeEuler)
604  {
605  json_object * o = json_object_new_array();
606  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.euler.x)));
607  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.euler.y)));
608  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.euler.z)));
609  json_object_object_add(js, "eulerRotation", o);
610  }
611  else
612  {
613  json_object * o = json_object_new_array();
614  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.target.x)));
615  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.target.y)));
616  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.target.z)));
617  json_object_object_add(js, "target", o);
618 
619  o = json_object_new_array();
620  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.upDirection.x)));
621  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.upDirection.y)));
622  json_object_array_add(o,json_object_new_double(cook(value.rotationSource.upDirection.z)));
623  json_object_object_add(js, "upDirection", o);
624  }
625 
626  if (value.type != VuoTransformTypeTargeted)
627  {
628  json_object * o = json_object_new_array();
629  json_object_array_add(o,json_object_new_double(cook(value.scale.x)));
630  json_object_array_add(o,json_object_new_double(cook(value.scale.y)));
631  json_object_array_add(o,json_object_new_double(cook(value.scale.z)));
632  json_object_object_add(js, "scale", o);
633  }
634 
635  return js;
636 }
637 
638 
644 {
645  if (VuoTransform_isIdentity(value))
646  return strdup("identity transform (no change)");
647 
648  if (value.type == VuoTransformTypeTargeted)
649  return VuoText_format("<div>position (%g, %g, %g)</div><div>target (%g, %g, %g)</div><div>up (%g, %g, %g)</div>",
650  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);
651 
652  char *rotation;
653  if (value.type == VuoTransformTypeQuaternion)
654  rotation = VuoText_format("(%g, %g, %g, %g) quaternion",
655  value.rotationSource.quaternion.x, value.rotationSource.quaternion.y, value.rotationSource.quaternion.z, value.rotationSource.quaternion.w);
656  else
657  {
658  VuoPoint3d r = VuoPoint3d_multiply(value.rotationSource.euler, 180./M_PI);
659  rotation = VuoText_format("(%g°, %g°, %g°) euler",
660  r.x, r.y, r.z);
661  }
662 
663  char *valueAsString = VuoText_format("<div>translation (%g, %g, %g)</div><div>rotation %s</div><div>scale (%g, %g, %g)</div>",
664  value.translation.x, value.translation.y, value.translation.z, rotation, value.scale.x, value.scale.y, value.scale.z);
665  free(rotation);
666  return valueAsString;
667 }