; Versioned authored lighting, RGB shading and exact occlusion traversal. .code InitLightingSystem PROTO BakeMapLighting PROTO UpdateDynamicLights PROTO AddLightSource PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD SpawnExplosionLight PROTO :DWORD,:DWORD SpawnMuzzleFlashLight PROTO :DWORD,:DWORD CastLightRay PROTO :DWORD,:DWORD,:DWORD,:DWORD ComputeWallLightLevel PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD ComputeSpriteLight PROTO :DWORD,:DWORD,:DWORD SamplePointLighting PROTO :DWORD,:DWORD,:DWORD,:DWORD LightCellFree PROTO :DWORD,:DWORD RefreshLightGrid PROTO ValidateLightingData PROC uses ebx ecx edx esi, settings:DWORD,sources:DWORD,count:DWORD mov esi,settings cmp DWORD PTR [esi],2 ja vl_invalid cmp DWORD PTR [esi+4],255 ja vl_invalid cmp DWORD PTR [esi+8],1 ja vl_invalid cmp DWORD PTR [esi+12],32 jb vl_invalid cmp DWORD PTR [esi+12],2048 ja vl_invalid cmp DWORD PTR [esi+16],255 ja vl_invalid cmp DWORD PTR [esi+20],1 jb vl_invalid cmp DWORD PTR [esi+20],96 ja vl_invalid cmp DWORD PTR [esi+24],1 jne vl_invalid cmp DWORD PTR [esi+28],0 jne vl_invalid cmp count,13 ja vl_invalid mov ecx,count mov esi,sources vl_source: test ecx,ecx jz vl_valid cmp [esi].LightSource.x,2048 jae vl_invalid cmp [esi].LightSource.y,2048 jae vl_invalid cmp [esi].LightSource.radius,1 jb vl_invalid cmp [esi].LightSource.radius,2048 ja vl_invalid cmp [esi].LightSource.intensity,255 ja vl_invalid cmp [esi].LightSource.curIntensity,255 ja vl_invalid cmp [esi].LightSource.lightType,1 jbe vl_type_ok cmp [esi].LightSource.lightType,4 jne vl_invalid vl_type_ok: mov eax,[esi].LightSource.flags and eax,0FFFFFFF8h jnz vl_invalid test [esi].LightSource.color,0FF000000h jnz vl_invalid add esi,40 dec ecx jmp vl_source vl_valid: mov eax,1 ret vl_invalid: xor eax,eax ret ValidateLightingData ENDP InitLightingSystem PROC uses eax ecx edi mov lightingMode,0 mov ambientLightLevel,40 mov isFlashlightOn,1 mov flashlightRadius,384 mov flashlightIntensity,240 mov flashlightConeAngle,45 mov authoredLightCount,0 mov lightingTick,0 mov edi,OFFSET lightSources mov ecx,MAX_LIGHTS*10 xor eax,eax rep stosd mov edi,OFFSET lightGridRGB mov ecx,MAP_SIZE mov eax,00282828h rep stosd call RefreshLightGrid ret InitLightingSystem ENDP CommitLightingData PROC uses eax ecx esi edi,settings:DWORD,sources:DWORD,count:DWORD,rgb:DWORD call InitLightingSystem mov esi,settings lodsd mov lightingMode,eax lodsd mov ambientLightLevel,eax lodsd mov isFlashlightOn,eax lodsd mov flashlightRadius,eax lodsd mov flashlightIntensity,eax lodsd mov flashlightConeAngle,eax mov esi,sources mov edi,OFFSET lightSources+40 mov ecx,count mov authoredLightCount,ecx imul ecx,10 rep movsd mov esi,rgb mov edi,OFFSET lightGridRGB mov ecx,MAP_SIZE rep movsd call RefreshLightGrid ret CommitLightingData ENDP LightCellFree PROC uses edx,x:DWORD,y:DWORD cmp x,32 jae lcf_no cmp y,32 jae lcf_no mov eax,y shl eax,5 add eax,x movzx eax,BYTE PTR [gameMap+eax] cmp eax,0 je lcf_yes cmp eax,6 je lcf_yes cmp eax,10 je lcf_yes lcf_no: xor eax,eax ret lcf_yes: mov eax,1 ret LightCellFree ENDP ; Supercover traversal: test both sides when crossing an exact grid corner. CastLightRay PROC uses ebx ecx edx esi edi,x1:DWORD,y1:DWORD,x2:DWORD,y2:DWORD LOCAL cellX:DWORD,cellY:DWORD,ex:DWORD,ey:DWORD,deltaX:DWORD,deltaY:DWORD,sx:DWORD,sy:DWORD,nx:DWORD,ny:DWORD,axv:DWORD mov eax,x1 or eax,y1 or eax,x2 or eax,y2 test eax,0FFFFF800h jnz lr_blocked mov eax,x1 shr eax,6 mov cellX,eax mov eax,y1 shr eax,6 mov cellY,eax mov eax,x2 shr eax,6 mov ex,eax mov eax,y2 shr eax,6 mov ey,eax mov sx,1 mov eax,x2 sub eax,x1 jns lr_dx neg eax mov sx,-1 lr_dx: mov deltaX,eax mov sy,1 mov eax,y2 sub eax,y1 jns lr_dy neg eax mov sy,-1 lr_dy: mov deltaY,eax mov eax,cellX shl eax,6 cmp sx,1 jne lr_nx_neg add eax,64 sub eax,x1 jmp lr_nx lr_nx_neg: neg eax add eax,x1 lr_nx: mov nx,eax mov eax,cellY shl eax,6 cmp sy,1 jne lr_ny_neg add eax,64 sub eax,y1 jmp lr_ny lr_ny_neg: neg eax add eax,y1 lr_ny: mov ny,eax mov edi,65 lr_step: mov eax,cellX cmp eax,ex jne lr_cross mov eax,cellY cmp eax,ey je lr_end lr_cross: mov eax,3FFFFFFFh cmp deltaX,0 je lr_a mov eax,nx imul eax,deltaY lr_a: mov axv,eax mov eax,3FFFFFFFh cmp deltaY,0 je lr_b mov eax,ny imul eax,deltaX lr_b: cmp axv,eax jb lr_x ja lr_y mov eax,cellX add eax,sx invoke LightCellFree,eax,cellY test eax,eax jz lr_blocked mov eax,cellY add eax,sy invoke LightCellFree,cellX,eax test eax,eax jz lr_blocked mov eax,sx add cellX,eax add nx,64 lr_y: mov eax,sy add cellY,eax add ny,64 jmp lr_test lr_x: mov eax,sx add cellX,eax add nx,64 lr_test: invoke LightCellFree,cellX,cellY test eax,eax jz lr_blocked dec edi jnz lr_step lr_blocked: xor eax,eax ret lr_end: invoke LightCellFree,cellX,cellY ret CastLightRay ENDP LightDistance PROC uses ebx edx,deltaX:DWORD,deltaY:DWORD mov eax,deltaX cdq xor eax,edx sub eax,edx mov ebx,eax mov eax,deltaY cdq xor eax,edx sub eax,edx cmp eax,ebx jae ld_max xchg eax,ebx ld_max: lea ebx,[ebx+ebx*2] shr ebx,3 add eax,ebx ret LightDistance ENDP AddLightColor PROC uses eax ebx ecx edx,color:DWORD,amount:DWORD mov ebx,255 mov eax,color and eax,255 imul eax,amount xor edx,edx div ebx add lightAccumB,eax mov eax,color shr eax,8 and eax,255 imul eax,amount xor edx,edx div ebx add lightAccumG,eax mov eax,color shr eax,16 and eax,255 imul eax,amount xor edx,edx div ebx add lightAccumR,eax ret AddLightColor ENDP PointLightAmount PROC uses ebx ecx edx esi,source:DWORD,x:DWORD,y:DWORD LOCAL distance:DWORD mov esi,source mov eax,x sub eax,[esi].LightSource.x mov ecx,y sub ecx,[esi].LightSource.y invoke LightDistance,eax,ecx mov distance,eax cmp eax,[esi].LightSource.radius jae pla_zero test [esi].LightSource.flags,LIGHT_FLAG_SHADOWS jz pla_visible invoke CastLightRay,[esi].LightSource.x,[esi].LightSource.y,x,y test eax,eax jz pla_zero pla_visible: mov eax,[esi].LightSource.radius sub eax,distance imul eax,[esi].LightSource.curIntensity xor edx,edx div [esi].LightSource.radius ret pla_zero: xor eax,eax ret PointLightAmount ENDP RefreshLightGrid PROC uses eax ebx ecx edx esi edi mov esi,OFFSET lightGridRGB mov edi,OFFSET lightGrid mov ecx,MAP_SIZE rlg_loop: lodsd mov edx,eax and eax,255 mov ebx,edx shr ebx,8 and ebx,255 add eax,ebx shr edx,16 and edx,255 add eax,edx xor edx,edx mov ebx,3 div ebx stosb loop rlg_loop ret RefreshLightGrid ENDP PackBakedRGB PROC uses ebx edx mov eax,lightAccumR cmp eax,255 jbe @F mov eax,255 @@: shl eax,16 mov ebx,eax mov eax,lightAccumG cmp eax,255 jbe @F mov eax,255 @@: shl eax,8 or ebx,eax mov eax,lightAccumB cmp eax,255 jbe @F mov eax,255 @@: or eax,ebx ret PackBakedRGB ENDP AccumulateBakedCell PROC uses eax ebx ecx edx,x:DWORD,y:DWORD,weight:DWORD invoke LightCellFree,x,y test eax,eax jz abc_done mov eax,y shl eax,5 add eax,x mov edx,[lightGridRGB+eax*4] mov eax,edx and eax,255 imul eax,weight add lightAccumB,eax mov eax,edx shr eax,8 and eax,255 imul eax,weight add lightAccumG,eax shr edx,16 imul edx,weight add lightAccumR,edx mov eax,weight add lightWeight,eax abc_done: ret AccumulateBakedCell ENDP BakeMapLighting PROC uses ebx ecx edx esi edi LOCAL index:DWORD,x:DWORD,y:DWORD mov index,0 bl_cell: mov eax,index and eax,31 mov x,eax mov eax,index shr eax,5 mov y,eax mov eax,ambientLightLevel mov lightAccumR,eax mov lightAccumG,eax mov lightAccumB,eax invoke LightCellFree,x,y test eax,eax jz bl_store mov eax,x shl eax,6 add eax,32 mov x,eax mov eax,y shl eax,6 add eax,32 mov y,eax mov esi,OFFSET lightSources+40 mov edi,13 bl_source: test [esi].LightSource.flags,LIGHT_FLAG_ACTIVE jz bl_next_source cmp [esi].LightSource.lightType,LIGHT_TYPE_STATIC jne bl_next_source mov eax,[esi].LightSource.intensity push [esi].LightSource.curIntensity mov [esi].LightSource.curIntensity,eax invoke PointLightAmount,esi,x,y pop [esi].LightSource.curIntensity invoke AddLightColor,[esi].LightSource.color,eax bl_next_source: add esi,40 dec edi jnz bl_source bl_store: call PackBakedRGB mov ebx,index mov [lightGridRGB+ebx*4],eax inc index cmp index,MAP_SIZE jb bl_cell ; Smooth out of place, without averaging through wall cells. mov index,0 bl_smooth: mov ebx,index mov eax,[lightGridRGB+ebx*4] mov [lightSmoothRGB+ebx*4],eax mov eax,ebx and eax,31 mov x,eax shr ebx,5 mov y,ebx invoke LightCellFree,x,y test eax,eax jz bl_next_smooth mov lightAccumR,0 mov lightAccumG,0 mov lightAccumB,0 mov lightWeight,0 invoke AccumulateBakedCell,x,y,2 mov eax,x dec eax invoke AccumulateBakedCell,eax,y,1 mov eax,x inc eax invoke AccumulateBakedCell,eax,y,1 mov eax,y dec eax invoke AccumulateBakedCell,x,eax,1 mov eax,y inc eax invoke AccumulateBakedCell,x,eax,1 mov eax,lightAccumR xor edx,edx div lightWeight mov lightAccumR,eax mov eax,lightAccumG xor edx,edx div lightWeight mov lightAccumG,eax mov eax,lightAccumB xor edx,edx div lightWeight mov lightAccumB,eax call PackBakedRGB mov ebx,index mov [lightSmoothRGB+ebx*4],eax bl_next_smooth: inc index cmp index,MAP_SIZE jb bl_smooth mov esi,OFFSET lightSmoothRGB mov edi,OFFSET lightGridRGB mov ecx,MAP_SIZE rep movsd call RefreshLightGrid ret BakeMapLighting ENDP UpdateDynamicLights PROC uses eax ebx ecx edx esi inc lightingTick mov esi,OFFSET lightSources+40 mov ecx,1 udl_next: test [esi].LightSource.flags,LIGHT_FLAG_ACTIVE jz udl_skip cmp [esi].LightSource.lightType,LIGHT_TYPE_BURST je udl_burst mov eax,[esi].LightSource.intensity cmp [esi].LightSource.lightType,LIGHT_TYPE_STATIC je udl_store cmp [esi].LightSource.lightType,LIGHT_TYPE_PULSE jne udl_flicker mov edx,lightingTick shl edx,2 add edx,ecx and edx,511 mov ebx,[tableSin+edx*4] sar ebx,1 add ebx,98304 imul eax,ebx sar eax,17 udl_flicker: test [esi].LightSource.flags,LIGHT_FLAG_FLICKER jz udl_store mov edx,lightingTick shr edx,2 imul edx,1103515245 imul ebx,ecx,12345 add edx,ebx shr edx,16 and edx,15 sub edx,7 add eax,edx cmp eax,0 jge @F xor eax,eax @@: cmp eax,255 jle udl_store mov eax,255 jmp udl_store udl_burst: mov eax,[esi].LightSource.curIntensity sub eax,[esi].LightSource.decaySpeed jg udl_store xor eax,eax mov [esi].LightSource.flags,0 udl_store: mov [esi].LightSource.curIntensity,eax udl_skip: add esi,40 inc ecx cmp ecx,16 jb udl_next ret UpdateDynamicLights ENDP SamplePointLighting PROC uses ebx ecx edx esi edi,x:DWORD,y:DWORD,distance:DWORD,side:DWORD LOCAL forward:DWORD,lateral:DWORD,beam:DWORD,atten:DWORD,fog:DWORD LOCAL originX:DWORD,originY:DWORD,originAngle:DWORD ; Camera-attached flashlight. Cinematics never move the gameplay body, but ; use the same lighting origin as the editor's camera-only preview packet. mov eax,playerX mov originX,eax mov eax,playerY mov originY,eax mov eax,playerAngle mov originAngle,eax cmp isCutsceneActive,0 je spl_origin_ready mov eax,csCameraX mov originX,eax mov eax,csCameraY mov originY,eax mov eax,csCameraAngle mov originAngle,eax spl_origin_ready: mov eax,256 mov lightRed,eax mov lightGreen,eax mov lightBlue,eax cmp lightingMode,0 je spl_done mov eax,x sar eax,6 and eax,31 mov edx,y sar edx,6 and edx,31 shl edx,5 add eax,edx mov eax,[lightGridRGB+eax*4] mov edx,eax and eax,255 mov lightAccumB,eax mov eax,edx shr eax,8 and eax,255 mov lightAccumG,eax shr edx,16 mov lightAccumR,edx cmp lightingMode,2 jne spl_fog mov esi,OFFSET lightSources+40 mov edi,15 spl_source: test [esi].LightSource.flags,LIGHT_FLAG_ACTIVE jz spl_next cmp [esi].LightSource.lightType,LIGHT_TYPE_STATIC je spl_next cmp [esi].LightSource.radius,0 jle spl_next invoke PointLightAmount,esi,x,y invoke AddLightColor,[esi].LightSource.color,eax spl_next: add esi,40 dec edi jnz spl_source cmp isFlashlightOn,0 je spl_fog mov eax,x sub eax,originX mov ebx,y sub ebx,originY invoke LightDistance,eax,ebx cmp eax,flashlightRadius jae spl_fog mov edx,flashlightRadius sub edx,eax mov eax,edx imul eax,flashlightIntensity xor edx,edx div flashlightRadius mov atten,eax mov ecx,originAngle and ecx,511 mov eax,x sub eax,originX imul [tableCos+ecx*4] shrd eax,edx,16 mov forward,eax mov eax,y sub eax,originY imul [tableSin+ecx*4] shrd eax,edx,16 add forward,eax cmp forward,0 jle spl_fog mov eax,y sub eax,originY imul [tableCos+ecx*4] shrd eax,edx,16 mov lateral,eax mov eax,x sub eax,originX imul [tableSin+ecx*4] shrd eax,edx,16 sub lateral,eax mov ecx,flashlightConeAngle mov eax,forward imul [tableSin+ecx*4] idiv DWORD PTR [tableCos+ecx*4] mov beam,eax test eax,eax jle spl_fog mov eax,lateral cdq xor eax,edx sub eax,edx cmp eax,beam jae spl_fog mov ebx,beam sub ebx,eax mov eax,ebx imul eax,atten xor edx,edx div beam mov atten,eax invoke CastLightRay,originX,originY,x,y test eax,eax jz spl_fog invoke AddLightColor,00FFFFFFh,atten spl_fog: mov fog,256 mov eax,distance cmp eax,280 jle spl_side cmp eax,720 jb spl_partial mov fog,32 jmp spl_side spl_partial: sub eax,280 imul eax,224 xor edx,edx mov ebx,440 div ebx sub fog,eax spl_side: cmp side,1 jne spl_coeff mov eax,fog imul eax,192 shr eax,8 mov fog,eax spl_coeff: mov eax,lightAccumR cmp eax,256 jbe @F mov eax,256 @@: imul eax,fog shr eax,8 mov lightRed,eax mov eax,lightAccumG cmp eax,256 jbe @F mov eax,256 @@: imul eax,fog shr eax,8 mov lightGreen,eax mov eax,lightAccumB cmp eax,256 jbe @F mov eax,256 @@: imul eax,fog shr eax,8 mov lightBlue,eax spl_done: mov eax,lightRed cmp eax,lightGreen jae @F mov eax,lightGreen @@: cmp eax,lightBlue jae @F mov eax,lightBlue @@: ret SamplePointLighting ENDP ComputeWallLightLevel PROC uses ebx ecx edx,screenX:DWORD,mapX:DWORD,mapY:DWORD,sideHit:DWORD,stepX:DWORD,stepY:DWORD,rawDist:DWORD,perpWallDist:DWORD,rayAngle:DWORD,rayDirX:DWORD,rayDirY:DWORD LOCAL x:DWORD,y:DWORD mov eax,rawDist imul rayDirX shrd eax,edx,24 add eax,viewX mov x,eax mov eax,rawDist imul rayDirY shrd eax,edx,24 add eax,viewY mov y,eax cmp sideHit,0 jne cwl_y mov eax,mapX shl eax,6 cmp stepX,0 jg @F add eax,64 @@: sub eax,stepX mov x,eax jmp cwl_ready cwl_y: mov eax,mapY shl eax,6 cmp stepY,0 jg @F add eax,64 @@: sub eax,stepY mov y,eax cwl_ready: invoke SamplePointLighting,x,y,perpWallDist,sideHit ret ComputeWallLightLevel ENDP ComputeSpriteLight PROC sprX:DWORD,sprY:DWORD,sprDist:DWORD invoke SamplePointLighting,sprX,sprY,sprDist,0 ret ComputeSpriteLight ENDP ShadeLightPixel PROC uses eax ebx mov eax,edx and eax,255 imul eax,lightBlue shr eax,8 mov ebx,eax mov eax,edx shr eax,8 and eax,255 imul eax,lightGreen shr eax,8 shl eax,8 or ebx,eax mov eax,edx shr eax,16 and eax,255 imul eax,lightRed shr eax,8 shl eax,16 or eax,ebx mov edx,eax ret ShadeLightPixel ENDP