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
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 );
|
|
*/
|
|
}
|
|
|