void RageMatrixAngles( RageMatrix* pOut, const RageVector3 &angles ) { const RageVector3 angles_radians( angles * 2*PI / 360 ); const float sy = RageFastSin( angles_radians[2] ); const float cy = RageFastCos( angles_radians[2] ); const float sp = RageFastSin( angles_radians[1] ); const float cp = RageFastCos( angles_radians[1] ); const float sr = RageFastSin( angles_radians[0] ); const float cr = RageFastCos( angles_radians[0] ); RageMatrixIdentity( pOut ); // matrix = (Z * Y) * X pOut->m[0][0] = cp*cy; pOut->m[0][1] = cp*sy; pOut->m[0][2] = -sp; pOut->m[1][0] = sr*sp*cy+cr*-sy; pOut->m[1][1] = sr*sp*sy+cr*cy; pOut->m[1][2] = sr*cp; pOut->m[2][0] = (cr*sp*cy+-sr*-sy); pOut->m[2][1] = (cr*sp*sy+-sr*cy); pOut->m[2][2] = cr*cp; }
/* Return RageMatrixRotationX(rX) * RageMatrixRotationY(rY) * RageMatrixRotationZ(rZ) * quickly (without actually doing two complete matrix multiplies), by removing the * parts of the matrix multiplies that we know will be 0. */ void RageMatrixRotationXYZ( RageMatrix* pOut, float rX, float rY, float rZ ) { rX *= PI/180; rY *= PI/180; rZ *= PI/180; const float cX = RageFastCos(rX); const float sX = RageFastSin(rX); const float cY = RageFastCos(rY); const float sY = RageFastSin(rY); const float cZ = RageFastCos(rZ); const float sZ = RageFastSin(rZ); /* * X*Y: * RageMatrix( * cY, sY*sX, sY*cX, 0, * 0, cX, -sX, 0, * -sY, cY*sX, cY*cX, 0, * 0, 0, 0, 1 * ); * * X*Y*Z: * * RageMatrix( * cZ*cY, cZ*sY*sX+sZ*cX, cZ*sY*cX+sZ*(-sX), 0, * (-sZ)*cY, (-sZ)*sY*sX+cZ*cX, (-sZ)*sY*cX+cZ*(-sX), 0, * -sY, cY*sX, cY*cX, 0, * 0, 0, 0, 1 * ); */ pOut->m00 = cZ*cY; pOut->m01 = cZ*sY*sX+sZ*cX; pOut->m02 = cZ*sY*cX+sZ*(-sX); pOut->m03 = 0; pOut->m10 = (-sZ)*cY; pOut->m11 = (-sZ)*sY*sX+cZ*cX; pOut->m12 = (-sZ)*sY*cX+cZ*(-sX); pOut->m13 = 0; pOut->m20 = -sY; pOut->m21 = cY*sX; pOut->m22 = cY*cX; pOut->m23 = 0; pOut->m30 = 0; pOut->m31 = 0; pOut->m32 = 0; pOut->m33 = 1; }
float ArrowEffects::GetYPos( const PlayerState* pPlayerState, int iCol, float fYOffset, float fYReverseOffsetPixels, bool WithReverse ) { float f = fYOffset; if( WithReverse ) { float fShift, fScale; ArrowGetReverseShiftAndScale( pPlayerState, iCol, fYReverseOffsetPixels, fShift, fScale ); f *= fScale; f += fShift; } const float* fEffects = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects; if( fEffects[PlayerOptions::EFFECT_TIPSY] != 0 ) f += fEffects[PlayerOptions::EFFECT_TIPSY] * ( RageFastCos( RageTimer::GetTimeSinceStartFast()*TIPSY_TIMER_FREQUENCY + iCol*TIPSY_COLUMN_FREQUENCY) * ARROW_SIZE*TIPSY_ARROW_MAGNITUDE ); // In beware's DDR Extreme-focused fork of StepMania 3.9, this value is // floored, making arrows show on integer Y coordinates. Supposedly it makes // the arrows look better, but testing needs to be done. // todo: make this a noteskin metric instead of a theme metric? -aj return QUANTIZE_ARROW_Y ? floor(f) : f; }
/* prh.xyz -> heading, pitch, roll */ void RageQuatFromHPR(RageVector4* pOut, RageVector3 hpr ) { hpr *= PI; hpr /= 180.0f; hpr /= 2.0f; const float sX = RageFastSin(hpr.x); const float cX = RageFastCos(hpr.x); const float sY = RageFastSin(hpr.y); const float cY = RageFastCos(hpr.y); const float sZ = RageFastSin(hpr.z); const float cZ = RageFastCos(hpr.z); pOut->w = cX * cY * cZ + sX * sY * sZ; pOut->x = sX * cY * cZ - cX * sY * sZ; pOut->y = cX * sY * cZ + sX * cY * sZ; pOut->z = cX * cY * sZ - sX * sY * cZ; }
RageVector4 RageQuatFromR(float theta ) { theta *= PI/180.0f; theta /= 2.0f; theta *= -1; const float c = RageFastCos(theta); const float s = RageFastSin(theta); return RageVector4(0, 0, s, c); }
void RageMatrixRotationZ( RageMatrix* pOut, float theta ) { theta *= PI/180; RageMatrixIdentity(pOut); pOut->m[0][0] = RageFastCos(theta); pOut->m[1][1] = pOut->m[0][0]; pOut->m[0][1] = RageFastSin(theta); pOut->m[1][0] = -pOut->m[0][1]; }
/* prh.xyz -> pitch, roll, heading */ void RageQuatFromPRH(RageVector4* pOut, RageVector3 prh ) { prh *= PI; prh /= 180.0f; prh /= 2.0f; /* Set cX to the cosine of the angle we want to rotate on the X axis, * and so on. Here, hpr.z (roll) rotates on the Z axis, hpr.x (heading) * on Y, and hpr.y (pitch) on X. */ const float sX = RageFastSin(prh.y); const float cX = RageFastCos(prh.y); const float sY = RageFastSin(prh.x); const float cY = RageFastCos(prh.x); const float sZ = RageFastSin(prh.z); const float cZ = RageFastCos(prh.z); pOut->w = cX * cY * cZ + sX * sY * sZ; pOut->x = sX * cY * cZ - cX * sY * sZ; pOut->y = cX * sY * cZ + sX * cY * sZ; pOut->z = cX * cY * sZ - sX * sY * cZ; }
float ArrowEffects::GetYOffsetFromYPos( const PlayerState* pPlayerState, int iCol, float YPos, float fYReverseOffsetPixels ) { float f = YPos; const float* fEffects = pPlayerState->m_CurrentPlayerOptions.m_fEffects; if( fEffects[PlayerOptions::EFFECT_TIPSY] != 0 ) f -= fEffects[PlayerOptions::EFFECT_TIPSY] * ( RageFastCos( RageTimer::GetTimeSinceStartFast()*1.2f + iCol*2.f) * ARROW_SIZE*0.4f ); float fShift, fScale; ArrowGetReverseShiftAndScale( pPlayerState, iCol, fYReverseOffsetPixels, fShift, fScale ); f -= fShift; if( fScale ) f /= fScale; return f; }
void RageDisplay::DrawCircleInternal( const RageSpriteVertex &p, float radius ) { const int subdivisions = 32; RageSpriteVertex v[subdivisions+2]; v[0] = p; for(int i = 0; i < subdivisions+1; ++i) { const float fRotation = float(i) / subdivisions * 2*PI; const float fX = RageFastCos(fRotation) * radius; const float fY = -RageFastSin(fRotation) * radius; v[1+i] = v[0]; v[1+i].p.x += fX; v[1+i].p.y += fY; } this->DrawFan( v, subdivisions+2 ); }
float ArrowEffects::GetYPos( const PlayerState* pPlayerState, int iCol, float fYOffset, float fYReverseOffsetPixels, bool WithReverse ) { float f = fYOffset; if( WithReverse ) { float fShift, fScale; ArrowGetReverseShiftAndScale( pPlayerState, iCol, fYReverseOffsetPixels, fShift, fScale ); f *= fScale; f += fShift; } const float* fEffects = pPlayerState->m_CurrentPlayerOptions.m_fEffects; if( fEffects[PlayerOptions::EFFECT_TIPSY] != 0 ) f += fEffects[PlayerOptions::EFFECT_TIPSY] * ( RageFastCos( RageTimer::GetTimeSinceStartFast()*1.2f + iCol*1.8f) * ARROW_SIZE*0.4f ); return f; }
float ArrowEffects::GetYOffsetFromYPos( const PlayerState* pPlayerState, int iCol, float YPos, float fYReverseOffsetPixels ) { float f = YPos; const float* fEffects = pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects; if( fEffects[PlayerOptions::EFFECT_TIPSY] != 0 ) f -= fEffects[PlayerOptions::EFFECT_TIPSY] * ( RageFastCos( RageTimer::GetTimeSinceStartFast()*TIPSY_OFFSET_TIMER_FREQUENCY + iCol*TIPSY_OFFSET_COLUMN_FREQUENCY) * ARROW_SIZE*TIPSY_OFFSET_ARROW_MAGNITUDE ); float fShift, fScale; ArrowGetReverseShiftAndScale( pPlayerState, iCol, fYReverseOffsetPixels, fShift, fScale ); f -= fShift; if( fScale ) f /= fScale; return f; }
float ArrowEffects::GetXPos( const PlayerState* pPlayerState, int iColNum, float fYOffset ) { float fPixelOffsetFromCenter = 0; // fill this in below const Style* pStyle = GAMESTATE->GetCurrentStyle(); const float* fEffects = pPlayerState->m_CurrentPlayerOptions.m_fEffects; if( fEffects[PlayerOptions::EFFECT_TORNADO] != 0 ) { // TRICKY: Tornado is very unplayable in doubles, so use a smaller // tornado width if there are many columns bool bWideField = pStyle->m_iColsPerPlayer > 4; int iTornadoWidth = bWideField ? 2 : 3; int iStartCol = iColNum - iTornadoWidth; int iEndCol = iColNum + iTornadoWidth; CLAMP( iStartCol, 0, pStyle->m_iColsPerPlayer-1 ); CLAMP( iEndCol, 0, pStyle->m_iColsPerPlayer-1 ); float fMinX = FLT_MAX; float fMaxX = FLT_MIN; // TODO: Don't index by PlayerNumber. PlayerNumber pn = pPlayerState->m_PlayerNumber; for( int i=iStartCol; i<=iEndCol; i++ ) { fMinX = min( fMinX, pStyle->m_ColumnInfo[pn][i].fXOffset ); fMaxX = max( fMaxX, pStyle->m_ColumnInfo[pn][i].fXOffset ); } const float fRealPixelOffset = pStyle->m_ColumnInfo[pn][iColNum].fXOffset; const float fPositionBetween = SCALE( fRealPixelOffset, fMinX, fMaxX, -1, 1 ); float fRads = acosf( fPositionBetween ); fRads += fYOffset * 6 / SCREEN_HEIGHT; const float fAdjustedPixelOffset = SCALE( RageFastCos(fRads), -1, 1, fMinX, fMaxX ); fPixelOffsetFromCenter += (fAdjustedPixelOffset - fRealPixelOffset) * fEffects[PlayerOptions::EFFECT_TORNADO]; } if( fEffects[PlayerOptions::EFFECT_DRUNK] != 0 ) fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_DRUNK] * ( RageFastCos( RageTimer::GetTimeSinceStartFast() + iColNum*0.2f + fYOffset*10/SCREEN_HEIGHT) * ARROW_SIZE*0.5f ); if( fEffects[PlayerOptions::EFFECT_FLIP] != 0 ) { // TODO: Don't index by PlayerNumber. PlayerNumber pn = pPlayerState->m_PlayerNumber; const int iNumCols = pStyle->m_iColsPerPlayer; int iFirstCol = 0; int iLastCol = iNumCols-1; const int iNewCol = SCALE( iColNum, iFirstCol, iLastCol, iLastCol, iFirstCol ); const float fOldPixelOffset = pStyle->m_ColumnInfo[pn][iColNum].fXOffset; const float fNewPixelOffset = pStyle->m_ColumnInfo[pn][iNewCol].fXOffset; const float fDistance = fNewPixelOffset - fOldPixelOffset; fPixelOffsetFromCenter += fDistance * fEffects[PlayerOptions::EFFECT_FLIP]; } if( fEffects[PlayerOptions::EFFECT_INVERT] != 0 ) { // TODO: Don't index by PlayerNumber. PlayerNumber pn = pPlayerState->m_PlayerNumber; const int iNumCols = pStyle->m_iColsPerPlayer; const int iNumSides = pStyle->m_StyleType==ONE_PLAYER_TWO_SIDES ? 2 : 1; const int iNumColsPerSide = iNumCols / iNumSides; const int iSideIndex = iColNum / iNumColsPerSide; const int iColOnSide = iColNum % iNumColsPerSide; const int iColLeftOfMiddle = (iNumColsPerSide-1)/2; const int iColRightOfMidde = (iNumColsPerSide+1)/2; int iFirstColOnSide = -1; int iLastColOnSide = -1; if( iColOnSide <= iColLeftOfMiddle ) { iFirstColOnSide = 0; iLastColOnSide = iColLeftOfMiddle; } else if( iColOnSide >= iColRightOfMidde ) { iFirstColOnSide = iColRightOfMidde; iLastColOnSide = iNumColsPerSide-1; } else { iFirstColOnSide = iColOnSide/2; iLastColOnSide = iColOnSide/2; } // mirror const int iNewColOnSide = SCALE( iColOnSide, iFirstColOnSide, iLastColOnSide, iLastColOnSide, iFirstColOnSide ); const int iNewCol = iSideIndex*iNumColsPerSide + iNewColOnSide; const float fOldPixelOffset = pStyle->m_ColumnInfo[pn][iColNum].fXOffset; const float fNewPixelOffset = pStyle->m_ColumnInfo[pn][iNewCol].fXOffset; const float fDistance = fNewPixelOffset - fOldPixelOffset; fPixelOffsetFromCenter += fDistance * fEffects[PlayerOptions::EFFECT_INVERT]; } if( fEffects[PlayerOptions::EFFECT_BEAT] != 0 ) do { float fAccelTime = 0.2f, fTotalTime = 0.5f; /* If the song is really fast, slow down the rate, but speed up the * acceleration to compensate or it'll look weird. */ const float fBPM = GAMESTATE->m_fCurBPS * 60; const float fDiv = max(1.0f, truncf( fBPM / 150.0f )); fAccelTime /= fDiv; fTotalTime /= fDiv; float fBeat = GAMESTATE->m_fSongBeat + fAccelTime; fBeat /= fDiv; const bool bEvenBeat = ( int(fBeat) % 2 ) != 0; /* -100.2 -> -0.2 -> 0.2 */ if( fBeat < 0 ) break; fBeat -= truncf( fBeat ); fBeat += 1; fBeat -= truncf( fBeat ); if( fBeat >= fTotalTime ) break; float fAmount; if( fBeat < fAccelTime ) { fAmount = SCALE( fBeat, 0.0f, fAccelTime, 0.0f, 1.0f); fAmount *= fAmount; } else /* fBeat < fTotalTime */ { fAmount = SCALE( fBeat, fAccelTime, fTotalTime, 1.0f, 0.0f); fAmount = 1 - (1-fAmount) * (1-fAmount); } if( bEvenBeat ) fAmount *= -1; const float fShift = 20.0f*fAmount*RageFastSin( fYOffset / 15.0f + PI/2.0f ); fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_BEAT] * fShift; } while(0); return fPixelOffsetFromCenter; }
void GrooveRadar::GrooveRadarValueMap::DrawPrimitives() { ActorFrame::DrawPrimitives(); // draw radar filling const float fRadius = GetUnzoomedWidth()/2.0f*1.1f; DISPLAY->ClearAllTextures(); DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Modulate ); RageSpriteVertex v[12]; // needed to draw 5 fan primitives and 10 strip primitives // xxx: We could either make the values invisible or draw a dot // (simulating real DDR). TODO: Make that choice up to the themer. -aj if( !m_bValuesVisible ) return; // use a fan to draw the volume RageColor color = this->m_pTempState->diffuse[0]; color.a = 0.5f; v[0].p = RageVector3( 0, 0, 0 ); RageColor midcolor = color; midcolor.a = RADAR_CENTER_ALPHA; v[0].c = midcolor; v[1].c = color; for( int i=0; i<NUM_SHOWN_RADAR_CATEGORIES+1; i++ ) // do one extra to close the fan { const int c = i%NUM_SHOWN_RADAR_CATEGORIES; const float fDistFromCenter = ( m_fValuesOld[c] * (1-m_PercentTowardNew) + m_fValuesNew[c] * m_PercentTowardNew + 0.07f ) * fRadius; const float fRotation = RADAR_VALUE_ROTATION(i); const float fX = RageFastCos(fRotation) * fDistFromCenter; const float fY = -RageFastSin(fRotation) * fDistFromCenter; v[1+i].p = RageVector3( fX, fY, 0 ); v[1+i].c = v[1].c; } DISPLAY->DrawFan( v, NUM_SHOWN_RADAR_CATEGORIES+2 ); // use a line loop to draw the thick line for( int i=0; i<=NUM_SHOWN_RADAR_CATEGORIES; i++ ) { const int c = i%NUM_SHOWN_RADAR_CATEGORIES; const float fDistFromCenter = ( m_fValuesOld[c] * (1-m_PercentTowardNew) + m_fValuesNew[c] * m_PercentTowardNew + 0.07f ) * fRadius; const float fRotation = RADAR_VALUE_ROTATION(i); const float fX = RageFastCos(fRotation) * fDistFromCenter; const float fY = -RageFastSin(fRotation) * fDistFromCenter; v[i].p = RageVector3( fX, fY, 0 ); v[i].c = this->m_pTempState->diffuse[0]; } // TODO: Add this back in -Chris // switch( PREFSMAN->m_iPolygonRadar ) // { // case 0: DISPLAY->DrawLoop_LinesAndPoints( v, NUM_SHOWN_RADAR_CATEGORIES, RADAR_EDGE_WIDTH ); break; // case 1: DISPLAY->DrawLoop_Polys( v, NUM_SHOWN_RADAR_CATEGORIES, RADAR_EDGE_WIDTH ); break; // default: // case -1: DISPLAY->DrawLineStrip( v, NUM_SHOWN_RADAR_CATEGORIES+1, RADAR_EDGE_WIDTH ); // break; // } }
void ArrowEffects::Update() { static float fLastTime = 0; float fTime = RageTimer::GetTimeSinceStartFast(); FOREACH_EnabledPlayer( pn ) { const Style* pStyle = GAMESTATE->GetCurrentStyle(pn); const Style::ColumnInfo* pCols = pStyle->m_ColumnInfo[pn]; const SongPosition &position = GAMESTATE->m_bIsUsingStepTiming ? GAMESTATE->m_pPlayerState[pn]->m_Position : GAMESTATE->m_Position; const float field_zoom= GAMESTATE->m_pPlayerState[pn]->m_NotefieldZoom; const float* effects= GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_fEffects; PerPlayerData &data = g_EffectData[pn]; if( !position.m_bFreeze || !position.m_bDelay ) { data.m_fExpandSeconds += fTime - fLastTime; data.m_fExpandSeconds = fmodf( data.m_fExpandSeconds, PI*2 ); } // Update Tornado for( int iColNum = 0; iColNum < MAX_COLS_PER_PLAYER; ++iColNum ) { // TRICKY: Tornado is very unplayable in doubles, so use a smaller // tornado width if there are many columns /* the below makes an assumption for dance mode. * perhaps check if we are actually playing on singles without, * say more than 6 columns. That would exclude IIDX, pop'n, and * techno-8, all of which would be very hectic. * certain non-singles modes (like halfdoubles 6cols) * could possibly have tornado enabled. * let's also take default resolution (640x480) into mind. -aj */ bool bWideField = pStyle->m_iColsPerPlayer > 4; int iTornadoWidth = bWideField ? 2 : 3; int iStartCol = iColNum - iTornadoWidth; int iEndCol = iColNum + iTornadoWidth; CLAMP( iStartCol, 0, pStyle->m_iColsPerPlayer-1 ); CLAMP( iEndCol, 0, pStyle->m_iColsPerPlayer-1 ); data.m_fMinTornadoX[iColNum] = FLT_MAX; data.m_fMaxTornadoX[iColNum] = FLT_MIN; for( int i=iStartCol; i<=iEndCol; i++ ) { data.m_fMinTornadoX[iColNum] = min( data.m_fMinTornadoX[iColNum], pCols[i].fXOffset * field_zoom ); data.m_fMaxTornadoX[iColNum] = max( data.m_fMaxTornadoX[iColNum], pCols[i].fXOffset * field_zoom); } } // Update Invert for( int iColNum = 0; iColNum < MAX_COLS_PER_PLAYER; ++iColNum ) { const int iNumCols = pStyle->m_iColsPerPlayer; const int iNumSides = (pStyle->m_StyleType==StyleType_OnePlayerTwoSides || pStyle->m_StyleType==StyleType_TwoPlayersSharedSides) ? 2 : 1; const int iNumColsPerSide = iNumCols / iNumSides; const int iSideIndex = iColNum / iNumColsPerSide; const int iColOnSide = iColNum % iNumColsPerSide; const int iColLeftOfMiddle = (iNumColsPerSide-1)/2; const int iColRightOfMiddle = (iNumColsPerSide+1)/2; int iFirstColOnSide = -1; int iLastColOnSide = -1; if( iColOnSide <= iColLeftOfMiddle ) { iFirstColOnSide = 0; iLastColOnSide = iColLeftOfMiddle; } else if( iColOnSide >= iColRightOfMiddle ) { iFirstColOnSide = iColRightOfMiddle; iLastColOnSide = iNumColsPerSide-1; } else { iFirstColOnSide = iColOnSide/2; iLastColOnSide = iColOnSide/2; } // mirror int iNewColOnSide; if( iFirstColOnSide == iLastColOnSide ) iNewColOnSide = 0; else iNewColOnSide = SCALE( iColOnSide, iFirstColOnSide, iLastColOnSide, iLastColOnSide, iFirstColOnSide ); const int iNewCol = iSideIndex*iNumColsPerSide + iNewColOnSide; const float fOldPixelOffset = pCols[iColNum].fXOffset; const float fNewPixelOffset = pCols[iNewCol].fXOffset; data.m_fInvertDistance[iColNum] = fNewPixelOffset - fOldPixelOffset; } // Update Tipsy if(effects[PlayerOptions::EFFECT_TIPSY] != 0) { const float time= RageTimer::GetTimeSinceStartFast(); const float time_times_timer= time * TIPSY_TIMER_FREQUENCY; const float arrow_times_mag= ARROW_SIZE * TIPSY_ARROW_MAGNITUDE; const float time_times_offset_timer= time * TIPSY_OFFSET_TIMER_FREQUENCY; const float arrow_times_offset_mag= ARROW_SIZE * TIPSY_OFFSET_ARROW_MAGNITUDE; for(int col= 0; col < MAX_COLS_PER_PLAYER; ++col) { data.m_tipsy_result[col]= RageFastCos( time_times_timer + (col * TIPSY_COLUMN_FREQUENCY)) * arrow_times_mag; data.m_tipsy_offset_result[col]= RageFastCos( time_times_offset_timer + (col * TIPSY_OFFSET_COLUMN_FREQUENCY)) * arrow_times_offset_mag; } } else { for(int col= 0; col < MAX_COLS_PER_PLAYER; ++col) { data.m_tipsy_result[col]= 0; } } // Update Beat do { float fAccelTime = 0.2f, fTotalTime = 0.5f; float fBeat = position.m_fSongBeatVisible + fAccelTime; const bool bEvenBeat = ( int(fBeat) % 2 ) != 0; data.m_fBeatFactor = 0; if( fBeat < 0 ) break; // -100.2 -> -0.2 -> 0.2 fBeat -= truncf( fBeat ); fBeat += 1; fBeat -= truncf( fBeat ); if( fBeat >= fTotalTime ) break; if( fBeat < fAccelTime ) { data.m_fBeatFactor = SCALE( fBeat, 0.0f, fAccelTime, 0.0f, 1.0f); data.m_fBeatFactor *= data.m_fBeatFactor; } else /* fBeat < fTotalTime */ { data.m_fBeatFactor = SCALE( fBeat, fAccelTime, fTotalTime, 1.0f, 0.0f); data.m_fBeatFactor = 1 - (1-data.m_fBeatFactor) * (1-data.m_fBeatFactor); } if( bEvenBeat ) data.m_fBeatFactor *= -1; data.m_fBeatFactor *= 20.0f; } while( false ); } fLastTime = fTime; }
float ArrowEffects::GetXPos( const PlayerState* pPlayerState, int iColNum, float fYOffset ) { float fPixelOffsetFromCenter = 0; // fill this in below const Style* pStyle = GAMESTATE->GetCurrentStyle(pPlayerState->m_PlayerNumber); const float* fEffects = curr_options->m_fEffects; // TODO: Don't index by PlayerNumber. const Style::ColumnInfo* pCols = pStyle->m_ColumnInfo[pPlayerState->m_PlayerNumber]; PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber]; if( fEffects[PlayerOptions::EFFECT_TORNADO] != 0 ) { const float fRealPixelOffset = pCols[iColNum].fXOffset * pPlayerState->m_NotefieldZoom; const float fPositionBetween = SCALE( fRealPixelOffset, data.m_fMinTornadoX[iColNum], data.m_fMaxTornadoX[iColNum], TORNADO_POSITION_SCALE_TO_LOW, TORNADO_POSITION_SCALE_TO_HIGH ); float fRads = acosf( fPositionBetween ); fRads += fYOffset * TORNADO_OFFSET_FREQUENCY / SCREEN_HEIGHT; const float fAdjustedPixelOffset = SCALE( RageFastCos(fRads), TORNADO_OFFSET_SCALE_FROM_LOW, TORNADO_OFFSET_SCALE_FROM_HIGH, data.m_fMinTornadoX[iColNum], data.m_fMaxTornadoX[iColNum] ); fPixelOffsetFromCenter += (fAdjustedPixelOffset - fRealPixelOffset) * fEffects[PlayerOptions::EFFECT_TORNADO]; } if( fEffects[PlayerOptions::EFFECT_DRUNK] != 0 ) fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_DRUNK] * ( RageFastCos( RageTimer::GetTimeSinceStartFast() + iColNum*DRUNK_COLUMN_FREQUENCY + fYOffset*DRUNK_OFFSET_FREQUENCY/SCREEN_HEIGHT) * ARROW_SIZE*DRUNK_ARROW_MAGNITUDE ); if( fEffects[PlayerOptions::EFFECT_FLIP] != 0 ) { const int iFirstCol = 0; const int iLastCol = pStyle->m_iColsPerPlayer-1; const int iNewCol = SCALE( iColNum, iFirstCol, iLastCol, iLastCol, iFirstCol ); const float fOldPixelOffset = pCols[iColNum].fXOffset * pPlayerState->m_NotefieldZoom; const float fNewPixelOffset = pCols[iNewCol].fXOffset * pPlayerState->m_NotefieldZoom; const float fDistance = fNewPixelOffset - fOldPixelOffset; fPixelOffsetFromCenter += fDistance * fEffects[PlayerOptions::EFFECT_FLIP]; } if( fEffects[PlayerOptions::EFFECT_INVERT] != 0 ) fPixelOffsetFromCenter += data.m_fInvertDistance[iColNum] * fEffects[PlayerOptions::EFFECT_INVERT]; if( fEffects[PlayerOptions::EFFECT_BEAT] != 0 ) { const float fShift = data.m_fBeatFactor*RageFastSin( fYOffset / BEAT_OFFSET_HEIGHT + PI/BEAT_PI_HEIGHT ); fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_BEAT] * fShift; } if( fEffects[PlayerOptions::EFFECT_XMODE] != 0 ) { // based off of code by v1toko for StepNXA, except it should work on // any gametype now. switch( pStyle->m_StyleType ) { case StyleType_OnePlayerTwoSides: case StyleType_TwoPlayersSharedSides: // fall through? { // find the middle, and split based on iColNum // it's unknown if this will work for routine. const int iMiddleColumn = static_cast<int>(floor(pStyle->m_iColsPerPlayer/2.0f)); if( iColNum > iMiddleColumn-1 ) fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_XMODE]*-(fYOffset); else fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_XMODE]*fYOffset; } break; case StyleType_OnePlayerOneSide: case StyleType_TwoPlayersTwoSides: // fall through { // the code was the same for both of these cases in StepNXA. if( pPlayerState->m_PlayerNumber == PLAYER_2 ) fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_XMODE]*-(fYOffset); else fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_XMODE]*fYOffset; } break; case StyleType_FourPlayersFourSides: // fall through { // the code was the same for both of these cases in StepNXA. if (pPlayerState->m_PlayerNumber == PLAYER_2) fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_XMODE] * -(fYOffset); else fPixelOffsetFromCenter += fEffects[PlayerOptions::EFFECT_XMODE] * fYOffset; } break; DEFAULT_FAIL(pStyle->m_StyleType); } } fPixelOffsetFromCenter += pCols[iColNum].fXOffset * pPlayerState->m_NotefieldZoom; if( fEffects[PlayerOptions::EFFECT_TINY] != 0 ) { // Allow Tiny to pull tracks together, but not to push them apart. float fTinyPercent = fEffects[PlayerOptions::EFFECT_TINY]; fTinyPercent = min( powf(TINY_PERCENT_BASE, fTinyPercent), (float)TINY_PERCENT_GATE ); fPixelOffsetFromCenter *= fTinyPercent; } return fPixelOffsetFromCenter; }
/* For visibility testing: if bAbsolute is false, random modifiers must return * the minimum possible scroll speed. */ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute ) { // Default values that are returned if boomerang is off. fPeakYOffsetOut = FLT_MAX; bIsPastPeakOut = true; float fYOffset = 0; const SongPosition &position = pPlayerState->GetDisplayedPosition(); float fSongBeat = position.m_fSongBeatVisible; Steps *pCurSteps = GAMESTATE->m_pCurSteps[pPlayerState->m_PlayerNumber]; /* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or * entirely time spacing (respectively). Occasionally, we tween between them. */ if( curr_options->m_fTimeSpacing != 1.0f ) { if( GAMESTATE->m_bInStepEditor ) { // Use constant spacing in step editor fYOffset = fNoteBeat - fSongBeat; } else { fYOffset = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat); fYOffset *= pCurSteps->GetTimingData()->GetDisplayedSpeedPercent( position.m_fSongBeatVisible, position.m_fMusicSecondsVisible ); } fYOffset *= 1 - curr_options->m_fTimeSpacing; } if( curr_options->m_fTimeSpacing != 0.0f ) { float fSongSeconds = GAMESTATE->m_Position.m_fMusicSecondsVisible; float fNoteSeconds = pCurSteps->GetTimingData()->GetElapsedTimeFromBeat(fNoteBeat); float fSecondsUntilStep = fNoteSeconds - fSongSeconds; float fBPM = curr_options->m_fScrollBPM; float fBPS = fBPM/60.f / GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; float fYOffsetTimeSpacing = fSecondsUntilStep * fBPS; fYOffset += fYOffsetTimeSpacing * curr_options->m_fTimeSpacing; } // TODO: If we allow noteskins to have metricable row spacing // (per issue 24), edit this to reflect that. -aj fYOffset *= ARROW_SPACING; // Factor in scroll speed float fScrollSpeed = curr_options->m_fScrollSpeed; if(curr_options->m_fMaxScrollBPM != 0) { fScrollSpeed= curr_options->m_fMaxScrollBPM / (pPlayerState->m_fReadBPM * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate); } // don't mess with the arrows after they've crossed 0 if( fYOffset < 0 ) { return fYOffset * fScrollSpeed; } const float* fAccels = curr_options->m_fAccels; //const float* fEffects = curr_options->m_fEffects; float fYAdjust = 0; // fill this in depending on PlayerOptions if( fAccels[PlayerOptions::ACCEL_BOOST] != 0 ) { float fEffectHeight = GetNoteFieldHeight(); float fNewYOffset = fYOffset * 1.5f / ((fYOffset+fEffectHeight/1.2f)/fEffectHeight); float fAccelYAdjust = fAccels[PlayerOptions::ACCEL_BOOST] * (fNewYOffset - fYOffset); // TRICKY: Clamp this value, or else BOOST+BOOMERANG will draw a ton of arrows on the screen. CLAMP( fAccelYAdjust, BOOST_MOD_MIN_CLAMP, BOOST_MOD_MAX_CLAMP ); fYAdjust += fAccelYAdjust; } if( fAccels[PlayerOptions::ACCEL_BRAKE] != 0 ) { float fEffectHeight = GetNoteFieldHeight(); float fScale = SCALE( fYOffset, 0.f, fEffectHeight, 0, 1.f ); float fNewYOffset = fYOffset * fScale; float fBrakeYAdjust = fAccels[PlayerOptions::ACCEL_BRAKE] * (fNewYOffset - fYOffset); // TRICKY: Clamp this value the same way as BOOST so that in BOOST+BRAKE, BRAKE doesn't overpower BOOST CLAMP( fBrakeYAdjust, BRAKE_MOD_MIN_CLAMP, BRAKE_MOD_MAX_CLAMP ); fYAdjust += fBrakeYAdjust; } if( fAccels[PlayerOptions::ACCEL_WAVE] != 0 ) fYAdjust += fAccels[PlayerOptions::ACCEL_WAVE] * WAVE_MOD_MAGNITUDE *RageFastSin( fYOffset/WAVE_MOD_HEIGHT ); fYOffset += fYAdjust; // Factor in boomerang if( fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0 ) { float fPeakAtYOffset = SCREEN_HEIGHT * BOOMERANG_PEAK_PERCENTAGE; // zero point of boomerang function fPeakYOffsetOut = (-1*fPeakAtYOffset*fPeakAtYOffset/SCREEN_HEIGHT) + 1.5f*fPeakAtYOffset; bIsPastPeakOut = fYOffset < fPeakAtYOffset; fYOffset = (-1*fYOffset*fYOffset/SCREEN_HEIGHT) + 1.5f*fYOffset; } if( curr_options->m_fRandomSpeed > 0 && !bAbsolute ) { // Generate a deterministically "random" speed for each arrow. unsigned seed = GAMESTATE->m_iStageSeed + ( BeatToNoteRow( fNoteBeat ) << 8 ) + (iCol * 100); for( int i = 0; i < 3; ++i ) seed = ((seed * 1664525u) + 1013904223u) & 0xFFFFFFFF; float fRandom = seed / 4294967296.0f; /* Random speed always increases speed: a random speed of 10 indicates * [1,11]. This keeps it consistent with other mods: 0 means no effect. */ fScrollSpeed *= SCALE( fRandom, 0.0f, 1.0f, 1.0f, curr_options->m_fRandomSpeed + 1.0f ); } if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 ) { // TODO: Don't index by PlayerNumber. PerPlayerData &data = g_EffectData[pPlayerState->m_PlayerNumber]; float fExpandMultiplier = SCALE( RageFastCos(data.m_fExpandSeconds*EXPAND_MULTIPLIER_FREQUENCY), EXPAND_MULTIPLIER_SCALE_FROM_LOW, EXPAND_MULTIPLIER_SCALE_FROM_HIGH, EXPAND_MULTIPLIER_SCALE_TO_LOW, EXPAND_MULTIPLIER_SCALE_TO_HIGH ); fScrollSpeed *= SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], EXPAND_SPEED_SCALE_FROM_LOW, EXPAND_SPEED_SCALE_FROM_HIGH, EXPAND_SPEED_SCALE_TO_LOW, fExpandMultiplier ); } fYOffset *= fScrollSpeed; fPeakYOffsetOut *= fScrollSpeed; return fYOffset; }
void NoteDisplay::DrawHoldBottomCap( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, float fYHead, float fYTail, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fYStartOffset, float fYEndOffset ) { // // Draw the bottom cap (always wavy) // StripBuffer queue; Sprite* pBottomCap = GetHoldBottomCapSprite( NoteRowToBeat(iRow), tn.subType == TapNote::hold_head_roll, bIsBeingHeld ); pBottomCap->SetZoom( ArrowEffects::GetZoom( m_pPlayerState ) ); // draw manually in small segments RageTexture* pTexture = pBottomCap->GetTexture(); const RectF *pRect = pBottomCap->GetCurrentTextureCoordRect(); DISPLAY->ClearAllTextures(); DISPLAY->SetTexture( 0, pTexture ); DISPLAY->SetBlendMode( BLEND_NORMAL ); DISPLAY->SetCullMode( CULL_NONE ); DISPLAY->SetTextureWrapping(false); const float fFrameWidth = pBottomCap->GetZoomedWidth(); const float fFrameHeight = pBottomCap->GetZoomedHeight(); const float fYCapTop = fYTail+cache->m_iStopDrawingHoldBodyOffsetFromTail; const float fYCapBottom = fYTail+cache->m_iStopDrawingHoldBodyOffsetFromTail+fFrameHeight; bool bReverse = m_pPlayerState->m_CurrentPlayerOptions.GetReversePercentForColumn(iCol) > 0.5f; if( bGlow ) fColorScale = 1; float fDrawYCapTop; float fDrawYCapBottom; { float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYStartOffset, m_fYReverseOffsetPixels ); float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYEndOffset, m_fYReverseOffsetPixels ); fDrawYCapTop = max( fYCapTop, bReverse ? fYEndPos : fYStartPos ); fDrawYCapBottom = min( fYCapBottom, bReverse ? fYStartPos : fYEndPos ); } bool bAllAreTransparent = true; bool bLast = false; // don't draw any part of the tail that is before the middle of the head float fY=max( fDrawYCapTop, fYHead ); for( ; !bLast; fY += fYStep ) { if( fY >= fDrawYCapBottom ) { fY = fDrawYCapBottom; bLast = true; } const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); // XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here. const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180; // if we're rotating, we need to modify the X and Z coords for the outer edges. const float fRotOffsetX = fFrameWidth/2 * RageFastCos(fRotationY); const float fRotOffsetZ = fFrameWidth/2 * RageFastSin(fRotationY); const float fXLeft = fX - fRotOffsetX; const float fXRight = fX + fRotOffsetX; const float fZLeft = fZ - fRotOffsetZ; const float fZRight = fZ + fRotOffsetZ; const float fTopDistFromTail = fY - fYCapTop; const float fTexCoordTop = SCALE( fTopDistFromTail, 0, fFrameHeight, pRect->top, pRect->bottom ); const float fTexCoordLeft = pRect->left; const float fTexCoordRight = pRect->right; const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const RageColor color = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); if( fAlpha > 0 ) bAllAreTransparent = false; queue.v[0].p = RageVector3(fXLeft, fY, fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); queue.v[1].p = RageVector3(fXRight, fY, fZRight); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordRight, fTexCoordTop); queue.v+=2; if( queue.Free() < 2 ) { /* The queue is full. Render it, clear the buffer, and move back a step to * start off the quad strip again. */ if( !bAllAreTransparent ) queue.Draw(); queue.Init(); bAllAreTransparent = true; fY -= fYStep; } } if( !bAllAreTransparent ) queue.Draw(); }
float Tween( float f ) const { return 1 - RageFastCos( f*PI*2.5f )/(1+f*3); }
/* For visibility testing: if bAbsolute is false, random modifiers must return the * minimum possible scroll speed. */ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute ) { // Default values that are returned if boomerang is off. fPeakYOffsetOut = FLT_MAX; bIsPastPeakOut = true; float fYOffset = 0; /* Usually, fTimeSpacing is 0 or 1, in which case we use entirely beat spacing or * entirely time spacing (respectively). Occasionally, we tween between them. */ if( pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing != 1.0f ) { float fSongBeat = GAMESTATE->m_fSongBeat; float fBeatsUntilStep = fNoteBeat - fSongBeat; float fYOffsetBeatSpacing = fBeatsUntilStep * ARROW_SPACING; fYOffset += fYOffsetBeatSpacing * (1-pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing); } if( pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing != 0.0f ) { float fSongSeconds = GAMESTATE->m_fMusicSeconds; float fNoteSeconds = GAMESTATE->m_pCurSong->GetElapsedTimeFromBeat(fNoteBeat); float fSecondsUntilStep = fNoteSeconds - fSongSeconds; float fBPM = pPlayerState->m_CurrentPlayerOptions.m_fScrollBPM; float fBPS = fBPM/60.f; float fYOffsetTimeSpacing = fSecondsUntilStep * fBPS * ARROW_SPACING; fYOffset += fYOffsetTimeSpacing * pPlayerState->m_CurrentPlayerOptions.m_fTimeSpacing; } // don't mess with the arrows after they've crossed 0 if( fYOffset < 0 ) return fYOffset * pPlayerState->m_CurrentPlayerOptions.m_fScrollSpeed; const float* fAccels = pPlayerState->m_CurrentPlayerOptions.m_fAccels; //const float* fEffects = pPlayerState->m_CurrentPlayerOptions.m_fEffects; float fYAdjust = 0; // fill this in depending on PlayerOptions if( fAccels[PlayerOptions::ACCEL_BOOST] != 0 ) { float fEffectHeight = GetNoteFieldHeight(pPlayerState); float fNewYOffset = fYOffset * 1.5f / ((fYOffset+fEffectHeight/1.2f)/fEffectHeight); float fAccelYAdjust = fAccels[PlayerOptions::ACCEL_BOOST] * (fNewYOffset - fYOffset); // TRICKY: Clamp this value, or else BOOST+BOOMERANG will draw a ton of arrows on the screen. CLAMP( fAccelYAdjust, -400.f, 400.f ); fYAdjust += fAccelYAdjust; } if( fAccels[PlayerOptions::ACCEL_BRAKE] != 0 ) { float fEffectHeight = GetNoteFieldHeight(pPlayerState); float fScale = SCALE( fYOffset, 0.f, fEffectHeight, 0, 1.f ); float fNewYOffset = fYOffset * fScale; float fBrakeYAdjust = fAccels[PlayerOptions::ACCEL_BRAKE] * (fNewYOffset - fYOffset); // TRICKY: Clamp this value the same way as BOOST so that in BOOST+BRAKE, BRAKE doesn't overpower BOOST CLAMP( fBrakeYAdjust, -400.f, 400.f ); fYAdjust += fBrakeYAdjust; } if( fAccels[PlayerOptions::ACCEL_WAVE] != 0 ) fYAdjust += fAccels[PlayerOptions::ACCEL_WAVE] * 20.0f*RageFastSin( fYOffset/38.0f ); fYOffset += fYAdjust; // // Factor in boomerang // if( fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0 ) { float fOriginalYOffset = fYOffset; fYOffset = (-1*fOriginalYOffset*fOriginalYOffset/SCREEN_HEIGHT) + 1.5f*fOriginalYOffset; float fPeakAtYOffset = SCREEN_HEIGHT * 0.75f; // zero point of function above fPeakYOffsetOut = (-1*fPeakAtYOffset*fPeakAtYOffset/SCREEN_HEIGHT) + 1.5f*fPeakAtYOffset; bIsPastPeakOut = fOriginalYOffset < fPeakAtYOffset; } // // Factor in scroll speed // float fScrollSpeed = pPlayerState->m_CurrentPlayerOptions.m_fScrollSpeed; if( pPlayerState->m_CurrentPlayerOptions.m_fRandomSpeed > 0 && !bAbsolute ) { int seed = GAMESTATE->m_iStageSeed + ( BeatToNoteRow( fNoteBeat ) << 8 ) + (iCol * 100); /* Temporary hack: the first call to RandomFloat isn't "random"; it takes an extra * call to get the RNG rolling. */ RandomFloat( seed ); float fRandom = RandomFloat( seed ); /* Random speed always increases speed: a random speed of 10 indicates [1,11]. * This keeps it consistent with other mods: 0 means no effect. */ fScrollSpeed *= SCALE( fRandom, 0.0f, 1.0f, 1.0f, pPlayerState->m_CurrentPlayerOptions.m_fRandomSpeed + 1.0f ); } if( fAccels[PlayerOptions::ACCEL_EXPAND] != 0 ) { /* Timers can't be global, since they'll be initialized before SDL. */ static RageTimer timerExpand; if( !GAMESTATE->m_bFreeze ) g_fExpandSeconds += timerExpand.GetDeltaTime(); else timerExpand.GetDeltaTime(); // throw away float fExpandMultiplier = SCALE( RageFastCos(g_fExpandSeconds*3), -1, 1, 0.75f, 1.75f ); fScrollSpeed *= SCALE( fAccels[PlayerOptions::ACCEL_EXPAND], 0.f, 1.f, 1.f, fExpandMultiplier ); } fYOffset *= fScrollSpeed; fPeakYOffsetOut *= fScrollSpeed; return fYOffset; }
void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, float fYHead, float fYTail, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fYStartOffset, float fYEndOffset ) { // // Draw the body (always wavy) // StripBuffer queue; Sprite* pSprBody = GetHoldBodySprite( NoteRowToBeat(iRow), tn.subType == TapNote::hold_head_roll, bIsBeingHeld ); pSprBody->SetZoom( ArrowEffects::GetZoom( m_pPlayerState ) ); // draw manually in small segments RageTexture* pTexture = pSprBody->GetTexture(); const RectF *pRect = pSprBody->GetCurrentTextureCoordRect(); DISPLAY->ClearAllTextures(); DISPLAY->SetTexture( 0, pTexture ); DISPLAY->SetBlendMode( BLEND_NORMAL ); DISPLAY->SetCullMode( CULL_NONE ); DISPLAY->SetTextureWrapping( true ); const float fFrameWidth = pSprBody->GetZoomedWidth(); const float fFrameHeight = pSprBody->GetZoomedHeight(); const float fYBodyTop = fYHead + cache->m_iStartDrawingHoldBodyOffsetFromHead; const float fYBodyBottom = fYTail + cache->m_iStopDrawingHoldBodyOffsetFromTail; const bool bReverse = m_pPlayerState->m_CurrentPlayerOptions.GetReversePercentForColumn(iCol) > 0.5f; bool bAnchorToBottom = bReverse && cache->m_bFlipHeadAndTailWhenReverse; if( bGlow ) fColorScale = 1; /* Only draw the section that's within the range specified. If a hold note is * very long, don't process or draw the part outside of the range. Don't change * fYBodyTop or fYBodyBottom; they need to be left alone to calculate texture * coordinates. */ float fDrawYBodyTop; float fDrawYBodyBottom; { float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYStartOffset, m_fYReverseOffsetPixels ); float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYEndOffset, m_fYReverseOffsetPixels ); fDrawYBodyTop = max( fYBodyTop, bReverse ? fYEndPos : fYStartPos ); fDrawYBodyBottom = min( fYBodyBottom, bReverse ? fYStartPos : fYEndPos ); } // top to bottom bool bAllAreTransparent = true; bool bLast = false; float fVertTexCoordOffset = 0; for( float fY = fDrawYBodyTop; !bLast; fY += fYStep ) { if( fY >= fDrawYBodyBottom ) { fY = fDrawYBodyBottom; bLast = true; } const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); // XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here. const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180; // if we're rotating, we need to modify the X and Z coords for the outer edges. const float fRotOffsetX = fFrameWidth/2 * RageFastCos(fRotationY); const float fRotOffsetZ = fFrameWidth/2 * RageFastSin(fRotationY); const float fXLeft = fX - fRotOffsetX; const float fXRight = fX + fRotOffsetX; const float fZLeft = fZ - fRotOffsetZ; const float fZRight = fZ + fRotOffsetZ; const float fDistFromBodyBottom = fYBodyBottom - fY; const float fDistFromBodyTop = fY - fYBodyTop; float fTexCoordTop = SCALE( bAnchorToBottom ? fDistFromBodyTop : fDistFromBodyBottom, 0, fFrameHeight, pRect->bottom, pRect->top ); /* For very large hold notes, shift the texture coordinates to be near 0, so we * don't send very large values to the renderer. */ if( fY == fDrawYBodyTop ) // first fVertTexCoordOffset = floorf( fTexCoordTop ); fTexCoordTop -= fVertTexCoordOffset; const float fTexCoordLeft = pRect->left; const float fTexCoordRight = pRect->right; const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const RageColor color = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); if( fAlpha > 0 ) bAllAreTransparent = false; queue.v[0].p = RageVector3(fXLeft, fY, fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); queue.v[1].p = RageVector3(fXRight, fY, fZRight); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordRight, fTexCoordTop); queue.v+=2; if( queue.Free() < 2 ) { /* The queue is full. Render it, clear the buffer, and move back a step to * start off the quad strip again. */ if( !bAllAreTransparent ) queue.Draw(); queue.Init(); bAllAreTransparent = true; fY -= fYStep; } } if( !bAllAreTransparent ) queue.Draw(); }