Fade into the Winners Circle instead of cutting to it
The podium arrived as a hard cut: the race was still on screen one frame and the stand was there the next. The race has its own fade-to-black already, and it was being suppressed to keep the fade from blacking out the podium behind it - which threw away the transition along with the problem. Now the two are sequenced. StopMission lets the race fade out as it always did and posts the podium to itself for when that fade has landed on black; the handler stands the finishers up behind the black and ramps back in. The fade-in is the end-of-mission fade run backwards - the same multiply on the fog colour and both fog distances, from nothing up to what the Winners Circle asked for. Timings: 0.7s of fade-out and black, then a 0.45s fade in. Both come out of the 11 second hold, leaving about ten seconds of podium. RP412PODIUMFADEIN sets the ramp. Verified by measuring frame brightness across the transition. The race falls away and the screen reaches black, then the stand comes up - and with the ramp stretched to 3s to make it resolvable at a half-second sampling interval, it climbs 71.8, 77, 78.7, 79.7, 80.5 rather than stepping, so it is a real fade and not a cut arriving late. The camera also comes down and tilts up across the tiers, which is how a podium wants to be shot. It is a balance in both directions: drop it further or tilt harder and the sky takes the top half while the winner's spot slides off the bottom of the frame; tilt down instead and the shot turns into a floor plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+74
-13
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user