CMSDK 2.0.1
Cross-platform C++ base library and SDK for the Psyclone AIOS platform
Loading...
Searching...
No Matches
MathClasses.cpp
Go to the documentation of this file.
1
3#include "MathClasses.h"
4#include "PsyTime.h"
5#include "UnitTestFramework.h"
6#include <algorithm>
7#include <random>
8
9namespace cmlabs {
10
12// Size Class
14
15Size::Size() { w=0; h=0; d=0; }
16Size::Size(double width, double height, double depth) { w=width; h=height; d=depth; }
18
19bool Size::equals(const Size &s) const {
20 return (
21 (this->d == s.d) &&
22 (this->w == s.w) &&
23 (this->h == s.h) );
24}
25
26bool Size::operator==(const Size &s) const {
27 return equals(s);
28}
29
30Size Size::operator*(const Vector2D &v) const {
31 Size s = *this;
32 s.w *= v.x;
33 s.h *= v.y;
34 return s;
35}
36
37
38double Size::getHeight() const { return h; }
39double Size::getWidth() const { return w; }
40double Size::getDepth() const { return d; }
41
42bool Size::setHeight(double height) { h = height; return true; }
43bool Size::setWidth(double width) { w = width; return true; }
44bool Size::setDepth(double depth) { d = depth; return true; }
45
46bool Size::isNonZero() const {
47 return ( (w != 0) || (h != 0) || (d != 0) );
48}
49
50double Size::getArea() const {
51 return ( getWidth() * getHeight() );
52}
53
54double Size::getDiagonalLenth() const {
55 return sqrt( pow(getWidth(), 2) + pow(getHeight(), 2) );
56}
57
58std::string Size::print() {
59 return utils::StringFormat("(w: %.3f, h: %.3f, d: %.3f)", w, h, d);
60}
61
62
64// Point Class
66
67Point::Point() { x=0; y=0; z=0; }
68Point::Point(int px, int py, int pz, Size psize) { x=px; y=py; z=pz; size=psize; }
70
71Point::operator PointFloat() const {
72 PointFloat pf;
73 pf.setSize(this->size);
74 pf.setX(this->x);
75 pf.setY(this->y);
76 pf.setZ(this->z);
77 return pf;
78}
79
80int Point::operator[](int n) const {
81 switch(n) {
82 case 0:
83 return x;
84 case 1:
85 return y;
86 case 2:
87 return z;
88 }
89 return 0;
90}
91
92bool Point::operator==(const Point &p) const {
93 return (
94 (this->x == p.x) &&
95 (this->y == p.y) &&
96 (this->z == p.z) &&
97 (this->size == p.size) );
98}
99
100bool Point::operator==(const PointFloat &p) const {
101 return (
102 (this->x == (int)p.x) &&
103 (this->y == (int)p.y) &&
104 (this->z == (int)p.z) &&
105 (this->size == p.size) );
106}
107
108Point Point::operator-(const Point &p) const {
109 Point np = *this;
110 np.x -= p.x;
111 np.y -= p.y;
112 np.z -= p.z;
113 return np;
114}
116 PointFloat np = *this;
117 np.x -= p.x;
118 np.y -= p.y;
119 np.z -= p.z;
120 return np;
121}
122Point Point::operator+(const Point &p) const {
123 Point np = *this;
124 np.x += p.x;
125 np.y += p.y;
126 np.z += p.z;
127 return np;
128}
130 PointFloat np = *this;
131 np.x += p.x;
132 np.y += p.y;
133 np.z += p.z;
134 return np;
135}
137 PointFloat np = *this;
138 np.x += v.x;
139 np.y += v.y;
140 return np;
141}
143 PointFloat np = *this;
144 np.x += v.x;
145 np.y += v.y;
146 np.z += v.z;
147 return np;
148}
149Point Point::operator*(const Point &p) const {
150 Point np = *this;
151 np.x *= p.x;
152 np.y *= p.y;
153 np.z *= p.z;
154 return np;
155}
157 PointFloat np = *this;
158 np.x *= p.x;
159 np.y *= p.y;
160 np.z *= p.z;
161 return np;
162}
163Point Point::operator-(double a) const {
164 Point np = *this;
165 np.x = (int)(np.x - a);
166 np.y = (int)(np.y - a);
167 np.z = (int)(np.z - a);
168 return np;
169}
170Point Point::operator+(double a) const {
171 Point np = *this;
172 np.x = (int)(np.x + a);
173 np.y = (int)(np.y + a);
174 np.z = (int)(np.z + a);
175 return np;
176}
177Point Point::operator*(double a) const {
178 Point np = *this;
179 np.x = (int)(np.x * a);
180 np.y = (int)(np.y * a);
181 np.z = (int)(np.z * a);
182 return np;
183}
184
185
186int Point::getX() const { return x; }
187int Point::getY() const { return y; }
188int Point::getZ() const { return z; }
189Size Point::getSize() const { return size; }
190
191bool Point::set(int xx, int yy, int zz) { x = xx; y = yy; z = zz; return true; }
192bool Point::setX(int n) { x = n; return true; }
193bool Point::setY(int n) { y = n; return true; }
194bool Point::setZ(int n) { z = n; return true; }
195bool Point::setSize(Size s) { size = s; return true; }
196
197double Point::getDistanceTo(Point &p) const {
198 return sqrt( pow((double)(getX()-p.getX()), 2) + pow((double)(getY()-p.getY()), 2) );
199}
201 return sqrt( pow((double)(getX()-p.getX()), 2) + pow((double)(getY()-p.getY()), 2) );
202}
203
204std::string Point::print() {
205 if (size.isNonZero())
206 return utils::StringFormat("(%d,%d,%d x %s)", x, y, z, size.print().c_str());
207 else
208 return utils::StringFormat("(%d,%d,%d)", x, y, z);
209}
210
211
213// PointFloat Class
215
217PointFloat::PointFloat(double px, double py, double pz, Size psize) { x=px; y=py; z=pz; size=psize; }
219
220PointFloat::operator Point() const {
221 Point p;
222 p.setSize(this->size);
223 p.setX((int)this->x);
224 p.setY((int)this->y);
225 p.setZ((int)this->z);
226 return p;
227}
228
229double PointFloat::operator[](int n) const {
230 switch(n) {
231 case 0:
232 return x;
233 case 1:
234 return y;
235 case 2:
236 return z;
237 }
238 return 0;
239}
240
241bool PointFloat::operator==(const Point &p) const {
242 return (
243 (this->x == p.x) &&
244 (this->y == p.y) &&
245 (this->z == p.z) &&
246 (this->size == p.size) );
247}
248
249bool PointFloat::operator==(const PointFloat &p) const {
250 return (
251 (this->x == p.x) &&
252 (this->y == p.y) &&
253 (this->z == p.z) &&
254 (this->size == p.size) );
255}
256
258 PointFloat np = *this;
259 np.x -= p.x;
260 np.y -= p.y;
261 np.z -= p.z;
262 return np;
263}
265 PointFloat np = *this;
266 np.x -= p.x;
267 np.y -= p.y;
268 np.z -= p.z;
269 return np;
270}
272 PointFloat np = *this;
273 np.x += p.x;
274 np.y += p.y;
275 np.z += p.z;
276 return np;
277}
279 PointFloat np = *this;
280 np.x += p.x;
281 np.y += p.y;
282 np.z += p.z;
283 return np;
284}
286 PointFloat np = *this;
287 np.x += v.x;
288 np.y += v.y;
289 return np;
290}
292 PointFloat np = *this;
293 np.x += v.x;
294 np.y += v.y;
295 np.z += v.z;
296 return np;
297}
299 PointFloat np = *this;
300 np.x *= p.x;
301 np.y *= p.y;
302 np.z *= p.z;
303 return np;
304}
306 PointFloat np = *this;
307 np.x *= p.x;
308 np.y *= p.y;
309 np.z *= p.z;
310 return np;
311}
313 PointFloat np = *this;
314 np.x -= a;
315 np.y -= a;
316 np.z -= a;
317 return np;
318}
320 PointFloat np = *this;
321 np.x += a;
322 np.y += a;
323 np.z += a;
324 return np;
325}
327 PointFloat np = *this;
328 np.x *= a;
329 np.y *= a;
330 np.z *= a;
331 return np;
332}
333
334double PointFloat::getX() const { return x; }
335double PointFloat::getY() const { return y; }
336double PointFloat::getZ() const { return z; }
337Size PointFloat::getSize() const { return size; }
338
339bool PointFloat::set(double xx, double yy, double zz) { x = xx; y = yy; z = zz; return true; }
340bool PointFloat::setX(double n) { x = n; return true; }
341bool PointFloat::setY(double n) { y = n; return true; }
342bool PointFloat::setZ(double n) { z = n; return true; }
343bool PointFloat::setSize(Size s) { size = s; return true; }
344
345double PointFloat::getDistanceTo(const Point &p) const {
346 return sqrt( pow((getX()-p.getX()), 2) + pow((getX()-p.getX()), 2) );
347}
348double PointFloat::getDistanceTo(const PointFloat &p) const {
349 return sqrt( pow((getX()-p.getX()), 2) + pow((getX()-p.getX()), 2) );
350}
351
352std::string PointFloat::print() {
353 if (size.isNonZero()) {
354 if (z != 0)
355 return utils::StringFormat("(%.3f,%.3f,%.3f x %s)", x, y, z, (char*)size.print().c_str());
356 else
357 return utils::StringFormat("(%.3f,%.3f x %s)", x, y, (char*)size.print().c_str());
358 }
359 else {
360 if (z != 0)
361 return utils::StringFormat("(%.3f,%.3f,%.3f)", x, y, z);
362 else
363 return utils::StringFormat("(%.3f,%.3f)", x, y);
364 }
365}
366
367
368
369
370
372// Line Class
374
376 lineWidth = 0;
377}
378
379Line::Line(PointFloat startpoint, PointFloat endpoint, double width) {
380 startPoint = startpoint;
381 endPoint = endpoint;
382 lineWidth = width;
383}
384
386}
387
389 return startPoint;
390}
392 return endPoint;
393}
394double Line::getLineWidth() const {
395 return lineWidth;
396}
397
399 startPoint = point;
400 return true;
401}
403 endPoint = point;
404 return true;
405}
406bool Line::setLineWidth(double width) {
407 lineWidth = width;
408 return true;
409}
410
411std::string Line::print() {
412 if (lineWidth != 0)
413 return utils::StringFormat("%s-%sx(W: %.3f)", (char*) startPoint.print().c_str(), (char*) endPoint.print().c_str(), lineWidth);
414 else
415 return utils::StringFormat("%s-%s", (char*) startPoint.print().c_str(), (char*) endPoint.print().c_str());
416}
417
418
419
421// PolyLine Class
423
426
428
430 return (uint32)lines.size();
431}
432
433Line PolyLine::getLine(uint32 pos) const {
434 if (pos > lines.size())
435 return Line();
436 return lines[pos];
437}
439 lines.push_back(line);
440 return true;
441}
442bool PolyLine::replaceLine(uint32 pos, Line newline) {
443 lines[pos] = newline;
444 return true;
445}
446bool PolyLine::removeLine(uint32 pos) {
447 lines[pos] = Line();
448 return true;
449}
450
451std::string PolyLine::print() {
452 if (lines.size() == 0)
453 return "PolyLine: (empty)";
454 std::string str = "PolyLine: ";
455 uint32 n;
456 for (n=0; n<=lines.size()-1; n++) {
457 str += utils::StringFormat("%s,", (char*)lines[n].print().c_str());
458 }
459 str += utils::StringFormat("%s", (char*)lines[n].print().c_str());
460 return str;
461}
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
486// Box Class
488
490 lineWidth = 0;
491 orientation = 0;
492}
493
494Box::Box(double x, double y, double w, double h, double linewidth) {
495 upperLeft = PointFloat(x, y);
496 size = Size(w, h);
497
498 if (size.getWidth() < 0) {
499 size.setWidth(-1*size.getWidth());
500 upperLeft.setX(upperLeft.getX()-size.getWidth());
501 }
502
503 if (size.getHeight() < 0) {
504 size.setHeight(-1*size.getHeight());
505 upperLeft.setY(upperLeft.getY()-size.getHeight());
506 }
507
508 lineWidth = linewidth;
509 orientation = 0;
510}
511
512Box::Box(PointFloat upperleft, Size boxsize, double linewidth) {
513 upperLeft = upperleft;
514 size = boxsize;
515
516 if (size.getWidth() < 0) {
517 size.setWidth(-1*size.getWidth());
518 upperLeft.setX(upperLeft.getX()-size.getWidth());
519 }
520
521 if (size.getHeight() < 0) {
522 size.setHeight(-1*size.getHeight());
523 upperLeft.setY(upperLeft.getY()-size.getHeight());
524 }
525
526 lineWidth = linewidth;
527 orientation = 0;
528}
529
530Box::Box(PointFloat upperleft, PointFloat lowerright, double linewidth) {
531 upperLeft = upperleft;
532 size = Size(lowerright.getX() - upperleft.getX(), lowerright.getY() - upperleft.getY());
533
534 if (size.getWidth() < 0) {
535 size.setWidth(-1*size.getWidth());
536 upperLeft.setX(upperLeft.getX()-size.getWidth());
537 }
538
539 if (size.getHeight() < 0) {
540 size.setHeight(-1*size.getHeight());
541 upperLeft.setY(upperLeft.getY()-size.getHeight());
542 }
543
544 lineWidth = linewidth;
545 orientation = 0;
546}
547
549}
550
551
552double Box::getCMX() const {
553 return getCM().getX();
554}
555
556double Box::getCMY() const {
557 return getCM().getY();
558}
559
561 return PointFloat(upperLeft.getX()+(size.getWidth()/2), upperLeft.getY()+(size.getHeight()/2));
562}
563
565 return upperLeft;
566}
567
569 return PointFloat(upperLeft.getX()+size.getWidth(), upperLeft.getY());
570}
571
573 return PointFloat(upperLeft.getX(), upperLeft.getY()+size.getHeight());
574}
575
577 return PointFloat(upperLeft.getX()+size.getWidth(), upperLeft.getY()+size.getHeight());
578}
579
580double Box::getUpperY() const {
581 return upperLeft.getY();
582}
583
584double Box::getLowerY() const {
585 return upperLeft.getY() + size.getHeight();
586}
587
588double Box::getLeftX() const {
589 return upperLeft.getX();
590}
591
592double Box::getRightX() const {
593 return upperLeft.getX() + size.getWidth();
594}
595
596
597double Box::getLineWidth() const {
598 return lineWidth;
599}
600
602 return size;
603}
604
605double Box::getArea() const {
606 return ( size.w * size.h );
607}
608
609double Box::getWidth() const {
610 return size.getWidth();
611}
612
613double Box::getHeight() const {
614 return size.getHeight();
615}
616
617
618Box Box::operator-(const Point &p) const {
619 Box newBox = *this;
620 newBox.upperLeft = newBox.upperLeft - p;
621 return newBox;
622}
623
625 Box newBox = *this;
626 newBox.upperLeft = newBox.upperLeft - p;
627 return newBox;
628}
629
630Box Box::operator+(const Point &p) const {
631 Box newBox = *this;
632 newBox.upperLeft = newBox.upperLeft + p;
633 return newBox;
634}
635
637 Box newBox = *this;
638 newBox.upperLeft = newBox.upperLeft + p;
639 return newBox;
640}
641
642bool Box::move(double dx, double dy) {
643 return set(getLeftX() + dx, getUpperY() + dy, getWidth(), getHeight());
644}
645
646bool Box::moveTo(double x, double y) {
647 return set(x, y, getWidth(), getHeight());
648}
649
650bool Box::grow(double dw, double dh) {
651 double w = getWidth() + dw;
652 double h = getHeight() + dh;
653 return setSize(Size(w, h));
654}
655
656
657
659 Box newbox = *this;
660 newbox.constrainTo(box);
661 return newbox;
662}
663
664Box Box::getConstrainedCopy(double x, double y, double w, double h) {
665 Box newbox = *this;
666 newbox.constrainTo(x, y, w, h);
667 return newbox;
668}
669
670bool Box::constrainTo(const Box& box) {
671 return constrainTo(box.upperLeft.x, box.upperLeft.y, box.size.w, box.size.h);
672}
673
674bool Box::constrainTo(double x, double y, double w, double h) {
675 if (upperLeft.x < x)
676 upperLeft.x = 0;
677 if (upperLeft.y < y)
678 upperLeft.y = 0;
679 if (upperLeft.x + size.w > w)
680 size.w = w - upperLeft.x;
681 if (upperLeft.y + size.h > h)
682 size.h = h - upperLeft.y;
683 return true;
684}
685
687 Box box;
688 box.upperLeft.x = upperLeft.x - (size.w / 2.0);
689 box.upperLeft.y = upperLeft.y - (size.h / 2.0);
690 box.size.w = size.w * 2.0;
691 box.size.h = size.h * 2.0;
692 return box;
693}
694
695bool Box::set(double x, double y, double w, double h, double linewidth) {
696 upperLeft = PointFloat(x, y);
697 size = Size(w, h);
698
699 if (size.getWidth() < 0) {
700 size.setWidth(-1*size.getWidth());
701 upperLeft.setX(upperLeft.getX()-size.getWidth());
702 }
703
704 if (size.getHeight() < 0) {
705 size.setHeight(-1*size.getHeight());
706 upperLeft.setY(upperLeft.getY()-size.getHeight());
707 }
708
709 lineWidth = linewidth;
710 return true;
711}
712
713
714bool Box::setUpperLeft(const PointFloat& point) {
715 upperLeft = point;
716 return true;
717}
718
719bool Box::setSize(const Size& boxsize) {
720 size = boxsize;
721 return true;
722}
723
724bool Box::setLineWidth(double width) {
725 lineWidth = width;
726 return true;
727}
728
729
730bool Box::isPointWithin(int x, int y) const {
731 if ( x > this->getRightX() )
732 return false;
733 else if ( x < this->getLeftX() )
734 return false;
735 else if ( y < this->getUpperY() )
736 return false;
737 else if ( y > this->getLowerY() )
738 return false;
739 return true;
740}
741
742bool Box::isPointWithin(const PointFloat& point) const {
743 if ( point.getX() > this->getRightX() )
744 return false;
745 else if ( point.getX() < this->getLeftX() )
746 return false;
747 else if ( point.getY() < this->getUpperY() )
748 return false;
749 else if ( point.getY() > this->getLowerY() )
750 return false;
751 return true;
752}
753
755 return PointFloat(upperLeft.getX()+(size.getWidth()/2), upperLeft.getY()+(size.getHeight()/2));
756}
757
758bool Box::hasZeroSize() const {
759 return (!size.isNonZero());
760}
761
762bool Box::equals(const Box &otherbox) const {
763 return (
764 (this->size == otherbox.size) &&
765 (this->upperLeft == otherbox.upperLeft) &&
766 (this->orientation == otherbox.orientation) );
767}
768
769bool Box::equals(const Box &otherbox, double maxerror) const {
770 if (maxerror == 0.0)
771 maxerror = -0.001;
772 return (percentOverlap(otherbox) <= maxerror);
773}
774
775bool Box::growToBoundingBox(const Box &otherbox) {
776 Box bb = getBoundingBox(otherbox);
777 if (bb.hasZeroSize())
778 return false;
779 this->upperLeft = bb.upperLeft;
780 this->size = bb.size;
781 return true;
782}
783
784bool Box::growToIncludePoint(const Point& point, int padX, int padY) {
785 double dif;
786 if ( ( dif = upperLeft.x - (point.x - padX) ) > 0 ) {
787 upperLeft.x -= dif;
788 size.w += dif;
789 }
790 if ( ( dif = upperLeft.y - (point.y - padY) ) > 0 ) {
791 upperLeft.y -= dif;
792 size.h += dif;
793 }
794
795 if ( ( dif = point.x + padX - (upperLeft.x + size.w) ) > 0 ) {
796 size.w += dif;
797 }
798 if ( ( dif = point.y + padY - (upperLeft.y + size.h) ) > 0 ) {
799 size.h += dif;
800 }
801 return true;
802}
803
804Box Box::getBoundingBox(const Box &otherbox) const {
805
806 double left, right, top, bottom;
807
808 if (this->getLeftX() <= otherbox.getLeftX())
809 left = this->getLeftX();
810 else
811 left = otherbox.getLeftX();
812
813 if (this->getRightX() <= otherbox.getRightX())
814 right = otherbox.getRightX();
815 else
816 right = this->getRightX();
817
818 if (this->getUpperY() <= otherbox.getUpperY())
819 top = this->getUpperY();
820 else
821 top = otherbox.getUpperY();
822
823 if (this->getLowerY() <= otherbox.getLowerY())
824 bottom = otherbox.getLowerY();
825 else
826 bottom = this->getLowerY();
827
828 return Box(PointFloat(left, top), PointFloat(right, bottom));
829}
830
831Box Box::getOverlapBox(const Box &otherbox) const {
832
833 Box zeroOverlap = Box();
834
835 // Rule out zero overlap first
836 if ( otherbox.getLeftX() > this->getRightX() )
837 return zeroOverlap;
838 else if ( otherbox.getRightX() < this->getLeftX() )
839 return zeroOverlap;
840 else if ( otherbox.getLowerY() < this->getUpperY() )
841 return zeroOverlap;
842 else if ( otherbox.getUpperY() > this->getLowerY() )
843 return zeroOverlap;
844
845 // Now we know there is an overlap
846
847 double left, right, top, bottom;
848
849 if (this->getLeftX() <= otherbox.getLeftX())
850 left = otherbox.getLeftX();
851 else
852 left = this->getLeftX();
853
854 if (this->getRightX() <= otherbox.getRightX())
855 right = this->getRightX();
856 else
857 right = otherbox.getRightX();
858
859 if (this->getUpperY() <= otherbox.getUpperY())
860 top = otherbox.getUpperY();
861 else
862 top = this->getUpperY();
863
864 if (this->getLowerY() <= otherbox.getLowerY())
865 bottom = this->getLowerY();
866 else
867 bottom = otherbox.getLowerY();
868
869 return Box(PointFloat(left, top), PointFloat(right, bottom));
870}
871
872double Box::percentOverlap(const Box &otherbox) const {
873
874 Box overlap = getOverlapBox(otherbox);
875 if (!overlap.getSize().isNonZero())
876 return 0.0;
877
878 double myArea = size.getArea();
879 double overlapArea = overlap.getSize().getArea();
880
881
882 if ( (myArea > overlapArea) && (myArea != 0.0) )
883 return ( overlapArea / myArea );
884 else if (overlapArea != 0.0)
885 return ( myArea / overlapArea );
886 else
887 return 0;
888}
889
890
891
892
893
894
895std::string Box::print() {
896 if (lineWidth != 0)
897 return utils::StringFormat("%s-%sx(W: %.3f)", (char*) getUpperLeft().print().c_str(), (char*) getLowerRight().print().c_str(), lineWidth);
898 else
899 return utils::StringFormat("%s-%s", (char*) getUpperLeft().print().c_str(), (char*) getLowerRight().print().c_str());
900}
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
921// Vector2D Class
923
925Vector2D::Vector2D(double x, double y) { this->x=x; this->y=y; }
927
928double Vector2D::operator[](int n) const {
929 switch(n) {
930 case 0:
931 return x;
932 case 1:
933 return y;
934 }
935 return 0;
936}
937
938bool Vector2D::operator==(const Vector2D &v) const {
939 return (
940 (this->x == v.x) &&
941 (this->y == v.y) );
942}
943
945 Vector2D np = *this;
946 np.x -= v.x;
947 np.y -= v.y;
948 return np;
949}
950
952 Vector2D np = *this;
953 np.x += v.x;
954 np.y += v.y;
955 return np;
956}
957
958double Vector2D::operator*(const Vector2D &v) const {
959 return (x*v.x) + (y*v.y);
960}
961
963 Vector2D np = *this;
964 np.x -= a;
965 np.y -= a;
966 return np;
967}
968
970 Vector2D np = *this;
971 np.x += a;
972 np.y += a;
973 return np;
974}
975
977 Vector2D np = *this;
978 np.x *= a;
979 np.y *= a;
980 return np;
981}
982
984 this->x -= v.x;
985 this->y -= v.y;
986 return *this;
987}
988
990 this->x += v.x;
991 this->y += v.y;
992 return *this;
993}
994
996 this->x -= a;
997 this->y -= a;
998 return *this;
999}
1000
1002 this->x += a;
1003 this->y += a;
1004 return *this;
1005}
1006
1008 this->x *= a;
1009 this->y *= a;
1010 return *this;
1011}
1012
1013
1014
1015double Vector2D::getX() const { return x; }
1016double Vector2D::getY() const { return y; }
1017
1018bool Vector2D::set(const PointFloat& p1, const PointFloat& p2) {
1019 return set(p1.x, p1.y, p2.x, p2.y);
1020}
1021
1022bool Vector2D::set(double x1, double y1, double x2, double y2) {
1023 return set(x2-x1, y2-y1);
1024}
1025
1026bool Vector2D::set(double xx, double yy) { x = xx; y = yy; return true; }
1027bool Vector2D::setX(double n) { x = n; return true; }
1028bool Vector2D::setY(double n) { y = n; return true; }
1029bool Vector2D::setLength(double l) {
1030 double len = length();
1031 if (len == 0) return false;
1032 double v = l / len;
1033 x *= v;
1034 y *= v;
1035 return true;
1036}
1037
1038std::string Vector2D::print() {
1039 return utils::StringFormat("(%.3f,%.3f)", x, y);
1040}
1041
1042double Vector2D::length() const {
1043 return sqrt((x*x) + (y*y));
1044}
1045
1046double Vector2D::det(const Vector2D &v) const {
1047 return (x*v.y) - (y*v.x);
1048}
1049
1051 return (*this*v == 0);
1052}
1053
1055 return (det(v) == 0);
1056}
1057
1059 Vector2D unit = v.getUnitVector();
1060 return unit*(*this*unit);
1061}
1062
1064 return setLength(1.0);
1065}
1066
1068 Vector2D vect;
1069 double len = length();
1070 if (len == 0) return vect;
1071 double v = 1.0 / len;
1072 vect.x = x*v;
1073 vect.y = y*v;
1074 return vect;
1075}
1076
1078 Vector2D vect;
1079 if ((x == 0) && (y == 0)) return vect;
1080 vect.x = x * cos(angle) - y * sin(angle);
1081 vect.y = x * sin(angle) + y * cos(angle);
1082 return vect;
1083}
1084
1085bool Vector2D::rotateIt(double angle) {
1086 if ((x == 0) && (y == 0)) return false;
1087 double newX = x * cos(angle) - y * sin(angle);
1088 double newY = x * sin(angle) + y * cos(angle);
1089 x = newX;
1090 y = newY;
1091 return true;
1092}
1093
1095 return Vector2D(0-y, x);
1096}
1097
1098double Vector2D::getAngle(const Vector2D &v) const {
1099 double lengths = this->length() * v.length();
1100 if (lengths == 0)
1101 return 0;
1102 return acos((*this*v)/lengths);
1103}
1104
1105double Vector2D::getArea(const Vector2D &v) const {
1106 return fabs(det(v));
1107}
1108
1109
1110
1111
1113// Vector3D Class
1115
1116Vector3D::Vector3D() { x=0; y=0; z=0; }
1117Vector3D::Vector3D(double x, double y, double z) { this->x=x; this->y=y; this->z=z; }
1119
1120double Vector3D::operator[](int n) const {
1121 switch(n) {
1122 case 0:
1123 return x;
1124 case 1:
1125 return y;
1126 case 2:
1127 return z;
1128 }
1129 return 0;
1130}
1131
1132bool Vector3D::operator==(const Vector3D &v) const {
1133 return (
1134 (this->x == v.x) &&
1135 (this->y == v.y) &&
1136 (this->z == v.z) );
1137}
1138
1140 Vector3D np = *this;
1141 np.x -= v.x;
1142 np.y -= v.y;
1143 np.z -= v.z;
1144 return np;
1145}
1146
1148 Vector3D np = *this;
1149 np.x += v.x;
1150 np.y += v.y;
1151 np.z += v.z;
1152 return np;
1153}
1154
1155double Vector3D::operator*(const Vector3D &v) const {
1156 return (x*v.x) + (y*v.y) + (z*v.z);
1157}
1158
1160 Vector3D np = *this;
1161 np.x -= a;
1162 np.y -= a;
1163 np.z -= a;
1164 return np;
1165}
1166
1168 Vector3D np = *this;
1169 np.x += a;
1170 np.y += a;
1171 np.z += a;
1172 return np;
1173}
1174
1176 Vector3D np = *this;
1177 np.x *= a;
1178 np.y *= a;
1179 np.z *= a;
1180 return np;
1181}
1182
1184 this->x -= v.x;
1185 this->y -= v.y;
1186 this->z -= v.z;
1187 return *this;
1188}
1189
1191 this->x += v.x;
1192 this->y += v.y;
1193 this->z += v.z;
1194 return *this;
1195}
1196
1198 this->x -= a;
1199 this->y -= a;
1200 this->z -= a;
1201 return *this;
1202}
1203
1205 this->x += a;
1206 this->y += a;
1207 this->z += a;
1208 return *this;
1209}
1210
1212 this->x *= a;
1213 this->y *= a;
1214 this->z *= a;
1215 return *this;
1216}
1217
1218
1219
1220double Vector3D::getX() const { return x; }
1221double Vector3D::getY() const { return y; }
1222double Vector3D::getZ() const { return z; }
1223
1224bool Vector3D::set(const PointFloat& p1, const PointFloat& p2) {
1225 return set(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
1226}
1227
1228bool Vector3D::set(double x1, double y1, double z1, double x2, double y2, double z2) {
1229 return set(x2-x1, y2-y1, z2-z1);
1230}
1231
1232bool Vector3D::set(double xx, double yy, double zz) { x = xx; y = yy; z = zz; return true; }
1233bool Vector3D::setX(double n) { x = n; return true; }
1234bool Vector3D::setY(double n) { y = n; return true; }
1235bool Vector3D::setZ(double n) { z = n; return true; }
1236
1237std::string Vector3D::print() {
1238 return utils::StringFormat("(%.3f,%.3f,%.3f)", x, y, z);
1239}
1240
1241double Vector3D::length() const {
1242 return sqrt((x*x) + (y*y) + (z*z));
1243}
1244
1246 return Vector3D(
1247 this->y*v.z - this->z*v.y,
1248 this->z*v.x - this->x*v.z,
1249 this->x*v.y - this->y*v.x);
1250}
1251
1253 return (*this*v == 0);
1254}
1255
1257 return (product(v).length() == 0);
1258}
1259
1261 Vector3D unit = v.getUnitVector();
1262 return unit*(*this*unit);
1263}
1264
1266 return *this*(1.0/length());
1267}
1268
1269double Vector3D::getAngle(const Vector3D &v) const {
1270 double lengths = this->length() * v.length();
1271 if (lengths == 0)
1272 return 0;
1273 return acos((*this*v)/lengths);
1274}
1275
1276double Vector3D::getArea(const Vector3D &v) const {
1277 return fabs(product(v).length());
1278}
1279
1280
1281
1282
1283
1284
1285
1287 init(0, 0, 0);
1288}
1289
1290Color::Color(uint8 red, uint8 green, uint8 blue) {
1291 init(red, green, blue);
1292}
1293
1294Color::Color(const Color &color) {
1295 init(color.r, color.g, color.b);
1296 size = color.size;
1297}
1298
1299Color::Color(const char* colorName) {
1300 init(colorName);
1301}
1302
1305
1306bool Color::init(uint8 red, uint8 green, uint8 blue) {
1307 r = red;
1308 g = green;
1309 b = blue;
1310 size = 1;
1311 return true;
1312}
1313
1314bool Color::init(const char* colorName) {
1315
1316 if (!colorName || !strlen(colorName))
1317 return init(0, 0, 0);
1318
1319 if (strchr(colorName, ',')) {
1320 std::vector<std::string> col = utils::TextListSplit(colorName, ",", true);
1321 if (col.size() < 3)
1322 return false;
1323 else if (col.size() == 3)
1324 return init(
1325 (uint8)utils::Ascii2Uint32(col[0].c_str()),
1326 (uint8)utils::Ascii2Uint32(col[1].c_str()),
1327 (uint8)utils::Ascii2Uint32(col[2].c_str())
1328 );
1329 else {
1330 size = utils::Ascii2Uint32(col[3].c_str());
1331 return init(
1332 (uint8)utils::Ascii2Uint32(col[0].c_str()),
1333 (uint8)utils::Ascii2Uint32(col[1].c_str()),
1334 (uint8)utils::Ascii2Uint32(col[2].c_str())
1335 );
1336 }
1337 }
1338
1339 std::map<std::string, std::array<uint8, 3> >::const_iterator i = ColorMap.find(colorName);
1340 if (i != ColorMap.end())
1341 return init((*i).second.at(0), (*i).second.at(1), (*i).second.at(2));
1342 else
1343 return init(0, 0, 0);
1344}
1345
1346//bool Color::setNoColor() {
1347// r = g = b = -1;
1348// return true;
1349//}
1350//
1351//bool Color::isValid() {
1352// return (!isNoColor());
1353//}
1354//
1355//bool Color::isNoColor() {
1356// return ((r < 0) || (g < 0) || (b < 0));
1357//}
1358
1359Color Color::mixOn(Color& color, double weight) {
1360 // color is the background color
1361 if (weight > 1)
1362 weight = 1;
1363 if (weight < 0)
1364 weight = 0;
1365 Color resColor;
1366 resColor.r = (uint8)(color.r*(1 - weight) + this->r*weight);
1367 resColor.g = (uint8)(color.g*(1 - weight) + this->g*weight);
1368 resColor.b = (uint8)(color.b*(1 - weight) + this->b*weight);
1369 return resColor;
1370}
1371
1373 return (uint8)(((double)r + g + b) / 3.0);
1374}
1375
1377 Color resColor;
1378 resColor.r = 255 - r;
1379 resColor.g = 255 - g;
1380 resColor.b = 255 - b;
1381 return resColor;
1382}
1383
1384double Color::distance(Color &otherColor) {
1385 return pow((double)pow((double)r - otherColor.r, 2) + pow((double)g - otherColor.g, 2) + pow((double)b - otherColor.b, 2), (1.0 / 3));
1386}
1387
1388std::vector<Color> Color::createColorsMaxDifference(uint32 count, bool mix) {
1389
1390 int rcount = (int)(count / 3);
1391 if (rcount <= 0) rcount = 1;
1392 int gcount = (int)(count / 3);
1393 if (gcount <= 0) gcount = 1;
1394 int bcount = count - rcount - gcount;
1395 if (bcount <= 0) bcount = 1;
1396
1397 int n;
1398 std::vector<Color> col;
1399 int value = 0;
1400 for (n = 0; n<rcount; n++) {
1401 value = (int)((n + 1)*(255.0 / rcount));
1402 if (value >= 256) value = 255;
1403 col.push_back(Color(value, 0, 0));
1404 }
1405 for (n = 0; n<gcount; n++) {
1406 value = (int)((n + 1)*(255.0 / gcount));
1407 if (value >= 256) value = 255;
1408 col.push_back(Color(0, value, 0));
1409 }
1410 for (n = 0; n<bcount; n++) {
1411 value = (int)((n + 1)*(255.0 / bcount));
1412 if (value >= 256) value = 255;
1413 col.push_back(Color(0, 0, value));
1414 }
1415
1416 if (mix) {
1417 // std::random_shuffle(col.begin(), col.end());
1418 std::random_device rd;
1419 std::mt19937 g(rd());
1420 std::shuffle(col.begin(), col.end(), g);
1421 }
1422 return col;
1423}
1424
1425
1426
1427
1429// RandomPathGenerator Class
1431
1433 lastPointTime = 0;
1434 srand((uint32)(uint64)this);
1435}
1436
1439
1440bool RandomPathGenerator::setStartPoint(double x, double y) {
1441 lastPoint.x = x;
1442 lastPoint.y = y;
1444 return true;
1445}
1446
1447bool RandomPathGenerator::setDirection(double dx, double dy) {
1448 direction.x = dx;
1449 direction.y = dy;
1450 return true;
1451}
1452
1454 variation.x = variation.y = var;
1455 return true;
1456}
1457
1459 double secs = (double)GetTimeAge(lastPointTime) / 1000000.0;
1460 //Interpolate next exact point from last point and timestamp
1461 PointFloat nextPoint, dynPoint;
1462 nextPoint.x = lastPoint.x + (secs * direction.x);
1463 nextPoint.y = lastPoint.y + (secs * direction.y);
1464 //Move exact point by random variation
1465 dynPoint.x = utils::RandomValue(nextPoint.x - variation.x, nextPoint.x + variation.x);
1466 dynPoint.y = utils::RandomValue(nextPoint.y - variation.y, nextPoint.y + variation.y);
1467 //Publish point
1468 lastPoint = nextPoint;
1470 return dynPoint;
1471}
1472
1473
1474// ################# Unit Test #################
1475
1477 const double EPS = 1e-9;
1478
1479 // ---- Size ----
1480 unittest::progress(5, "Size");
1481 {
1482 Size s(3.0, 4.0);
1483 if (fabs(s.getArea() - 12.0) > EPS) { unittest::fail("MathClasses test: Size::getArea expected 12 got %.6f\n", s.getArea()); return false; }
1484 // diagonal of 3,4 right triangle = 5
1485 if (fabs(s.getDiagonalLenth() - 5.0) > EPS) { unittest::fail("MathClasses test: Size::getDiagonalLenth expected 5 got %.6f\n", s.getDiagonalLenth()); return false; }
1486 if (!s.isNonZero()) { unittest::fail("MathClasses test: Size::isNonZero expected true\n"); return false; }
1487 Size zero;
1488 if (zero.isNonZero()) { unittest::fail("MathClasses test: empty Size::isNonZero expected false\n"); return false; }
1489 Size s2(3.0, 4.0);
1490 if (!(s == s2)) { unittest::fail("MathClasses test: Size::operator== expected equal\n"); return false; }
1491 // Size scaled by a Vector2D multiplies w by v.x and h by v.y
1492 Size scaled = s * Vector2D(2.0, 0.5);
1493 if (fabs(scaled.getWidth() - 6.0) > EPS || fabs(scaled.getHeight() - 2.0) > EPS) {
1494 unittest::fail("MathClasses test: Size::operator*(Vector2D) expected (6,2) got (%.3f,%.3f)\n", scaled.getWidth(), scaled.getHeight()); return false;
1495 }
1496 }
1497
1498 // ---- Point ----
1499 unittest::progress(15, "Point");
1500 {
1501 Point a(0, 0);
1502 Point b(3, 4);
1503 if (fabs(a.getDistanceTo(b) - 5.0) > EPS) { unittest::fail("MathClasses test: Point::getDistanceTo expected 5 got %.6f\n", a.getDistanceTo(b)); return false; }
1504 Point diff = b - a;
1505 if (diff.getX() != 3 || diff.getY() != 4) { unittest::fail("MathClasses test: Point::operator- expected (3,4) got (%d,%d)\n", diff.getX(), diff.getY()); return false; }
1506 Point sum = a + b;
1507 if (sum.getX() != 3 || sum.getY() != 4) { unittest::fail("MathClasses test: Point::operator+ expected (3,4)\n"); return false; }
1508 if (!(b == Point(3, 4))) { unittest::fail("MathClasses test: Point::operator== expected equal\n"); return false; }
1509 }
1510
1511 // ---- Vector2D ----
1512 unittest::progress(30, "Vector2D");
1513 {
1514 Vector2D v(3.0, 4.0);
1515 if (fabs(v.length() - 5.0) > EPS) { unittest::fail("MathClasses test: Vector2D::length expected 5 got %.6f\n", v.length()); return false; }
1516
1517 // dot product
1518 Vector2D a(1.0, 2.0), b(3.0, 4.0);
1519 double dot = a * b; // 1*3 + 2*4 = 11
1520 if (fabs(dot - 11.0) > EPS) { unittest::fail("MathClasses test: Vector2D dot expected 11 got %.6f\n", dot); return false; }
1521
1522 // determinant: 1*4 - 2*3 = -2
1523 if (fabs(a.det(b) - (-2.0)) > EPS) { unittest::fail("MathClasses test: Vector2D::det expected -2 got %.6f\n", a.det(b)); return false; }
1524
1525 // area = |det| = 2
1526 if (fabs(a.getArea(b) - 2.0) > EPS) { unittest::fail("MathClasses test: Vector2D::getArea expected 2 got %.6f\n", a.getArea(b)); return false; }
1527
1528 // orthogonality: (1,0) and (0,1)
1529 Vector2D ex(1.0, 0.0), ey(0.0, 1.0);
1530 if (!ex.isOrthogonalWith(ey)) { unittest::fail("MathClasses test: Vector2D::isOrthogonalWith expected true\n"); return false; }
1531 if (ex.isOrthogonalWith(ex)) { unittest::fail("MathClasses test: Vector2D::isOrthogonalWith(self) expected false\n"); return false; }
1532
1533 // parallel: (1,1) and (2,2)
1534 if (!Vector2D(1.0, 1.0).isParallelWith(Vector2D(2.0, 2.0))) { unittest::fail("MathClasses test: Vector2D::isParallelWith expected true\n"); return false; }
1535
1536 // angle between ex and ey = pi/2
1537 if (fabs(ex.getAngle(ey) - (M_PI / 2.0)) > 1e-6) { unittest::fail("MathClasses test: Vector2D::getAngle expected pi/2 got %.6f\n", ex.getAngle(ey)); return false; }
1538
1539 // unit vector of (3,4) has length 1, direction preserved
1540 Vector2D unit = v.getUnitVector();
1541 if (fabs(unit.length() - 1.0) > EPS) { unittest::fail("MathClasses test: Vector2D::getUnitVector length expected 1 got %.6f\n", unit.length()); return false; }
1542 if (fabs(unit.x - 0.6) > EPS || fabs(unit.y - 0.8) > EPS) { unittest::fail("MathClasses test: Vector2D::getUnitVector expected (0.6,0.8) got (%.3f,%.3f)\n", unit.x, unit.y); return false; }
1543
1544 // orthogonal vector of (1,0) is (0,1)
1545 Vector2D ortho = ex.getOrthogonalVector();
1546 if (fabs(ortho.x - 0.0) > EPS || fabs(ortho.y - 1.0) > EPS) { unittest::fail("MathClasses test: Vector2D::getOrthogonalVector expected (0,1) got (%.3f,%.3f)\n", ortho.x, ortho.y); return false; }
1547 if (!ex.isOrthogonalWith(ortho)) { unittest::fail("MathClasses test: orthogonal vector not orthogonal\n"); return false; }
1548
1549 // projection of (2,3) on x-axis is (2,0)
1550 Vector2D proj = Vector2D(2.0, 3.0).getProjectionOn(ex);
1551 if (fabs(proj.x - 2.0) > EPS || fabs(proj.y - 0.0) > EPS) { unittest::fail("MathClasses test: Vector2D::getProjectionOn expected (2,0) got (%.3f,%.3f)\n", proj.x, proj.y); return false; }
1552
1553 // rotate (1,0) by 90 degrees -> (0,1)
1554 Vector2D rot = ex.rotate(M_PI / 2.0);
1555 if (fabs(rot.x - 0.0) > 1e-9 || fabs(rot.y - 1.0) > 1e-9) { unittest::fail("MathClasses test: Vector2D::rotate(pi/2) expected (0,1) got (%.6f,%.6f)\n", rot.x, rot.y); return false; }
1556
1557 // setLength scales to requested length, makeUnitVector -> length 1
1558 Vector2D sl(3.0, 4.0);
1559 if (!sl.setLength(10.0)) { unittest::fail("MathClasses test: Vector2D::setLength returned false\n"); return false; }
1560 if (fabs(sl.length() - 10.0) > 1e-6) { unittest::fail("MathClasses test: Vector2D::setLength expected length 10 got %.6f\n", sl.length()); return false; }
1561 Vector2D mu(5.0, 0.0);
1562 mu.makeUnitVector();
1563 if (fabs(mu.length() - 1.0) > EPS) { unittest::fail("MathClasses test: Vector2D::makeUnitVector expected length 1 got %.6f\n", mu.length()); return false; }
1564
1565 // compound assignment & arithmetic operators
1566 Vector2D c(1.0, 1.0);
1567 c += Vector2D(2.0, 3.0); // (3,4)
1568 if (fabs(c.x - 3.0) > EPS || fabs(c.y - 4.0) > EPS) { unittest::fail("MathClasses test: Vector2D::operator+= expected (3,4)\n"); return false; }
1569 c *= 2.0; // (6,8)
1570 if (fabs(c.x - 6.0) > EPS || fabs(c.y - 8.0) > EPS) { unittest::fail("MathClasses test: Vector2D::operator*= expected (6,8)\n"); return false; }
1571 Vector2D scaledV = Vector2D(2.0, 3.0) * 2.0;
1572 if (!(scaledV == Vector2D(4.0, 6.0))) { unittest::fail("MathClasses test: Vector2D::operator*(double) expected (4,6)\n"); return false; }
1573 }
1574
1575 // ---- Vector3D ----
1576 unittest::progress(50, "Vector3D");
1577 {
1578 // length of (2,3,6) = 7
1579 Vector3D v(2.0, 3.0, 6.0);
1580 if (fabs(v.length() - 7.0) > EPS) { unittest::fail("MathClasses test: Vector3D::length expected 7 got %.6f\n", v.length()); return false; }
1581
1582 // dot product (1,2,3).(4,5,6) = 32
1583 Vector3D a(1.0, 2.0, 3.0), b(4.0, 5.0, 6.0);
1584 double dot = a * b;
1585 if (fabs(dot - 32.0) > EPS) { unittest::fail("MathClasses test: Vector3D dot expected 32 got %.6f\n", dot); return false; }
1586
1587 // cross product x cross y = z
1588 Vector3D ex(1.0, 0.0, 0.0), ey(0.0, 1.0, 0.0), ez(0.0, 0.0, 1.0);
1589 Vector3D cross = ex.product(ey);
1590 if (!(cross == ez)) { unittest::fail("MathClasses test: Vector3D::product(x,y) expected z got (%.3f,%.3f,%.3f)\n", cross.x, cross.y, cross.z); return false; }
1591
1592 // orthogonality of axes
1593 if (!ex.isOrthogonalWith(ey)) { unittest::fail("MathClasses test: Vector3D::isOrthogonalWith expected true\n"); return false; }
1594 if (ex.isOrthogonalWith(ex)) { unittest::fail("MathClasses test: Vector3D::isOrthogonalWith(self) expected false\n"); return false; }
1595
1596 // parallel
1597 if (!Vector3D(1.0, 2.0, 3.0).isParallelWith(Vector3D(2.0, 4.0, 6.0))) { unittest::fail("MathClasses test: Vector3D::isParallelWith expected true\n"); return false; }
1598
1599 // angle between x and y = pi/2
1600 if (fabs(ex.getAngle(ey) - (M_PI / 2.0)) > 1e-6) { unittest::fail("MathClasses test: Vector3D::getAngle expected pi/2 got %.6f\n", ex.getAngle(ey)); return false; }
1601
1602 // unit vector length 1
1603 Vector3D unit = v.getUnitVector();
1604 if (fabs(unit.length() - 1.0) > EPS) { unittest::fail("MathClasses test: Vector3D::getUnitVector length expected 1 got %.6f\n", unit.length()); return false; }
1605
1606 // area = |cross| ; for unit x,y axes that is 1
1607 if (fabs(ex.getArea(ey) - 1.0) > EPS) { unittest::fail("MathClasses test: Vector3D::getArea expected 1 got %.6f\n", ex.getArea(ey)); return false; }
1608
1609 // projection of (2,3,5) on x-axis is (2,0,0)
1610 Vector3D proj = Vector3D(2.0, 3.0, 5.0).getProjectionOn(ex);
1611 if (fabs(proj.x - 2.0) > EPS || fabs(proj.y) > EPS || fabs(proj.z) > EPS) { unittest::fail("MathClasses test: Vector3D::getProjectionOn expected (2,0,0)\n"); return false; }
1612 }
1613
1614 // ---- Box ----
1615 unittest::progress(70, "Box");
1616 {
1617 Box box(0.0, 0.0, 10.0, 20.0);
1618 if (fabs(box.getArea() - 200.0) > EPS) { unittest::fail("MathClasses test: Box::getArea expected 200 got %.6f\n", box.getArea()); return false; }
1619 if (fabs(box.getRightX() - 10.0) > EPS || fabs(box.getLowerY() - 20.0) > EPS) { unittest::fail("MathClasses test: Box edges wrong\n"); return false; }
1620
1621 // centre of mass at (5,10)
1622 PointFloat cm = box.getCM();
1623 if (fabs(cm.getX() - 5.0) > EPS || fabs(cm.getY() - 10.0) > EPS) { unittest::fail("MathClasses test: Box::getCM expected (5,10) got (%.3f,%.3f)\n", cm.getX(), cm.getY()); return false; }
1624
1625 // point containment
1626 if (!box.isPointWithin(PointFloat(5.0, 5.0))) { unittest::fail("MathClasses test: Box::isPointWithin inside expected true\n"); return false; }
1627 if (box.isPointWithin(PointFloat(15.0, 5.0))) { unittest::fail("MathClasses test: Box::isPointWithin outside expected false\n"); return false; }
1628
1629 // negative size box gets normalised so upperLeft is the true upper-left
1630 Box neg(10.0, 10.0, -5.0, -5.0);
1631 if (fabs(neg.getLeftX() - 5.0) > EPS || fabs(neg.getUpperY() - 5.0) > EPS) { unittest::fail("MathClasses test: Box negative-size normalisation wrong (%.3f,%.3f)\n", neg.getLeftX(), neg.getUpperY()); return false; }
1632 if (fabs(neg.getWidth() - 5.0) > EPS || fabs(neg.getHeight() - 5.0) > EPS) { unittest::fail("MathClasses test: Box negative-size dimensions wrong\n"); return false; }
1633
1634 // overlap of two 10x10 boxes offset by 5 -> 5x5 = 25 area
1635 Box b1(0.0, 0.0, 10.0, 10.0);
1636 Box b2(5.0, 5.0, 10.0, 10.0);
1637 Box overlap = b1.getOverlapBox(b2);
1638 if (fabs(overlap.getArea() - 25.0) > EPS) { unittest::fail("MathClasses test: Box::getOverlapBox area expected 25 got %.6f\n", overlap.getArea()); return false; }
1639
1640 // bounding box of the two should be 15x15 = 225 area, upper-left at (0,0)
1641 Box bb = b1.getBoundingBox(b2);
1642 if (fabs(bb.getLeftX()) > EPS || fabs(bb.getUpperY()) > EPS) { unittest::fail("MathClasses test: Box::getBoundingBox origin expected (0,0)\n"); return false; }
1643 if (fabs(bb.getArea() - 225.0) > EPS) { unittest::fail("MathClasses test: Box::getBoundingBox area expected 225 got %.6f\n", bb.getArea()); return false; }
1644
1645 // percentOverlap: overlap 25 / myArea 100 = 0.25
1646 double pct = b1.percentOverlap(b2);
1647 if (fabs(pct - 0.25) > EPS) { unittest::fail("MathClasses test: Box::percentOverlap expected 0.25 got %.6f\n", pct); return false; }
1648
1649 // disjoint boxes => zero overlap area
1650 Box far(100.0, 100.0, 10.0, 10.0);
1651 Box noOverlap = b1.getOverlapBox(far);
1652 if (noOverlap.getSize().isNonZero()) { unittest::fail("MathClasses test: disjoint Box::getOverlapBox expected zero size\n"); return false; }
1653 if (fabs(b1.percentOverlap(far)) > EPS) { unittest::fail("MathClasses test: disjoint percentOverlap expected 0\n"); return false; }
1654
1655 // move / grow
1656 Box mv(0.0, 0.0, 4.0, 4.0);
1657 mv.move(2.0, 3.0);
1658 if (fabs(mv.getLeftX() - 2.0) > EPS || fabs(mv.getUpperY() - 3.0) > EPS) { unittest::fail("MathClasses test: Box::move wrong\n"); return false; }
1659 mv.grow(2.0, 0.0);
1660 if (fabs(mv.getWidth() - 6.0) > EPS) { unittest::fail("MathClasses test: Box::grow width expected 6 got %.6f\n", mv.getWidth()); return false; }
1661 }
1662
1663 // ---- Throughput metric ----
1664 unittest::progress(85, "throughput");
1665 {
1666 const int N = 200000;
1667 Vector2D acc(1.0, 1.0);
1668 double sink = 0.0;
1669 uint64 t0 = GetTimeNow();
1670 for (int i = 0; i < N; i++) {
1671 Vector2D a((double)(i % 17), (double)(i % 13));
1672 Vector2D b((double)(i % 7) + 1.0, (double)(i % 5) + 1.0);
1673 sink += a * b; // dot
1674 sink += a.det(b); // det
1675 acc = acc + a.getUnitVector(); // unit + add
1676 }
1677 double us = (double)(GetTimeNow() - t0);
1678 unittest::detail("Vector2D ops: N=%d, sink=%.3f, acc=(%.3f,%.3f)\n", N, sink, acc.x, acc.y);
1679 if (us > 0.0)
1680 unittest::metric("vector2d_op_throughput", (double)N / us * 1e6, "ops/s", true);
1681 }
1682
1683 unittest::progress(100, "done");
1684 return true;
1685}
1686
1689 "Geometry math: Size, Point, Vector2D, Vector3D, Box", "math");
1690}
1691
1692} // namespace cmlabs
Geometry and 2D/3D math value classes: Color, Size, Point, PointFloat, Line, PolyLine,...
CMSDK time: µs-resolution 64-bit timestamps and the Time Mapping Constant (TMC).
Small, dependency-free unit test harness used by all CMSDK object tests.
Axis-aligned rectangle defined by an upper-left corner and a Size, with overlap/containment math.
bool setSize(const Size &boxsize)
Set the extent.
bool setUpperLeft(const PointFloat &point)
Set the corner.
Size size
Extent.
bool growToIncludePoint(const Point &point, int padX=0, int padY=0)
Enlarge to include point plus padding.
double getArea() const
double getWidth() const
Box getOverlapBox(const Box &otherbox) const
double getLineWidth() const
PointFloat getLowerLeft() const
virtual ~Box()
Size getSize() const
PointFloat getCM() const
bool move(double dx, double dy)
Translate in place.
bool moveTo(double x, double y)
Move the upper-left corner to (x,y).
double percentOverlap(const Box &otherbox) const
Overlap between the two boxes as a fraction of the larger area.
bool isPointWithin(const PointFloat &point) const
PointFloat getCentreMass() const
Box operator-(const Point &p) const
Box translated by -p.
double lineWidth
Stroke width (drawing hint).
bool setLineWidth(double width)
Set the stroke width.
bool hasZeroSize() const
double getUpperY() const
bool grow(double dw, double dh)
Enlarge by (dw,dh), keeping the upper-left corner.
bool growToBoundingBox(const Box &otherbox)
Enlarge in place to include otherbox.
double getHeight() const
PointFloat getUpperLeft() const
Box getDoubleSizeSameCenter()
double getCMX() const
double orientation
Optional rotation metadata (radians); not applied by the geometric queries.
Box operator+(const Point &p) const
Box translated by +p.
double getLeftX() const
Box getConstrainedCopy(const Box &box)
std::string print()
Human-readable representation for debugging.
PointFloat upperLeft
Upper-left corner.
bool equals(const Box &otherbox) const
Exact geometric equality.
PointFloat getLowerRight() const
bool constrainTo(const Box &box)
Clip this box in place to lie within box.
Box getBoundingBox(const Box &otherbox) const
double getLowerY() const
bool set(double x, double y, double w, double h, double linewidth=0)
Reset all geometry.
double getCMY() const
double getRightX() const
PointFloat getUpperRight() const
Box()
Empty box at the origin.
Color mixOn(Color &color, double weight)
Blend this color onto a background color.
static std::vector< Color > createColorsMaxDifference(uint32 count, bool mix=false)
Generate count colors spread for maximum mutual visual difference (e.g.
Color getReverseColor()
Component-wise inverse (255-r, 255-g, 255-b).
uint8 getGreyValue()
Grey value as the plain average (r+g+b)/3.
Color()
Black (0,0,0).
double distance(Color &otherColor)
Euclidean distance to another color in RGB space.
Line segment between two PointFloat endpoints with a drawing width.
double lineWidth
Stroke width (drawing hint).
Line()
Degenerate line at the origin.
PointFloat getStartPoint() const
double getLineWidth() const
std::string print()
Human-readable representation for debugging.
bool setLineWidth(double width)
Set the stroke width.
bool setStartPoint(PointFloat point)
Set the start point.
bool setEndPoint(PointFloat point)
Set the end point.
virtual ~Line()
PointFloat getEndPoint() const
PointFloat startPoint
First endpoint.
PointFloat endPoint
Second endpoint.
Floating-point 3D point with an optional attached Size; float counterpart of Point.
PointFloat()
Origin (0,0,0).
double getY() const
bool setX(double v)
Set x.
std::string print()
Human-readable representation for debugging.
double getDistanceTo(const Point &p) const
Euclidean distance to an integer point.
double getZ() const
double operator[](int n) const
Component access: 0=x, 1=y, 2=z.
Size getSize() const
bool setSize(Size s)
Set the attached size.
PointFloat operator+(const Point &p) const
Element-wise sum.
bool set(double x, double y, double z=0)
Set all coordinates.
bool setY(double v)
Set y.
bool operator==(const Point &p) const
Equality against an integer point.
bool setZ(double v)
Set z.
double getX() const
PointFloat operator*(const Point &p) const
Element-wise product.
PointFloat operator-(const Point &p) const
Element-wise difference.
Integer 3D point with an optional attached Size; interoperates with PointFloat and vectors.
Point operator*(const Point &p) const
Element-wise product.
bool setX(int n)
Set x.
int operator[](int n) const
Component access: 0=x, 1=y, 2=z.
bool setY(int n)
Set y.
bool setZ(int n)
Set z.
bool setSize(Size s)
Set the attached size.
Point operator-(const Point &p) const
Element-wise difference.
double getDistanceTo(Point &p) const
Euclidean distance to another point.
Point()
Origin (0,0,0).
Size getSize() const
std::string print()
Human-readable representation for debugging.
int getZ() const
bool operator==(const Point &p) const
Exact coordinate equality.
int getX() const
bool set(int x, int y, int z=0)
Set all coordinates.
Point operator+(const Point &p) const
Element-wise sum.
int getY() const
virtual ~Point()
uint32 getLineCount() const
std::string print()
Human-readable representation for debugging.
bool replaceLine(uint32 pos, Line newline)
Replace the segment at pos.
bool addLine(Line line)
Append a segment.
bool removeLine(uint32 pos)
Remove the segment at pos.
std::vector< Line > lines
The segments, in order.
Line getLine(uint32 pos) const
uint64 lastPointTime
Timestamp of the last generated point (ms).
bool setDirection(double dx, double dy)
Set the base movement direction per step.
Vector2D direction
Current movement direction.
Vector2D variation
Random variation applied per step.
bool setStartPoint(double x, double y)
Set the initial position.
bool setVariation(double var)
Set the random perturbation magnitude.
PointFloat generateNextPoint()
Advance one step and return the new position.
PointFloat lastPoint
Most recently generated position.
2D/3D extent (width, height, optional depth) stored as doubles.
bool setDepth(double depth)
Set depth.
bool equals(const Size &size) const
Exact component equality.
virtual ~Size()
bool setHeight(double height)
Set height.
double d
Depth (0 for 2D use).
Size()
Zero size.
bool operator==(const Size &p) const
Same as equals().
double getHeight() const
double getDiagonalLenth() const
double h
Height.
double getDepth() const
double getArea() const
bool setWidth(double width)
Set width.
double w
Width.
bool isNonZero() const
Size operator*(const Vector2D &v) const
Scale w by v.x and h by v.y.
double getWidth() const
std::string print()
Human-readable representation for debugging.
static UnitTestRunner & instance()
Access the singleton (created on first use).
void registerTest(const char *name, UnitTestFunc func, const char *description="", const char *category="", bool inDefaultRun=true)
Register a test with the runner.
2D vector with the usual linear algebra: dot product, determinant, projection, rotation,...
double getX() const
double length() const
Vector2D getProjectionOn(const Vector2D &v) const
bool setLength(double l)
Rescale to length l, keeping direction.
Vector2D rotate(double angle)
Rotated copy; this vector is unchanged.
const Vector2D & operator-=(const Vector2D &v)
In-place subtract.
Vector2D getUnitVector() const
double getY() const
bool isParallelWith(const Vector2D &v) const
double operator*(const Vector2D &v) const
Dot product.
bool setX(double v)
Set x.
const Vector2D & operator*=(double a)
In-place scale.
double operator[](int n) const
Component access: 0=x, 1=y.
Vector2D getOrthogonalVector() const
Vector2D()
Zero vector.
Vector2D operator-(const Vector2D &v) const
Difference.
double getAngle(const Vector2D &v) const
Unsigned angle to v via acos of the normalized dot product.
bool set(const PointFloat &p1, const PointFloat &p2)
Set as p2-p1.
double y
Y component.
std::string print()
Human-readable representation for debugging.
Vector2D operator+(const Vector2D &v) const
Sum.
bool isOrthogonalWith(const Vector2D &v) const
const Vector2D & operator+=(const Vector2D &v)
In-place add.
bool makeUnitVector()
Normalize in place.
double x
X component.
bool operator==(const Vector2D &v) const
Component equality.
bool setY(double v)
Set y.
bool rotateIt(double angle)
Rotate in place by angle radians (same convention as rotate()).
double det(const Vector2D &v) const
2D cross product / determinant (x*v.y - y*v.x); sign gives turn direction.
double getArea(const Vector2D &v) const
3D vector with dot/cross products, projection and angle math.
double getX() const
double getAngle(const Vector3D &v) const
bool operator==(const Vector3D &v) const
Component equality.
double getY() const
bool setZ(double v)
Set z.
Vector3D getProjectionOn(const Vector3D &v) const
bool setY(double v)
Set y.
double z
Z component.
bool isParallelWith(const Vector3D &v) const
double getArea(const Vector3D &v) const
bool setX(double v)
Set x.
std::string print()
Human-readable representation for debugging.
Vector3D()
Zero vector.
double operator[](int n) const
Component access: 0=x, 1=y, 2=z.
bool set(const PointFloat &p1, const PointFloat &p2)
Set as p2-p1.
double length() const
Vector3D getUnitVector() const
bool isOrthogonalWith(const Vector3D &v) const
Vector3D product(const Vector3D &v) const
Cross product with v.
double operator*(const Vector3D &v) const
Dot product.
const Vector3D & operator+=(const Vector3D &v)
In-place add.
double y
Y component.
Vector3D operator+(const Vector3D &v) const
Sum.
const Vector3D & operator*=(double a)
In-place scale.
double getZ() const
const Vector3D & operator-=(const Vector3D &v)
In-place subtract.
Vector3D operator-(const Vector3D &v) const
Difference.
double x
X component.
uint64 GetTimeNow()
Return the current absolute time (µs since year 0) according to the TMC.
Definition PsyTime.cpp:69
int64 GetTimeAge(uint64 t)
Age of a timestamp relative to now.
Definition PsyTime.cpp:25
uint32 Ascii2Uint32(const char *ascii, uint32 start=0, uint32 end=0)
Parse an unsigned 32-bit decimal integer from a substring.
Definition Utils.cpp:7526
std::vector< std::string > TextListSplit(const char *text, const char *split, bool keepEmpty=true, bool autoTrim=false)
Split text on a separator.
Definition Utils.cpp:6511
double RandomValue()
Uniform random double in [0,1).
Definition Utils.cpp:7678
std::string StringFormat(const char *format,...)
printf into a std::string.
Definition Utils.cpp:6626
void fail(const char *fmt,...)
Set an explanatory reason shown on the FAIL line.
void metric(const char *name, double value, const char *unit="", bool higherIsBetter=true)
Record a performance metric.
void detail(const char *fmt,...)
Verbose-only indented diagnostic line (shown only when verbose=1).
void progress(int percent, const char *action)
Report progress with a short description of the current action.
const std::map< std::string, std::array< uint8, 3 > > ColorMap
Lookup table mapping ~200 CSS/X11 color names (with and without spaces) to 8-bit RGB triplets; used b...
Definition MathClasses.h:29
bool MathClasses_UnitTest()
Unit test for the MathClasses module (geometry / vectors / boxes).
void Register_MathClasses_Tests()