diff --git a/MUNGA_L4/L4VIDEO.cpp b/MUNGA_L4/L4VIDEO.cpp index ae38013..6e6d6eb 100644 --- a/MUNGA_L4/L4VIDEO.cpp +++ b/MUNGA_L4/L4VIDEO.cpp @@ -1803,6 +1803,12 @@ DPLRenderer::DPLRenderer( mPresentationCamera = False; mPresentationFog = False; mPresentationAspect = 0.0f; + mPresentationFadeTime = 0.0f; + mPresentationFogRed = 0.0f; + mPresentationFogGreen = 0.0f; + mPresentationFogBlue = 0.0f; + mPresentationFogNear = 0.0f; + mPresentationFogFar = 0.0f; D3DXMatrixIdentity(&mPresentationView); dplMainView = NULL; dplDeathZone = NULL; @@ -6109,6 +6115,36 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte lastFrameTime = mTargetRenderTime; currentFrameTime = Now(); + // + // Fade the presentation up out of black, if one is running. Same lever + // the end-of-mission fade pulls: scale the fog colour and both fog + // distances, here from nothing up to what the podium asked for. + // + if (mPresentationFadeTime > 0.0f) + { + Scalar remaining = mPresentationFadeEnd - Now(); + Scalar fade = 1.0f - (remaining / mPresentationFadeTime); + if (fade >= 1.0f) + { + fade = 1.0f; + mPresentationFadeTime = 0.0f; // done + } + if (fade < 0.0f) + { + fade = 0.0f; + } + + fogRed = mPresentationFogRed * fade; + fogGreen = mPresentationFogGreen * fade; + fogBlue = mPresentationFogBlue * fade; + currentFogNear = mPresentationFogNear * fade; + currentFogFar = mPresentationFogFar * fade; + + mDevice->SetRenderState(D3DRS_FOGCOLOR, + D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), + (int)(255 * fogBlue))); + } + // // Pillarbox: black the whole target first, then narrow the viewport so // everything after this - clear, scene, reticle - lands inside the crop @@ -7010,6 +7046,28 @@ void Check(this); mPresentationCamera = False; } + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Come up out of the black. SetFogStyle(winnersCircleFogStyle) has already +// put the target fog in place, so remember it and ramp toward it - the same +// multiply the end-of-mission fade uses, run the other way. +// +void + DPLRenderer::StartPresentationFadeIn(Scalar seconds) +{ + Check(this); + + mPresentationFogRed = fogRed; + mPresentationFogGreen = fogGreen; + mPresentationFogBlue = fogBlue; + mPresentationFogNear = currentFogNear; + mPresentationFogFar = currentFogFar; + + mPresentationFadeTime = (seconds > 0.0f) ? seconds : 0.01f; + mPresentationFadeEnd = Now(); + mPresentationFadeEnd += mPresentationFadeTime; +} // //############################################################################# // Startup the implementation of the Division video renderer diff --git a/MUNGA_L4/L4VIDEO.h b/MUNGA_L4/L4VIDEO.h index 4a1726c..5b2e3b2 100644 --- a/MUNGA_L4/L4VIDEO.h +++ b/MUNGA_L4/L4VIDEO.h @@ -335,6 +335,14 @@ public: //------------------------------------------------------------------ Logical InPresentation() const { return mPresentationFog; } + //------------------------------------------------------------------ + // Bring the presentation up out of black over the given seconds. The + // race's own fade-to-black has already run by this point; this is + // its mirror image, ramping the fog colour and both fog distances + // back up from nothing to what the Winners Circle asked for. + //------------------------------------------------------------------ + void StartPresentationFadeIn(Scalar seconds); + unsigned int* MakeBitSliceStorage(); void SortAndReloadNameBitmaps(); @@ -473,6 +481,11 @@ private: // out at the sides of a 16:9 canvas, so the shot is cropped to the // shape it was composed for and the surround left black. float mPresentationAspect; + // fade-in: end time, duration, and the fog it is ramping toward + Time mPresentationFadeEnd; + Scalar mPresentationFadeTime; + float mPresentationFogRed, mPresentationFogGreen, mPresentationFogBlue; + float mPresentationFogNear, mPresentationFogFar; void FindBestAdapterIndices(bool isWindowed); diff --git a/RP_L4/RPL4APP.cpp b/RP_L4/RPL4APP.cpp index 3a819b5..c43b5ef 100644 --- a/RP_L4/RPL4APP.cpp +++ b/RP_L4/RPL4APP.cpp @@ -29,7 +29,8 @@ const Receiver::HandlerEntry RPL4Application::MessageHandlerEntries[]= { - MESSAGE_ENTRY(RPL4Application, StopMission) + MESSAGE_ENTRY(RPL4Application, StopMission), + MESSAGE_ENTRY(RPL4Application, WinnersCircle) }; Receiver::MessageHandlerSet& RPL4Application::GetMessageHandlers() @@ -54,6 +55,16 @@ RPL4Application::SharedData RPL4Application::GetMessageHandlers() ); +// +// How long to sit on black between the race fading out and the podium +// fading in. The race fade is FADE_OUT_TIME (half a second), so this is +// that plus a beat, to land on black rather than on the tail of it. +// +const Scalar winnersCircleFadeOutTime = 0.7f; + +// and how quickly the stand comes up out of the black afterwards +const Scalar winnersCircleFadeInTime = 0.45f; + RPL4Application::RPL4Application( HINSTANCE hInstance, HWND hWnd, @@ -247,15 +258,16 @@ void // RP412PODIUMAIM height of the aim point above the group // // - // Framed off the stand itself: close enough that the platform fills the - // crop rather than trailing off into sky, high enough to look down the - // tiers, and aimed slightly below the group so the tilt buys back the - // dead sky above the grandstand. Aiming above the group tips the camera - // up and walks the winner's spot off the bottom of the frame. + // Framed off the stand itself: down low and tilted up across the tiers, + // which is how you photograph a podium. It is a balance in both + // directions - drop the camera further or tilt harder and the sky takes + // the top half while the winner's spot slides off the bottom; tilt down + // instead and it becomes a floor plan. Pillarboxing to 4:3 is what lets + // it sit this close without the platform trailing off at the sides. // - Scalar standoff = 33.0f; - Scalar height = 20.0f; - Scalar aim_lift = -2.0f; + Scalar standoff = 36.0f; + Scalar height = 12.0f; + Scalar aim_lift = 2.0f; const char *tune = getenv("RP412PODIUMSTANDOFF"); if (tune != NULL && atof(tune) != 0.0) standoff = (Scalar) atof(tune); tune = getenv("RP412PODIUMHEIGHT"); @@ -307,6 +319,18 @@ void << std::flush; } + // + // Everything is in place behind the black - bring it up. + // RP412PODIUMFADEIN sets the ramp in seconds. + // + Scalar fade_in = winnersCircleFadeInTime; + const char *fade_tune = getenv("RP412PODIUMFADEIN"); + if (fade_tune != NULL && atof(fade_tune) > 0.0) + { + fade_in = (Scalar) atof(fade_tune); + } + dpl_renderer->StartPresentationFadeIn(fade_in); + DEBUG_STREAM << "WinnersCircle: " << placed << " placed; centre " << standCentre.x << "," << standCentre.y << "," << standCentre.z << " eye " << eye.x << "," << eye.y << "," << eye.z @@ -331,19 +355,56 @@ void // StopMission arrives twice: once from the console at the buzzer, and // again from the player when the ending fade runs out - that second one // is what actually retires the application. Only the first is the end of - // the race, so only the first stands anybody up. + // the race. // if (GetApplicationState() != Application::EndingMission) { - DEBUG_STREAM << "WinnersCircle: mission stopped, standing the finishers up\n" - << std::flush; - ShowWinnersCircle(); + // + //------------------------------------------------------------- + // Don't cut straight to the podium. The race gets its own + // fade-to-black first - that fade is already running by the time + // this returns - and the Winners Circle comes up out of the + // black afterwards. Standing everyone up now would just fade out + // the podium instead of the race. + // + // FADE_OUT_TIME is half a second; a beat more than that lands on + // black rather than on the tail of the fade. + //------------------------------------------------------------- + // + Receiver::Message podium_message( + WinnersCircleMessageID, sizeof(Receiver::Message)); + + Time event_time; + event_time = Now(); + event_time += winnersCircleFadeOutTime; + + Post(LowEventPriority, this, &podium_message, event_time); + + DEBUG_STREAM << "WinnersCircle: race over, fading out\n" << std::flush; } L4Application::StopMissionMessageHandler(message); Check_Fpu(); } +// +//############################################################################# +// WinnersCircleMessageHandler +//############################################################################# +// +// The race has faded to black. Set the stand up behind the black and fade +// back in to it. +// +void + RPL4Application::WinnersCircleMessageHandler(Receiver::Message *) +{ + Check(this); + + DEBUG_STREAM << "WinnersCircle: standing the finishers up\n" << std::flush; + ShowWinnersCircle(); + Check_Fpu(); +} + // //############################################################################# // MakeRegistry diff --git a/RP_L4/RPL4APP.h b/RP_L4/RPL4APP.h index 1615ea6..c1a6037 100644 --- a/RP_L4/RPL4APP.h +++ b/RP_L4/RPL4APP.h @@ -36,6 +36,13 @@ private: // file. This is the same sequence on the path the pods actually race. // public: + enum + { + // posted at the buzzer, fires once the race has faded out + WinnersCircleMessageID = L4Application::NextMessageID, + NextMessageID + }; + static const HandlerEntry MessageHandlerEntries[]; static MessageHandlerSet& GetMessageHandlers(); @@ -45,6 +52,10 @@ public: void StopMissionMessageHandler(StopMissionMessage *message); + // the podium proper, once the screen is already black + void + WinnersCircleMessageHandler(Receiver::Message *message); + private: // stand the finishers on their ranked spots and frame the shot void