class Effect_ShadowProjector extends Projector config(user); var() vector LightDirection; var() float LightDistance, InterpolationRate, MaxFOV, FadeSpeed, DarknessScale; var ShadowBitmapMaterial ShadowTexture; var bool bFadeIn; var bool bLastAggressiveLOD; var bool bCurrentBlobbiness; var bool bDirtyNextTime; var float DetailDropTime; var float DetailIncreaseTime; var globalconfig enum DetailEnum { VeryLow, Low, Medium, High, Maximum } ShadowDetail; 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.Owner != None) { // set the Owner SetOwner(ShadowController.Owner); // allocate the shadow texture if(ShadowTexture == None) CreateShadowTexture(); // set projector texture //ProjTexture = ShadowTexture; // already done in Create // initialize the shadow texture if (ShadowTexture != None) { ShadowTexture.Invalid = false; ShadowTexture.ShadowActor = Owner; ShadowTexture.bBlobShadow = false; ShadowTexture.CullDistance = CullDistance; //Owner.AttachToBone(self, 'shadow'); } else { log(Name$".InitializeFor: No shadow texture! Escaping..."); Destroy(); } } else log(Name$".InitializeFor: No Owner!"); } // updates this shadow's location, rotation, and FOV simulated 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; local float DrawS; if(Level.bAggressiveLOD) return; DetachProjector(true); if (Owner == None || Owner.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 - Owner.LastRenderTime > 5) CullDistance = 0.5*Default.CullDistance; else CullDistance = Default.CullDistance; // cull shadows much earlier if below min framerate, important if (Level.bDropDetail) { if(Level.bAggressiveLOD && bLastAggressiveLOD) { //log(self@"aggressivelod true"); DropShadowDetail(); } ShadowTexture.CullDistance = 0.6*CullDistance; } else { if(!bLastAggressiveLOD) IncreaseShadowDetail(); ShadowTexture.CullDistance = CullDistance; } bLastAggressiveLOD = Level.bAggressiveLOD; // in case shadow was disabled earlier ShadowTexture.ShadowActor = Owner; // 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 - Owner.Location; // set light distance if (ShadowLight.LightEffect == LE_Sunlight) LightDistance = ShadowController.MaxLightDistance*0.3; else LightDistance = FClamp(VSize(Diff), ShadowController.MaxLightDistance*0.1, ShadowController.MaxLightDistance*0.3); MaxTraceDistance = LightDistance * 3; // get a location along the path of the light slightly away from center of player ShadowLocation = Owner.Location + 4*Normal(Diff); if (ShadowLocation.Z < Owner.Location.Z) ShadowLocation.Z = Owner.Location.Z+4; //ShadowLocation = Owner.GetBoneCoords('shadow').Origin; // set location SetLocation(ShadowLocation + vect(0,0,-8)); // calculate FOV BoundingSphere = Owner.GetRenderBoundingSphere(); FOV = (Atan(BoundingSphere.W*2 + 160, LightDistance) * 180/PI); // set rotation, compensate for FOV warping -- kinda hackish, but fixes shadows that // bend so much they detach from the pawn AdjustedRotation = rotator(Owner.Location-ShadowLight.Location); AdjustedRotation.Pitch = Clamp(AdjustedRotation.Pitch, -20500, -9500); SetRotation(AdjustedRotation); // determine correct direction of light LightDirection = -vector(AdjustedRotation)*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 DrawS = (LightDistance * 0.82) * tan(0.5 * FOV * PI / 180) / (0.45 * ShadowTexture.USize); SetDrawScale(DrawS); // fade out gracefully if (bFadeOut) ShadowTexture.ShadowDarkness = Max(ShadowTexture.ShadowDarkness - (FadeSpeed*dt), 0); else { ShadowTexture.ShadowDarkness = 140*(1.0-(FClamp(VSize(ShadowLight.Location-Owner.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 bDirtyNextTime = !bDirtyNextTime; if(!Level.bAggressiveLOD || bDirtyNextTime) ShadowTexture.Dirty = true; // reattach projector AttachProjector(); } 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(); } simulated function DropShadowDetail() { local bool bChangeQuality; if(ShadowTexture == None) return; if(!Level.bAggressiveLOD) return; if(Level.TimeSeconds - DetailDropTime < 0.5) return; // ignore dropdetails if it's been less than 0.5 sec //log(self@" ... bing... Decreasing shadow detail from"@ShadowDetail); switch(ShadowDetail) { case Maximum: bCurrentBlobbiness = False; ShadowDetail = High; bChangeQuality = True; break; case High: bCurrentBlobbiness = False; ShadowDetail = Medium; bChangeQuality = True; break; case Medium: bCurrentBlobbiness = False; ShadowDetail = Low; bChangeQuality = True; break; case Low: bCurrentBlobbiness = False; ShadowDetail = VeryLow; bChangeQuality = True; break; case VeryLow: bCurrentBlobbiness = True; ShadowDetail = VeryLow; ShadowTexture.bBlobShadow = true; break; } if(bChangeQuality) { DetailDropTime = Level.TimeSeconds; if(!ShadowTexture.Invalid) Level.ObjectPool.FreeObject(ShadowTexture); ShadowTexture = None; } if(ShadowTexture == None) { CreateShadowTexture(); } } simulated function IncreaseShadowDetail() { local bool bChangeQuality; if(Level.TimeSeconds - DetailIncreaseTime < 5 || ShadowDetail == Maximum) return; if(!Level.bDropDetail) { //log(self@" ... bing... Increasing shadow detail from"@ShadowDetail); switch(ShadowDetail) { case VeryLow: if(ShadowTexture.bBlobShadow) { ShadowTexture.bBlobShadow = false; bCurrentBlobbiness = false; } else { ShadowDetail = Low; bCurrentBlobbiness = false; bChangeQuality = True; } break; case Low: bCurrentBlobbiness = False; ShadowDetail = Medium; bChangeQuality = True; break; case Medium: bCurrentBlobbiness = False; ShadowDetail = High; bChangeQuality = True; break; case High: bCurrentBlobbiness = False; ShadowDetail = Maximum; bChangeQuality = True; break; } log(self@"New shadow detail="@ShadowDetail); } if(bChangeQuality && ShadowTexture != None) { DetailIncreaseTime = Level.TimeSeconds; if(!ShadowTexture.Invalid) Level.ObjectPool.FreeObject(ShadowTexture); ShadowTexture = None; } if(ShadowTexture == None) CreateShadowTexture(); } simulated function CreateShadowTexture() { switch (ShadowDetail) { case VeryLow: ShadowTexture=ShadowBitmapMaterial(Level.ObjectPool.AllocateObject(class'Effect_ShadowBitmapMaterialVLow')); break; case Medium: ShadowTexture = ShadowBitmapMaterial(Level.ObjectPool.AllocateObject(class'Effect_ShadowBitmapMaterialMed')); 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; } if(ShadowTexture != None) { ShadowTexture.bBlobShadow = bCurrentBlobbiness; ShadowTexture.Invalid = false; ShadowTexture.ShadowActor = Owner; ShadowTexture.CullDistance = CullDistance; ProjTexture=ShadowTexture; } } // You can delete this function if you're using it on a game that has bNoProjectOnOwner, then set that // in the Default Props. simulated event Touch(Actor Other) { if(Other == Owner) return; super.Touch(Other); } defaultproperties { ShadowDetail=Low bProjectActor=true // how quickly we should interpolate InterpolationRate=2.5 // this should be dynamically updated also MaxTraceDistance=3500 // maximum FOV MaxFOV=80.0 // how quickly to fade in/out FadeSpeed=500.0 bProjectOnParallelBSP=true bProjectOnAlpha=false bProjectStaticMesh=True bProjectParticles=False bClipBSP=False bGradient=True bStatic=false bOwnerNoSee=False bDynamicAttach=true bHidden=False RemoteRole=ROLE_None CullDistance=1200.0 }