diff options
Diffstat (limited to '')
-rw-r--r-- | linden/indra/test/math.cpp | 442 |
1 files changed, 441 insertions, 1 deletions
diff --git a/linden/indra/test/math.cpp b/linden/indra/test/math.cpp index 9cab3fe..405b8a3 100644 --- a/linden/indra/test/math.cpp +++ b/linden/indra/test/math.cpp | |||
@@ -34,9 +34,13 @@ | |||
34 | #include "linden_common.h" | 34 | #include "linden_common.h" |
35 | #include "lltut.h" | 35 | #include "lltut.h" |
36 | 36 | ||
37 | #include "llcrc.h" | ||
38 | #include "llline.h" | ||
37 | #include "llmath.h" | 39 | #include "llmath.h" |
40 | #include "llrand.h" | ||
41 | #include "llsphere.h" | ||
38 | #include "lluuid.h" | 42 | #include "lluuid.h" |
39 | #include "llcrc.h" | 43 | #include "v3math.h" |
40 | 44 | ||
41 | namespace tut | 45 | namespace tut |
42 | { | 46 | { |
@@ -277,3 +281,439 @@ namespace tut | |||
277 | ensure_equals("crc update 2", c1.getCRC(), c2.getCRC()); | 281 | ensure_equals("crc update 2", c1.getCRC(), c2.getCRC()); |
278 | } | 282 | } |
279 | } | 283 | } |
284 | |||
285 | namespace tut | ||
286 | { | ||
287 | struct sphere_data | ||
288 | { | ||
289 | }; | ||
290 | typedef test_group<sphere_data> sphere_test; | ||
291 | typedef sphere_test::object sphere_object; | ||
292 | tut::sphere_test tsphere("LLSphere"); | ||
293 | |||
294 | template<> template<> | ||
295 | void sphere_object::test<1>() | ||
296 | { | ||
297 | // test LLSphere::contains() and ::overlaps() | ||
298 | S32 number_of_tests = 10; | ||
299 | for (S32 test = 0; test < number_of_tests; ++test) | ||
300 | { | ||
301 | LLVector3 first_center(1.f, 1.f, 1.f); | ||
302 | F32 first_radius = 3.f; | ||
303 | LLSphere first_sphere( first_center, first_radius ); | ||
304 | |||
305 | F32 half_millimeter = 0.0005f; | ||
306 | LLVector3 direction( ll_frand(2.f) - 1.f, ll_frand(2.f) - 1.f, ll_frand(2.f) - 1.f); | ||
307 | direction.normalize(); | ||
308 | |||
309 | F32 distance = ll_frand(first_radius - 2.f * half_millimeter); | ||
310 | LLVector3 second_center = first_center + distance * direction; | ||
311 | F32 second_radius = first_radius - distance - half_millimeter; | ||
312 | LLSphere second_sphere( second_center, second_radius ); | ||
313 | ensure("first sphere should contain the second", first_sphere.contains(second_sphere)); | ||
314 | ensure("first sphere should overlap the second", first_sphere.overlaps(second_sphere)); | ||
315 | |||
316 | distance = first_radius + ll_frand(first_radius); | ||
317 | second_center = first_center + distance * direction; | ||
318 | second_radius = distance - first_radius + half_millimeter; | ||
319 | second_sphere.set( second_center, second_radius ); | ||
320 | ensure("first sphere should NOT contain the second", !first_sphere.contains(second_sphere)); | ||
321 | ensure("first sphere should overlap the second", first_sphere.overlaps(second_sphere)); | ||
322 | |||
323 | distance = first_radius + ll_frand(first_radius) + half_millimeter; | ||
324 | second_center = first_center + distance * direction; | ||
325 | second_radius = distance - first_radius - half_millimeter; | ||
326 | second_sphere.set( second_center, second_radius ); | ||
327 | ensure("first sphere should NOT contain the second", !first_sphere.contains(second_sphere)); | ||
328 | ensure("first sphere should NOT overlap the second", !first_sphere.overlaps(second_sphere)); | ||
329 | } | ||
330 | } | ||
331 | |||
332 | template<> template<> | ||
333 | void sphere_object::test<2>() | ||
334 | { | ||
335 | // test LLSphere::getBoundingSphere() | ||
336 | S32 number_of_tests = 100; | ||
337 | S32 number_of_spheres = 10; | ||
338 | F32 sphere_center_range = 32.f; | ||
339 | F32 sphere_radius_range = 5.f; | ||
340 | |||
341 | for (S32 test = 0; test < number_of_tests; ++test) | ||
342 | { | ||
343 | // gegnerate a bunch of random sphere | ||
344 | std::vector< LLSphere > sphere_list; | ||
345 | for (S32 sphere_count=0; sphere_count < number_of_spheres; ++sphere_count) | ||
346 | { | ||
347 | LLVector3 direction( ll_frand(2.f) - 1.f, ll_frand(2.f) - 1.f, ll_frand(2.f) - 1.f); | ||
348 | direction.normalize(); | ||
349 | F32 distance = ll_frand(sphere_center_range); | ||
350 | LLVector3 center = distance * direction; | ||
351 | F32 radius = ll_frand(sphere_radius_range); | ||
352 | LLSphere sphere( center, radius ); | ||
353 | sphere_list.push_back(sphere); | ||
354 | } | ||
355 | |||
356 | // compute the bounding sphere | ||
357 | LLSphere bounding_sphere = LLSphere::getBoundingSphere(sphere_list); | ||
358 | |||
359 | // make sure all spheres are inside the bounding sphere | ||
360 | { | ||
361 | std::vector< LLSphere >::const_iterator sphere_itr; | ||
362 | for (sphere_itr = sphere_list.begin(); sphere_itr != sphere_list.end(); ++sphere_itr) | ||
363 | { | ||
364 | ensure("sphere should be contained by the bounding sphere", bounding_sphere.contains(*sphere_itr)); | ||
365 | } | ||
366 | } | ||
367 | |||
368 | // TODO -- improve LLSphere::getBoundingSphere() to the point where | ||
369 | // we can reduce the 'expansion' in the two tests below to about | ||
370 | // 2 mm or less | ||
371 | |||
372 | F32 expansion = 0.005f; | ||
373 | // move all spheres out a little bit | ||
374 | // and count how many are NOT contained | ||
375 | { | ||
376 | std::vector< LLVector3 > uncontained_directions; | ||
377 | std::vector< LLSphere >::iterator sphere_itr; | ||
378 | for (sphere_itr = sphere_list.begin(); sphere_itr != sphere_list.end(); ++sphere_itr) | ||
379 | { | ||
380 | LLVector3 direction = sphere_itr->getCenter() - bounding_sphere.getCenter(); | ||
381 | direction.normalize(); | ||
382 | |||
383 | sphere_itr->setCenter( sphere_itr->getCenter() + expansion * direction ); | ||
384 | if (! bounding_sphere.contains( *sphere_itr ) ) | ||
385 | { | ||
386 | uncontained_directions.push_back(direction); | ||
387 | } | ||
388 | } | ||
389 | ensure("when moving spheres out there should be at least two uncontained spheres", | ||
390 | uncontained_directions.size() > 1); | ||
391 | |||
392 | /* TODO -- when the bounding sphere algorithm is improved we can open up this test | ||
393 | * at the moment it occasionally fails when the sphere collection is tight and small | ||
394 | * (2 meters or less) | ||
395 | if (2 == uncontained_directions.size() ) | ||
396 | { | ||
397 | // if there were only two uncontained spheres then | ||
398 | // the two directions should be nearly opposite | ||
399 | F32 dir_dot = uncontained_directions[0] * uncontained_directions[1]; | ||
400 | ensure("two uncontained spheres should lie opposite the bounding center", dir_dot < -0.95f); | ||
401 | } | ||
402 | */ | ||
403 | } | ||
404 | |||
405 | // compute the new bounding sphere | ||
406 | bounding_sphere = LLSphere::getBoundingSphere(sphere_list); | ||
407 | |||
408 | // increase the size of all spheres a little bit | ||
409 | // and count how many are NOT contained | ||
410 | { | ||
411 | std::vector< LLVector3 > uncontained_directions; | ||
412 | std::vector< LLSphere >::iterator sphere_itr; | ||
413 | for (sphere_itr = sphere_list.begin(); sphere_itr != sphere_list.end(); ++sphere_itr) | ||
414 | { | ||
415 | LLVector3 direction = sphere_itr->getCenter() - bounding_sphere.getCenter(); | ||
416 | direction.normalize(); | ||
417 | |||
418 | sphere_itr->setRadius( sphere_itr->getRadius() + expansion ); | ||
419 | if (! bounding_sphere.contains( *sphere_itr ) ) | ||
420 | { | ||
421 | uncontained_directions.push_back(direction); | ||
422 | } | ||
423 | } | ||
424 | ensure("when boosting sphere radii there should be at least two uncontained spheres", | ||
425 | uncontained_directions.size() > 1); | ||
426 | |||
427 | /* TODO -- when the bounding sphere algorithm is improved we can open up this test | ||
428 | * at the moment it occasionally fails when the sphere collection is tight and small | ||
429 | * (2 meters or less) | ||
430 | if (2 == uncontained_directions.size() ) | ||
431 | { | ||
432 | // if there were only two uncontained spheres then | ||
433 | // the two directions should be nearly opposite | ||
434 | F32 dir_dot = uncontained_directions[0] * uncontained_directions[1]; | ||
435 | ensure("two uncontained spheres should lie opposite the bounding center", dir_dot < -0.95f); | ||
436 | } | ||
437 | */ | ||
438 | } | ||
439 | } | ||
440 | } | ||
441 | } | ||
442 | |||
443 | namespace tut | ||
444 | { | ||
445 | F32 SMALL_RADIUS = 1.0f; | ||
446 | F32 MEDIUM_RADIUS = 5.0f; | ||
447 | F32 LARGE_RADIUS = 10.0f; | ||
448 | |||
449 | struct line_data | ||
450 | { | ||
451 | }; | ||
452 | typedef test_group<line_data> line_test; | ||
453 | typedef line_test::object line_object; | ||
454 | tut::line_test tline("LLLine"); | ||
455 | |||
456 | template<> template<> | ||
457 | void line_object::test<1>() | ||
458 | { | ||
459 | // this is a test for LLLine::intersects(point) which returns TRUE | ||
460 | // if the line passes within some tolerance of point | ||
461 | |||
462 | // these tests will have some floating point error, | ||
463 | // so we need to specify how much error is ok | ||
464 | F32 allowable_relative_error = 0.00001f; | ||
465 | S32 number_of_tests = 100; | ||
466 | for (S32 test = 0; test < number_of_tests; ++test) | ||
467 | { | ||
468 | // generate some random point to be on the line | ||
469 | LLVector3 point_on_line( ll_frand(2.f) - 1.f, | ||
470 | ll_frand(2.f) - 1.f, | ||
471 | ll_frand(2.f) - 1.f); | ||
472 | point_on_line.normalize(); | ||
473 | point_on_line *= ll_frand(LARGE_RADIUS); | ||
474 | |||
475 | // generate some random point to "intersect" | ||
476 | LLVector3 random_direction ( ll_frand(2.f) - 1.f, | ||
477 | ll_frand(2.f) - 1.f, | ||
478 | ll_frand(2.f) - 1.f); | ||
479 | random_direction.normalize(); | ||
480 | |||
481 | LLVector3 random_offset( ll_frand(2.f) - 1.f, | ||
482 | ll_frand(2.f) - 1.f, | ||
483 | ll_frand(2.f) - 1.f); | ||
484 | random_offset.normalize(); | ||
485 | random_offset *= ll_frand(SMALL_RADIUS); | ||
486 | |||
487 | LLVector3 point = point_on_line + MEDIUM_RADIUS * random_direction | ||
488 | + random_offset; | ||
489 | |||
490 | // compute the axis of approach (a unit vector between the points) | ||
491 | LLVector3 axis_of_approach = point - point_on_line; | ||
492 | axis_of_approach.normalize(); | ||
493 | |||
494 | // compute the direction of the the first line (perp to axis_of_approach) | ||
495 | LLVector3 first_dir( ll_frand(2.f) - 1.f, | ||
496 | ll_frand(2.f) - 1.f, | ||
497 | ll_frand(2.f) - 1.f); | ||
498 | first_dir.normalize(); | ||
499 | F32 dot = first_dir * axis_of_approach; | ||
500 | first_dir -= dot * axis_of_approach; // subtract component parallel to axis | ||
501 | first_dir.normalize(); | ||
502 | |||
503 | // construct the line | ||
504 | LLVector3 another_point_on_line = point_on_line + ll_frand(LARGE_RADIUS) * first_dir; | ||
505 | LLLine line(another_point_on_line, point_on_line); | ||
506 | |||
507 | // test that the intersection point is within MEDIUM_RADIUS + SMALL_RADIUS | ||
508 | F32 test_radius = MEDIUM_RADIUS + SMALL_RADIUS; | ||
509 | test_radius += (LARGE_RADIUS * allowable_relative_error); | ||
510 | ensure("line should pass near intersection point", line.intersects(point, test_radius)); | ||
511 | |||
512 | test_radius = allowable_relative_error * (point - point_on_line).length(); | ||
513 | ensure("line should intersect point used to define it", line.intersects(point_on_line, test_radius)); | ||
514 | } | ||
515 | } | ||
516 | |||
517 | template<> template<> | ||
518 | void line_object::test<2>() | ||
519 | { | ||
520 | // this is a test for LLLine::nearestApproach(LLLIne) method | ||
521 | // which computes the point on a line nearest another line | ||
522 | |||
523 | // these tests will have some floating point error, | ||
524 | // so we need to specify how much error is ok | ||
525 | // TODO -- make nearestApproach() algorithm more accurate so | ||
526 | // we can tighten the allowable_error. Most tests are tighter | ||
527 | // than one milimeter, however when doing randomized testing | ||
528 | // you can walk into inaccurate cases. | ||
529 | F32 allowable_relative_error = 0.001f; | ||
530 | S32 number_of_tests = 100; | ||
531 | for (S32 test = 0; test < number_of_tests; ++test) | ||
532 | { | ||
533 | // generate two points to be our known nearest approaches | ||
534 | LLVector3 some_point( ll_frand(2.f) - 1.f, | ||
535 | ll_frand(2.f) - 1.f, | ||
536 | ll_frand(2.f) - 1.f); | ||
537 | some_point.normalize(); | ||
538 | some_point *= ll_frand(LARGE_RADIUS); | ||
539 | |||
540 | LLVector3 another_point( ll_frand(2.f) - 1.f, | ||
541 | ll_frand(2.f) - 1.f, | ||
542 | ll_frand(2.f) - 1.f); | ||
543 | another_point.normalize(); | ||
544 | another_point *= ll_frand(LARGE_RADIUS); | ||
545 | |||
546 | // compute the axis of approach (a unit vector between the points) | ||
547 | LLVector3 axis_of_approach = another_point - some_point; | ||
548 | axis_of_approach.normalize(); | ||
549 | |||
550 | // compute the direction of the the first line (perp to axis_of_approach) | ||
551 | LLVector3 first_dir( ll_frand(2.f) - 1.f, | ||
552 | ll_frand(2.f) - 1.f, | ||
553 | ll_frand(2.f) - 1.f); | ||
554 | F32 dot = first_dir * axis_of_approach; | ||
555 | first_dir -= dot * axis_of_approach; // subtract component parallel to axis | ||
556 | first_dir.normalize(); // normalize | ||
557 | |||
558 | // compute the direction of the the second line | ||
559 | LLVector3 second_dir( ll_frand(2.f) - 1.f, | ||
560 | ll_frand(2.f) - 1.f, | ||
561 | ll_frand(2.f) - 1.f); | ||
562 | dot = second_dir * axis_of_approach; | ||
563 | second_dir -= dot * axis_of_approach; | ||
564 | second_dir.normalize(); | ||
565 | |||
566 | // make sure the lines aren't too parallel, | ||
567 | dot = fabsf(first_dir * second_dir); | ||
568 | if (dot > 0.99f) | ||
569 | { | ||
570 | // skip this test, we're not interested in testing | ||
571 | // the intractible cases | ||
572 | continue; | ||
573 | } | ||
574 | |||
575 | // construct the lines | ||
576 | LLVector3 first_point = some_point + ll_frand(LARGE_RADIUS) * first_dir; | ||
577 | LLLine first_line(first_point, some_point); | ||
578 | |||
579 | LLVector3 second_point = another_point + ll_frand(LARGE_RADIUS) * second_dir; | ||
580 | LLLine second_line(second_point, another_point); | ||
581 | |||
582 | // compute the points of nearest approach | ||
583 | LLVector3 some_computed_point = first_line.nearestApproach(second_line); | ||
584 | LLVector3 another_computed_point = second_line.nearestApproach(first_line); | ||
585 | |||
586 | // compute the error | ||
587 | F32 first_error = (some_point - some_computed_point).length(); | ||
588 | F32 scale = llmax((some_point - another_point).length(), some_point.length()); | ||
589 | scale = llmax(scale, another_point.length()); | ||
590 | scale = llmax(scale, 1.f); | ||
591 | F32 first_relative_error = first_error / scale; | ||
592 | |||
593 | F32 second_error = (another_point - another_computed_point).length(); | ||
594 | F32 second_relative_error = second_error / scale; | ||
595 | |||
596 | //if (first_relative_error > allowable_relative_error) | ||
597 | //{ | ||
598 | // std::cout << "first_error = " << first_error | ||
599 | // << " first_relative_error = " << first_relative_error | ||
600 | // << " scale = " << scale | ||
601 | // << " dir_dot = " << (first_dir * second_dir) | ||
602 | // << std::endl; | ||
603 | //} | ||
604 | //if (second_relative_error > allowable_relative_error) | ||
605 | //{ | ||
606 | // std::cout << "second_error = " << second_error | ||
607 | // << " second_relative_error = " << second_relative_error | ||
608 | // << " scale = " << scale | ||
609 | // << " dist = " << (some_point - another_point).length() | ||
610 | // << " dir_dot = " << (first_dir * second_dir) | ||
611 | // << std::endl; | ||
612 | //} | ||
613 | |||
614 | // test that the errors are small | ||
615 | ensure("first line should accurately compute its closest approach", | ||
616 | first_relative_error <= allowable_relative_error); | ||
617 | ensure("second line should accurately compute its closest approach", | ||
618 | second_relative_error <= allowable_relative_error); | ||
619 | } | ||
620 | } | ||
621 | |||
622 | F32 ALMOST_PARALLEL = 0.99f; | ||
623 | template<> template<> | ||
624 | void line_object::test<3>() | ||
625 | { | ||
626 | // this is a test for LLLine::getIntersectionBetweenTwoPlanes() method | ||
627 | |||
628 | // first some known tests | ||
629 | LLLine xy_plane(LLVector3(0.f, 0.f, 2.f), LLVector3(0.f, 0.f, 3.f)); | ||
630 | LLLine yz_plane(LLVector3(2.f, 0.f, 0.f), LLVector3(3.f, 0.f, 0.f)); | ||
631 | LLLine zx_plane(LLVector3(0.f, 2.f, 0.f), LLVector3(0.f, 3.f, 0.f)); | ||
632 | |||
633 | LLLine x_line; | ||
634 | LLLine y_line; | ||
635 | LLLine z_line; | ||
636 | |||
637 | bool x_success = LLLine::getIntersectionBetweenTwoPlanes(x_line, xy_plane, zx_plane); | ||
638 | bool y_success = LLLine::getIntersectionBetweenTwoPlanes(y_line, yz_plane, xy_plane); | ||
639 | bool z_success = LLLine::getIntersectionBetweenTwoPlanes(z_line, zx_plane, yz_plane); | ||
640 | |||
641 | ensure("xy and zx planes should intersect", x_success); | ||
642 | ensure("yz and xy planes should intersect", y_success); | ||
643 | ensure("zx and yz planes should intersect", z_success); | ||
644 | |||
645 | LLVector3 direction = x_line.getDirection(); | ||
646 | ensure("x_line should be parallel to x_axis", fabs(direction.mV[VX]) == 1.f | ||
647 | && 0.f == direction.mV[VY] | ||
648 | && 0.f == direction.mV[VZ] ); | ||
649 | direction = y_line.getDirection(); | ||
650 | ensure("y_line should be parallel to y_axis", 0.f == direction.mV[VX] | ||
651 | && fabs(direction.mV[VY]) == 1.f | ||
652 | && 0.f == direction.mV[VZ] ); | ||
653 | direction = z_line.getDirection(); | ||
654 | ensure("z_line should be parallel to z_axis", 0.f == direction.mV[VX] | ||
655 | && 0.f == direction.mV[VY] | ||
656 | && fabs(direction.mV[VZ]) == 1.f ); | ||
657 | |||
658 | // next some random tests | ||
659 | F32 allowable_relative_error = 0.0001f; | ||
660 | S32 number_of_tests = 20; | ||
661 | for (S32 test = 0; test < number_of_tests; ++test) | ||
662 | { | ||
663 | // generate the known line | ||
664 | LLVector3 some_point( ll_frand(2.f) - 1.f, | ||
665 | ll_frand(2.f) - 1.f, | ||
666 | ll_frand(2.f) - 1.f); | ||
667 | some_point.normalize(); | ||
668 | some_point *= ll_frand(LARGE_RADIUS); | ||
669 | LLVector3 another_point( ll_frand(2.f) - 1.f, | ||
670 | ll_frand(2.f) - 1.f, | ||
671 | ll_frand(2.f) - 1.f); | ||
672 | another_point.normalize(); | ||
673 | another_point *= ll_frand(LARGE_RADIUS); | ||
674 | LLLine known_intersection(some_point, another_point); | ||
675 | |||
676 | // compute a plane that intersect the line | ||
677 | LLVector3 point_on_plane( ll_frand(2.f) - 1.f, | ||
678 | ll_frand(2.f) - 1.f, | ||
679 | ll_frand(2.f) - 1.f); | ||
680 | point_on_plane.normalize(); | ||
681 | point_on_plane *= ll_frand(LARGE_RADIUS); | ||
682 | LLVector3 plane_normal = (point_on_plane - some_point) % known_intersection.getDirection(); | ||
683 | plane_normal.normalize(); | ||
684 | LLLine first_plane(point_on_plane, point_on_plane + plane_normal); | ||
685 | |||
686 | // compute a different plane that intersect the line | ||
687 | LLVector3 point_on_different_plane( ll_frand(2.f) - 1.f, | ||
688 | ll_frand(2.f) - 1.f, | ||
689 | ll_frand(2.f) - 1.f); | ||
690 | point_on_different_plane.normalize(); | ||
691 | point_on_different_plane *= ll_frand(LARGE_RADIUS); | ||
692 | LLVector3 different_plane_normal = (point_on_different_plane - another_point) % known_intersection.getDirection(); | ||
693 | different_plane_normal.normalize(); | ||
694 | LLLine second_plane(point_on_different_plane, point_on_different_plane + different_plane_normal); | ||
695 | |||
696 | if (fabs(plane_normal * different_plane_normal) > ALMOST_PARALLEL) | ||
697 | { | ||
698 | // the two planes are approximately parallel, so we won't test this case | ||
699 | continue; | ||
700 | } | ||
701 | |||
702 | LLLine measured_intersection; | ||
703 | bool success = LLLine::getIntersectionBetweenTwoPlanes( | ||
704 | measured_intersection, | ||
705 | first_plane, | ||
706 | second_plane); | ||
707 | |||
708 | ensure("plane intersection should succeed", success); | ||
709 | |||
710 | F32 dot = fabs(known_intersection.getDirection() * measured_intersection.getDirection()); | ||
711 | ensure("measured intersection should be parallel to known intersection", | ||
712 | dot > ALMOST_PARALLEL); | ||
713 | |||
714 | ensure("measured intersection should pass near known point", | ||
715 | measured_intersection.intersects(some_point, LARGE_RADIUS * allowable_relative_error)); | ||
716 | } | ||
717 | } | ||
718 | } | ||
719 | |||