void NoteDisplay::DrawHoldHead( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, float fYHead, float fPercentFadeToFail, float fColorScale, bool bGlow, float fYStartOffset, float fYEndOffset ) { // // Draw the head // Actor* pActor = GetHoldHeadActor( NoteRowToBeat(iRow), tn.subType == TapNote::hold_head_roll, bIsBeingHeld ); pActor->SetZoom( ArrowEffects::GetZoom( m_pPlayerState ) ); // draw with normal Sprite const float fY = fYHead; const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); if( fYOffset < fYStartOffset || fYOffset > fYEndOffset ) return; // TRICKY: skew the rotation by a few pixels so this lines up with the start of the twirly hold. const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset+16 ); const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const RageColor colorDiffuse= RageColor(fColorScale,fColorScale,fColorScale,fAlpha); const RageColor colorGlow = RageColor(1,1,1,fGlow); pActor->SetRotationY( fRotationY ); pActor->SetRotationZ( 0 ); pActor->SetXY( fX, fY ); pActor->SetZ( fZ ); if( bGlow ) { pActor->SetDiffuse( RageColor(1,1,1,0) ); pActor->SetGlow( colorGlow ); } else { pActor->SetDiffuse( colorDiffuse ); pActor->SetGlow( RageColor(0,0,0,0) ); } if( cache->m_bHoldHeadUseLighting ) { DISPLAY->SetLighting( true ); DISPLAY->SetLightDirectional( 0, RageColor(1,1,1,1), RageColor(1,1,1,1), RageColor(1,1,1,1), RageVector3(1, 0, +1) ); } pActor->Draw(); if( cache->m_bHoldHeadUseLighting ) { DISPLAY->SetLightOff( 0 ); DISPLAY->SetLighting( false ); } }
void NoteDisplay::DrawHoldTail( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, float fYTail, float fPercentFadeToFail, float fColorScale, bool bGlow, float fYStartOffset, float fYEndOffset ) { // // Draw the tail // Actor* pSprTail = GetHoldTailActor( NoteRowToBeat(iRow), tn.subType == TapNote::hold_head_roll, bIsBeingHeld ); pSprTail->SetZoom( ArrowEffects::GetZoom( m_pPlayerState ) ); const float fY = fYTail; const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); if( fYOffset < fYStartOffset || fYOffset > fYEndOffset ) return; const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ); const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const RageColor colorDiffuse= RageColor(fColorScale,fColorScale,fColorScale,fAlpha); const RageColor colorGlow = RageColor(1,1,1,fGlow); pSprTail->SetRotationY( fRotationY ); pSprTail->SetXY( fX, fY ); pSprTail->SetZ( fZ ); if( bGlow ) { pSprTail->SetDiffuse( RageColor(1,1,1,0) ); pSprTail->SetGlow( colorGlow ); } else { pSprTail->SetDiffuse( colorDiffuse ); pSprTail->SetGlow( RageColor(0,0,0,0) ); } if( cache->m_bHoldTailUseLighting ) { DISPLAY->SetLighting( true ); DISPLAY->SetLightDirectional( 0, RageColor(1,1,1,1), RageColor(1,1,1,1), RageColor(1,1,1,1), RageVector3(1, 0, +1) ); } pSprTail->Draw(); if( cache->m_bHoldTailUseLighting ) { DISPLAY->SetLightOff( 0 ); DISPLAY->SetLighting( false ); } }
static void Serialize( const NoteData &o, Json::Value &root ) { root = Json::Value(Json::arrayValue); for(int t=0; t < o.GetNumTracks(); t++ ) { NoteData::TrackMap::const_iterator begin, end; o.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end ); //NoteData::TrackMap tm = o.GetTrack(t); //FOREACHM_CONST( int, TapNote, tm, iter ) for( ; begin != end; ++begin ) { int iRow = begin->first; TapNote tn = begin->second; root.resize( root.size()+1 ); Json::Value &root2 = root[ root.size()-1 ]; root2 = Json::Value(Json::arrayValue); root2.resize(3); root2[(unsigned)0] = NoteRowToBeat(iRow); root2[1] = t; Serialize( tn, root2[2] ); } } }
void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change ) { int iStartRow = BeatToNoteRow(fFirstBeat); int iEndRow = BeatToNoteRow(fEndBeat); const TimingData &timing = m_pSong->m_SongTiming; // change BG every time signature change or 4 measures const vector<TimingSegment *> &tSigs = timing.GetTimingSegments(SEGMENT_TIME_SIG); for (unsigned i = 0; i < tSigs.size(); i++) { TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[i]); int iSegmentEndRow = (i + 1 == tSigs.size()) ? iEndRow : tSigs[i+1]->GetRow(); for(int j=max(ts->GetRow(),iStartRow); j<min(iEndRow,iSegmentEndRow); j+=4*ts->GetNoteRowsPerMeasure()) { // Don't fade. It causes frame rate dip, especially on slower machines. BackgroundDef bd = m_Layer[0].CreateRandomBGA(m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this); if( !bd.IsEmpty() ) { BackgroundChange c = change; c.m_def = bd; c.m_fStartBeat = NoteRowToBeat(j); m_Layer[0].m_aBGChanges.push_back( c ); } } } // change BG every BPM change that is at the beginning of a measure const vector<TimingSegment *> &bpms = timing.GetTimingSegments(SEGMENT_BPM); for( unsigned i=0; i<bpms.size(); i++ ) { bool bAtBeginningOfMeasure = false; for (unsigned j=0; j<tSigs.size(); j++) { TimeSignatureSegment *ts = static_cast<TimeSignatureSegment *>(tSigs[j]); if ((bpms[i]->GetRow() - ts->GetRow()) % ts->GetNoteRowsPerMeasure() == 0) { bAtBeginningOfMeasure = true; break; } } if( !bAtBeginningOfMeasure ) continue; // skip // start so that we don't create a BGChange right on top of fEndBeat bool bInRange = bpms[i]->GetRow() >= iStartRow && bpms[i]->GetRow() < iEndRow; if( !bInRange ) continue; // skip BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this ); if( !bd.IsEmpty() ) { BackgroundChange c = change; c.m_def.m_sFile1 = bd.m_sFile1; c.m_def.m_sFile2 = bd.m_sFile2; c.m_fStartBeat = bpms[i]->GetBeat(); m_Layer[0].m_aBGChanges.push_back( c ); } } }
float NoteData::GetLastBeat() const { return NoteRowToBeat( GetLastRow() ); }
void AutoKeysounds::LoadAutoplaySoundsInto( RageSoundReader_Chain *pChain ) { // // Load sounds. // Song* pSong = GAMESTATE->m_pCurSong; RString sSongDir = pSong->GetSongDir(); /* * Add all current autoplay sounds in both players to the chain. */ int iNumTracks = m_ndAutoKeysoundsOnly[GAMESTATE->GetMasterPlayerNumber()].GetNumTracks(); for( int t = 0; t < iNumTracks; t++ ) { int iRow = -1; while(1) { /* Find the next row that either player has a note on. */ int iNextRow = INT_MAX; FOREACH_EnabledPlayer(pn) { // XXX Hack. Enabled players need not have their own note data. if( t >= m_ndAutoKeysoundsOnly[pn].GetNumTracks() ) continue; int iNextRowForPlayer = iRow; /* XXX: If a BMS file only has one tap note per track, * this will prevent any keysounds from loading. * This leads to failure later on. * We need a better way to prevent this. */ if( m_ndAutoKeysoundsOnly[pn].GetNextTapNoteRowForTrack( t, iNextRowForPlayer ) ) iNextRow = min( iNextRow, iNextRowForPlayer ); } if( iNextRow == INT_MAX ) break; iRow = iNextRow; TapNote tn[NUM_PLAYERS]; FOREACH_EnabledPlayer(pn) tn[pn] = m_ndAutoKeysoundsOnly[pn].GetTapNote( t, iRow ); FOREACH_EnabledPlayer(pn) { if( tn[pn] == TAP_EMPTY ) continue; ASSERT( tn[pn].type == TapNoteType_AutoKeysound ); if( tn[pn].iKeysoundIndex >= 0 ) { RString sKeysoundFilePath = sSongDir + pSong->m_vsKeysoundFile[tn[pn].iKeysoundIndex]; float fSeconds = GAMESTATE->m_pCurSteps[pn]->GetTimingData()->GetElapsedTimeFromBeatNoOffset( NoteRowToBeat(iRow) ) + SOUNDMAN->GetPlayLatency(); float fPan = 0; // If two players are playing, pan the keysounds to each player's respective side if( GAMESTATE->GetNumPlayersEnabled() == 2 ) fPan = (pn == PLAYER_1)? -1.0f:+1.0f; int iIndex = pChain->LoadSound( sKeysoundFilePath ); pChain->AddSound( iIndex, fSeconds, fPan ); } } } } }
void NoteDataWithScoring::GetActualRadarValues(const NoteData &in, const PlayerStageStats &pss, float song_seconds, RadarValues& out) { // Anybody editing this function should also examine // NoteDataUtil::CalculateRadarValues to make sure it handles things the // same way. // Some of this logic is similar or identical to // NoteDataUtil::CalculateRadarValues because I couldn't figure out a good // way to combine them into one. -Kyz PlayerNumber pn= pss.m_player_number; garv_state state; NoteData::all_tracks_const_iterator curr_note= in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); TimingData* timing= GAMESTATE->GetProcessedTimingData(); // first_hittable_row and last_hittable_row exist so that // GetActualVoltageRadarValue can be passed the correct song length. // GetActualVoltageRadarValue scores based on the max combo, a full combo // is a full voltage score. The song length is used instead of trying to // figure out the max combo for the song because rolls mean there isn't a // limit to the max combo. -Kyz int first_hittable_row= -1; int last_hittable_row= -1; bool tick_holds= GAMESTATE->GetCurrentGame()->m_bTickHolds; while(!curr_note.IsAtEnd()) { if(curr_note.Row() != state.curr_row) { DoRowEndRadarActualCalc(state, out); state.curr_row= curr_note.Row(); state.num_notes_on_curr_row= 0; state.num_holds_on_curr_row= 0; state.judgable= timing->IsJudgableAtRow(state.curr_row); for(size_t n= 0; n < state.hold_ends.size(); ++n) { if(state.hold_ends[n].end_row < state.curr_row) { state.hold_ends.erase(state.hold_ends.begin() + n); --n; } } state.last_tns_on_row= TapNoteScore_Invalid; state.last_time_on_row= -9999; state.worst_tns_on_row= TapNoteScore_Invalid; } bool for_this_player= curr_note->pn == pn || pn == PLAYER_INVALID || curr_note->pn == PLAYER_INVALID; if(state.judgable && for_this_player) { switch(curr_note->type) { case TapNoteType_HoldTail: // If there are tick holds, then the hold tail needs to be counted // in last_hittable_row because that's where the combo will end. // -Kyz if(tick_holds) { UpdateHittable(state.curr_row, first_hittable_row, last_hittable_row); } break; case TapNoteType_Tap: case TapNoteType_HoldHead: // HoldTails and Attacks are counted by IsTap. But it doesn't // make sense to count HoldTails as hittable notes. -Kyz case TapNoteType_Attack: case TapNoteType_Lift: UpdateHittable(state.curr_row, first_hittable_row, last_hittable_row); ++state.num_notes_on_curr_row; state.notes_hit_for_stream+= (curr_note->result.tns >= state.stream_tns); state.notes_hit+= (curr_note->result.tns >= state.taps_tns); if(curr_note->result.tns < state.worst_tns_on_row) { state.worst_tns_on_row= curr_note->result.tns; } if(curr_note->result.fTapNoteOffset > state.last_time_on_row) { state.last_time_on_row= curr_note->result.fTapNoteOffset; state.last_tns_on_row= curr_note->result.tns; } if(curr_note->type == TapNoteType_HoldHead) { if(curr_note->subType == TapNoteSubType_Hold) { state.holds_held+= (curr_note->HoldResult.hns == HNS_Held); } else if(curr_note->subType == TapNoteSubType_Roll) { state.rolls_held+= (curr_note->HoldResult.hns == HNS_Held); } state.hold_ends.push_back( hold_status(state.curr_row + curr_note->iDuration, curr_note->HoldResult.iLastHeldRow)); ++state.num_holds_on_curr_row; } else if(curr_note->type == TapNoteType_Lift) { state.lifts_hit+= (curr_note->result.tns >= state.lifts_tns); } break; case TapNoteType_Mine: state.mines_avoided+= (curr_note->result.tns == TNS_AvoidMine); break; case TapNoteType_Fake: default: break; } } ++curr_note; } DoRowEndRadarActualCalc(state, out); // ScreenGameplay passes in the RadarValues that were calculated by // NoteDataUtil::CalculateRadarValues, so those are reused here. -Kyz int note_count= out[RadarCategory_Notes]; int jump_count= out[RadarCategory_Jumps]; int hold_count= out[RadarCategory_Holds]; int tap_count= out[RadarCategory_TapsAndHolds]; float hittable_steps_length= max(0, timing->GetElapsedTimeFromBeat(NoteRowToBeat(last_hittable_row)) - timing->GetElapsedTimeFromBeat(NoteRowToBeat(first_hittable_row))); // The for loop and the assert are used to ensure that all fields of // RadarValue get set in here. FOREACH_ENUM(RadarCategory, rc) { switch(rc) { case RadarCategory_Stream: out[rc]= clamp(float(state.notes_hit_for_stream) / note_count, 0.0f, 1.0f); break; case RadarCategory_Voltage: out[rc]= GetActualVoltageRadarValue(in, hittable_steps_length, pss); break; case RadarCategory_Air: out[rc]= clamp(float(state.jumps_hit_for_air) / jump_count, 0.0f, 1.0f); break; case RadarCategory_Freeze: out[rc]= clamp(float(state.holds_held) / hold_count, 0.0f, 1.0f); break; case RadarCategory_Chaos: out[rc]= GetActualChaosRadarValue(in, song_seconds, pss); break; case RadarCategory_TapsAndHolds: out[rc]= state.taps_hit; break; case RadarCategory_Jumps: out[rc]= state.jumps_hit; break; case RadarCategory_Holds: out[rc]= state.holds_held; break; case RadarCategory_Mines: out[rc]= state.mines_avoided; break; case RadarCategory_Hands: out[rc]= state.hands_hit; break; case RadarCategory_Rolls: out[rc]= state.rolls_held; break; case RadarCategory_Lifts: out[rc]= state.lifts_hit; break; case RadarCategory_Fakes: out[rc]= out[rc]; break; case RadarCategory_Notes: out[rc]= state.notes_hit; break; DEFAULT_FAIL(rc); } } }
void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeingHeld, bool bIsActive, const HoldNoteResult &Result, float fPercentFadeToFail, bool bDrawGlowOnly, float fReverseOffsetPixels, float fYStartOffset, float fYEndOffset ) { int iEndRow = iRow + tn.iDuration; // bDrawGlowOnly is a little hacky. We need to draw the diffuse part and the glow part one pass at a time to minimize state changes bool bReverse = m_pPlayerState->m_CurrentPlayerOptions.GetReversePercentForColumn(iCol) > 0.5f; float fStartBeat = NoteRowToBeat( max(tn.HoldResult.iLastHeldRow, iRow) ); float fThrowAway = 0; // HACK: If active, don't set YOffset to 0 so that it doesn't jiggle around the receptor. bool bStartIsPastPeak = true; float fStartYOffset = 0; if( bIsActive ) ; // use the default values filled in above else fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fStartBeat, fThrowAway, bStartIsPastPeak ); float fStartYPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fStartYOffset, fReverseOffsetPixels ); float fEndPeakYOffset = 0; bool bEndIsPastPeak = false; float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, NoteRowToBeat(iEndRow), fEndPeakYOffset, bEndIsPastPeak ); // In boomerang, the arrows reverse direction at Y offset value fPeakAtYOffset. // If fPeakAtYOffset lies inside of the hold we're drawing, then the we // want to draw the tail at that max Y offset, or else the hold will appear // to magically grow as the tail approaches the max Y offset. if( bStartIsPastPeak && !bEndIsPastPeak ) fEndYOffset = fEndPeakYOffset; // use the calculated PeakYOffset so that long holds don't appear to grow float fEndYPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fEndYOffset, fReverseOffsetPixels ); const float fYHead = bReverse ? fEndYPos : fStartYPos; // the center of the head const float fYTail = bReverse ? fStartYPos : fEndYPos; // the center the tail // const bool bWavy = GAMESTATE->m_PlayerOptions[m_PlayerNumber].m_fEffects[PlayerOptions::EFFECT_DRUNK] > 0; const bool WavyPartsNeedZBuffer = ArrowEffects::NeedZBuffer( m_pPlayerState ); /* Hack: Z effects need a finer grain step. */ const int fYStep = WavyPartsNeedZBuffer? 4: 16; //bWavy ? 16 : 128; // use small steps only if wavy const float fColorScale = tn.HoldResult.fLife + (1-tn.HoldResult.fLife)*cache->m_fHoldNGGrayPercent; bool bFlipHeadAndTail = bReverse && cache->m_bFlipHeadAndTailWhenReverse; /* The body and caps should have no overlap, so their order doesn't matter. * Draw the head last, so it appears on top. */ if( !cache->m_bHoldHeadIsAboveWavyParts ) DrawHoldHead( tn, iCol, iRow, bIsBeingHeld, bFlipHeadAndTail ? fYTail : fYHead, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); if( !cache->m_bHoldTailIsAboveWavyParts ) DrawHoldTail( tn, iCol, iRow, bIsBeingHeld, bFlipHeadAndTail ? fYHead : fYTail, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); if( bDrawGlowOnly ) DISPLAY->SetTextureModeGlow(); else DISPLAY->SetTextureModeModulate(); DISPLAY->SetZTestMode( WavyPartsNeedZBuffer?ZTEST_WRITE_ON_PASS:ZTEST_OFF ); DISPLAY->SetZWrite( WavyPartsNeedZBuffer ); if( !bFlipHeadAndTail ) DrawHoldBottomCap( tn, iCol, iRow, bIsBeingHeld, fYHead, fYTail, fYStep, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); DrawHoldBody( tn, iCol, iRow, bIsBeingHeld, fYHead, fYTail, fYStep, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); if( bFlipHeadAndTail ) DrawHoldTopCap( tn, iCol, iRow, bIsBeingHeld, fYHead, fYTail, fYStep, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); /* These set the texture mode themselves. */ if( cache->m_bHoldTailIsAboveWavyParts ) DrawHoldTail( tn, iCol, iRow, bIsBeingHeld, bFlipHeadAndTail ? fYHead : fYTail, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); if( cache->m_bHoldHeadIsAboveWavyParts ) DrawHoldHead( tn, iCol, iRow, bIsBeingHeld, bFlipHeadAndTail ? fYTail : fYHead, fPercentFadeToFail, fColorScale, bDrawGlowOnly, fYStartOffset, fYEndOffset ); // now, draw the glow pass if( !bDrawGlowOnly ) DrawHold( tn, iCol, iRow, bIsBeingHeld, bIsActive, Result, fPercentFadeToFail, true, fReverseOffsetPixels, fYStartOffset, fYEndOffset ); }
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(); }
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(); }
static int GetHoldDuration( T* p, lua_State* L ) { lua_pushnumber(L, NoteRowToBeat(p->iDuration)); return 1; }
float HoldNoteResult::GetLastHeldBeat() const { return NoteRowToBeat(iLastHeldRow); }