Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1765 lines
74 KiB

//========= Copyright © Valve Corporation, All rights reserved. ============//
//
// Quat-dominant finite element mesh simulation, a distillation of experiments made in femodel-v0
// Multigrid didn't work as well as advertised, so I dropped it - this is a single-level solver without hierarchy
// CG works really well, but is not very stable due to linearization issues, so it may make it here eventually as an auxiliary pass
// Warmstarting works well, but doesn't offer any advantages in extreme cases (it can even add energy sometimes), so I don't see it as high value add-on
// The latest simple axial bend model ( distribute parallel impulses across 4 vertices of 2 adjacent triangles, precompute proportions) worked very well
// 2D LSq shape matching for triangles worked really well, too.
// So I found an fast approximate solution to Wahba's problem, which allows me to quickly shape-match
// more than 3 vertices, with different masses. 2D LSq is still used for 2-vert-constrained elements
//
#include "mathlib/femodel.h"
#include "mathlib/cholesky.h"
#include "mathlib/ssecholesky.h"
#include "tier0/microprofiler.h"
#include "mathlib/svd.h"
#include "mathlib/femodel.inl"
const fltx4 Four_SinCosRotation2DMinWeights = { 1e-14f, 1e-14f, 1e-14f, 1e-14f };
void RelaxRod( const FeRodConstraint_t &c, float flModelScale, VectorAligned &a, VectorAligned &b )
{
Vector vDist = b - a;
float flDist = vDist.Length( );
float flReqDist;
if ( flDist < flModelScale * c.flMinDist )
{
flReqDist = flModelScale * c.flMinDist;
}
else if ( flDist > flModelScale * c.flMaxDist )
{
flReqDist = flModelScale * c.flMaxDist;
}
else
{
return; // no need to adjust the distance
}
Vector vDelta = vDist * ( c.flRelaxationFactor * ( flReqDist / flDist - 1 ) );
a -= vDelta * c.flWeight0;
b += vDelta * ( 1 - c.flWeight0 );
}
//----------------------------------------------------------------------------------------------------------
void CFeModel::RelaxRods( VectorAligned *pPos, float flStiffness, float flModelScale )const
{
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nRodCount - 1 );
for ( uint i = 0; i < m_nRodCount; ++i )
{
const FeRodConstraint_t &c = m_pRods[ i ];
VectorAligned &a = pPos[ c.nNode[ 0 ] ], &b = pPos[ c.nNode[ 1 ] ];
RelaxRod( c, flModelScale, a, b );
}
}
//----------------------------------------------------------------------------------------------------------
void CFeModel::RelaxRods2( VectorAligned *pPos, float flStiffness, float flModelScale )const
{
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nRodCount - 1 );
for ( uint i = 0; i < m_nRodCount; ++i )
{
FeRodConstraint_t c = m_pRods[ i ];
VectorAligned &a = pPos[ c.nNode[ 0 ] ], &b = pPos[ c.nNode[ 1 ] ];
Vector vDist = b - a;
float flDist = vDist.Length();
float flReqDist = Min( Max( flDist, flModelScale * c.flMinDist ), flModelScale * c.flMaxDist );
Vector vDelta = vDist * ( c.flRelaxationFactor * ( flReqDist / flDist - 1 ) );
a -= vDelta * c.flWeight0;
b += vDelta * ( 1 - c.flWeight0 );
}
}
//----------------------------------------------------------------------------------------------------------
void CFeModel::RelaxRods2Ftl( VectorAligned *pPos, float flStiffness, float flModelScale )const
{
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nRodCount - 1 );
for ( uint i = 0; i < m_nRodCount; ++i )
{
FeRodConstraint_t c = m_pRods[ i ];
VectorAligned &a = pPos[ c.nNode[ 0 ] ], &b = pPos[ c.nNode[ 1 ] ];
Vector vDist = b - a;
float flDist = vDist.Length( );
float flReqDist = Min( Max( flDist, flModelScale * c.flMinDist ), flModelScale * c.flMaxDist );
Vector vDelta = vDist * ( c.flRelaxationFactor * ( flReqDist / flDist - 1 ) );
b += vDelta;
}
}
//----------------------------------------------------------------------------------------------------------
void CFeModel::RelaxRodsUninertial( VectorAligned *pPos1, VectorAligned *pPos0, float flStiffness, float flModelScale )const
{
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nRodCount - 1 );
for ( uint i = 0; i < m_nRodCount; ++i )
{
FeRodConstraint_t c = m_pRods[ i ];
VectorAligned &a1 = pPos1[ c.nNode[ 0 ] ], &b1 = pPos1[ c.nNode[ 1 ] ];
Vector vDist = b1 - a1;
float flDist = vDist.Length( ), flMaxDist = flModelScale * c.flMaxDist;
if ( flDist > flMaxDist )
{
VectorAligned &a0 = pPos0[ c.nNode[ 0 ] ], &b0 = pPos0[ c.nNode[ 1 ] ];
Vector vDelta = vDist * ( c.flRelaxationFactor * ( flMaxDist / flDist - 1 ) );
a1 -= vDelta * c.flWeight0;
b1 += vDelta * ( 1 - c.flWeight0 );
a0 -= vDelta * c.flWeight0;
b0 += vDelta * ( 1 - c.flWeight0 );
}
}
}
const fltx4 Four_2ToTheMinus30 = { 1.0f / float( 1 << 30 ), 1.0f / float( 1 << 30 ), 1.0f / float( 1 << 30 ), 1.0f / float( 1 << 30 ) };
const fltx4 Four_MaxWorldSize = { 1 << 20, 1 << 20, 1 << 20, 1 << 20 };
//----------------------------------------------------------------------------------------------------------
// 128 ticks (32 ticks per rod) on an i7
void CFeModel::RelaxSimdRods( fltx4 *pPos, float flStiffness, float flModelScale )const
{
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nSimdRodCount * 4 - 1 );
fltx4 /*f4Stiffness = ReplicateX4( flStiffness ),*/ f4ModelScale = ReplicateX4( flModelScale );
for ( uint i = 0; i < m_nSimdRodCount; ++i )
{
const FeSimdRodConstraint_t &c = m_pSimdRods[ i ];
AssertDbg( c.nNode[ 0 ][ 0 ] < m_nNodeCount && c.nNode[ 1 ][ 0 ] < m_nNodeCount && c.nNode[ 0 ][ 1 ] < m_nNodeCount && c.nNode[ 1 ][ 1 ] < m_nNodeCount && c.nNode[ 0 ][ 2 ] < m_nNodeCount && c.nNode[ 1 ][ 2 ] < m_nNodeCount && c.nNode[ 0 ][ 3 ] < m_nNodeCount && c.nNode[ 1 ][ 3 ] < m_nNodeCount );
FourVectors a, b;
LOAD_NODES( a, c.nNode[ 0 ] );
LOAD_NODES( b, c.nNode[ 1 ] );
FourVectors vDist = b - a;
fltx4 f4DistSqr = vDist.LengthSqr( );
{
// dealing with collapsed nodes, when two nodes have exactly the same coordinates
f4DistSqr = MaxSIMD( f4DistSqr, Four_2ToTheMinus30 );
}
fltx4 f4DistRcp = ReciprocalSqrtSIMD( f4DistSqr );
fltx4 f4Dist = f4DistRcp * f4DistSqr;
fltx4 f4ReqDist = MinSIMD( MaxSIMD( f4Dist, f4ModelScale * c.f4MinDist ), f4ModelScale * c.f4MaxDist );
FourVectors vDelta = vDist * ( c.f4RelaxationFactor * ( f4ReqDist * f4DistRcp - Four_Ones ) );
a -= vDelta * c.f4Weight0;
b += vDelta * ( Four_Ones - c.f4Weight0 );
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, a.Length( ) + b.Length( ) ) );
SAVE_NODES( a, c.nNode[ 0 ] );
SAVE_NODES( b, c.nNode[ 1 ] );
}
}
//----------------------------------------------------------------------------------------------------------
// 128 ticks (32 ticks per rod) on an i7
void CFeModel::RelaxSimdRodsFtl( fltx4 *pPos, float flStiffness, float flModelScale )const
{
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nSimdRodCount * 4 - 1 );
fltx4 /*f4Stiffness = ReplicateX4( flStiffness ),*/ f4ModelScale = ReplicateX4( flModelScale );
for ( uint i = 0; i < m_nSimdRodCount; ++i )
{
const FeSimdRodConstraint_t &c = m_pSimdRods[ i ];
AssertDbg( c.nNode[ 0 ][ 0 ] < m_nNodeCount && c.nNode[ 1 ][ 0 ] < m_nNodeCount && c.nNode[ 0 ][ 1 ] < m_nNodeCount && c.nNode[ 1 ][ 1 ] < m_nNodeCount && c.nNode[ 0 ][ 2 ] < m_nNodeCount && c.nNode[ 1 ][ 2 ] < m_nNodeCount && c.nNode[ 0 ][ 3 ] < m_nNodeCount && c.nNode[ 1 ][ 3 ] < m_nNodeCount );
FourVectors a, b;
LOAD_NODES( a, c.nNode[ 0 ] );
LOAD_NODES( b, c.nNode[ 1 ] );
FourVectors vDist = b - a;
fltx4 f4DistSqr = vDist.LengthSqr( );
{
// dealing with collapsed nodes, when two nodes have exactly the same coordinates
f4DistSqr = MaxSIMD( f4DistSqr, Four_2ToTheMinus30 );
}
fltx4 f4DistRcp = ReciprocalSqrtSIMD( f4DistSqr );
fltx4 f4Dist = f4DistRcp * f4DistSqr;
fltx4 f4ReqDist = MinSIMD( MaxSIMD( f4Dist, f4ModelScale * c.f4MinDist ), f4ModelScale * c.f4MaxDist );
FourVectors vDelta = vDist * ( c.f4RelaxationFactor * ( f4ReqDist * f4DistRcp - Four_Ones ) );
b += vDelta;
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, a.Length( ) + b.Length( ) ) );
SAVE_NODES( b, c.nNode[ 1 ] );
}
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxTris( VectorAligned *pPos, float flStiffness, float flModelScale )const
{
float flSumError = 0;
// first the simplest case of quads hanging on the edge 0-1: this is a 2D case with 2 moving nodes and only 1 DOF (rotation around edge 0-1)
for ( uint i = 0; i < m_nTriCount[ 2 ]; ++i )
{
const FeTri_t &tri = m_pTris[ i ];
flSumError += RelaxTri2( flStiffness, flModelScale, tri, pPos[ tri.nNode[ 0 ] ], pPos[ tri.nNode[ 1 ] ], pPos[ tri.nNode[ 2 ] ] );
}
for ( uint i = m_nTriCount[ 2 ]; i < m_nTriCount[ 1 ]; ++i )
{
const FeTri_t &tri = m_pTris[ i ];
flSumError += RelaxTri1( flStiffness, flModelScale, tri, pPos[ tri.nNode[ 0 ] ], pPos[ tri.nNode[ 1 ] ], pPos[ tri.nNode[ 2 ] ] );
}
for ( uint i = m_nTriCount[ 1 ]; i < m_nTriCount[ 0 ]; ++i )
{
const FeTri_t &tri = m_pTris[ i ];
flSumError += RelaxTri0( flStiffness, flModelScale, tri, pPos[ tri.nNode[ 0 ] ], pPos[ tri.nNode[ 1 ] ], pPos[ tri.nNode[ 2 ] ] );
}
return flSumError;
}
//----------------------------------------------------------------------------------------------------------
fltx4 CFeModel::RelaxSimdTris( VectorAligned *pPos, float flStiffness, float flModelScale )const
{
fltx4 flSumError = Four_Zeros, fl4Stiffness = ReplicateX4( flStiffness ), fl4ModelScale = ReplicateX4( flModelScale);
// first the simplest case of quads hanging on the edge 0-1: this is a 2D case with 2 moving nodes and only 1 DOF (rotation around edge 0-1)
for ( uint i = 0; i < m_nSimdTriCount[ 2 ]; ++i )
{
const FeSimdTri_t &tri = m_pSimdTris[ i ];
flSumError += RelaxSimdTri2( fl4Stiffness, fl4ModelScale, tri, ( fltx4* ) pPos );
}
for ( uint i = m_nSimdTriCount[ 2 ]; i < m_nSimdTriCount[ 1 ]; ++i )
{
const FeSimdTri_t &tri = m_pSimdTris[ i ];
flSumError += RelaxSimdTri1( fl4Stiffness, fl4ModelScale, tri, ( fltx4* ) pPos );
}
for ( uint i = m_nSimdTriCount[ 1 ]; i < m_nSimdTriCount[ 0 ]; ++i )
{
const FeSimdTri_t &tri = m_pSimdTris[ i ];
flSumError += RelaxSimdTri0( fl4Stiffness, fl4ModelScale, tri, ( fltx4* ) pPos );
}
return flSumError;
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxQuads( VectorAligned *pPos, float flStiffness, float flModelScale, int nExperimental )const
{
float flSumError = 0;
// first the simplest case of quads hanging on the edge 0-1: this is a 2D case with 2 moving nodes and only 1 DOF (rotation around edge 0-1)
for ( uint i = 0; i < m_nQuadCount[ 2 ]; ++i )
{
const FeQuad_t &quad = m_pQuads[ i ];
flSumError += RelaxQuad2( flStiffness, flModelScale, quad, pPos[ quad.nNode[ 0 ] ], pPos[ quad.nNode[ 1 ] ], pPos[ quad.nNode[ 2 ] ], pPos[ quad.nNode[ 3 ] ] );
}
for ( uint i = m_nQuadCount[ 2 ]; i < m_nQuadCount[ 1 ]; ++i )
{
const FeQuad_t &quad = m_pQuads[ i ];
flSumError += RelaxQuad1( flStiffness, flModelScale, quad, pPos[ quad.nNode[ 0 ] ], pPos[ quad.nNode[ 1 ] ], pPos[ quad.nNode[ 2 ] ], pPos[ quad.nNode[ 3 ] ] );
}
if ( nExperimental )
{
for ( uint i = m_nQuadCount[ 1 ]; i < m_nQuadCount[ 0 ]; ++i )
{
const FeQuad_t &quad = m_pQuads[ i ];
flSumError += RelaxQuad0flat( flStiffness, flModelScale, quad, pPos[ quad.nNode[ 0 ] ], pPos[ quad.nNode[ 1 ] ], pPos[ quad.nNode[ 2 ] ], pPos[ quad.nNode[ 3 ] ] );
}
}
else
{
for ( uint i = m_nQuadCount[ 1 ]; i < m_nQuadCount[ 0 ]; ++i )
{
const FeQuad_t &quad = m_pQuads[ i ];
flSumError += RelaxQuad0( flStiffness, flModelScale, quad, pPos[ quad.nNode[ 0 ] ], pPos[ quad.nNode[ 1 ] ], pPos[ quad.nNode[ 2 ] ], pPos[ quad.nNode[ 3 ] ] );
}
}
return flSumError;
}
//----------------------------------------------------------------------------------------------------------
// Measured on i7-4930K Ivy Bridge: ~600 ticks / 4 quads
fltx4 CFeModel::RelaxSimdQuads( VectorAligned *pPos, float flStiffness, float flModelScale, int nExperimental )const
{
fltx4 flSumError = Four_Zeros, fl4Stiffness = ReplicateX4( flStiffness ), fl4ModelScale = ReplicateX4( flModelScale );
// first the simplest case of quads hanging on the edge 0-1: this is a 2D case with 2 moving nodes and only 1 DOF (rotation around edge 0-1)
for ( uint i = 0; i < m_nSimdQuadCount[ 2 ]; ++i )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ i ];
flSumError += RelaxSimdQuad2( fl4Stiffness, fl4ModelScale, quad, ( fltx4* )pPos );
}
for ( uint i = m_nSimdQuadCount[ 2 ]; i < m_nSimdQuadCount[ 1 ]; ++i )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ i ];
flSumError += RelaxSimdQuad1( fl4Stiffness, fl4ModelScale, quad, ( fltx4* ) pPos );
}
if ( nExperimental )
{
for ( uint i = m_nSimdQuadCount[ 1 ]; i < m_nSimdQuadCount[ 0 ]; ++i )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ i ];
flSumError += RelaxSimdQuad0flat( fl4Stiffness, fl4ModelScale, quad, ( fltx4* ) pPos );
}
}
else
{
for ( uint i = m_nSimdQuadCount[ 1 ]; i < m_nSimdQuadCount[ 0 ]; ++i )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ i ];
flSumError += RelaxSimdQuad0( fl4Stiffness, fl4ModelScale, quad, ( fltx4* ) pPos );
}
}
return flSumError;
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxTri2( float flStiffness, float flModelScale, const FeTri_t &tri, const VectorAligned &p0, const VectorAligned &p1, VectorAligned &p2 )const
{
CFeTriBasis basis( p1 - p0, p2 - p0 ); // p1-p0 is the best choice for the axis, because we're rotating around it
p2 = p0 + basis.LocalXYToWorld( flModelScale * tri.v2.x + 0.5f * ( basis.v1x - flModelScale * tri.v1x ), flModelScale * tri.v2.y );
return 0;
}
FORCEINLINE float CrossProductZ( float v1x, float v1y, float v2x, float v2y )
{
return v1x * v2y - v1y * v2x;
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxTri1( float flStiffness, float flModelScale, const FeTri_t &tri, const VectorAligned &p0, VectorAligned &p1, VectorAligned &p2 )const
{
CFeTriBasis basis( p1 - p0, p2 - p0 ); // rotating around p0, because center of mass = p0, because it's the only infinite-mass corner of this triangle
float tri_v1x = flModelScale * tri.v1x;
Vector2D tri_v2 = flModelScale * tri.v2;
CSinCosRotation2D rotation( tri.w2 * DotProduct2D( tri_v2, basis.v2 ) + tri.w1 * ( tri_v1x * basis.v1x ), tri.w2 * CrossProductZ( tri_v2, basis.v2 ) );
// we computed omega (sin, cos), apply it..
p1 = p0 + basis.LocalXYToWorld( rotation.m_flCos * tri_v1x, rotation.m_flSin * tri_v1x );
p2 = p0 + basis.LocalXYToWorld( rotation * tri_v2 );
return 1.0f - rotation.m_flCos;
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxTri0( float flStiffness, float flModelScale, const FeTri_t &tri, VectorAligned &p0, VectorAligned &p1, VectorAligned &p2 )const
{
CFeTriBasis basis( p1 - p0, p2 - p0 ); // rotating around p0, because center of mass = p0, because it's the only infinite-mass corner of this triangle
float tri_v1x = flModelScale * tri.v1x;
Vector2D tri_v2 = flModelScale * tri.v2;
Vector2D x0neg( basis.v1x * tri.w1 + basis.v2.x * tri.w2, basis.v2.y * tri.w2 ); // center of mass of the deformed triangle p0,p1,p2 in local coordinate system
Vector2D r0neg( tri_v1x * tri.w1 + tri_v2.x * tri.w2, tri_v2.y * tri.w2 ); // center of mass of the target triangle configuration, in its coordinate system
float w0 = 1.0f - ( tri.w1 + tri.w2 );
Vector2D x2 = basis.v2 - x0neg, r2 = tri_v2 - r0neg;
float x1x = basis.v1x - x0neg.x; // x1.y = -x0neg.y
float r1x = tri_v1x - r0neg.x; // r1.y = -r0neg.y
CSinCosRotation2D rotation(
tri.w2 * DotProduct2D( x2, r2 ) + tri.w1 * ( x1x * r1x ) + w0 * ( x0neg.x * r0neg.x ) + ( tri.w1 + w0 ) * ( x0neg.y * r0neg.y ),
tri.w2 * CrossProductZ( r2, x2 ) - tri.w1 * CrossProductZ( r1x, r0neg.y, x1x, x0neg.y ) + w0 * CrossProductZ( r0neg, x0neg )
);
#ifdef _DEBUG
Vector2D x0 = -x0neg, x1 = Vector2D( basis.v1x, 0 ) + x0;
Vector2D r0 = -r0neg, r1 = Vector2D( tri_v1x, 0 ) + r0;
CSinCosRotation2D rotation2(
tri.w2 * DotProduct2D( x2, r2 ) + tri.w1 * DotProduct2D( x1, r1 ) + w0 * DotProduct2D( x0, r0 ),
tri.w2 * CrossProductZ( r2, x2 ) + tri.w1 * CrossProductZ( r1, x1 ) + w0 * CrossProductZ( r0, x0 )
);
Assert( Diff( rotation, rotation2 ) < 1e-4f );
Vector2D y0 = rotation * r0, y1 = rotation * r1, y2 = rotation * r2;
CSinCosRotation2D rotation3(
tri.w2 * DotProduct2D( y2, x2 ) + tri.w1 * DotProduct2D( y1, x1 ) + w0 * DotProduct2D( y0, x0 ),
tri.w2 * CrossProductZ( y2, x2 ) + tri.w1 * CrossProductZ( y1, x1 ) + w0 * CrossProductZ( y0, x0 )
);
Assert( fabsf( rotation3.m_flSin ) < 1e-5f );
#endif
Vector2D dX0 = x0neg - rotation * r0neg;
// we computed omega (sin, cos), apply it..
p0 += basis.LocalXYToWorld( dX0 );
p1 = p0 + basis.LocalXYToWorld( rotation.m_flCos * tri_v1x, rotation.m_flSin * tri_v1x );
p2 = p0 + basis.LocalXYToWorld( rotation * tri_v2 );
return 1.0f - rotation.m_flCos;
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxQuad2( float flStiffness, float flModelScale, const FeQuad_t &quad, const VectorAligned &p0, const VectorAligned &p1, VectorAligned &p2, VectorAligned &p3 )const
{
Vector vCoM = ( p0 + p1 ) * 0.5f;
CFeBasis basis( p1 - p0, p2 + p3 - 2 * p0 ); // p1-p0 is the best choice for the axis, because we're rotating around it ; Should be synced up with Builder:AdjustQuads
// Numerical stability note: if we choose cross product or some other nice sounding but insanely arbitrary axis to rotate around here,
// not only is it wrong, it'd also make it impossible to do a square element with diagonal static and diagonal dynamic elements,
// because the order of vertices around this quad would not be natural (the diagonal vertices have to go first for the solve because that's our convention here)
// so p2-p0 and p3-p1 tentative axes would be parallel, producing a very unstable solve (since we don't solve full Wahba problem, but just finding an approximate solution;
// although in this very special case of RelaxQuad2, because we make the problem essentially 2D, we solve pretty close)
// assume center of mass = p0, because it doesn't matter where it is along the line p0..p1
// find omega, refer to wahba1d.nb. We only have points 2 and 3 to compute and move, and even that only in the plane YZ (X is fixed, as we rotate around axis X)
Vector x2 = p2 - vCoM, x3 = p3 - vCoM;
Vector2D vLocalP2 = basis.WorldToLocalYZ( x2 ), vLocalP3 = basis.WorldToLocalYZ( x3 );
// Note: e x X = CrossProduct( vAxisX, (?, x1, x2) ) = (0, -x2, x1 ). Note that we rename YZ->XY for Vector2D conversion, so (-x2, x1) -> ( -p.y, p.x )
float flMass2 = quad.vShape[ 2 ].w, flMass3 = quad.vShape[ 3 ].w;
CSinCosRotation2D rotation( ( vLocalP2.x * quad.vShape[ 2 ].y + vLocalP2.y * quad.vShape[ 2 ].z ) * flMass2 + ( vLocalP3.x * quad.vShape[ 3 ].y + vLocalP3.y * quad.vShape[ 3 ].z ) * flMass3,
( vLocalP2.x * quad.vShape[ 2 ].z - vLocalP2.y * quad.vShape[ 2 ].y ) * flMass2 + ( vLocalP3.x * quad.vShape[ 3 ].z - vLocalP3.y * quad.vShape[ 3 ].y ) * flMass3 );
Vector r2 = basis.LocalToWorld( quad.vShape[ 2 ].x, quad.vShape[ 2 ].y * rotation.GetCosine() - quad.vShape[ 2 ].z * rotation.GetSine(), quad.vShape[ 2 ].z * rotation.GetCosine() + quad.vShape[ 2 ].y * rotation.GetSine() );
Vector r3 = basis.LocalToWorld( quad.vShape[ 3 ].x, quad.vShape[ 3 ].y * rotation.GetCosine() - quad.vShape[ 3 ].z * rotation.GetSine(), quad.vShape[ 3 ].z * rotation.GetCosine() + quad.vShape[ 3 ].y * rotation.GetSine() );
float flError = ( r2 - x2 ).LengthSqr( ) + ( r3 - x3 ).LengthSqr( );
p2 = vCoM + r2 * flModelScale;
p3 = vCoM + r3 * flModelScale;
return flError;
}
//----------------------------------------------------------------------------------------------------------
float CFeModel::RelaxQuad1( float flStiffness, float flModelScale, const FeQuad_t &quad, const VectorAligned &p0, VectorAligned &p1, VectorAligned &p2, VectorAligned &p3 )const
{
Vector vCoM = p0;
CFeBasis basis( p2 - p0, p3 - p1 ); // rotating around p0, because center of mass = p0, because it's the only infinite-mass corner of this quad; Should be synced up with Builder:AdjustQuads
// find omega, refer to wahba.nb. We only have points 1, 2 and 3 to compute and move, but otherwise this is the same as the free-quad version
Vector x1 = p1 - vCoM, x2 = p2 - vCoM, x3 = p3 - vCoM;
Vector r1 = basis.LocalToWorld( quad.vShape[ 1 ] * flModelScale ), r2 = basis.LocalToWorld( quad.vShape[ 2 ] * flModelScale ), r3 = basis.LocalToWorld( quad.vShape[ 3 ] * flModelScale );
float m1 = quad.vShape[ 1 ].w, m2 = quad.vShape[ 2 ].w, m3 = quad.vShape[ 3 ].w;
// refer to wahba.nb
Vector rhs = -(m1 * CrossProduct( x1, r1 ) + m2 * CrossProduct( x2, r2 ) + m3 * CrossProduct( x3, r3 ) );
CovMatrix3 cov;
cov.InitForWahba( m1, x1 );
cov.AddForWahba( m2, x2 );
cov.AddForWahba( m3, x3 );
Cholesky3x3_t chol( cov.m_vDiag.x, cov.m_flXY, cov.m_vDiag.y, cov.m_flXZ, cov.m_flYZ, cov.m_vDiag.z );
CSinCosRotation rotation( chol.Solve( rhs ) );
// yay! we computed omega (sin), apply it..
r1 = rotation * r1; r2 = rotation * r2; r3 = rotation * r3;
float flError = ( r1 - x1 ).LengthSqr( ) + ( r2 - x2 ).LengthSqr( ) + ( r3 - x3 ).LengthSqr( );
p1 = r1 + vCoM; p2 = r2 + vCoM; p3 = r3 + vCoM;
return flError;
}
//----------------------------------------------------------------------------------------------------------
// total theoretical cost is ~300 flops, practically taking 670 ticks (170/single quad) which when SIMDized can be 300 ticks to solve 4 quads, or 8 quads when using AVX
// this is as fast as solving 12 distance constraints, but the quad is solved precisely every time
// considering that a quad requires at least 6 distance constraints to make it rigid, and 2 passes won't solve it precisely, it's a prety good deal
float CFeModel::RelaxQuad0( float flStiffness, float flModelScale, const FeQuad_t &quad, VectorAligned &p0, VectorAligned &p1, VectorAligned &p2, VectorAligned &p3 )const
{
CFeBasis basis( p2 - p0, p3 - p1 ); // the agreed upon local frame of reference of the quad Finite Element. Should be synced up with Builder:AdjustQuads
float m[ 4 ] = { quad.vShape[ 0 ].w, quad.vShape[ 1 ].w, quad.vShape[ 2 ].w, quad.vShape[ 3 ].w };
Vector vCoM = p0 * m[ 0 ] + p1 * m[ 1 ] + p2 * m[ 2 ] + p3 * m[ 3 ]; // ~12 flops
// find omega, refer to wahba.nb. We only have points 1, 2 and 3 to compute and move, but otherwise this is the same as the free-quad version
Vector x[ 4 ] = { p0 - vCoM, p1 - vCoM, p2 - vCoM, p3 - vCoM }; // ~9 flops
Vector r[ 4 ] = { basis.LocalToWorld( flModelScale * quad.vShape[ 0 ] ), basis.LocalToWorld( flModelScale * quad.vShape[ 1 ] ), basis.LocalToWorld( flModelScale * quad.vShape[ 2 ] ), basis.LocalToWorld( flModelScale * quad.vShape[ 3 ] ) }; // ~36 flops
float flError;
// for ( int i = 0; i < 3; ++i )
{
#ifdef _DEBUG
Vector q[ 4 ] = { basis.WorldToLocal( x[ 0 ] ), basis.WorldToLocal( x[ 1 ] ), basis.WorldToLocal( x[ 2 ] ), basis.WorldToLocal( x[ 3 ] ) }; NOTE_UNUSED( q );
#endif
// refer to wahba.nb
Vector rhs = - ( m[ 0 ] * CrossProduct( x[ 0 ], r[ 0 ] ) + m[ 1 ] * CrossProduct( x[ 1 ], r[ 1 ] ) + m[ 2 ] * CrossProduct( x[ 2 ], r[ 2 ] ) + m[ 3 ] * CrossProduct( x[ 3 ], r[ 3 ] ) ); // ~27 flops
// ~84 flops to sum up the covariance matrix
CovMatrix3 cov;
cov.InitForWahba( m[ 0 ], x[ 0 ] );
cov.AddForWahba( m[ 1 ], x[ 1 ] );
cov.AddForWahba( m[ 2 ], x[ 2 ] );
cov.AddForWahba( m[ 3 ], x[ 3 ] );
Cholesky3x3_t chol( cov.m_vDiag.x, cov.m_flXY, cov.m_vDiag.y, cov.m_flXZ, cov.m_flYZ, cov.m_vDiag.z ); // ~30 flops in SIMD
CSinCosRotation rotation( chol.Solve( rhs ) ); // ~20 flops to solve and init rotation
// yay! we computed omega (sin), apply it..
r[ 0 ] = rotation * r[ 0 ]; r[ 1 ] = rotation * r[ 1 ]; r[ 2 ] = rotation * r[ 2 ]; r[ 3 ] = rotation * r[ 3 ];
#ifdef _DEBUG
Vector s[ 4 ] = { basis.WorldToLocal( r[ 0 ] ), basis.WorldToLocal( r[ 1 ] ), basis.WorldToLocal( r[ 2 ] ), basis.WorldToLocal( r[ 3 ] ) }; NOTE_UNUSED( s );
#endif
flError = ( r[ 0 ] - x[ 0 ] ).LengthSqr() + ( r[ 1 ] - x[ 1 ] ).LengthSqr() + ( r[ 2 ] - x[ 2 ] ).LengthSqr() + ( r[ 3 ] - x[ 3 ] ).LengthSqr();
} // ~24 flops
p0 = r[ 0 ] + vCoM; p1 = r[ 1 ] + vCoM; p2 = r[ 2 ] + vCoM; p3 = r[ 3 ] + vCoM; // 33 flops
return flError;
}
// ~121 flops
float CFeModel::RelaxQuad0flat( float flStiffness, float flModelScale, const FeQuad_t &quad, VectorAligned &p0, VectorAligned &p1, VectorAligned &p2, VectorAligned &p3 )const
{
CFeBasis basis( p2 - p0, p3 - p1 ); // ~9 flops; the agreed upon local frame of reference of the quad Finite Element
float m[ 4 ] = { quad.vShape[ 0 ].w, quad.vShape[ 1 ].w, quad.vShape[ 2 ].w, quad.vShape[ 3 ].w };
Vector vCoM = p0 * m[ 0 ] + p1 * m[ 1 ] + p2 * m[ 2 ] + p3 * m[ 3 ]; // ~12 flops
// find omega, refer to wahba.nb. We only have points 1, 2 and 3 to compute and move, but otherwise this is the same as the free-quad version
Vector2D x[ 4 ] = { basis.WorldToLocalXY( p0 - vCoM ), basis.WorldToLocalXY( p1 - vCoM ), basis.WorldToLocalXY( p2 - vCoM ), basis.WorldToLocalXY( p3 - vCoM ) }; // ~24 flops
// refer to wahba.nb
CSinCosRotation2D rotation( m[ 0 ] * DotProduct( quad.vShape[ 0 ], x[ 0 ] ) + m[ 1 ] * DotProduct( quad.vShape[ 1 ], x[ 1 ] ) + m[ 2 ] * DotProduct( quad.vShape[ 2 ], x[ 2 ] ) + m[ 3 ] * DotProduct( quad.vShape[ 3 ], x[ 3 ] ) ,
m[ 0 ] * CrossProductZ( quad.vShape[ 0 ], x[ 0 ] ) + m[ 1 ] * CrossProductZ( quad.vShape[ 1 ], x[ 1 ] ) + m[ 2 ] * CrossProductZ( quad.vShape[ 2 ], x[ 2 ] ) + m[ 3 ] * CrossProductZ( quad.vShape[ 3 ], x[ 3 ] ) ); // flops
basis.UnrotateXY( rotation ); // ~12 flops
Vector r[ 4 ] = { basis.LocalToWorld( flModelScale * quad.vShape[ 0 ].AsVector3D( ) ), basis.LocalToWorld( flModelScale * quad.vShape[ 1 ].AsVector3D( ) ), basis.LocalToWorld( flModelScale * quad.vShape[ 2 ].AsVector3D( ) ), basis.LocalToWorld( flModelScale * quad.vShape[ 3 ].AsVector3D( ) ) }; // ~40 flops
p0 = r[ 0 ] + vCoM; p1 = r[ 1 ] + vCoM; p2 = r[ 2 ] + vCoM; p3 = r[ 3 ] + vCoM; // 12 flops
return 1.0f - rotation.m_flCos; // I really should scale it by the quad's perimeter or something...
}
//----------------------------------------------------------------------------------------------------------
fltx4 CFeModel::RelaxSimdQuad2( const fltx4& fl4Stiffness, const fltx4 &fl4ModelScale, const FeSimdQuad_t &quad, fltx4 *pPos )const
{
FourVectors p0, p1, p2, p3;
LOAD_NODES( p0, quad.nNode[ 0 ] );
LOAD_NODES( p1, quad.nNode[ 1 ] );
LOAD_NODES( p2, quad.nNode[ 2 ] );
LOAD_NODES( p3, quad.nNode[ 3 ] );
FourVectors vCoM = ( p0 + p1 ) * Four_PointFives;
CFeSimdBasis basis( p1 - p0, p2 + p3 - ( p0 + p0 ) ); // p1-p0 is the best choice for the axis, because we're rotating around it
// assume center of mass = p0, because it doesn't matter where it is along the line p0..p1
// find omega, refer to wahba1d.nb. We only have points 2 and 3 to compute and move, and even that only in the plane YZ (X is fixed, as we rotate around axis X)
FourVectors x2 = p2 - vCoM, x3 = p3 - vCoM;
FourVectorsYZ vLocalP2 = basis.WorldToLocalYZ( x2 ), vLocalP3 = basis.WorldToLocalYZ( x3 );
// Note: e x X = CrossProduct( vAxisX, (?, x1, x2) ) = (0, -x2, x1 ). Note that we rename YZ->XY for Vector2D conversion, so (-x2, x1) -> ( -p.y, p.x )
fltx4 flMass2 = quad.f4Weights[ 2 ], flMass3 = quad.f4Weights[ 3 ];
CSimdSinCosRotation2D rotation( ( vLocalP2.y * quad.vShape[ 2 ].y + vLocalP2.z * quad.vShape[ 2 ].z ) * flMass2 + ( vLocalP3.y * quad.vShape[ 3 ].y + vLocalP3.z * quad.vShape[ 3 ].z ) * flMass3,
( vLocalP2.y * quad.vShape[ 2 ].z - vLocalP2.z * quad.vShape[ 2 ].y ) * flMass2 + ( vLocalP3.y * quad.vShape[ 3 ].z - vLocalP3.z * quad.vShape[ 3 ].y ) * flMass3 );
FourVectors r2 = basis.LocalToWorld( quad.vShape[ 2 ].x, quad.vShape[ 2 ].y * rotation.GetCosine( ) - quad.vShape[ 2 ].z * rotation.GetSine( ), quad.vShape[ 2 ].z * rotation.GetCosine( ) + quad.vShape[ 2 ].y * rotation.GetSine( ) );
FourVectors r3 = basis.LocalToWorld( quad.vShape[ 3 ].x, quad.vShape[ 3 ].y * rotation.GetCosine( ) - quad.vShape[ 3 ].z * rotation.GetSine( ), quad.vShape[ 3 ].z * rotation.GetCosine( ) + quad.vShape[ 3 ].y * rotation.GetSine( ) );
fltx4 flError = ( r2 - x2 ).LengthSqr( ) + ( r3 - x3 ).LengthSqr( );
fltx4 wr = fl4ModelScale * fl4Stiffness, wx = Four_Ones - fl4Stiffness;
p2 = vCoM + r2 * wr + x2 * wx;
p3 = vCoM + r3 * wr + x3 * wx;
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length( ) + p3.Length( ) ) );
SAVE_NODES( p2, quad.nNode[ 2 ] );
SAVE_NODES( p3, quad.nNode[ 3 ] );
return flError;
}
//----------------------------------------------------------------------------------------------------------
fltx4 CFeModel::RelaxSimdQuad1( const fltx4& fl4Stiffness, const fltx4 &fl4ModelScale, const FeSimdQuad_t &quad, fltx4 *pPos )const
{
FourVectors p0, p1, p2, p3;
LOAD_NODES( p0, quad.nNode[ 0 ] );
LOAD_NODES( p1, quad.nNode[ 1 ] );
LOAD_NODES( p2, quad.nNode[ 2 ] );
LOAD_NODES( p3, quad.nNode[ 3 ] );
const FourVectors &vCoM = p0;
CFeSimdBasis basis( p2 - p0, p3 - p1 ); // rotating around p0, because center of mass = p0, because it's the only infinite-mass corner of this quad
// find omega, refer to wahba.nb. We only have points 1, 2 and 3 to compute and move, but otherwise this is the same as the free-quad version
FourVectors x1 = p1 - vCoM, x2 = p2 - vCoM, x3 = p3 - vCoM;
FourVectors r1 = basis.LocalToWorld( fl4ModelScale * quad.vShape[ 1 ] ), r2 = basis.LocalToWorld( fl4ModelScale * quad.vShape[ 2 ] ), r3 = basis.LocalToWorld( fl4ModelScale * quad.vShape[ 3 ] );
fltx4 m1 = quad.f4Weights[ 1 ], m2 = quad.f4Weights[ 2 ], m3 = quad.f4Weights[ 3 ];
// refer to wahba.nb
FourVectors rhs = m1 * CrossProduct( x1, r1 ) + m2 * CrossProduct( x2, r2 ) + m3 * CrossProduct( x3, r3 );
FourCovMatrices3 cov;
cov.InitForWahba( m1, x1 );
cov.AddForWahba( m2, x2 );
cov.AddForWahba( m3, x3 );
SimdCholesky3x3_t chol( cov.m_vDiag.x, cov.m_flXY, cov.m_vDiag.y, cov.m_flXZ, cov.m_flYZ, cov.m_vDiag.z );
CSimdSinCosRotation rotation( AndSIMD( chol.Solve( rhs ), chol.GetValidMask() ) );
// yay! we computed omega (sin), apply it..
r1 = rotation.Unrotate( r1 ); r2 = rotation.Unrotate( r2 ); r3 = rotation.Unrotate( r3 );
fltx4 flError = ( r1 - x1 ).LengthSqr( ) + ( r2 - x2 ).LengthSqr( ) + ( r3 - x3 ).LengthSqr( );
p1 = r1 + vCoM; p2 = r2 + vCoM; p3 = r3 + vCoM;
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length( ) + p3.Length( ) ) );
SAVE_NODES( p1, quad.nNode[ 1 ] );
SAVE_NODES( p2, quad.nNode[ 2 ] );
SAVE_NODES( p3, quad.nNode[ 3 ] );
return flError;
}
fltx4 CFeModel::RelaxSimdQuad0( const fltx4& flStiffness, const fltx4 &fl4ModelScale, const FeSimdQuad_t &quad, fltx4 *pPos )const
{
FourVectors p0, p1, p2, p3;
LOAD_NODES( p0, quad.nNode[ 0 ] );
LOAD_NODES( p1, quad.nNode[ 1 ] );
LOAD_NODES( p2, quad.nNode[ 2 ] );
LOAD_NODES( p3, quad.nNode[ 3 ] );
CFeSimdBasis basis( p2 - p0, p3 - p1 ); // the agreed upon local frame of reference of the quad Finite Element
const fltx4 *m = quad.f4Weights;
FourVectors vCoM = p0 * m[ 0 ] + p1 * m[ 1 ] + p2 * m[ 2 ] + p3 * m[ 3 ]; // ~12 flops
// find omega, refer to wahba.nb. We only have points 1, 2 and 3 to compute and move, but otherwise this is the same as the free-quad version
FourVectors x[ 4 ] = { p0 - vCoM, p1 - vCoM, p2 - vCoM, p3 - vCoM }; // ~9 flops
FourVectors r[ 4 ] = { basis.LocalToWorld( fl4ModelScale * quad.vShape[ 0 ] ), basis.LocalToWorld( fl4ModelScale * quad.vShape[ 1 ] ), basis.LocalToWorld( fl4ModelScale * quad.vShape[ 2 ] ), basis.LocalToWorld( fl4ModelScale * quad.vShape[ 3 ] ) }; // ~48 flops
fltx4 fl4Error;
// for ( int i = 0; i < 3; ++i )
{
#ifdef _DEBUG
//FourVectors q[ 4 ] = { basis.WorldToLocal( x[ 0 ] ), basis.WorldToLocal( x[ 1 ] ), basis.WorldToLocal( x[ 2 ] ), basis.WorldToLocal( x[ 3 ] ) }; NOTE_UNUSED( q );
#endif
// refer to wahba.nb
FourVectors rhs = ( m[ 0 ] * CrossProduct( x[ 0 ], r[ 0 ] ) + m[ 1 ] * CrossProduct( x[ 1 ], r[ 1 ] ) + m[ 2 ] * CrossProduct( x[ 2 ], r[ 2 ] ) + m[ 3 ] * CrossProduct( x[ 3 ], r[ 3 ] ) ); // ~27 flops
// ~84 flops to sum up the covariance matrix
FourCovMatrices3 cov;
cov.InitForWahba( m[ 0 ], x[ 0 ] );
cov.AddForWahba( m[ 1 ], x[ 1 ] );
cov.AddForWahba( m[ 2 ], x[ 2 ] );
cov.AddForWahba( m[ 3 ], x[ 3 ] );
SimdCholesky3x3_t chol( cov.m_vDiag.x, cov.m_flXY, cov.m_vDiag.y, cov.m_flXZ, cov.m_flYZ, cov.m_vDiag.z ); // ~30 flops in SIMD
CSimdSinCosRotation rotation( AndSIMD( chol.Solve( rhs ), chol.GetValidMask( ) ) ); // ~20 + 7 flops to solve and init rotation + mask out invalid columns
// yay! we computed omega (sin), apply it..
r[ 0 ] = rotation.Unrotate( r[ 0 ] ); r[ 1 ] = rotation.Unrotate( r[ 1 ] ); r[ 2 ] = rotation.Unrotate( r[ 2 ] ); r[ 3 ] = rotation.Unrotate( r[ 3 ] );
fl4Error = ( r[ 0 ] - x[ 0 ] ).LengthSqr( ) + ( r[ 1 ] - x[ 1 ] ).LengthSqr( ) + ( r[ 2 ] - x[ 2 ] ).LengthSqr( ) + ( r[ 3 ] - x[ 3 ] ).LengthSqr( );
#ifdef _DEBUG
//FourVectors s[ 4 ] = { basis.WorldToLocal( r[ 0 ] ), basis.WorldToLocal( r[ 1 ] ), basis.WorldToLocal( r[ 2 ] ), basis.WorldToLocal( r[ 3 ] ) }; NOTE_UNUSED( s );
//const fltx4 fl4MaxExpectedError = { 64, 64, 64, 64 };
//AssertDbg( IsAllGreaterThan( fl4MaxExpectedError, fl4Error ) );
#endif
} // ~24 flops
fltx4 flUnStiffness = Four_Ones - flStiffness;
p0 = r[ 0 ] * flStiffness + x[ 0 ] * flUnStiffness + vCoM;
p1 = r[ 1 ] * flStiffness + x[ 1 ] * flUnStiffness + vCoM;
p2 = r[ 2 ] * flStiffness + x[ 2 ] * flUnStiffness + vCoM;
p3 = r[ 3 ] * flStiffness + x[ 3 ] * flUnStiffness + vCoM; // 33 flops
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length( ) + p3.Length() ) );
SAVE_NODES( p0, quad.nNode[ 0 ] );
SAVE_NODES( p1, quad.nNode[ 1 ] );
SAVE_NODES( p2, quad.nNode[ 2 ] );
SAVE_NODES( p3, quad.nNode[ 3 ] );
return fl4Error;
}
FORCEINLINE fltx4 DotProduct( const FourVectors &v1, const FourVectors2D &v2 )
{
return v1.x * v2.x + v1.y * v2.y;
}
fltx4 CFeModel::RelaxSimdQuad0flat( const fltx4& flStiffness, const fltx4 &fl4ModelScale, const FeSimdQuad_t &quad, fltx4 *pPos )const
{
FourVectors p0, p1, p2, p3;
LOAD_NODES( p0, quad.nNode[ 0 ] );
LOAD_NODES( p1, quad.nNode[ 1 ] );
LOAD_NODES( p2, quad.nNode[ 2 ] );
LOAD_NODES( p3, quad.nNode[ 3 ] );
CFeSimdBasis basis( p2 - p0, p3 - p1 ); // the agreed upon local frame of reference of the quad Finite Element
FourVectors vCoM = p0 * quad.f4Weights[ 0 ] + p1 * quad.f4Weights[ 1 ] + p2 * quad.f4Weights[ 2 ] + p3 * quad.f4Weights[ 3 ]; // ~12 flops
FourVectors2D x[ 4 ] = { basis.WorldToLocalXY( p0 - vCoM ), basis.WorldToLocalXY( p1 - vCoM ), basis.WorldToLocalXY( p2 - vCoM ), basis.WorldToLocalXY( p3 - vCoM ) }; // ~24 flops
// refer to wahba.nb
CSimdSinCosRotation2D rotation(
quad.f4Weights[ 0 ] * DotProduct( quad.vShape[ 0 ], x[ 0 ] ) + quad.f4Weights[ 1 ] * DotProduct( quad.vShape[ 1 ], x[ 1 ] ) + quad.f4Weights[ 2 ] * DotProduct( quad.vShape[ 2 ], x[ 2 ] ) + quad.f4Weights[ 3 ] * DotProduct( quad.vShape[ 3 ], x[ 3 ] ),
quad.f4Weights[ 0 ] * CrossProductZ( quad.vShape[ 0 ], x[ 0 ] ) + quad.f4Weights[ 1 ] * CrossProductZ( quad.vShape[ 1 ], x[ 1 ] ) + quad.f4Weights[ 2 ] * CrossProductZ( quad.vShape[ 2 ], x[ 2 ] ) + quad.f4Weights[ 3 ] * CrossProductZ( quad.vShape[ 3 ], x[ 3 ] )
); // ~flops
basis.UnrotateXY( rotation ); // ~12 flops
FourVectors r[ 4 ] = { basis.LocalToWorld( fl4ModelScale * quad.vShape[ 0 ] ), basis.LocalToWorld( fl4ModelScale * quad.vShape[ 1 ] ), basis.LocalToWorld( fl4ModelScale * quad.vShape[ 2 ] ), basis.LocalToWorld( fl4ModelScale * quad.vShape[ 3 ] ) }; // ~36 flops
p0 = r[ 0 ] + vCoM; p1 = r[ 1 ] + vCoM; p2 = r[ 2 ] + vCoM; p3 = r[ 3 ] + vCoM; // 33 flops
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length( ) + p3.Length( ) ) );
SAVE_NODES( p0, quad.nNode[ 0 ] );
SAVE_NODES( p1, quad.nNode[ 1 ] );
SAVE_NODES( p2, quad.nNode[ 2 ] );
SAVE_NODES( p3, quad.nNode[ 3 ] );
return Four_Ones - rotation.m_fl4Cos;
}
//----------------------------------------------------------------------------------------------------------
fltx4 CFeModel::RelaxSimdTri2( const fltx4& flStiffness, const fltx4 &fl4ModelScale, const FeSimdTri_t &tri, fltx4 *pPos )const
{
FourVectors p0, p1, p2;
LOAD_NODES( p0, tri.nNode[ 0 ] );
LOAD_NODES( p1, tri.nNode[ 1 ] );
LOAD_NODES( p2, tri.nNode[ 2 ] );
CFeSimdTriBasis basis( p1 - p0, p2 - p0 ); // p1-p0 is the best choice for the axis, because we're rotating around it
p2 = p0 + basis.LocalXYToWorld( fl4ModelScale * ( tri.v2.x + Four_PointFives * ( basis.v1x - tri.v1x ) ), fl4ModelScale * tri.v2.y );
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length( ) ) );
SAVE_NODES( p2, tri.nNode[ 2 ] );
return Four_Zeros;
}
FORCEINLINE fltx4 CrossProductZ( const fltx4& v1x, const fltx4& v1y, const fltx4& v2x, const fltx4& v2y )
{
return v1x * v2y - v1y * v2x;
}
FORCEINLINE fltx4 DotProduct2D( const FourVectors2D &v1, const FourVectors2D &v2 )
{
return v1.x * v2.x + v1.y * v2.y;
}
FORCEINLINE fltx4 CrossProductZ( const FourVectors2D &v1, const FourVectors2D &v2 )
{
return v1.x * v2.y - v1.y * v2.x;
}
//----------------------------------------------------------------------------------------------------------
fltx4 CFeModel::RelaxSimdTri1( const fltx4& flStiffness, const fltx4 &fl4ModelScale, const FeSimdTri_t &tri, fltx4 *pPos )const
{
FourVectors p0, p1, p2;
LOAD_NODES( p0, tri.nNode[ 0 ] );
LOAD_NODES( p1, tri.nNode[ 1 ] );
LOAD_NODES( p2, tri.nNode[ 2 ] );
CFeSimdTriBasis basis( p1 - p0, p2 - p0 ); // rotating around p0, because center of mass = p0, because it's the only infinite-mass corner of this triangle
CSimdSinCosRotation2D rotation( tri.w2 * DotProduct2D( tri.v2, basis.v2 ) + tri.w1 * ( tri.v1x * basis.v1x ), tri.w2 * CrossProductZ( tri.v2, basis.v2 ) );
// we computed omega (sin, cos), apply it..
p1 = p0 + basis.LocalXYToWorld( fl4ModelScale * ( rotation.m_fl4Cos * tri.v1x ), fl4ModelScale * ( rotation.m_fl4Sin * tri.v1x ) );
p2 = p0 + basis.LocalXYToWorld( fl4ModelScale * ( rotation * tri.v2 ) );
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length( ) ) );
SAVE_NODES( p1, tri.nNode[ 1 ] );
SAVE_NODES( p2, tri.nNode[ 2 ] );
return Four_Ones - rotation.m_fl4Cos;
}
//----------------------------------------------------------------------------------------------------------
fltx4 CFeModel::RelaxSimdTri0( const fltx4& flStiffness, const fltx4 &fl4ModelScale, const FeSimdTri_t &tri, fltx4 *pPos )const
{
FourVectors p0, p1, p2;
LOAD_NODES( p0, tri.nNode[ 0 ] );
LOAD_NODES( p1, tri.nNode[ 1 ] );
LOAD_NODES( p2, tri.nNode[ 2 ] );
fltx4 tri_v1x = fl4ModelScale * tri.v1x;
FourVectors2D tri_v2 = fl4ModelScale * tri.v2;
CFeSimdTriBasis basis( p1 - p0, p2 - p0 );
FourVectors2D x0neg( basis.v1x * tri.w1 + basis.v2.x * tri.w2, basis.v2.y * tri.w2 ); // center of mass of the deformed triangle p0,p1,p2 in local coordinate system
FourVectors2D r0neg( tri_v1x * tri.w1 + tri_v2.x * tri.w2, tri_v2.y * tri.w2 ); // center of mass of the target triangle configuration, in its coordinate system
fltx4 w0 = Four_Ones - ( tri.w1 + tri.w2 );
FourVectors2D x2 = basis.v2 - x0neg, r2 = tri_v2 - r0neg;
fltx4 x1x = basis.v1x - x0neg.x; // x1.y = -x0neg.y
fltx4 r1x = tri_v1x - r0neg.x; // r1.y = -r0neg.y
CSimdSinCosRotation2D rotation(
tri.w2 * DotProduct2D( x2, r2 ) + tri.w1 * ( x1x * r1x ) + w0 * ( x0neg.x * r0neg.x ) + ( tri.w1 + w0 ) * ( x0neg.y * r0neg.y ),
tri.w2 * CrossProductZ( r2, x2 ) - tri.w1 * CrossProductZ( r1x, r0neg.y, x1x, x0neg.y ) + w0 * CrossProductZ( r0neg, x0neg )
);
FourVectors2D dX0 = x0neg - rotation * r0neg;
// we computed omega (sin, cos), apply it..
p0 += basis.LocalXYToWorld( dX0 );
p1 = p0 + basis.LocalXYToWorld( rotation.m_fl4Cos * tri_v1x, rotation.m_fl4Sin * tri_v1x );
p2 = p0 + basis.LocalXYToWorld( rotation * tri_v2 );
AssertDbg( IsAllGreaterThan( Four_MaxWorldSize, p0.Length( ) + p1.Length( ) + p2.Length() ) );
SAVE_NODES( p0, tri.nNode[ 0 ] );
SAVE_NODES( p1, tri.nNode[ 1 ] );
SAVE_NODES( p2, tri.nNode[ 2 ] );
return Four_Ones - rotation.m_fl4Cos;
}
//----------------------------------------------------------------------------------------------------------
void CFeModel::RelaxBend( VectorAligned *pPos, float flStiffness )const
{
for ( uint nEdge = 0; nEdge < m_nAxialEdgeCount; ++nEdge )
{
const FeAxialEdgeBend_t& edge = m_pAxialEdges[ nEdge ];
VectorAligned &p0 = pPos[ edge.nNode[ 0 ] ], &p1 = pPos[ edge.nNode[ 1 ] ], &p2 = pPos[ edge.nNode[ 2 ] ], &p3 = pPos[ edge.nNode[ 3 ] ], &p4 = pPos[ edge.nNode[ 4 ] ], &p5 = pPos[ edge.nNode[ 5 ] ];
Vector fe = p0 * ( 1.0f - edge.te ) + p1 * edge.te;
float etvHalf = edge.tv * 0.5f;
Vector fv = ( p2 + p3 ) * ( 0.5f - etvHalf ) + ( p4 + p5 ) * etvHalf;
// alternative axis
Vector vAxis = fv - fe;
float flAxis = vAxis.Length( );
Vector vEdge = p1 - p0, vVirtualEdge = ( p4 + p5 ) - ( p2 + p3 );
Vector vCrossEdges = CrossProduct( vEdge, vVirtualEdge );
float flCorrection;
if ( flAxis > 0.001f ) // this is the upper bound on the distance between edges; I hope edges themselves are never this short, so we can consider them almost-intersecting if they're this close and fallback
{
float flAdjDist = DotProduct( vAxis, vCrossEdges ) > 0 ? -edge.flDist : edge.flDist; // -1.0f: detect flipped edge, flip it back
flCorrection = 1.0f + flAdjDist / flAxis;
}
else
{
// recover from intersecting edges; not necessary for high curvatures if there are rigid rods that enforce some bend, but crucial to maintain low or zero angles of curvature
// generally, rods are better for high curvature and useless for low; bends are better for low curvature and useless for high
float flCrossEdges = vCrossEdges.Length();
if ( flCrossEdges > FLT_EPSILON )
{
vAxis = vCrossEdges;
flCorrection = edge.flDist / flCrossEdges;
}
else
{
vAxis.z = 1; // degenerate case, should probably recover anyway when polygon shape recovers
flCorrection = edge.flDist;
}
}
Vector vDelta = ( flStiffness * flCorrection ) * vAxis;
p0 += vDelta * edge.flWeight[ 0 ];
p1 += vDelta * edge.flWeight[ 1 ];
Vector dw2 = vDelta * edge.flWeight[ 2 ];
// Careful! The same weight will be applied to the same node twice sometimes here
Vector p2new = p2 + dw2;
Vector p3new = p3 + dw2;
p2 = p2new;
p3 = p3new;
Vector dw3 = vDelta * edge.flWeight[ 3 ];
Vector p4new = p4 + dw3;
Vector p5new = p5 + dw3;
p4 = p4new;
p5 = p5new;
}
}
void IntegrateSpring( const FeSpringIntegrator_t &integ, float flTimeStep, float flModelScale, VectorAligned &pos10, VectorAligned &pos11, VectorAligned &pos00, VectorAligned &pos01 )
{
Vector vDeltaOrigin1 = pos10 - pos11;
Vector vDeltaOrigin0 = pos00 - pos01;
Vector vDeltaVelDt = ( vDeltaOrigin1 - vDeltaOrigin0 );
float flDistance = vDeltaOrigin1.Length( );
if ( flDistance < 1.0f )
{
return; // nothing to integrate
}
Vector vSpringDir = vDeltaOrigin1 / flDistance;
float flHTerm = ( flDistance - integ.flSpringRestLength * flModelScale ) * integ.flSpringConstant * flTimeStep;
float flDTerm = ( vDeltaVelDt.Dot( vSpringDir ) * integ.flSpringDamping );
//Vector vSpringDeltaVel = vSpringAcceleration * -( flHTerm + flDTerm );
Vector vIntegral = vSpringDir * ( ( flHTerm + flDTerm ) * ( flTimeStep /** 0.5f*/ ) );
// reference from source1 code:
// if ( ( m_SpringType == CLOTH_SPRING_STRUCTURAL_HORIZONTAL || m_SpringType == CLOTH_SPRING_STRUCTURAL_VERTICAL ) && ( pParticle1->IsFixed( ) == true || pParticle2->IsFixed( ) == true ) )
// {
// m_vSpringForce *= 2.0f; // <--- this is why I'm multiplying by dt^2, not dt^2 / 2
// }
// all working springs are structural horizontal or vertical
// SERGIY TEST: is the sign correct here? the cloth1 cloth uses spring force = -(HTerm+DTerm)*d
pos00 -= vIntegral * integ.flNodeWeight0;
pos01 += vIntegral * ( 1.0f - integ.flNodeWeight0 );
}
//----------------------------------------------------------------------------------------------------------
// for integrating accelerations into positions, pass dt*dt/2 for subsequent verlet integration;
// for integrating accelerations into velocities or velocities into positions, pass dt;
void CFeModel::IntegrateSprings( VectorAligned *pPos0, VectorAligned *pPos1, float flTimeStep, float flModelScale ) const
{
for ( int i = 0; i < m_nSpringIntegratorCount; ++i )
{
const FeSpringIntegrator_t &integ = m_pSpringIntegrator[ i ];
uint n0 = integ.nNode[ 0 ], n1 = integ.nNode[ 1 ];
VectorAligned &pos10 = pPos1[ n0 ], &pos11 = pPos1[ n1 ];
VectorAligned &pos00 = pPos0[ n0 ], &pos01 = pPos0[ n1 ];
IntegrateSpring( integ, flTimeStep, flModelScale, pos10, pos11, pos00, pos01 );
}
}
float CFeModel::ComputeElasticEnergyQuads( const VectorAligned *pPos, float flModelScale )const
{
float flStiffness = 1.0f, flSumEnergy = 0;
#define QUAD_ENERGY(FUNC) \
const FeQuad_t &quad = m_pQuads[ i ]; \
VectorAligned pos[ 4 ] = { pPos[ quad.nNode[ 0 ] ], pPos[ quad.nNode[ 1 ] ], pPos[ quad.nNode[ 2 ] ], pPos[ quad.nNode[ 3 ] ] }; \
FUNC( flStiffness, flModelScale, quad, pos[ 0 ], pos[ 1 ], pos[ 2 ], pos[ 3 ] ); \
for ( int j = 0; j < 4; ++j ) \
if ( m_pNodeInvMasses[ quad.nNode[ j ] ] > 0 ) \
flSumEnergy += ( pos[ j ] - pPos[ quad.nNode[ j ] ] ).LengthSqr( ) * 0.5f / m_pNodeInvMasses[ quad.nNode[ j ] ];
// first the simplest case of quads hanging on the edge 0-1: this is a 2D case with 2 moving nodes and only 1 DOF (rotation around edge 0-1)
for ( uint i = 0; i < m_nQuadCount[ 2 ]; ++i )
{
QUAD_ENERGY( RelaxQuad2 );
}
for ( uint i = m_nQuadCount[ 2 ]; i < m_nQuadCount[ 1 ]; ++i )
{
QUAD_ENERGY( RelaxQuad1 );
}
for ( uint i = m_nQuadCount[ 1 ]; i < m_nQuadCount[ 0 ]; ++i )
{
QUAD_ENERGY( RelaxQuad0 );
}
#undef QUAD_ENERGY
return flSumEnergy;
}
float CFeModel::ComputeElasticEnergyRods( const VectorAligned *pPos, float flModelScale )const
{
float flSumEnergy = 0;
//CMicroProfilerGuard mpg( &g_GlobalStats[ PhysicsGlobalProfiler::RelaxRods ], m_nRodCount - 1 );
for ( uint i = 0; i < m_nRodCount; ++i )
{
const FeRodConstraint_t &c = m_pRods[ i ];
uint n0 = c.nNode[ 0 ], n1 = c.nNode[ 1 ];
VectorAligned a = pPos[ n0 ], b = pPos[ n1 ];
RelaxRod( c, flModelScale, a, b );
if ( m_pNodeInvMasses[ n0 ] > 0 )
flSumEnergy += ( a - pPos[ n0 ] ).LengthSqr( ) * 0.5f / m_pNodeInvMasses[ n0 ];
if ( m_pNodeInvMasses[ n1 ] > 0 )
flSumEnergy += ( b - pPos[ n1 ] ).LengthSqr( ) * 0.5f / m_pNodeInvMasses[ n1 ];
}
return flSumEnergy;
}
float CFeModel::ComputeElasticEnergySprings( const VectorAligned *pPos0, const VectorAligned *pPos1, float flTimeStep, float flModelScale ) const
{
float flSumEnergy = 0, dtSqr = flTimeStep;
for ( int i = 0; i < m_nSpringIntegratorCount; ++i )
{
const FeSpringIntegrator_t &integ = m_pSpringIntegrator[ i ];
uint n0 = integ.nNode[ 0 ], n1 = integ.nNode[ 1 ];
VectorAligned pos10 = pPos1[ n0 ], pos11 = pPos1[ n1 ];
VectorAligned pos00 = pPos0[ n0 ], pos01 = pPos0[ n1 ];
IntegrateSpring( integ, flTimeStep, flModelScale, pos10, pos11, pos00, pos01 );
if ( m_pNodeInvMasses[ n0 ] > 0 )
flSumEnergy += ( pos10 - pPos1[ n0 ] ).LengthSqr( ) / ( m_pNodeInvMasses[ n0 ] * dtSqr );
if ( m_pNodeInvMasses[ n1 ] > 0)
flSumEnergy += ( pos11 - pPos1[ n1 ] ).LengthSqr( ) / ( m_pNodeInvMasses[ n1 ] * dtSqr );
}
return flSumEnergy;
}
int CFeModel::GetComplexity( )const
{
return
( m_nNodeCount - m_nStaticNodes ) * ( m_pNodeIntegrator ? 6 : 5 ) + m_nStaticNodes +
m_nSimdQuadCount[ 0 ] * 20 +
m_nSimdRodCount * 5 +
m_nSimdTriCount[ 0 ] * 10 +
m_nSimdSpringIntegratorCount * 3;
}
uint CFeModel::ComputeCollisionTreeDepthTopDown() const
{
CUtlVector< uint16 > levels;
return ComputeCollisionTreeDepthTopDown( levels );
}
uint CFeModel::ComputeCollisionTreeDepthTopDown( CUtlVector< uint16 > &levels ) const
{
if ( GetDynamicNodeCount() > 0 )
{
levels.SetCount( GetDynamicNodeCount() * 2 - 1 );
return ComputeCollisionTreeDepthTopDown( levels.Base() );
}
else
{
return 0;
}
}
uint CFeModel::ComputeCollisionTreeDepthTopDown( uint16 *pLevels ) const
{
if ( !m_pTreeChildren )
return 0;
const uint nDynCount = GetDynamicNodeCount();
const uint nInvalidLevel = 0xFFFF;
if ( IsDebug() )
{
for ( int nCluster = 2 * nDynCount - 1; nCluster-- > 0; )
{
pLevels[ nCluster ] = nInvalidLevel;
}
}
uint nLevel = 0;
pLevels[ 2 * nDynCount - 2 ] = nLevel; // top
for ( int nParent = nDynCount - 1; nParent-- > 0; )
{
const FeTreeChildren_t &children = m_pTreeChildren[ nParent ];
uint nNewLevel = 1 + pLevels[ nParent + nDynCount ];
pLevels[ children.nChild[ 0 ] ] = pLevels[ children.nChild[ 1 ] ] = nNewLevel;
nLevel = Max( nNewLevel, nLevel );
}
if ( IsDebug() )
{
// we should've filled out everything
for ( int nCluster = 2 * nDynCount - 1; nCluster-- > 0; )
{
Assert( pLevels[ nCluster ] != nInvalidLevel );
}
for ( uint i = 0; i < 2 * nDynCount - 2; ++i )
{
Assert( pLevels[ i ] == 1 + pLevels[ m_pTreeParents[ i ] ] );
}
}
return nLevel;
}
uint CFeModel::ComputeCollisionTreeHeightBottomUp( CUtlVector< uint16 > &levels ) const
{
if ( GetDynamicNodeCount() > 1 )
{
levels.SetCount( GetDynamicNodeCount() - 1 );
return ComputeCollisionTreeDepthTopDown( levels.Base() );
}
else
{
return 0;
}
}
uint CFeModel::ComputeCollisionTreeHeightBottomUp() const
{
CUtlVector< uint16 > levels;
return ComputeCollisionTreeHeightBottomUp( levels );
}
uint CFeModel::ComputeCollisionTreeHeightBottomUp( uint16 *pLevels ) const
{
if ( !m_pTreeParents )
return 0;
const uint nDynCount = GetDynamicNodeCount();
for ( uint i = nDynCount - 1; i-- > 0; )
{
pLevels[ i ] = 1; // all clusters have at least level 1
}
uint nLevel = 0;
for ( uint i = 0; i < nDynCount - 2; ++i )
{
AssertDbg( m_pTreeParents[ i + nDynCount ] > i + nDynCount && m_pTreeParents[ i + nDynCount ] < 2 * nDynCount - 1 ); // nodes and clusters go strictly child-to-parent
uint16 &refParentLevel = pLevels[ m_pTreeParents[ i + nDynCount ] - nDynCount ];
uint nChildLevel = pLevels[ i ];
refParentLevel = Max< uint >( refParentLevel, nChildLevel + 1 );
nLevel = Max< uint >( refParentLevel, nLevel );
}
if ( IsDebug() )
{
for ( uint i = nDynCount - 1; i-- > 0; )
{
const FeTreeChildren_t &tc = m_pTreeChildren[ i ];
uint nChildLevel[ 2 ];
for ( uint j = 0; j < 2; ++j )
{
nChildLevel[ j ] = tc.nChild[ j ] < nDynCount ? 0 : pLevels[ tc.nChild[ j ] - nDynCount ];
}
Assert( pLevels[ i ] == Max( nChildLevel[ 0 ], nChildLevel[ 1 ] ) + 1 );
}
}
return nLevel;
}
// Careful! pDynPos is the array of DYNAMIC nodes, excluding m_nStaticNodes static nodes at the beginning of the array (don't need static positions to compute dynamic collision)
// tree parents are defined for each dynamic node, and for each compound node. There are always nDynCount - 1 compound nodes. AABB is always defined only for compound nodes.
void CFeModel::ComputeCollisionTreeBounds( const fltx4 *pDynPos, FeAabb_t *pClusters )const
{
const uint nDynCount = GetDynamicNodeCount();
for ( uint i = nDynCount - 1; i-- > 0; )
{
pClusters[ i ].m_vMaxBounds = -Four_FLT_MAX;
pClusters[ i ].m_vMinBounds = Four_FLT_MAX;
}
if ( m_pNodeCollisionRadii )
{
for ( uint i = 0; i < nDynCount; ++i )
{
uint nParent = m_pTreeParents[ i ] - nDynCount;
Assert( nParent < nDynCount - 1 );
pClusters[ nParent ].AddCenterAndExtents( pDynPos[ i ], ReplicateX4( m_pNodeCollisionRadii[ i ] ) );
}
}
else
{
for ( uint i = 0; i < nDynCount; ++i )
{
uint nParent = m_pTreeParents[ i ] - nDynCount;
Assert( nParent < nDynCount - 1 );
pClusters[ nParent ] |= pDynPos[ i ];
}
}
for ( uint i = 0; i < nDynCount - 2; ++i )
{
uint nParent = m_pTreeParents[ i + nDynCount ] - nDynCount;
Assert( nParent < nDynCount - 1 );
pClusters[ nParent ] |= pClusters[ i ];
}
}
float CFeModel::ComputeCollisionTreeBoundsError( const fltx4 *pDynPos, const FeAabb_t *pClusters )const
{
const uint nDynCount = GetDynamicNodeCount();
fltx4 f4Error = Four_Zeros;
if ( m_pNodeCollisionRadii )
{
for ( uint i = 0; i < nDynCount; ++i )
{
uint nParent = m_pTreeParents[ i ] - nDynCount;
Assert( nParent < nDynCount - 1 );
f4Error = MaxSIMD( f4Error, pClusters[ nParent ].GetDistVector( pDynPos[ i ] - ReplicateX4( m_pNodeCollisionRadii[ i ] ) ) );
f4Error = MaxSIMD( f4Error, pClusters[ nParent ].GetDistVector( pDynPos[ i ] + ReplicateX4( m_pNodeCollisionRadii[ i ] ) ) );
}
}
else
{
for ( uint i = 0; i < nDynCount; ++i )
{
uint nParent = m_pTreeParents[ i ] - nDynCount;
Assert( nParent < nDynCount - 1 );
f4Error = MaxSIMD( f4Error, pClusters[ nParent ].GetDistVector( pDynPos[ i ] ) );
}
}
for ( uint i = 0; i < nDynCount - 2; ++i )
{
uint nParent = m_pTreeParents[ i + nDynCount ] - nDynCount;
Assert( nParent < nDynCount - 1 );
f4Error = MaxSIMD( f4Error, pClusters[ nParent ].GetDistVector( pClusters[ i ].m_vMinBounds ) );
f4Error = MaxSIMD( f4Error, pClusters[ nParent ].GetDistVector( pClusters[ i ].m_vMaxBounds ) );
}
return SubFloat( SqrtSIMD( Dot3SIMD( f4Error, f4Error ) ), 0 );
}
uint CFeModel::ComputeCollisionTreeNodeCount( uint16 *pCounts ) const
{
uint nDynCount = GetDynamicNodeCount();
for ( uint i = 0; i < nDynCount - 1; ++i )
{
pCounts[ i ] = 0;
}
for ( uint i = 0; i < nDynCount; ++i )
{
uint nParent = m_pTreeParents[ i ] - nDynCount;
AssertDbg( nParent < nDynCount - 1 );
pCounts[ nParent ]++;
}
for ( uint i = nDynCount; i < 2 * nDynCount - 2; ++i )
{
uint nParent = m_pTreeParents[ i ] - nDynCount;
AssertDbg( nParent < nDynCount - 1 );
pCounts[ nParent ] += pCounts[ i - nDynCount ];
}
AssertDbg( nDynCount == pCounts[ nDynCount - 2 ] );
return nDynCount;
}
void CFeModel::ApplyAirDrag( VectorAligned *pPos0, const VectorAligned *pPos1, float flExpDrag, float flVelDrag )
{
fltx4 f4ExpDrag = ReplicateX4( flExpDrag ), f4VelDrag = ReplicateX4( flVelDrag );
for ( uint i = m_nStaticNodes; i < m_nNodeCount; ++i )
{
fltx4 pos0 = LoadAlignedSIMD( pPos0[ i ].Base() ), pos1 = LoadAlignedSIMD( pPos1[ i ].Base() );
fltx4 dvdt = pos1 - pos0, lenSqr = Dot3SIMD( dvdt, dvdt );
fltx4 add = dvdt * MinSIMD( f4ExpDrag + f4VelDrag * SqrtEstSIMD( lenSqr ), Four_Ones );
StoreAlignedSIMD( pPos0[ i ].Base(), pos0 + add );
}
}
FORCEINLINE FourVectors ComputeQuadAirDrag( const FourVectors &vAn, const FourVectors &d, const fltx4 &f4ExpDrag, const fltx4 &f4VelDrag, const fltx4 &lenSqInvAn )
{
//return MinSIMD( AbsSIMD( DotProduct( vAxisZ, q0 - p0 ) * f4VelDrag ), Four_Ones ) * ( q0 - p0 );
fltx4 An_d = DotProduct( vAn, d );
return ( MinSIMD( Four_Ones, f4ExpDrag + AbsSIMD( An_d ) * f4VelDrag ) * An_d * lenSqInvAn ) * vAn;
}
void CFeModel::ApplyQuadAirDrag( fltx4 *pPos0, const fltx4 *pPos1, float flExpDrag, float flVelDrag )
{
fltx4 f4ExpDrag = ReplicateX4( flExpDrag ), f4VelDrag = ReplicateX4( flVelDrag );
for ( uint nQuad = m_nSimdQuadCount[ 1 ]; nQuad < m_nSimdQuadCount[ 0 ]; ++nQuad )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ nQuad ];
FourVectors p0, p1, p2, p3, q0, q1, q2, q3;
LOAD_NODES_POS( pPos0, p0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
LOAD_NODES_POS( pPos1, q0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos1, q1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos1, q2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos1, q3, quad.nNode[ 3 ] );
FourVectors vAn = CrossProduct( p2 - p0, p3 - p1);
fltx4 lenSqAn = MaxSIMD( vAn.LengthSqr(), Four_2ToTheMinus30 );
fltx4 lenSqInvAn = ReciprocalEstSIMD( lenSqAn );
// fltx4 f4ScaledVelDrag = f4VelDrag * f4AxisZlenInv;
// fltx4 f4AxisZlen = f4AxisZlenSqr * f4AxisZlenInv;
p0 += ComputeQuadAirDrag( vAn, q0 - p0, f4ExpDrag, f4VelDrag, lenSqInvAn );
p1 += ComputeQuadAirDrag( vAn, q1 - p1, f4ExpDrag, f4VelDrag, lenSqInvAn );
p2 += ComputeQuadAirDrag( vAn, q2 - p2, f4ExpDrag, f4VelDrag, lenSqInvAn );
p3 += ComputeQuadAirDrag( vAn, q3 - p3, f4ExpDrag, f4VelDrag, lenSqInvAn );
SAVE_NODES_POS( pPos0, p0, quad.nNode[ 0 ] );
SAVE_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
SAVE_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
SAVE_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
}
}
void CFeModel::ApplyRodAirDrag( fltx4 *pPos0, const fltx4 *pPos1, float flExpDrag, float flVelDrag )
{
}
void CFeModel::ApplyQuadWind( VectorAligned *pPos1, const Vector &vWind, float flAirDrag )
{
for ( uint nQuad = 0; nQuad < m_nQuadCount[ 0 ]; ++nQuad )
{
const FeQuad_t &quad = m_pQuads[ nQuad ];
VectorAligned &p0 = pPos1[ quad.nNode[ 0 ] ], &p1 = pPos1[ quad.nNode[ 1 ] ], &p2 = pPos1[ quad.nNode[ 2 ] ], &p3 = pPos1[ quad.nNode[ 3 ] ];
Vector vArea = CrossProduct( p2 - p0, p3 - p1 );
float flArea = vArea.Length();
Vector vNormal = vArea / flArea;
float flNormalFlow = DotProduct( vNormal, vWind );
Vector vNormalFlow = flNormalFlow * vNormal;
Vector vTangentFlow = vWind - vNormalFlow;
Vector vNormalImpulse = vArea * flNormalFlow;// == vNormalFlow * flArea;
Vector vTangentImpulse = ( flAirDrag * flArea ) * vTangentFlow;
Vector vQuadImpulse = vNormalImpulse + vTangentImpulse;
p0 += quad.vShape[ 0 ].w * vQuadImpulse;
p1 += quad.vShape[ 1 ].w * vQuadImpulse;
p2 += quad.vShape[ 2 ].w * vQuadImpulse;
p3 += quad.vShape[ 3 ].w * vQuadImpulse;
}
}
void CFeModel::SmoothQuadVelocityField( fltx4 *pPos0, const fltx4 *pPos1, float flBlendFactor )
{
fltx4 f4MulOriginal = ReplicateX4( flBlendFactor ), f4MulTarget = Four_Ones - f4MulOriginal, f4MulDoubleTarget = Four_PointFives * f4MulTarget;
// with 2 points fixed, the target velocity is controlled by them only (conservation of momentum in massive-light particle interaction, assuming the 2 top particles have the same mass)
for ( uint nQuad = 0; nQuad < m_nSimdQuadCount[ 2 ]; ++nQuad )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ nQuad ];
FourVectors p0, p1, p2, p3, q0, q1, q2, q3;
LOAD_NODES_POS( pPos0, p0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
LOAD_NODES_POS( pPos1, q0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos1, q1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos1, q2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos1, q3, quad.nNode[ 3 ] );
// note: this is negative velocitie (backward-time velocities)
FourVectors v0 = p0 - q0, v1 = p1 - q1, v2 = p2 - q2, v3 = p3 - q3;
FourVectors vPremulTarget = ( v0 + v1 ) * f4MulDoubleTarget;
p2 = q2 + v2 * f4MulOriginal + vPremulTarget;
p3 = q3 + v3 * f4MulOriginal + vPremulTarget;
SAVE_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
SAVE_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
}
// with 1 point fixed, the target velocity is that point's velocity (conservation of momentum in massive-light particle interaction)
for ( uint nQuad = m_nSimdQuadCount[ 2 ]; nQuad < m_nSimdQuadCount[ 1 ]; ++nQuad )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ nQuad ];
FourVectors p0, p1, p2, p3, q0, q1, q2, q3;
LOAD_NODES_POS( pPos0, p0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
LOAD_NODES_POS( pPos1, q0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos1, q1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos1, q2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos1, q3, quad.nNode[ 3 ] );
// note: this is negative velocitie (backward-time velocities)
FourVectors v0 = p0 - q0, v1 = p1 - q1, v2 = p2 - q2, v3 = p3 - q3;
FourVectors vPremulTarget = v0 * f4MulTarget;
p1 = q1 + v1 * f4MulOriginal + vPremulTarget;
p2 = q2 + v2 * f4MulOriginal + vPremulTarget;
p3 = q3 + v3 * f4MulOriginal + vPremulTarget;
SAVE_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
SAVE_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
SAVE_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
}
// with no points fixed, we're conserving momentum
for ( uint nQuad = m_nSimdQuadCount[ 1 ]; nQuad < m_nSimdQuadCount[ 0 ]; ++nQuad )
{
const FeSimdQuad_t &quad = m_pSimdQuads[ nQuad ];
FourVectors p0, p1, p2, p3, q0, q1, q2, q3;
LOAD_NODES_POS( pPos0, p0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
LOAD_NODES_POS( pPos1, q0, quad.nNode[ 0 ] );
LOAD_NODES_POS( pPos1, q1, quad.nNode[ 1 ] );
LOAD_NODES_POS( pPos1, q2, quad.nNode[ 2 ] );
LOAD_NODES_POS( pPos1, q3, quad.nNode[ 3 ] );
// note: this is negative velocitie (backward-time velocities)
FourVectors v0 = p0 - q0, v1 = p1 - q1, v2 = p2 - q2, v3 = p3 - q3;
const fltx4 *m = quad.f4Weights;
FourVectors vPremulTarget = ( v0 * m[ 0 ] + v1 * m[ 1 ] + v2 * m[ 2 ] + v3 * m[ 3 ] ) * f4MulTarget; // ~12 flops
p0 = q0 + v0 * f4MulOriginal + vPremulTarget;
p1 = q1 + v1 * f4MulOriginal + vPremulTarget;
p2 = q2 + v2 * f4MulOriginal + vPremulTarget;
p3 = q3 + v3 * f4MulOriginal + vPremulTarget;
SAVE_NODES_POS( pPos0, p0, quad.nNode[ 0 ] );
SAVE_NODES_POS( pPos0, p1, quad.nNode[ 1 ] );
SAVE_NODES_POS( pPos0, p2, quad.nNode[ 2 ] );
SAVE_NODES_POS( pPos0, p3, quad.nNode[ 3 ] );
}
}
void CFeModel::SmoothRodVelocityField( fltx4 *pPos0, const fltx4 *pPos1, float flBlendFactor )
{
fltx4 f4MulOriginal = ReplicateX4( flBlendFactor ), f4MulTarget = Four_Ones - f4MulOriginal;
for ( uint i = 0; i < m_nSimdRodCount; ++i )
{
const FeSimdRodConstraint_t &rod = m_pSimdRods[ i ];
AssertDbg(
rod.nNode[ 0 ][ 0 ] < m_nNodeCount && rod.nNode[ 1 ][ 0 ] < m_nNodeCount
&& rod.nNode[ 0 ][ 1 ] < m_nNodeCount && rod.nNode[ 1 ][ 1 ] < m_nNodeCount
&& rod.nNode[ 0 ][ 2 ] < m_nNodeCount && rod.nNode[ 1 ][ 2 ] < m_nNodeCount
&& rod.nNode[ 0 ][ 3 ] < m_nNodeCount && rod.nNode[ 1 ][ 3 ] < m_nNodeCount
);
FourVectors p0, p1, q0, q1;
LOAD_NODES_POS( pPos0, p0, rod.nNode[ 0 ] );
LOAD_NODES_POS( pPos0, p1, rod.nNode[ 1 ] );
LOAD_NODES_POS( pPos1, q0, rod.nNode[ 0 ] );
LOAD_NODES_POS( pPos1, q1, rod.nNode[ 1 ] );
// this is conservation of momentum principle, and it works directly with m1 = 1-w0 because: invM1/(invM0+invM1) == m0/(m0+m1)
FourVectors v0 = p0 - q0, v1 = p1 - q1;
FourVectors vPremulTarget = ( v0 + ( v1 - v0 ) * rod.f4Weight0 ) * f4MulTarget;// v0 * ( Four_Ones - rod.f4Weight0 ) + v1 * rod.f4Weight0;
p0 = q0 + v0 * f4MulOriginal + vPremulTarget;
p1 = q1 + v1 * f4MulOriginal + vPremulTarget;
SAVE_NODES_POS( pPos0, p0, rod.nNode[ 0 ] );
SAVE_NODES_POS( pPos0, p1, rod.nNode[ 1 ] );
}
}
SVD::SymMatrix3< float > AsSymMatrix3( const CovMatrix3 &cov )
{
SVD::SymMatrix3< float > sym;
sym.m00() = cov.m_vDiag.x;
sym.m11() = cov.m_vDiag.y;
sym.m22() = cov.m_vDiag.z;
sym.m01() = cov.m_flXY;
sym.m02() = cov.m_flXZ;
sym.m12() = cov.m_flYZ;
return sym;
}
matrix3x4_t AsMatrix3x4( SVD::Matrix3< float > &m, const Vector &vOrigin )
{
matrix3x4_t res;
for ( int i = 0; i < 3; ++i )
for ( int j = 0; j < 3; ++j )
res.m_flMatVal[ i ][ j ] = m.m[ i ][ j ];
res.SetOrigin( vOrigin );
return res;
}
matrix3x4_t AsMatrix3x4_Transposed( SVD::Matrix3< float > &m, const Vector &vOrigin )
{
matrix3x4_t res;
for ( int i = 0; i < 3; ++i )
for ( int j = 0; j < 3; ++j )
res.m_flMatVal[ i ][ j ] = m.m[ j ][ i ];
res.SetOrigin( vOrigin );
return res;
}
CovMatrix3 CovMatrix3::GetPseudoInverse()
{
SVD::SymMatrix3< float > sym = AsSymMatrix3( *this );
SVD::SymMatrix3< float > pi = SVD::PseudoInverse( sym );
CovMatrix3 covPi;
covPi.m_vDiag.x = pi.m00();
covPi.m_vDiag.y = pi.m11();
covPi.m_vDiag.z = pi.m22();
covPi.m_flXY = pi.m01();
covPi.m_flYZ = pi.m12();
covPi.m_flXZ = pi.m02();
return covPi;
}
void CFeModel::FitCenters( fltx4 *pPos )const
{
uint nMatrix = 0, nRunningWeight = 0;
for ( ; nMatrix < m_nFitMatrixCount[ 2 ]; ++nMatrix )
{
//AssertDbg( nRunningWeight == fm.nBegin );
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
AssertDbg( nRunningWeight + 2 <= fm.nEnd );
const FeFitWeight_t *w = m_pFitWeights + nRunningWeight;
// weights don't matter, because we'll just use the line between the two nodes as a hinge
pPos[ fm.nNode ] = Four_PointFives * ( pPos[ w[ 0 ].nNode ] + pPos[ w[ 1 ].nNode ] );
nRunningWeight = fm.nEnd;
}
for ( ; nMatrix < m_nFitMatrixCount[ 1 ]; ++nMatrix )
{
//AssertDbg( nRunningWeight == fm.nBegin );
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t &w = m_pFitWeights[ nRunningWeight ];
pPos[ fm.nNode ] = pPos[ w.nNode ];// we'll hinge around this point
nRunningWeight = fm.nEnd;
}
for ( ; nMatrix < m_nFitMatrixCount[ 0 ]; ++nMatrix )
{
//AssertDbg( nRunningWeight == fm.nBegin );
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *w = m_pFitWeights;
fltx4 v4Center = Four_Zeros;
fltx4 f4Sum = Four_Zeros;
uint nEndDominant = fm.nBeginDynamic == nRunningWeight ? fm.nEnd : fm.nBeginDynamic; // count only static nodes if there are any static nodes here
for ( ; nRunningWeight < nEndDominant; ++nRunningWeight )
{
fltx4 f4Weight = ReplicateX4( &w[ nRunningWeight ].flWeight );
f4Sum += f4Weight;
v4Center += pPos[ w[ nRunningWeight ].nNode ] * f4Weight;
}
pPos[ fm.nNode ] = v4Center * ReciprocalSIMD( f4Sum );// we'll hinge around this point
nRunningWeight = fm.nEnd; // go to the next
}
}
void CFeModel::FitTransforms( const VectorAligned *pPos, matrix3x4a_t *pOut )const
{
uint nMatrix = 0;
const FeFitWeight_t *pWeights = m_pFitWeights;
matrix3x4a_t tm;
/*
for ( ; nMatrix < m_nFitMatrixCount[ 2 ]; ++nMatrix )
{
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *pWeightsEnd = m_pFitWeights + fm.nEnd;
//Vector vAxis = pPos[ pWeights[ 0 ].nNode ] - pPos[ pWeights[ 1 ].nNode ];
FitTransform( &tm, fm, pPos, pWeights + 2, pWeightsEnd );
pOut[ fm.nCtrl ] = tm * TransformMatrix( fm.bone );
pWeights = pWeightsEnd;
}
for ( ; nMatrix < m_nFitMatrixCount[ 1 ]; ++nMatrix )
{
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *pWeightsEnd = m_pFitWeights + fm.nEnd;
AssertDbg( pWeights + 1 <= pWeightsEnd );
FitTransform( &tm, fm, pPos, pWeights + 1, pWeightsEnd );
pOut[ fm.nCtrl ] = tm * TransformMatrix( fm.bone );
pWeights = pWeightsEnd;
}
*/
for ( ; nMatrix < m_nFitMatrixCount[ 0 ]; ++nMatrix )
{
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *pWeightsEnd = m_pFitWeights + fm.nEnd;
FitTransform( &tm, fm, pPos, pWeights, pWeightsEnd );
pOut[ fm.nCtrl ] = tm * TransformMatrix( fm.bone );
pWeights = pWeightsEnd;
}
}
void CFeModel::FeedbackFitTransforms( VectorAligned *pPos, float flStiffness )const
{
uint nMatrix = 0;
matrix3x4a_t tm;
const FeFitWeight_t *pWeights = m_pFitWeights;
/*
for ( ; nMatrix < m_nFitMatrixCount[ 2 ]; ++nMatrix )
{
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *pWeightsEnd = m_pFitWeights + fm.nEnd;
FitTransform( &tm, fm, pPos, pWeights + 2, pWeightsEnd );
FeedbackFitTransform( tm, fm, pPos, m_pFitWeights + fm.nBeginDynamic, pWeightsEnd, flStiffness );
pWeights = pWeightsEnd;
}
for ( ; nMatrix < m_nFitMatrixCount[ 1 ]; ++nMatrix )
{
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *pWeightsEnd = m_pFitWeights + fm.nEnd;
AssertDbg( pWeights + 1 <= pWeightsEnd );
FitTransform( &tm, fm, pPos, pWeights + 1, pWeightsEnd );
FeedbackFitTransform( tm, fm, pPos, m_pFitWeights + fm.nBeginDynamic, pWeightsEnd, flStiffness );
pWeights = pWeightsEnd;
}
*/
for ( ; nMatrix < m_nFitMatrixCount[ 0 ]; ++nMatrix )
{
const FeFitMatrix_t &fm = m_pFitMatrices[ nMatrix ];
const FeFitWeight_t *pWeightsEnd = m_pFitWeights + fm.nEnd;
FitTransform( &tm, fm, pPos, pWeights, pWeightsEnd );
FeedbackFitTransform( tm, fm, pPos, m_pFitWeights + fm.nBeginDynamic, pWeightsEnd, flStiffness );
pWeights = pWeightsEnd;
}
}
Vector GetColumn( const SVD::Matrix3< float > &m, int c )
{
return Vector( m.m[ 0 ][ c ], m.m[ 1 ][ c ], m.m[ 2 ][ c ] );
}
void CFeModel::FeedbackFitTransform( const matrix3x4a_t &tm, const FeFitMatrix_t &fm, VectorAligned *pPos, const FeFitWeight_t *pWeights, const FeFitWeight_t *pWeightsEnd, float flStiffness )const
{
for ( const FeFitWeight_t *p = pWeights; p < pWeightsEnd; ++p )
{
VectorAligned &refPos = pPos[ p->nNode ];
Vector vQ = m_pInitPose[ p->nNode ].m_vPosition - fm.vCenter;
refPos = refPos * ( 1.0f - flStiffness ) + VectorTransform( vQ, tm ) * flStiffness;
}
}
void CFeModel::FitTransform( matrix3x4a_t*pOut, const FeFitMatrix_t &fm, const VectorAligned *pPos, const FeFitWeight_t *pWeights, const FeFitWeight_t *pWeightsEnd )const
{
const VectorAligned &vDynCenter = pPos[ fm.nNode ];
SVD::Matrix3<float> Apq;
Apq.SetZero();
for ( const FeFitWeight_t *p = pWeights; p < pWeightsEnd; ++p )
{
Vector vP = pPos[ p->nNode ] - vDynCenter, vQ = m_pInitPose[ p->nNode ].m_vPosition - fm.vCenter;
float m = p->flWeight;
for ( int i = 0; i < 3; ++i )
for ( int j = 0; j < 3; ++j )
Apq.m[ i ][ j ] += vP[ i ] * vQ[ j ] * m;
};
SVD::SvdIterator<float> svd;
//SVD::Matrix3<float> a = Apq * SVD::Matrix3<float>( AsSymMatrix3( fm.AqqInv ) ); // optimal but skewed fitting matrix; Aqq is not really necessary except when we clamp the singular values (allow some stretch)
svd.Init( Apq );
svd.Iterate( 6, FLT_EPSILON ); // the epsilon is the sum of squares of sines of Givens rotations, I think it's a very good measure of quality in this case
SVD::Matrix3< float > v = svd.ComputeV();
// B = US = AV
SVD::Matrix3< float > us = Apq * v;
// columns of U*S matrix have length of singular values. Clamp them, negate the smallest if needed (if the matrix is mirror matrix)
// TODO: measure and maybe rewrite with SIMD, as this seems slow
// sort by singular values
int nLarge, nMedium, nSmall;
float flSmallAxisParity;
if ( svd.ata.m00() > svd.ata.m11() )
{
if ( svd.ata.m11() > svd.ata.m22() )
{
nLarge = 0; nMedium = 1; nSmall = 2; flSmallAxisParity = 1.0f;
}
else if ( svd.ata.m22() > svd.ata.m00() )
{
nLarge = 2; nMedium = 0; nSmall = 1; flSmallAxisParity = 1.0f;
}
else
{
nLarge = 0; nMedium = 2; nSmall = 1; flSmallAxisParity = -1.0f;
}
}
else
{
if ( svd.ata.m00() > svd.ata.m22() )
{
nLarge = 1; nMedium = 0; nSmall = 2; flSmallAxisParity = -1.0f;
}
else if ( svd.ata.m22() > svd.ata.m11() )
{
nLarge = 2; nMedium = 1; nSmall = 0; flSmallAxisParity = -1.0f;
}
else
{
nLarge = 1; nMedium = 2; nSmall = 0; flSmallAxisParity = 1.0f;
}
}
#if defined( _DEBUG ) && defined( DBGFLAG_ASSERT )
float s[ 3 ] = { svd.ata.m00(), svd.ata.m11(), svd.ata.m22() };
Assert( s[ nLarge ] >= s[ nMedium ] && s[ nMedium ] >= s[ nSmall ] );
#endif
matrix3x4a_t u;
Vector vMainAxis( us.m[0][nLarge], us.m[1][nLarge], us.m[2][nLarge ] );
float flMainAxisLength = vMainAxis.Length();
if ( flMainAxisLength < FLT_EPSILON )
{
// really, really small - very undefined matrix
pOut->SetToIdentity();
return;
}
vMainAxis /= flMainAxisLength;
u.SetColumn( vMainAxis, ( MatrixAxisType_t )nLarge );
Vector vMedAxis( us.m[0][nMedium], us.m[1][nMedium], us.m[2][nMedium ] );
vMedAxis -= DotProduct( vMainAxis, vMedAxis ) * vMainAxis;
float flMedAxisLength = vMedAxis.Length();
if( flMedAxisLength > FLT_EPSILON)
{
vMedAxis /= flMedAxisLength;
}
else
{
// fallback
vMedAxis = VectorPerpendicularToVector(vMainAxis);
}
u.SetColumn(vMedAxis, ( MatrixAxisType_t)nMedium );
// the third axis doesn't matter if we don't clamp, and we can only recover it up to the sign
u.SetColumn( CrossProduct( vMainAxis, vMedAxis ) * flSmallAxisParity, ( MatrixAxisType_t )nSmall );
u.SetOrigin( vec3_origin );
#if 0
// U matrix columns are the axes we need; some of them will be Zero
fltx4 uRow0 = LoadUnaligned3SIMD( us.m[ 0 ] );
fltx4 uRow1 = LoadUnaligned3SIMD( us.m[ 1 ] );
fltx4 uRow2 = LoadUnaligned3SIMD( us.m[ 2 ] );
fltx4 uLengthSqr = uRow0 * uRow0 + uRow1 * uRow1 + uRow2 * uRow2;
fltx4 uLengthInv = ReciprocalSqrtSIMD( MaxSIMD( uLengthSqr, Four_Epsilons ) );
// When/if we support non-orthouniform animation matrices, we should clamp the length, not set it to 1. That will make for much nicer softbody
matrix3x4a_t u;
u.SIMDRow( 0 ) = /*SetWToZeroSIMD*/( uRow0 * uLengthInv );
u.SIMDRow( 1 ) = /*SetWToZeroSIMD*/( uRow1 * uLengthInv );
u.SIMDRow( 2 ) = /*SetWToZeroSIMD*/( uRow2 * uLengthInv );
#ifdef _DEBUG
fltx4 sSqr = { svd.ata.m00(), svd.ata.m11(), svd.ata.m22(), 0.0f }; NOTE_UNUSED( sSqr );
fltx4 sInv = ReciprocalSqrtSIMD( MaxSIMD( sSqr, Four_Epsilons ) ); NOTE_UNUSED( sInv );// reciprocal estimate is actually fine here, but may produce slightly non-orthonormal matrices (error < 1%) which assert in a bunch of places
// ( SVD::RsqrtEst( Max( us.ColLenSqr( 0 ), 1e-30f ) ), SVD::RsqrtEst( Max( us.ColLenSqr( 1 ), 1e-30f ) ), SVD::RsqrtEst( Max( us.ColLenSqr( 2 ), 1e-30f ) ) );
// TODO: implement QR or some other recovery from near-0 singular values
#endif
float det = u.GetDeterminant();
if ( det < 0 )
{
// this is a mirror matrix;
int nColumn = AsVector( uLengthSqr ).SmallestComponent(); //smallest sigma, largest sigma^-1
u[ 0 ][ nColumn ] = -u[ 0 ][ nColumn ];
u[ 1 ][ nColumn ] = -u[ 1 ][ nColumn ];
u[ 2 ][ nColumn ] = -u[ 2 ][ nColumn ];
}
#endif
matrix3x4_t vt = AsMatrix3x4_Transposed( v, vec3_origin ), uvt;
ConcatTransforms( u, vt, uvt );
uvt.SetOrigin( vDynCenter );
*pOut = uvt;
}
void CFeModel::FitTransform2D( matrix3x4a_t *pOut, const FeFitMatrix_t &fm, const Vector &vAxis, const VectorAligned *pPos, const FeFitWeight_t *pWeights, const FeFitWeight_t *pWeightsEnd )const
{
FitTransform( pOut, fm, pPos, pWeights, pWeightsEnd );
/*
const VectorAligned &vDynCenter = pPos[ fm.nNode ];
class CFitTransform2D
{
public:
CFitTransform2D( const Vector &vAxis )
{
m_vAxisZ = vAxis.NormalizedSafe( Vector( 0, 0, 1 ) );
m_vAxisX = VectorPerpendicularToVector( m_vAxisZ );
m_vAxisY = CrossProduct( m_vAxisZ, m_vAxisX );
}
Vector2D ToLocalXY( const Vector &v ) { return Vector2D( DotProduct( m_vAxisX, v ), DotProduct( m_vAxisY, v ) ); }
Vector m_vAxisX;
Vector m_vAxisY;
Vector m_vAxisZ;
};
CFitTransform2D fitTm( vAxis );
float flCos = 0, flSin = 0;
for ( const FeFitWeight_t *p = pWeights; p < pWeightsEnd; ++p )
{
Vector2D p = fitTm.ToLocalXY( pPos[ p->nNode ] - vDynCenter ), q = fitTm.ToLocalXY( m_pInitPose[ p->nNode ].m_vPosition - fm.vCenter );
}
CSinCosRotation2D rotation( flCos, flSin );
*/
}