From a62788fb9fcf33eac30061a93b1ba119c12e2233 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Mon, 23 Jan 2006 05:48:40 +0000 Subject: [PATCH] Split out tween weighting functions. Add tweening from 1d and 2d bezier curves. The "bouncebegin" and "bounceend" tweens are now implemented in Lua, using 2D beziers which approximate the old behavior very closely. Added "ease" tween, which works like Flash's. Custom tweens can be used, passing in a table of 4 values for a 1D bezier, or 8 values for 2D. tween,.5,TweenBezier,{ 0, p1, p2, 1 } tween,.5,TweenBezier,{ 0, 0, x1, y1, x2, y2, 1, 1 } As with all tweens, the first parameter is the duration. The 1D bezier { a, b, c, d } is equivalent to the 2D bezier { 0, a, 1/3, b, 2/3, c, 1, d }, but a 1D bezier is more efficient. --- stepmania/src/Actor.cpp | 93 +++++++++--------- stepmania/src/Actor.h | 19 ++-- stepmania/src/RageMath.cpp | 63 +++++++++++- stepmania/src/RageMath.h | 33 ++++++- stepmania/src/Tween.cpp | 192 +++++++++++++++++++++++++++++++++++++ stepmania/src/Tween.h | 54 +++++++++++ 6 files changed, 396 insertions(+), 58 deletions(-) create mode 100644 stepmania/src/Tween.cpp create mode 100644 stepmania/src/Tween.h diff --git a/stepmania/src/Actor.cpp b/stepmania/src/Actor.cpp index b680b094f5..f337e79a67 100644 --- a/stepmania/src/Actor.cpp +++ b/stepmania/src/Actor.cpp @@ -622,39 +622,7 @@ void Actor::UpdateTweening( float fDeltaTime ) const float fPercentThroughTween = 1-(TI.m_fTimeLeftInTween / TI.m_fTweenTime); // distort the percentage if appropriate - float fPercentAlongPath = 0.f; - switch( TI.m_TweenType ) - { - case TWEEN_LINEAR: fPercentAlongPath = fPercentThroughTween; break; - case TWEEN_ACCELERATE: fPercentAlongPath = fPercentThroughTween * fPercentThroughTween; break; - case TWEEN_DECELERATE: fPercentAlongPath = 1 - (1-fPercentThroughTween) * (1-fPercentThroughTween); break; - case TWEEN_SMOOTH: - { - /* Accelerate, reaching full speed at fShift, then decelerate the rest of - * the way. fShift = 1 degrades to TWEEN_ACCELERATE. fShift = 0 degrades - * to TWEEN_DECELERATE. (This is a rough approximation of a sigmoid.) */ - const float fShift = 0.5f; - if( fPercentThroughTween < fShift ) - { - fPercentAlongPath = SCALE( fPercentThroughTween, 0.0f, fShift, 0.0f, 1.0f ); - fPercentAlongPath = fPercentAlongPath * fPercentAlongPath; - fPercentAlongPath = SCALE( fPercentAlongPath, 0.0f, 1.0f, 0.0f, fShift ); - } - else - { - fPercentAlongPath = SCALE( fPercentThroughTween, fShift, 1.0f, 0.0f, 1.0f ); - fPercentAlongPath = 1 - (1-fPercentAlongPath) * (1-fPercentAlongPath); - fPercentAlongPath = SCALE( fPercentAlongPath, 0.0f, 1.0f, fShift, 1.0f ); - } - break; - } - - case TWEEN_BOUNCE_BEGIN:fPercentAlongPath = 1 - RageFastSin( 1.1f + fPercentThroughTween*(PI-1.1f) ) / 0.89f; break; - case TWEEN_BOUNCE_END: fPercentAlongPath = RageFastSin( 1.1f + (1-fPercentThroughTween)*(PI-1.1f) ) / 0.89f; break; - case TWEEN_SPRING: fPercentAlongPath = 1 - RageFastCos( fPercentThroughTween*PI*2.5f )/(1+fPercentThroughTween*3); break; - default: ASSERT(0); - } - + float fPercentAlongPath = TI.m_pTween->Tween( fPercentThroughTween ); TweenState::MakeWeightedAverage( m_current, m_start, TS, fPercentAlongPath ); } } @@ -734,7 +702,7 @@ void Actor::UpdateInternal( float fDeltaTime ) UpdateTweening( fDeltaTime ); } -void Actor::BeginTweening( float time, TweenType tt ) +void Actor::BeginTweening( float time, ITween *pTween ) { ASSERT( time >= 0 ); @@ -769,11 +737,17 @@ void Actor::BeginTweening( float time, TweenType tt ) TS = m_current; } - TI.m_TweenType = tt; + TI.m_pTween = pTween; TI.m_fTweenTime = time; TI.m_fTimeLeftInTween = time; } +void Actor::BeginTweening( float time, TweenType tt ) +{ + ITween *pTween = ITween::CreateFromType( tt ); + BeginTweening( time, pTween ); +} + void Actor::StopTweening() { for( unsigned i = 0; i < m_Tweens.size(); ++i ) @@ -1316,6 +1290,32 @@ void Actor::SubscribeToMessage( Message message ) m_vsSubscribedTo.push_back( MessageToString(message) ); } +Actor::TweenInfo::TweenInfo() +{ + m_pTween = NULL; +} + +Actor::TweenInfo::~TweenInfo() +{ + delete m_pTween; +} + +Actor::TweenInfo::TweenInfo( const TweenInfo &cpy ) +{ + m_pTween = NULL; + *this = cpy; +} + +Actor::TweenInfo &Actor::TweenInfo::operator=( const TweenInfo &rhs ) +{ + delete m_pTween; + m_pTween = (rhs.m_pTween? m_pTween->Copy():NULL); + m_fTimeLeftInTween = rhs.m_fTimeLeftInTween; + m_fTweenTime = rhs.m_fTweenTime; + m_sCommandName = rhs.m_sCommandName; + return *this; +} + // lua start #include "LuaBinding.h" @@ -1325,14 +1325,18 @@ public: LunaActor() { LUA->Register( Register ); } static int sleep( T* p, lua_State *L ) { p->Sleep(FArg(1)); return 0; } - static int linear( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_LINEAR); return 0; } - static int accelerate( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_ACCELERATE); return 0; } - static int decelerate( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_DECELERATE); return 0; } - static int smooth( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_SMOOTH); return 0; } - static int bouncebegin( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_BOUNCE_BEGIN); return 0; } - static int bounceend( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_BOUNCE_END); return 0; } - static int spring( T* p, lua_State *L ) { p->BeginTweening(FArg(1),Actor::TWEEN_SPRING); return 0; } - static int stoptweening( T* p, lua_State *L ) { p->StopTweening(); p->BeginTweening( 0.0001f, Actor::TWEEN_LINEAR ); return 0; } + static int linear( T* p, lua_State *L ) { p->BeginTweening(FArg(1),TWEEN_LINEAR); return 0; } + static int accelerate( T* p, lua_State *L ) { p->BeginTweening(FArg(1),TWEEN_ACCELERATE); return 0; } + static int decelerate( T* p, lua_State *L ) { p->BeginTweening(FArg(1),TWEEN_DECELERATE); return 0; } + static int smooth( T* p, lua_State *L ) { p->BeginTweening(FArg(1),TWEEN_SMOOTH); return 0; } + static int spring( T* p, lua_State *L ) { p->BeginTweening(FArg(1),TWEEN_SPRING); return 0; } + static int tween( T* p, lua_State *L ) + { + ITween *pTween = ITween::CreateFromStack( L, 2 ); + p->BeginTweening( FArg(1), pTween ); + return 0; + } + static int stoptweening( T* p, lua_State *L ) { p->StopTweening(); p->BeginTweening( 0.0001f, TWEEN_LINEAR ); return 0; } static int finishtweening( T* p, lua_State *L ) { p->FinishTweening(); return 0; } static int hurrytweening( T* p, lua_State *L ) { p->HurryTweening(FArg(1)); return 0; } static int x( T* p, lua_State *L ) { p->SetX(FArg(1)); return 0; } @@ -1466,9 +1470,8 @@ public: ADD_METHOD( accelerate ); ADD_METHOD( decelerate ); ADD_METHOD( smooth ); - ADD_METHOD( bouncebegin ); - ADD_METHOD( bounceend ); ADD_METHOD( spring ); + ADD_METHOD( tween ); ADD_METHOD( stoptweening ); ADD_METHOD( finishtweening ); ADD_METHOD( hurrytweening ); diff --git a/stepmania/src/Actor.h b/stepmania/src/Actor.h index 18b4a184de..e28be95cb9 100644 --- a/stepmania/src/Actor.h +++ b/stepmania/src/Actor.h @@ -12,6 +12,7 @@ struct lua_State; class LuaReference; class LuaClass; #include "MessageManager.h" +#include "Tween.h" #define DRAW_ORDER_BEFORE_EVERYTHING -200 @@ -21,7 +22,6 @@ class LuaClass; #define DRAW_ORDER_TRANSITIONS +110 #define DRAW_ORDER_AFTER_EVERYTHING +200 - class Actor : public IMessageSubscriber { public: @@ -36,15 +36,6 @@ public: static void SetBGMTime( float fTime, float fBeat ); static void SetBGMLight( int iLightNumber, float fCabinetLights ); - enum TweenType { - TWEEN_LINEAR, - TWEEN_ACCELERATE, - TWEEN_DECELERATE, - TWEEN_SMOOTH, - TWEEN_BOUNCE_BEGIN, - TWEEN_BOUNCE_END, - TWEEN_SPRING, - }; enum Effect { no_effect, effect_lua, diffuse_blink, diffuse_shift, diffuse_ramp, glow_blink, glow_shift, @@ -199,6 +190,7 @@ public: void SetAux( float f ) { DestTweenState().aux = f; } float GetAux() const { return m_current.aux; } + void BeginTweening( float time, ITween *pInterp ); void BeginTweening( float time, TweenType tt = TWEEN_LINEAR ); void StopTweening(); void Sleep( float time ); @@ -376,7 +368,12 @@ protected: struct TweenInfo { // counters for tweening - TweenType m_TweenType; + TweenInfo(); + ~TweenInfo(); + TweenInfo( const TweenInfo &cpy ); + TweenInfo &operator=( const TweenInfo &rhs ); + + ITween *m_pTween; float m_fTimeLeftInTween; // how far into the tween are we? float m_fTweenTime; // seconds between Start and End positions/zooms RString m_sCommandName; // command to execute when this TweenState goes into effect diff --git a/stepmania/src/RageMath.cpp b/stepmania/src/RageMath.cpp index 3e2fb3e815..127537865c 100644 --- a/stepmania/src/RageMath.cpp +++ b/stepmania/src/RageMath.cpp @@ -646,8 +646,69 @@ float RageFastCos( float x ) return RageFastSin( x + 0.5f*PI ); } +float RageQuadradtic::Evaluate( float fT ) const +{ + // optimized (m_fA * fT*fT*fT) + (m_fB * fT*fT) + (m_fC * fT) + m_fD; + return ((m_fA*fT + m_fB)*fT + m_fC)*fT + m_fD; +} + +void RageQuadradtic::SetFromBezier( float fX1, float fX2, float fX3, float fX4 ) +{ + m_fD = fX1; + m_fC = 3.0f * (fX2 - fX1); + m_fB = 3.0f * (fX3 - fX2) - m_fC; + m_fA = fX4 - fX1 - m_fC - m_fB; +} + +void RageQuadradtic::GetBezier( float &fX1, float &fX2, float &fX3, float &fX4 ) const +{ + fX1 = m_fD; + fX2 = m_fD + m_fC/3.0f; + fX3 = m_fD + 2*m_fC/3.0f + m_fB/3.0f; + fX4 = m_fD + m_fC + m_fB + m_fA; +} + +float RageQuadradtic::GetSlope( float fT ) const +{ + return 3*m_fA*fT*fT + 2*m_fB*fT + m_fC; +} + +void RageBezier2D::Evaluate( float fT, float *pX, float *pY ) const +{ + *pX = m_X.Evaluate( fT ); + *pY = m_Y.Evaluate( fT ); +} + +float RageBezier2D::EvaluateYFromX( float fX ) const +{ + /* Quickly approximate T using Newton-Raphelson successive optimization (see + * http://www.tinaja.com/text/bezmath.html). This usually finds T within an + * acceptable error margin in a few steps. */ + float fT = SCALE( fX, m_X.GetBezierStart(), m_X.GetBezierEnd(), 0, 1 ); + while(1) + { + float fGuessedX = m_X.Evaluate( fT ); + float fError = fX-fGuessedX; + + /* If our guess is good enough, evaluate the result Y and return. */ + if( unlikely(fabsf(fError) < 0.0001f) ) + return m_Y.Evaluate( fT ); + + float fSlope = m_X.GetSlope( fT ); + fT += fError/fSlope; + } +} + +void RageBezier2D::SetFromBezier( + float fC1X, float fC1Y, float fC2X, float fC2Y, + float fC3X, float fC3Y, float fC4X, float fC4Y ) +{ + m_X.SetFromBezier( fC1X, fC2X, fC3X, fC4X ); + m_Y.SetFromBezier( fC1Y, fC2Y, fC3Y, fC4Y ); +} + /* - * Copyright (c) 2001-2003 Chris Danford + * Copyright (c) 2001-2006 Chris Danford, Glenn Maynard * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/stepmania/src/RageMath.h b/stepmania/src/RageMath.h index 6fccb00a35..17232d6c9d 100644 --- a/stepmania/src/RageMath.h +++ b/stepmania/src/RageMath.h @@ -51,10 +51,41 @@ void RageMatrixTranspose( RageMatrix* pOut, const RageMatrix* pIn ); float RageFastSin( float x ) CONST_FUNCTION; float RageFastCos( float x ) CONST_FUNCTION; +class RageQuadradtic +{ +public: + void SetFromBezier( float fC1, float fC2, float fC3, float fC4 ); + void GetBezier( float &fC1, float &fC2, float &fC3, float &fC4 ) const; + + float Evaluate( float fT ) const; + float GetSlope( float fT ) const; + + /* Equivalent to Evaluate(0.0f) and Evaluate(1.0f), but faster: */ + float GetBezierStart() const { return m_fD; } + float GetBezierEnd() const { return m_fA + m_fB + m_fC + m_fD; } + +private: + float m_fA, m_fB, m_fC, m_fD; +}; + +class RageBezier2D +{ +public: + void SetFromBezier( float fC1X, float fC2X, float fC3X, float fC4X, + float fC1Y, float fC2Y, float fC3Y, float fC4Y ); + + void Evaluate( float fT, float *pX, float *pY ) const; + float EvaluateYFromX( float fX ) const; + +private: + RageQuadradtic m_X; + RageQuadradtic m_Y; +}; + #endif /* - * Copyright (c) 2001-2003 Chris Danford + * Copyright (c) 2001-2006 Chris Danford, Glenn Maynard * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/stepmania/src/Tween.cpp b/stepmania/src/Tween.cpp new file mode 100644 index 0000000000..41504bd7cb --- /dev/null +++ b/stepmania/src/Tween.cpp @@ -0,0 +1,192 @@ +#include "global.h" +#include "Tween.h" +#include "RageUtil.h" +#include "RageMath.h" +#include "LuaManager.h" +#include "EnumHelper.h" + +static const char *TweenTypeNames[] = { + "Linear", + "Accelerate", + "Decelerate", + "Smooth", + "Spring", + "Bezier" +}; +XToString( TweenType, NUM_TweenType ); + +static void LuaTweenType(lua_State* L) +{ + FOREACH_TweenType( tt ) + { + RString s = TweenTypeToString( tt ); + LUA->SetGlobal( "Tween"+s, tt ); + } +} +REGISTER_WITH_LUA_FUNCTION( LuaTweenType ); + + +struct TweenLinear: public ITween +{ + float Tween( float f ) const { return f; } + ITween *Copy() const { return new TweenLinear(*this); } +}; +struct TweenAccelerate: public ITween +{ + float Tween( float f ) const { return f*f; } + ITween *Copy() const { return new TweenAccelerate(*this); } +}; +struct TweenDecelerate: public ITween +{ + float Tween( float f ) const { return 1 - (1-f) * (1-f); } + ITween *Copy() const { return new TweenDecelerate(*this); } +}; +struct TweenSmooth: public ITween +{ + float Tween( float f ) const + { + /* Accelerate, reaching full speed at fShift, then decelerate the rest of + * the way. fShift = 1 degrades to TWEEN_ACCELERATE. fShift = 0 degrades + * to TWEEN_DECELERATE. (This is a rough approximation of a sigmoid.) */ + const float fShift = 0.5f; + if( f < fShift ) + { + f = SCALE( f, 0.0f, fShift, 0.0f, 1.0f ); + f = f * f; + f = SCALE( f, 0.0f, 1.0f, 0.0f, fShift ); + } + else + { + f = SCALE( f, fShift, 1.0f, 0.0f, 1.0f ); + f = 1 - (1-f) * (1-f); + f = SCALE( f, 0.0f, 1.0f, fShift, 1.0f ); + } + + return f; + } + + ITween *Copy() const { return new TweenSmooth(*this); } +}; + +struct TweenSpring: public ITween +{ + float Tween( float f ) const { return 1 - RageFastCos( f*PI*2.5f )/(1+f*3); } + ITween *Copy() const { return new TweenSpring(*this); } +}; + + +/* + * Interpolation with 1-dimensional cubic Bezier curves. + */ +struct InterpolateBezier1D: public ITween +{ + float Tween( float f ) const; + ITween *Copy() const { return new InterpolateBezier1D(*this); } + + RageQuadradtic m_Bezier; +}; + +float InterpolateBezier1D::Tween( float f ) const +{ + return m_Bezier.Evaluate( f ); +} + +/* + * Interpolation with 2-dimensional cubic Bezier curves. + */ +struct InterpolateBezier2D: public ITween +{ + float Tween( float f ) const; + ITween *Copy() const { return new InterpolateBezier2D(*this); } + + RageBezier2D m_Bezier; +}; + +float InterpolateBezier2D::Tween( float f ) const +{ + return m_Bezier.EvaluateYFromX( f ); +} + +/* This interpolator combines multiple other interpolators, to allow + * generating more complex tweens. For example, "compound,10,linear,0.25,accelerate,0.75" + * means 25% of the tween is linear, and the rest is accelerate. This can be + * used with Bezier to create spline tweens. */ +// InterpolateCompound + +ITween *ITween::CreateFromType( TweenType tt ) +{ + switch( tt ) + { + case TWEEN_LINEAR: return new TweenLinear; + case TWEEN_ACCELERATE: return new TweenAccelerate; + case TWEEN_DECELERATE: return new TweenDecelerate; + case TWEEN_SMOOTH: return new TweenSmooth; + + /* + * These may actually be faster than InterpolateBounceBegin/InterpolateBounceEnd, since + * they don't use RageFastSin/RageFastCos. + */ + case TWEEN_SPRING: return new TweenSpring; + default: ASSERT(0); + } +} + +ITween *ITween::CreateFromStack( Lua *L, int iStackPos ) +{ + TweenType iType = (TweenType) IArg(iStackPos); + if( iType == TWEEN_BEZIER ) + { + luaL_checktype( L, iStackPos+1, LUA_TTABLE ); + int iArgs = luaL_getn( L, iStackPos+1 ); + if( iArgs != 4 && iArgs != 8 ) + RageException::Throw( "CreateFromStack: table argument must have 4 or 8 entries" ); + + float fC[8]; + for( int i = 0; i < iArgs; ++i ) + { + lua_rawgeti( L, iStackPos+1, i+1 ); + fC[i] = (float) lua_tonumber( L, -1 ); + } + + lua_pop( L, iArgs ); + if( iArgs == 4 ) + { + InterpolateBezier1D *pBezier = new InterpolateBezier1D; + pBezier->m_Bezier.SetFromBezier( fC[0], fC[1], fC[2], fC[3] ); + return pBezier; + } + else if( iArgs == 8 ) + { + InterpolateBezier2D *pBezier = new InterpolateBezier2D; + pBezier->m_Bezier.SetFromBezier( fC[0], fC[1], fC[2], fC[3], fC[4], fC[5], fC[6], fC[7] ); + return pBezier; + } + } + + return CreateFromType( iType ); +} + +/* + * (c) 2001-2006 Glenn Maynard, Chris Danford + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/stepmania/src/Tween.h b/stepmania/src/Tween.h new file mode 100644 index 0000000000..02249605a4 --- /dev/null +++ b/stepmania/src/Tween.h @@ -0,0 +1,54 @@ +#ifndef INTERPOLATE_H +#define INTERPOLATE_H + +struct lua_State; +typedef lua_State Lua; + +enum TweenType +{ + TWEEN_LINEAR, + TWEEN_ACCELERATE, + TWEEN_DECELERATE, + TWEEN_SMOOTH, + TWEEN_SPRING, + TWEEN_BEZIER, + NUM_TweenType +}; +#define FOREACH_TweenType( tt ) FOREACH_ENUM( TweenType, NUM_TweenType, tt ) + +class ITween +{ +public: + virtual float Tween( float f ) const = 0; + virtual ITween *Copy() const = 0; + + static ITween *CreateFromType( TweenType iType ); + static ITween *CreateFromStack( Lua *L, int iStackPos ); +}; + +#endif + +/* + * (c) 2006 Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */