summaryrefslogtreecommitdiffstats
path: root/src/renderer/PointLights.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer/PointLights.cpp')
-rw-r--r--src/renderer/PointLights.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/src/renderer/PointLights.cpp b/src/renderer/PointLights.cpp
new file mode 100644
index 00000000..13872401
--- /dev/null
+++ b/src/renderer/PointLights.cpp
@@ -0,0 +1,330 @@
+#include "common.h"
+
+#include "main.h"
+#include "CutsceneMgr.h"
+#include "Lights.h"
+#include "Camera.h"
+#include "Weather.h"
+#include "World.h"
+#include "Collision.h"
+#include "Sprite.h"
+#include "Timer.h"
+#include "PointLights.h"
+
+int16 CPointLights::NumLights;
+CRegisteredPointLight CPointLights::aLights[NUMPOINTLIGHTS];
+CVector CPointLights::aCachedMapReads[32];
+float CPointLights::aCachedMapReadResults[32];
+int32 CPointLights::NextCachedValue;
+
+void
+CPointLights::Init(void)
+{
+ for(int i = 0; i < ARRAY_SIZE(aCachedMapReads); i++){
+ aCachedMapReads[i] = CVector(0.0f, 0.0f, 0.0f);
+ aCachedMapReadResults[i] = 0.0f;
+ }
+ NextCachedValue = 0;
+}
+
+void
+CPointLights::InitPerFrame(void)
+{
+ NumLights = 0;
+}
+
+#define MAX_DIST 22.0f
+
+void
+CPointLights::AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows)
+{
+ CVector dist;
+ float distance;
+
+ // The check is done in some weird way in the game
+ // we're doing it a bit better here
+ if(NumLights >= NUMPOINTLIGHTS)
+ return;
+
+ dist = coors - TheCamera.GetPosition();
+ if(Abs(dist.x) < MAX_DIST && Abs(dist.y) < MAX_DIST){
+ distance = dist.Magnitude();
+ if(distance < MAX_DIST){
+ aLights[NumLights].type = type;
+ aLights[NumLights].fogType = fogType;
+ aLights[NumLights].coors = coors;
+ aLights[NumLights].dir = dir;
+ aLights[NumLights].radius = radius;
+ aLights[NumLights].castExtraShadows = castExtraShadows;
+ if(distance < MAX_DIST*0.75f){
+ aLights[NumLights].red = red;
+ aLights[NumLights].green = green;
+ aLights[NumLights].blue = blue;
+ }else{
+ float fade = 1.0f - (distance/MAX_DIST - 0.75f)*4.0f;
+ aLights[NumLights].red = red * fade;
+ aLights[NumLights].green = green * fade;
+ aLights[NumLights].blue = blue * fade;
+ }
+ NumLights++;
+ }
+ }
+}
+
+float
+CPointLights::GenerateLightsAffectingObject(Const CVector *objCoors)
+{
+ int i;
+ float ret;
+ CVector dist;
+ float radius, distance;
+
+ ret = 1.0f;
+ for(i = 0; i < NumLights; i++){
+ if(aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS)
+ continue;
+
+ // same weird distance calculation. simplified here
+ dist = aLights[i].coors - *objCoors;
+ radius = aLights[i].radius;
+ if(Abs(dist.x) < radius &&
+ Abs(dist.y) < radius &&
+ Abs(dist.z) < radius){
+
+ distance = dist.Magnitude();
+ if(distance < radius){
+
+ float distNorm = distance/radius;
+ if(aLights[i].type == LIGHT_DARKEN){
+ // darken the object the closer it is
+ ret *= distNorm;
+ }else{
+ float intensity;
+ // distance fade
+ if(distNorm < 0.5f)
+ intensity = 1.0f;
+ else
+ intensity = 1.0f - (distNorm - 0.5f)/(1.0f - 0.5f);
+
+ if(distance != 0.0f){
+ CVector dir = dist / distance;
+
+ if(aLights[i].type == LIGHT_DIRECTIONAL){
+ float dot = -DotProduct(dir, aLights[i].dir);
+ intensity *= Max((dot-0.5f)*2.0f, 0.0f);
+ }
+
+ if(intensity > 0.0f)
+ AddAnExtraDirectionalLight(Scene.world,
+ dir.x, dir.y, dir.z,
+ aLights[i].red*intensity, aLights[i].green*intensity, aLights[i].blue*intensity);
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+extern RwRaster *gpPointlightRaster;
+
+void
+CPointLights::RemoveLightsAffectingObject(void)
+{
+ RemoveExtraDirectionalLights(Scene.world);
+}
+
+// for directional fog
+#define FOG_AREA_LENGTH 12.0f
+#define FOG_AREA_WIDTH 5.0f
+// for pointlight fog
+#define FOG_AREA_RADIUS 9.0f
+
+float FogSizes[8] = { 1.3f, 2.0f, 1.7f, 2.0f, 1.4f, 2.1f, 1.5f, 2.3f };
+
+void
+CPointLights::RenderFogEffect(void)
+{
+ int i;
+ float fogginess;
+ CColPoint point;
+ CEntity *entity;
+ float xmin, ymin;
+ float xmax, ymax;
+ int16 xi, yi;
+ CVector spriteCoors;
+ float spritew, spriteh;
+
+ if(CCutsceneMgr::IsRunning())
+ return;
+
+ PUSH_RENDERGROUP("CPointLights::RenderFogEffect");
+
+ RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
+ RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+ RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
+ RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
+ RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpPointlightRaster);
+
+ CSprite::InitSpriteBuffer();
+
+ for(i = 0; i < NumLights; i++){
+ if(aLights[i].fogType != FOG_NORMAL && aLights[i].fogType != FOG_ALWAYS)
+ continue;
+
+ fogginess = aLights[i].fogType == FOG_NORMAL ? CWeather::Foggyness : 1.0f;
+ if(fogginess == 0.0f)
+ continue;
+
+ if(aLights[i].type == LIGHT_DIRECTIONAL){
+
+ // TODO: test this. haven't found directional fog so far
+
+ float coors2X = aLights[i].coors.x + FOG_AREA_LENGTH*aLights[i].dir.x;
+ float coors2Y = aLights[i].coors.y + FOG_AREA_LENGTH*aLights[i].dir.y;
+
+ if(coors2X < aLights[i].coors.x){
+ xmin = coors2X;
+ xmax = aLights[i].coors.x;
+ }else{
+ xmax = coors2X;
+ xmin = aLights[i].coors.x;
+ }
+ if(coors2Y < aLights[i].coors.y){
+ ymin = coors2Y;
+ ymax = aLights[i].coors.y;
+ }else{
+ ymax = coors2Y;
+ ymin = aLights[i].coors.y;
+ }
+
+ xmin -= 5.0f;
+ ymin -= 5.0f;
+ xmax += 5.0f;
+ ymax += 5.0f;
+
+ for(xi = (int16)xmin - (int16)xmin % 4; xi <= (int16)xmax + 4; xi += 4){
+ for(yi = (int16)ymin - (int16)ymin % 4; yi <= (int16)ymax + 4; yi += 4){
+ // Some kind of pseudo random number?
+ int r = (xi ^ yi)>>2 & 0xF;
+ if((r & 1) == 0)
+ continue;
+
+ // Check if fog effect is close enough to directional line in x and y
+ float dx = xi - aLights[i].coors.x;
+ float dy = yi - aLights[i].coors.y;
+ float dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y;
+ float distsq = sq(dx) + sq(dy);
+ float linedistsq = distsq - sq(dot);
+ if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){
+ CVector fogcoors(xi, yi, aLights[i].coors.z + 10.0f);
+ if(CWorld::ProcessVerticalLine(fogcoors, fogcoors.z - 20.0f,
+ point, entity, true, false, false, false, true, false, nil)){
+ // Now same check again in xyz
+ fogcoors.z = point.point.z + 1.3f;
+ // actually we don't have to recalculate x and y, but the game does it that way
+ dx = xi - aLights[i].coors.x;
+ dy = yi - aLights[i].coors.y;
+ float dz = fogcoors.z - aLights[i].coors.z;
+ dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y + dz*aLights[i].dir.z;
+ distsq = sq(dx) + sq(dy) + sq(dz);
+ linedistsq = distsq - sq(dot);
+ if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){
+ float intensity = 158.0f * fogginess;
+ // more intensity the smaller the angle
+ intensity *= dot/Sqrt(distsq);
+ // more intensity the closer to light source
+ intensity *= 1.0f - sq(dot/FOG_AREA_LENGTH);
+ // more intensity the closer to line
+ intensity *= 1.0f - sq(Sqrt(linedistsq) / FOG_AREA_WIDTH);
+
+ if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)) {
+ float rotation = (CTimer::GetTimeInMilliseconds()&0x1FFF) * 2*3.14f / 0x2000;
+ float size = FogSizes[r>>1];
+ CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z,
+ spritew * size, spriteh * size,
+ aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity,
+ intensity, 1/spriteCoors.z, rotation, 255);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }else if(aLights[i].type == LIGHT_POINT || aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS){
+ float groundZ;
+ if(ProcessVerticalLineUsingCache(aLights[i].coors, &groundZ)){
+ xmin = aLights[i].coors.x - FOG_AREA_RADIUS;
+ ymin = aLights[i].coors.y - FOG_AREA_RADIUS;
+ xmax = aLights[i].coors.x + FOG_AREA_RADIUS;
+ ymax = aLights[i].coors.y + FOG_AREA_RADIUS;
+
+ for(xi = (int16)xmin - (int16)xmin % 2; xi <= (int16)xmax + 2; xi += 2){
+ for(yi = (int16)ymin - (int16)ymin % 2; yi <= (int16)ymax + 2; yi += 2){
+ // Some kind of pseudo random number?
+ int r = (xi ^ yi)>>1 & 0xF;
+ if((r & 1) == 0)
+ continue;
+
+ float dx = xi - aLights[i].coors.x;
+ float dy = yi - aLights[i].coors.y;
+ float lightdist = Sqrt(sq(dx) + sq(dy));
+ if(lightdist < FOG_AREA_RADIUS){
+ dx = xi - TheCamera.GetPosition().x;
+ dy = yi - TheCamera.GetPosition().y;
+ float camdist = Sqrt(sq(dx) + sq(dy));
+ if(camdist < MAX_DIST){
+ float intensity;
+ // distance fade
+ if(camdist < MAX_DIST/2)
+ intensity = 1.0f;
+ else
+ intensity = 1.0f - (camdist - MAX_DIST/2) / (MAX_DIST/2);
+ intensity *= 132.0f * fogginess;
+ // more intensity the closer to light source
+ intensity *= 1.0f - sq(lightdist / FOG_AREA_RADIUS);
+
+ CVector fogcoors(xi, yi, groundZ + 1.6f);
+ if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)) {
+ float rotation = (CTimer::GetTimeInMilliseconds()&0x3FFF) * 2*3.14f / 0x4000;
+ float size = FogSizes[r>>1];
+ CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z,
+ spritew * size, spriteh * size,
+ aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity,
+ intensity, 1/spriteCoors.z, rotation, 255);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ CSprite::FlushSpriteBuffer();
+
+ POP_RENDERGROUP();
+}
+
+bool
+CPointLights::ProcessVerticalLineUsingCache(CVector coors, float *groundZ)
+{
+ for(int i = 0; i < ARRAY_SIZE(aCachedMapReads); i++)
+ if(aCachedMapReads[i] == coors){
+ *groundZ = aCachedMapReadResults[i];
+ return true;
+ }
+
+ CColPoint point;
+ CEntity *entity;
+ if(CWorld::ProcessVerticalLine(coors, coors.z - 20.0f, point, entity, true, false, false, false, true, false, nil)){
+ aCachedMapReads[NextCachedValue] = coors;
+ aCachedMapReadResults[NextCachedValue] = point.point.z;
+ NextCachedValue = (NextCachedValue+1) % ARRAY_SIZE(aCachedMapReads);
+ *groundZ = point.point.z;
+ return true;
+ }
+ return false;
+}