Vuo  2.4.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
31
37void 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
66void 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
130VuoPoint3d 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
141VuoPoint4d 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
155VuoPoint3d 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
201VuoTransform 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
216void 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
232void 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
251VuoTransform 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{
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
298VuoTransform 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
360VuoPoint2d VuoTransform_transform_VuoPoint2d(VuoTransform transform, VuoPoint2d point)
361{
362 return VuoTransform_transform_VuoPoint3d(transform, (VuoPoint3d){point.x, point.y, 0}).xy;
363}
364
368VuoPoint3d 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
413void 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
481VuoPoint4d 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
583static 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>\n<div>Target (%g, %g, %g)</div>\n<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>\n<div>Rotation %s</div>\n<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}