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