//============================================================================= // © 2004 Matt 'SquirrelZero' Farber //============================================================================= // Similar to ShadowProjector, just a lot better. This shadow projector, while // always in the same relative location to the player model, will dynamically // adjust its FOV and also feed the ShadowBitmapMaterial new lighting data on // the fly. It uses a more exaggerated FOV than the standard UT200x shadows, // because it looks so much cooler :). It also uses the ShadowCrispness // setting of the ShadowController to set a larger ShadowBitmapMaterial size. // Note that the bigger the shadow material the more data is being passed to // video each frame, so performance will worsen exponentially as you go higher. // I had to do a lot of little tweaks and hack some settings in to get the // projector to do what i wanted, and not cut off at strange angles. //============================================================================= class Effect_ShadowProjector extends Projector; var() vector LightDirection; var() float LightDistance, InterpolationRate, MaxFOV, FadeSpeed, DarknessScale; var ShadowBitmapMaterial ShadowTexture; var bool bFadeIn; function PostBeginPlay() { Super(Actor).PostBeginPlay(); } // these don't need to tick, we update all shadows at the same time in the controller function Tick(float dt) {} // this turns the shadow off function DisableShadow() { // detach DetachProjector(); // stop shadow texture from being reuploaded to video here if (ShadowTexture != None) { ShadowTexture.Dirty = false; ShadowTexture.ShadowActor = None; } } // initialize function InitializeFor(Effect_ShadowController ShadowController) { if (ShadowController.Instigator != None) { // set the instigator Instigator = ShadowController.Instigator; // allocate the shadow texture switch (ShadowController.ShadowCrispness) { case Medium: ShadowTexture = ShadowBitmapMaterial(Level.ObjectPool.AllocateObject(class'Effect_ShadowBitmapMaterialMedium')); break; case High: ShadowTexture = ShadowBitmapMaterial(Level.ObjectPool.AllocateObject(class'Effect_ShadowBitmapMaterialHigh')); break; case Maximum: ShadowTexture = ShadowBitmapMaterial(Level.ObjectPool.AllocateObject(class'Effect_ShadowBitmapMaterialMax')); break; Default: ShadowTexture = ShadowBitmapMaterial(Level.ObjectPool.AllocateObject(class'Effect_ShadowBitmapMaterialLow')); break; } // set projector texture ProjTexture = ShadowTexture; // initialize the shadow texture if (ShadowTexture != None) { ShadowTexture.Invalid = false; ShadowTexture.ShadowActor = Instigator; ShadowTexture.bBlobShadow = false; ShadowTexture.CullDistance = CullDistance; } else { log(Name$".InitializeFor: No shadow texture! Escaping..."); Destroy(); } } else log(Name$".InitializeFor: No Instigator!"); } // updates this shadow's location, rotation, and FOV function UpdateShadow(float dt, int LN, Effect_ShadowController ShadowController) { local Plane BoundingSphere; local Actor ShadowLight; local vector Diff, ShadowLocation, Origin; local rotator LightRotation, AdjustedRotation; local float Interpolation; local bool bFadeOut; // detach projector DetachProjector(true); // fallback, don't draw if hidden or no shadow texture if (Instigator == None || Instigator.bHidden || ShadowTexture == None) return; // fallback and destroy if (ShadowTexture.Invalid) { Destroy(); return; } // cull more if we haven't rendered this pawn in 5 seconds if (Level.TimeSeconds - Instigator.LastRenderTime > 5) CullDistance = 0.5*Default.CullDistance; else CullDistance = Default.CullDistance; // cull shadows much earlier if below min framerate, important if (Level.bDropDetail) ShadowTexture.CullDistance = 0.6*CullDistance; else ShadowTexture.CullDistance = CullDistance; // in case shadow was disabled earlier ShadowTexture.ShadowActor = Instigator; // set light ShadowLight = ShadowController.Lights[LN].CurrentLight; // fade out if necessary if (ShadowController.Lights[LN].LastLight != None) { if (ShadowTexture.ShadowDarkness > 5) { ShadowLight = ShadowController.Lights[LN].LastLight; bFadeOut = true; } else { ShadowController.Lights[LN].LastLight = None; bFadeOut = false; bFadeIn = true; DarknessScale = 0; } } // fallback if no more lights after fadeout if (ShadowLight == None) return; // get the direction of the light Diff = ShadowLight.Location - Instigator.Location; // set light distance if (ShadowLight.LightEffect == LE_Sunlight) LightDistance = ShadowController.MaxLightDistance*0.75; else LightDistance = FMin(VSize(Diff), ShadowController.MaxLightDistance); // get a location along the path of the light slightly away from center of player ShadowLocation = Instigator.Location + (4*vector(rotator(Diff))); // if the projector is behind the light, move it forward if (VSize(ShadowLocation-Instigator.Location) > LightDistance) ShadowLocation = ShadowLight.Location; // set location SetLocation(ShadowLocation + vect(0,0,-8)); // determine correct rotation, interpolate Origin = ShadowLocation + ShadowTexture.LightDirection * ShadowTexture.LightDistance; Interpolation = FMin(1.0, (dt*InterpolationRate)); Origin += (ShadowLight.Location - Origin) * Interpolation; Diff = ShadowLocation - Origin; LightRotation = rotator(Diff); // calculate FOV BoundingSphere = Instigator.GetRenderBoundingSphere(); FOV = (Atan(BoundingSphere.W*2 + 160, LightDistance) * 180/PI)*0.9; // set rotation, compensate for FOV warping -- kinda hackish, but fixes shadows that // bend so much they detach from the pawn AdjustedRotation = rotator(Instigator.Location-ShadowLight.Location); AdjustedRotation.Pitch -= 1280*(FOV/MaxFOV); SetRotation(AdjustedRotation); // determine correct direction of light LightDirection = -vector(rotator(Instigator.Location-ShadowLight.Location))*LightDistance; // set light direction ShadowTexture.LightDirection = Normal(LightDirection); // set lightdistance ShadowTexture.LightDistance = LightDistance; // update shadow texture FOV ShadowTexture.LightFOV = FOV; // set the drawscale, exaggerate a bit SetDrawScale( (LightDistance*0.82) * tan(0.5*FOV*PI/180) / (0.45*ShadowTexture.USize)); // fade out gracefully if (bFadeOut) ShadowTexture.ShadowDarkness = Max(ShadowTexture.ShadowDarkness - (FadeSpeed*dt), 0); else { ShadowTexture.ShadowDarkness = 180*(1.0-(FClamp(VSize(ShadowLight.Location-Instigator.Location)/ShadowController.MaxLightDistance, 0.0, 1.0))) + 70; if (bFadeIn && DarknessScale < 1.0) { DarknessScale = FMin(DarknessScale + ((FadeSpeed*0.007)*dt), 1.0); ShadowTexture.ShadowDarkness = float(ShadowTexture.ShadowDarkness)*DarknessScale; } else bFadeIn = false; } // dirty texture for reuploading to video ShadowTexture.Dirty = true; // reattach projector AttachProjector(); } simulated function Destroyed() { // free shadow texture from the object pool if (ShadowTexture != None) { ShadowTexture.ShadowActor = None; if (!ShadowTexture.Invalid) Level.ObjectPool.FreeObject(ShadowTexture); // must set to none ShadowTexture = None; ProjTexture = None; } Super.Destroyed(); } defaultproperties { InterpolationRate=2.500000 MaxFOV=80.000000 FadeSpeed=550.000000 MaxTraceDistance=275 bClipBSP=True bProjectOnParallelBSP=True bDynamicAttach=True bNoProjectOnOwner=True CullDistance=1200.000000 bStatic=False bAlwaysRelevant=True RemoteRole=ROLE_SimulatedProxy bOwnerNoSee=True bNotOnDedServer=False }