369 std::vector<REGION>
result;
371 if( !aModel.
m_Meshes || !std::isfinite( aScale.x ) || !std::isfinite( aScale.y ) || !std::isfinite( aScale.z )
372 || aScale.x == 0 || aScale.y == 0 || aScale.z == 0 )
375 using POINT_KEY = std::array<double, 3>;
376 using TRIANGLE_KEY = std::array<POINT_KEY, 3>;
377 std::map<std::pair<unsigned int, std::vector<TRIANGLE_KEY>>,
size_t> known;
378 auto makeSignature = [](
const REGION& aRegion,
bool aOriented,
bool aReverse )
380 std::vector<TRIANGLE_KEY> signature;
381 signature.reserve( aRegion.
triangles.size() );
383 for(
const auto& triangle : aRegion.
triangles )
387 for(
unsigned int corner = 0; corner < 3; ++corner )
389 const auto& point = aRegion.
vertices[triangle[corner]];
390 key[corner] = { point.x, point.y, point.z };
394 std::swap( key[1], key[2] );
397 std::rotate( key.begin(), std::min_element( key.begin(), key.end() ), key.end() );
399 std::sort( key.begin(), key.end() );
401 signature.push_back( key );
404 std::sort( signature.begin(), signature.end() );
408 for(
unsigned int meshIndex = 0; meshIndex < aModel.
m_MeshesSize; ++meshIndex )
416 std::vector<unsigned int> valid;
418 for(
unsigned int offset = 0; offset + 2 < mesh.
m_FaceIdxSize; offset += 3 )
425 for(
unsigned int corner = 0; corner < 3; ++corner )
428 finite &= std::isfinite( point.x * aScale.x ) && std::isfinite( point.y * aScale.y )
429 && std::isfinite( point.z * aScale.z );
435 valid.push_back( offset );
440 std::map<size_t, std::vector<unsigned int>> components;
442 for(
unsigned int offset : valid )
445 for(
const auto& [component, offsets] : components )
449 std::map<unsigned int, unsigned int> indices;
451 for(
unsigned int offset : offsets )
453 std::array<unsigned int, 3> triangle;
455 for(
unsigned int corner = 0; corner < 3; ++corner )
457 unsigned int source = mesh.
m_FaceIdx[offset + corner];
458 auto [entry, inserted] = indices.emplace( source, indices.size() );
463 region.
vertices.push_back( glm::dvec3( point.x, point.y, point.z ) * aScale );
466 triangle[corner] = entry->second;
469 if( aScale.x * aScale.y * aScale.z < 0 )
470 std::swap( triangle[1], triangle[2] );
478 if( region.
area <= 0 )
481 auto [previous, inserted] =
482 known.emplace( std::make_pair( region.
material, makeSignature( region,
false,
false ) ),
488 original.
twoSided |= makeSignature( original,
true,
true ) == makeSignature( region,
true,
false );
494 result.push_back( std::move( region ) );
535std::vector<ALIGN_SOLUTION>
SolveAlignment(
const std::vector<REGION>& aRegions,
size_t aSeed,
536 const std::vector<PAD>& aPadGroup,
const std::vector<PAD>& aAllPads,
537 const glm::dvec3& aCurrentRotation )
539 std::vector<ALIGN_SOLUTION>
result;
541 if( aSeed >= aRegions.size() || aPadGroup.empty() || aAllPads.empty() )
544 const REGION& seed = aRegions[aSeed];
548 for(
const REGION& region : aRegions )
550 for(
const auto& vertex : region.vertices )
551 modelBox.extend( vec( vertex ) );
554 for(
const PAD&
pad : aAllPads )
555 allPadBox.extend( vec(
pad.position ) );
557 double length = modelBox.sizes().norm();
559 if( !std::isfinite( length ) )
562 double epsilon = std::max( 1e-3 * length, 0.01 );
563 double grow = seed.
extents.y / 2;
564 V2 padCentre = V2::Zero();
567 for(
const PAD&
pad : aPadGroup )
569 padCentre += vec(
pad.position );
570 padBox.extend( vec(
pad.position ) );
573 padCentre /= aPadGroup.size();
574 std::vector<V3> directions{ V3::UnitX(), -V3::UnitX(), V3::UnitY(), -V3::UnitY(), V3::UnitZ(), -V3::UnitZ() };
577 if(
normal.norm() > 0 &&
normal.cwiseAbs().maxCoeff() < std::cos( 0.5 * PI / 180 ) )
579 directions.push_back(
normal );
582 directions.push_back( -
normal );
585 std::vector<ALIGN_CANDIDATE> full;
586 std::vector<ALIGN_CANDIDATE> partial;
587 std::vector<ALIGN_CANDIDATE> single;
588 double minimumPitch = -1;
590 for(
const V3&
down : directions )
600 std::vector<double> levels;
602 for(
const REGION& region : aRegions )
606 for(
const auto& vertex : region.vertices )
607 level = std::max( level, vec( vertex ).dot(
down ) );
609 levels.push_back( level );
612 double level = levels[aSeed];
613 std::vector<CONTACT> contacts;
614 V3 seedContact = vec( seed.
centroid );
616 for(
size_t i = 0; i < aRegions.size(); ++i )
618 const REGION& region = aRegions[i];
620 double peerFacing = vec( region.
normal ).dot(
down );
623 peerFacing =
std::abs( peerFacing );
625 if(
std::abs( levels[i] - level ) >
epsilon || !congruent( seed, region, length )
626 || ( region.
planar && peerFacing < 0.25 ) )
629 V3 accumulator = V3::Zero();
632 for(
const auto& triangle : region.
triangles )
634 V3 a = vec( region.
vertices[triangle[0]] );
635 V3 b = vec( region.
vertices[triangle[1]] );
636 V3 c = vec( region.
vertices[triangle[2]] );
641 double area = ( b - a ).cross( c - a ).norm() / 2;
642 accumulator += area * ( a + b + c ) / 3;
648 for(
const auto& vertex : region.
vertices )
652 accumulator += vec( vertex );
661 CONTACT contact{ accumulator / weight, region.
area };
664 seedContact = contact.position;
666 auto peer = std::find_if( contacts.begin(), contacts.end(),
667 [&](
const CONTACT& aContact )
669 return ( aContact.position - contact.position ).norm() < seed.extents.x / 2;
672 if( peer == contacts.end() )
674 contacts.push_back( contact );
678 peer->position = ( peer->position * peer->weight + contact.position * contact.weight )
679 / ( peer->weight + contact.weight );
680 peer->weight += contact.weight;
684 if( contacts.empty() )
687 M3 seat =
down.z() > 1 - 1e-12
688 ? Eigen::AngleAxisd( PI, V3::UnitX() ).toRotationMatrix()
689 : Eigen::Quaterniond::FromTwoVectors(
down, V3( 0, 0, -1 ) ).toRotationMatrix();
691 V2 centre = V2::Zero();
692 std::vector<V2> points;
694 for(
const CONTACT& contact : contacts )
696 V3 point = seat * contact.position;
697 points.push_back( point.head<2>() );
698 contactBox.extend( points.back() );
699 centre += points.back();
702 centre /= points.size();
703 V2 contactExtents = contactBox.sizes();
704 V2 padExtents = padBox.sizes();
705 std::sort( contactExtents.data(), contactExtents.data() + 2 );
706 std::sort( padExtents.data(), padExtents.data() + 2 );
707 bool subset = contacts.size() < aPadGroup.size() && contacts.size() >= 3
708 && ( contactExtents - padExtents ).cwiseAbs().maxCoeff() <= seed.
extents.x / 2;
710 if( ( contacts.size() != aPadGroup.size() && !subset ) || aPadGroup.size() == 1 )
712 double bestDistance = INF;
713 ALIGN_CANDIDATE best;
716 for(
double angle : { 0.0, PI / 2, PI, -PI / 2 } )
718 M3 rotation = Eigen::AngleAxisd( angle, V3::UnitZ() ).toRotationMatrix() * seat;
719 V3 offset = -rotation * seedContact;
720 offset.head<2>() += vec( aPadGroup.front().position );
724 if( !geometryGate( candidate, aRegions, aPadGroup, aAllPads, { { seedContact, 1 } }, level,
down,
728 V2 centre = bodyBox( aRegions, rotation, candidate.offset ).center();
729 double distance = ( centre - allPadBox.center() ).squaredNorm();
741 single.push_back( best );
747 centre = contactBox.center();
749 V2 target = subset ? padBox.center() : padCentre;
751 for( V2& point : points )
754 std::vector<double> angles;
756 if(
down.cwiseAbs().maxCoeff() > std::cos( 0.5 * PI / 180 ) )
758 angles = { 0, PI / 2, PI, -PI / 2 };
762 auto farthest = std::max_element( points.begin(), points.end(),
763 [](
const V2& a,
const V2& b )
765 return a.squaredNorm() < b.squaredNorm();
769 for(
const PAD&
pad : aPadGroup )
770 maxPad = std::max( { maxPad,
pad.size.x,
pad.size.y } );
772 for(
const PAD&
pad : aPadGroup )
774 V2
delta = vec(
pad.position ) - target;
776 if(
std::abs(
delta.norm() - farthest->norm() ) > maxPad / 2 )
779 double angle = std::atan2(
delta.y(),
delta.x() ) - std::atan2( farthest->y(), farthest->x() );
781 if( std::none_of( angles.begin(), angles.end(),
784 return std::abs( std::remainder( angle - aOther, 2 * PI ) ) < PI / 180;
786 angles.push_back( angle );
790 std::vector<const REGION*> copperRegions;
792 for(
size_t i = 0; i < aRegions.size(); ++i )
794 if(
std::abs( levels[i] - level ) <=
epsilon && !congruent( seed, aRegions[i], length ) )
795 copperRegions.push_back( &aRegions[i] );
798 for(
bool isPartial : {
false,
true } )
800 if( isPartial && !full.empty() )
803 for(
double angle : angles )
805 std::vector<V2> transformed;
807 for(
const V2& point : points )
808 transformed.push_back( Eigen::Rotation2Dd( angle ) * point + target );
810 std::vector<int> assignment = match( transformed, aPadGroup,
false, grow );
812 if( !isPartial && countMatches( assignment ) < points.size() )
815 V2 shift = V2::Zero();
819 for(
int iteration = 0; iteration < 4; ++iteration )
821 std::vector<V2> shifted;
823 for(
const V2& point : transformed )
824 shifted.push_back( point + shift );
826 assignment = match( shifted, aPadGroup,
true, grow );
827 V2 accumulator = V2::Zero();
828 size_t count = countMatches( assignment );
830 for(
size_t i = 0; i < shifted.size(); ++i )
832 if( assignment[i] >= 0 )
833 accumulator += vec( aPadGroup[assignment[i]].position ) - shifted[i];
837 shift += accumulator / count;
840 if( minimumPitch < 0 )
844 for(
size_t i = 0; i < aPadGroup.size(); ++i )
846 for(
size_t j = i + 1; j < aPadGroup.size(); ++j )
848 double distance = glm::length( aPadGroup[i].position - aPadGroup[j].position );
851 minimumPitch = std::min( minimumPitch,
distance );
856 if( countMatches( assignment ) < std::ceil( 0.9 * points.size() )
857 || shift.norm() >= minimumPitch / 2 )
865 for(
size_t i = 0; i < points.size(); ++i )
867 V2
delta = vec( aPadGroup[assignment[i]].position ) - target;
868 cross += points[i].x() *
delta.y() - points[i].y() *
delta.x();
869 dot += points[i].dot(
delta );
872 angle = std::atan2( cross, dot );
873 double snapped = std::round( angle / ( PI / 2 ) ) * ( PI / 2 );
875 if(
std::abs( angle - snapped ) < 0.5 * PI / 180 )
879 M3 inPlane = Eigen::AngleAxisd( angle, V3::UnitZ() ).toRotationMatrix();
880 ALIGN_CANDIDATE candidate{ inPlane * seat,
881 V3( target.x() + shift.x(), target.y() + shift.y(), 0 )
882 - inPlane * V3( centre.x(), centre.y(), 0 ),
883 0,
static_cast<unsigned int>( contacts.size() ),
887 candidate.turn =
std::abs( std::remainder( angle, 2 * PI ) );
890 centreSlack( candidate, aRegions, contacts, aPadGroup, allPadBox.center(), assignment );
892 if( !geometryGate( candidate, aRegions, aPadGroup, aAllPads, contacts, level,
down,
epsilon, grow ) )
895 for(
const REGION* region : copperRegions )
897 V3 point = candidate.rotation * vec( region->centroid ) + candidate.offset;
899 if( std::any_of( aAllPads.begin(), aAllPads.end(),
900 [&](
const PAD& aPad )
902 return padContains( aPad, point.head<2>(), 0 );
904 candidate.copper += region->area;
907 ( isPartial ? partial : full ).push_back( candidate );
912 std::vector<ALIGN_CANDIDATE>& candidates = !full.empty() ? full : !partial.empty() ? partial : single;
913 std::vector<ALIGN_CANDIDATE> unique;
915 for(
const ALIGN_CANDIDATE& candidate : candidates )
917 if( !candidate.rotation.allFinite() || !candidate.offset.allFinite() )
920 bool duplicate = std::any_of( unique.begin(), unique.end(),
921 [&](
const ALIGN_CANDIDATE& aPrevious )
923 return ( candidate.rotation - aPrevious.rotation ).norm() < 1e-6
924 && ( candidate.offset - aPrevious.offset ).norm() < 1e-3;
928 unique.push_back( candidate );
931 std::stable_sort( unique.begin(), unique.end(),
932 [](
const ALIGN_CANDIDATE& a,
const ALIGN_CANDIDATE& b )
934 return a.copper > b.copper;
938 for(
auto first = unique.begin(); first != unique.end(); )
940 auto last = std::find_if( first, unique.end(),
941 [&](
const ALIGN_CANDIDATE& aCandidate )
943 return aCandidate.copper < 0.9 * first->copper;
945 std::stable_sort( first, last,
946 [](
const ALIGN_CANDIDATE& a,
const ALIGN_CANDIDATE& b )
948 return a.turn < b.turn;
953 for(
const ALIGN_CANDIDATE& candidate : unique )
956 const M3& rotation = candidate.rotation;
958 angles.z = std::atan2( rotation( 1, 0 ), rotation( 0, 0 ) );
959 angles.y = std::atan2( -rotation( 2, 0 ), std::hypot( rotation( 2, 1 ), rotation( 2, 2 ) ) );
960 double sine = std::sin( angles.z );
961 double cosine = std::cos( angles.z );
962 angles.x = std::atan2( sine * rotation( 0, 2 ) - cosine * rotation( 1, 2 ),
963 cosine * rotation( 1, 1 ) - sine * rotation( 0, 1 ) );
966 for(
int axis = 0; axis < 3; ++axis )
968 double snapped = std::round( angles[axis] / 90 ) * 90;
970 if(
std::abs( angles[axis] - snapped ) < 1e-6 )
971 angles[axis] = snapped;
973 angles[axis] += 360 * std::round( ( aCurrentRotation[axis] - angles[axis] ) / 360 );
976 result.push_back( { angles, vec( candidate.offset ), candidate.matched, candidate.total, candidate.kind } );