Decouple gameplay from presentation and preserve signed movement fractions. Separate cinematic camera ownership with safe body handoff and transactional current-room restarts. Resample input latches on focus regain, discard modal timing debt, and attach cinematic lighting to the view to match editor preview. Verify editor 137/137 and ASM 288/288, cinematic traces, RGB bake and IPC framebuffer parity without changing project assets.
1184 lines
29 KiB
PHP
1184 lines
29 KiB
PHP
; ==============================================================================
|
|
; EBLAN ENGINE - 2.5D Software Raycaster Core & Sprite Rasterizer
|
|
; ==============================================================================
|
|
|
|
.code
|
|
|
|
RenderSprites PROTO :DWORD
|
|
RenderGlass PROTO
|
|
|
|
SelectRenderCamera PROC
|
|
mov eax, playerX
|
|
mov viewX, eax
|
|
mov eax, playerY
|
|
mov viewY, eax
|
|
mov eax, playerAngle
|
|
mov viewAngle, eax
|
|
cmp isCutsceneActive, 0
|
|
je render_camera_ready
|
|
mov eax, csCameraX
|
|
mov viewX, eax
|
|
mov eax, csCameraY
|
|
mov viewY, eax
|
|
mov eax, csCameraAngle
|
|
mov viewAngle, eax
|
|
render_camera_ready:
|
|
ret
|
|
SelectRenderCamera ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; Render3DScene: Full frame 2.5D raycasting pass (Floor, Ceiling, Walls, Sprites)
|
|
; ------------------------------------------------------------------------------
|
|
Render3DScene PROC uses ebx ecx edx esi edi
|
|
LOCAL screenX:DWORD
|
|
LOCAL rayAngle:DWORD
|
|
LOCAL rayDirX:DWORD, rayDirY:DWORD
|
|
LOCAL mapX:DWORD, mapY:DWORD
|
|
LOCAL stepX:DWORD, stepY:DWORD
|
|
LOCAL sideDistX:DWORD, sideDistY:DWORD
|
|
LOCAL deltaDistX:DWORD, deltaDistY:DWORD
|
|
LOCAL rawDist:DWORD
|
|
LOCAL perpWallDistFixed:DWORD ; 24.8 until projection
|
|
LOCAL perpWallDist:DWORD
|
|
LOCAL sideHit:DWORD ; 0 = X-wall (Vertical), 1 = Y-wall (Horizontal)
|
|
LOCAL wallType:DWORD
|
|
LOCAL lineHeight:DWORD
|
|
LOCAL drawStart:DWORD, drawEnd:DWORD
|
|
LOCAL texX:DWORD, texY:DWORD
|
|
LOCAL texStep:DWORD, texPos:DWORD
|
|
LOCAL texPtr:DWORD
|
|
LOCAL pScreenPixel:DWORD
|
|
LOCAL hasGlassHit:DWORD
|
|
LOCAL glassRawDist:DWORD
|
|
LOCAL glassPerpDistFixed:DWORD
|
|
LOCAL glassPerpDist:DWORD
|
|
LOCAL glassLineHeight:DWORD
|
|
LOCAL glassDrawStart:DWORD, glassDrawEnd:DWORD
|
|
LOCAL glassTexX:DWORD, glassSideHit:DWORD
|
|
LOCAL colLightLevel:DWORD
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 1. Fast Background Clear: Ceiling (Top Half) and Floor (Bottom Half)
|
|
; --------------------------------------------------------------------------
|
|
call SelectRenderCamera
|
|
invoke SamplePointLighting,viewX,viewY,0,0
|
|
mov edi, OFFSET screenBuffer
|
|
mov edx,mapCeilingColor
|
|
call ShadeLightPixel
|
|
mov eax,edx
|
|
cmp lightingMode,0
|
|
jne @F
|
|
cmp playerFlashTimer, 0
|
|
je @F
|
|
add eax, 00202020h ; Dynamic Muzzle Flash ambient lighting boost
|
|
@@:
|
|
mov ecx, (SCREEN_W * SCREEN_HALF_H)
|
|
rep stosd
|
|
|
|
mov edx,mapFloorColor
|
|
call ShadeLightPixel
|
|
mov eax,edx
|
|
cmp lightingMode,0
|
|
jne @F
|
|
cmp playerFlashTimer, 0
|
|
je @F
|
|
add eax, 00202020h ; Dynamic Muzzle Flash ambient lighting boost
|
|
@@:
|
|
mov ecx, (SCREEN_W * SCREEN_HALF_H)
|
|
rep stosd
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 2. Raycasting: 320 Columns across Field of View
|
|
; --------------------------------------------------------------------------
|
|
mov screenX, 0
|
|
|
|
ray_column_loop:
|
|
mov hasGlassHit, 0
|
|
|
|
; Lookup Tangential Ray Angle Offset (flat plane perspective correction)
|
|
mov eax, screenX
|
|
mov edx, [tableRayAngleOffset + eax*4]
|
|
add edx, viewAngle
|
|
and edx, ANGLE_MASK
|
|
mov rayAngle, edx
|
|
mov eax, edx
|
|
|
|
; Ray Direction (16.16 Fixed Point)
|
|
mov edx, [tableCos + eax*4]
|
|
mov rayDirX, edx
|
|
mov edx, [tableSin + eax*4]
|
|
mov rayDirY, edx
|
|
|
|
; Current Grid Cell
|
|
mov eax, viewX
|
|
sar eax, 6
|
|
mov mapX, eax
|
|
|
|
mov eax, viewY
|
|
sar eax, 6
|
|
mov mapY, eax
|
|
|
|
; Delta Dist X in 24.8: (64 << 24) / abs(rayDirX)
|
|
mov eax, rayDirX
|
|
test eax, eax
|
|
jnz calc_delta_x
|
|
mov deltaDistX, 3FFFFFFFh
|
|
jmp delta_x_ready
|
|
calc_delta_x:
|
|
cdq
|
|
xor eax, edx
|
|
sub eax, edx ; eax = abs(rayDirX)
|
|
mov ecx, eax
|
|
mov eax, (64 shl 24)
|
|
xor edx, edx
|
|
div ecx
|
|
mov deltaDistX, eax
|
|
delta_x_ready:
|
|
|
|
; Delta Dist Y in 24.8: (64 << 24) / abs(rayDirY)
|
|
mov eax, rayDirY
|
|
test eax, eax
|
|
jnz calc_delta_y
|
|
mov deltaDistY, 3FFFFFFFh
|
|
jmp delta_y_ready
|
|
calc_delta_y:
|
|
cdq
|
|
xor eax, edx
|
|
sub eax, edx ; eax = abs(rayDirY)
|
|
mov ecx, eax
|
|
mov eax, (64 shl 24)
|
|
xor edx, edx
|
|
div ecx
|
|
mov deltaDistY, eax
|
|
delta_y_ready:
|
|
|
|
; Calculate Step and Initial Side Dist
|
|
cmp rayDirX, 0
|
|
jne nonzero_step_x
|
|
mov stepX, 1
|
|
mov sideDistX, 3FFFFFFFh
|
|
jmp check_step_y
|
|
nonzero_step_x:
|
|
cmp rayDirX, 0
|
|
jge ray_dir_x_pos
|
|
mov stepX, -1
|
|
; sideDistX = (playerX - (mapX * 64)) * deltaDistX / 64
|
|
mov eax, mapX
|
|
shl eax, 6
|
|
mov edx, viewX
|
|
sub edx, eax
|
|
mov eax, edx
|
|
imul deltaDistX
|
|
shrd eax, edx, 6
|
|
mov sideDistX, eax
|
|
jmp check_step_y
|
|
|
|
ray_dir_x_pos:
|
|
mov stepX, 1
|
|
; sideDistX = ((mapX + 1) * 64 - playerX) * deltaDistX / 64
|
|
mov eax, mapX
|
|
inc eax
|
|
shl eax, 6
|
|
sub eax, viewX
|
|
imul deltaDistX
|
|
shrd eax, edx, 6
|
|
mov sideDistX, eax
|
|
|
|
check_step_y:
|
|
cmp rayDirY, 0
|
|
jne nonzero_step_y
|
|
mov stepY, 1
|
|
mov sideDistY, 3FFFFFFFh
|
|
jmp dda_loop
|
|
nonzero_step_y:
|
|
cmp rayDirY, 0
|
|
jge ray_dir_y_pos
|
|
mov stepY, -1
|
|
mov eax, mapY
|
|
shl eax, 6
|
|
mov edx, viewY
|
|
sub edx, eax
|
|
mov eax, edx
|
|
imul deltaDistY
|
|
shrd eax, edx, 6
|
|
mov sideDistY, eax
|
|
jmp dda_loop
|
|
|
|
ray_dir_y_pos:
|
|
mov stepY, 1
|
|
mov eax, mapY
|
|
inc eax
|
|
shl eax, 6
|
|
sub eax, viewY
|
|
imul deltaDistY
|
|
shrd eax, edx, 6
|
|
mov sideDistY, eax
|
|
|
|
; --------------------------------------------------------------------------
|
|
; DDA Traversal
|
|
; --------------------------------------------------------------------------
|
|
dda_loop:
|
|
mov eax, sideDistX
|
|
cmp eax, sideDistY
|
|
jge step_in_y
|
|
|
|
; Step in X
|
|
mov eax, sideDistX
|
|
add eax, deltaDistX
|
|
mov sideDistX, eax
|
|
mov eax, mapX
|
|
add eax, stepX
|
|
mov mapX, eax
|
|
mov sideHit, 0
|
|
jmp test_hit
|
|
|
|
step_in_y:
|
|
mov eax, sideDistY
|
|
add eax, deltaDistY
|
|
mov sideDistY, eax
|
|
mov eax, mapY
|
|
add eax, stepY
|
|
mov mapY, eax
|
|
mov sideHit, 1
|
|
|
|
test_hit:
|
|
invoke GetMapCell, mapX, mapY
|
|
mov wallType, eax
|
|
cmp eax, CELL_GLASS
|
|
jne not_glass_hit
|
|
|
|
; If this ray already recorded a glass wall, continue stepping through subsequent glass cells
|
|
cmp hasGlassHit, 0
|
|
jne dda_loop
|
|
|
|
; Record first glass hit:
|
|
mov hasGlassHit, 1
|
|
cmp sideHit, 0
|
|
jne glass_dist_from_y
|
|
mov eax, sideDistX
|
|
sub eax, deltaDistX
|
|
jmp glass_apply_fisheye
|
|
glass_dist_from_y:
|
|
mov eax, sideDistY
|
|
sub eax, deltaDistY
|
|
glass_apply_fisheye:
|
|
mov glassRawDist, eax
|
|
mov ecx, screenX
|
|
mov ecx, [tableRayCos + ecx*4]
|
|
imul ecx
|
|
shrd eax, edx, 16
|
|
cmp eax, 1
|
|
jg @F
|
|
mov eax, 1
|
|
@@:
|
|
mov glassPerpDistFixed, eax
|
|
add eax, 128
|
|
shr eax, 8
|
|
cmp eax, 1
|
|
jae @F
|
|
mov eax, 1
|
|
@@:
|
|
mov glassPerpDist, eax
|
|
|
|
; Glass line height & drawStart / drawEnd
|
|
mov eax, (64 * PROJ_DIST shl 8)
|
|
xor edx, edx
|
|
div glassPerpDistFixed
|
|
cmp eax, 1
|
|
jge @F
|
|
mov eax, 1
|
|
@@:
|
|
mov glassLineHeight, eax
|
|
|
|
mov eax, SCREEN_HALF_H
|
|
mov edx, glassLineHeight
|
|
shr edx, 1
|
|
sub eax, edx
|
|
cmp eax, 0
|
|
jge @F
|
|
xor eax, eax
|
|
@@:
|
|
mov glassDrawStart, eax
|
|
|
|
mov eax, SCREEN_HALF_H
|
|
mov edx, glassLineHeight
|
|
shr edx, 1
|
|
add eax, edx
|
|
cmp eax, SCREEN_H
|
|
jl @F
|
|
mov eax, SCREEN_H - 1
|
|
@@:
|
|
mov glassDrawEnd, eax
|
|
|
|
; Glass texX
|
|
cmp sideHit, 0
|
|
jne glass_tex_hit_y
|
|
mov eax, glassRawDist
|
|
mov ebx, rayDirY
|
|
imul ebx
|
|
shrd eax, edx, 8
|
|
mov ebx, viewY
|
|
shl ebx, 16
|
|
add eax, ebx
|
|
jmp glass_compute_tex_x
|
|
glass_tex_hit_y:
|
|
mov eax, glassRawDist
|
|
mov ebx, rayDirX
|
|
imul ebx
|
|
shrd eax, edx, 8
|
|
mov ebx, viewX
|
|
shl ebx, 16
|
|
add eax, ebx
|
|
glass_compute_tex_x:
|
|
shr eax, 15
|
|
and eax, TEX_MASK
|
|
cmp sideHit, 0
|
|
jne glass_check_flip_y
|
|
cmp rayDirX, 0
|
|
jle @F
|
|
mov ecx, TEX_MASK
|
|
sub ecx, eax
|
|
mov eax, ecx
|
|
jmp @F
|
|
glass_check_flip_y:
|
|
cmp rayDirY, 0
|
|
jge @F
|
|
mov ecx, TEX_MASK
|
|
sub ecx, eax
|
|
mov eax, ecx
|
|
@@:
|
|
mov glassTexX, eax
|
|
mov eax, sideHit
|
|
mov glassSideHit, eax
|
|
|
|
; Continue stepping through the glass to the back wall!
|
|
jmp dda_loop
|
|
|
|
not_glass_hit:
|
|
invoke IsSolidCell, wallType
|
|
test eax, eax
|
|
jz dda_loop
|
|
|
|
; --------------------------------------------------------------------------
|
|
; Calculate Perpendicular Wall Distance & Fish-eye Correction
|
|
; --------------------------------------------------------------------------
|
|
cmp sideHit, 0
|
|
jne dist_from_y
|
|
mov eax, sideDistX
|
|
sub eax, deltaDistX
|
|
jmp apply_fisheye_fix
|
|
dist_from_y:
|
|
mov eax, sideDistY
|
|
sub eax, deltaDistY
|
|
|
|
apply_fisheye_fix:
|
|
mov rawDist, eax ; Euclidean distance in 24.8, also used for texture X
|
|
; Fast tableRayCos lookup (precomputed perpendicular cosine per screen column)
|
|
mov ecx, screenX
|
|
mov ecx, [tableRayCos + ecx*4]
|
|
imul ecx ; edx:eax = rawDist * cos(rayAngleOffset)
|
|
shrd eax, edx, 16
|
|
cmp eax, 1 ; Prevent division by zero
|
|
jg @F
|
|
mov eax, 1
|
|
@@:
|
|
mov perpWallDistFixed, eax
|
|
add eax, 128
|
|
shr eax, 8
|
|
cmp eax, 1
|
|
jae rounded_distance_ready
|
|
mov eax, 1
|
|
rounded_distance_ready:
|
|
mov perpWallDist, eax
|
|
|
|
; Store in Depth Buffer for Sprite Occlusion
|
|
mov ecx, screenX
|
|
mov [depthBuffer + ecx*4], eax
|
|
|
|
; --------------------------------------------------------------------------
|
|
; Projected Line Height on Screen: (64 * PROJ_DIST) / perpWallDist
|
|
; --------------------------------------------------------------------------
|
|
mov eax, (64 * PROJ_DIST shl 8)
|
|
xor edx, edx
|
|
div perpWallDistFixed
|
|
cmp eax, 1
|
|
jge @F
|
|
mov eax, 1 ; Clamp to >= 1 to prevent div by zero in texStep
|
|
@@:
|
|
mov lineHeight, eax
|
|
|
|
; Calculate drawStart and drawEnd
|
|
mov eax, SCREEN_HALF_H
|
|
mov edx, lineHeight
|
|
shr edx, 1
|
|
sub eax, edx
|
|
mov drawStart, eax
|
|
cmp drawStart, 0
|
|
jge @F
|
|
mov drawStart, 0
|
|
@@:
|
|
|
|
mov eax, SCREEN_HALF_H
|
|
mov edx, lineHeight
|
|
shr edx, 1
|
|
add eax, edx
|
|
mov drawEnd, eax
|
|
cmp drawEnd, SCREEN_H
|
|
jl @F
|
|
mov drawEnd, SCREEN_H - 1
|
|
@@:
|
|
|
|
; --------------------------------------------------------------------------
|
|
; Texture Coordinate X (texX) with full 128-texel sub-unit precision
|
|
; --------------------------------------------------------------------------
|
|
cmp sideHit, 0
|
|
jne tex_hit_y
|
|
; wallHitFixed = (playerY << 16) + rawDist * rayDirY
|
|
mov eax, rawDist
|
|
mov ebx, rayDirY
|
|
imul ebx
|
|
shrd eax, edx, 8 ; (24.8 * 16.16) -> 16.16
|
|
mov ebx, viewY
|
|
shl ebx, 16
|
|
add eax, ebx
|
|
jmp compute_tex_x
|
|
|
|
tex_hit_y:
|
|
; wallHitFixed = (playerX << 16) + rawDist * rayDirX
|
|
mov eax, rawDist
|
|
mov ebx, rayDirX
|
|
imul ebx
|
|
shrd eax, edx, 8
|
|
mov ebx, viewX
|
|
shl ebx, 16
|
|
add eax, ebx
|
|
|
|
compute_tex_x:
|
|
shr eax, 15
|
|
and eax, TEX_MASK ; full 0..127 range (all 128 texture columns)
|
|
mov texX, eax
|
|
|
|
; Flip texture coordinate depending on ray direction
|
|
cmp sideHit, 0
|
|
jne check_flip_y
|
|
cmp rayDirX, 0
|
|
jle set_tex_ptr
|
|
mov eax, TEX_MASK
|
|
sub eax, texX
|
|
mov texX, eax
|
|
jmp set_tex_ptr
|
|
|
|
check_flip_y:
|
|
cmp rayDirY, 0
|
|
jge set_tex_ptr
|
|
mov eax, TEX_MASK
|
|
sub eax, texX
|
|
mov texX, eax
|
|
|
|
set_tex_ptr:
|
|
; Pointer to Texture = textureBuffer + (wallType * TEX_SIZE_BYTES)
|
|
mov eax, wallType
|
|
and eax, 15
|
|
shl eax, 16 ; eax = wallType * 65536
|
|
add eax, OFFSET textureBuffer
|
|
cmp wallType, CELL_PORTAL
|
|
jne wall_texture_ready
|
|
mov eax, OFFSET portalTextureBuffer
|
|
wall_texture_ready:
|
|
mov texPtr, eax
|
|
|
|
; Texture vertical sampling step (16.16 fixed point)
|
|
; texStep = (128 << 16) / lineHeight
|
|
mov eax, (128 shl 16)
|
|
xor edx, edx
|
|
div lineHeight
|
|
mov texStep, eax
|
|
|
|
; texPos = (drawStart - SCREEN_HALF_H + lineHeight / 2) * texStep
|
|
mov eax, drawStart
|
|
sub eax, SCREEN_HALF_H
|
|
mov ebx, lineHeight
|
|
shr ebx, 1
|
|
add eax, ebx
|
|
imul eax, texStep
|
|
mov texPos, eax
|
|
|
|
; Compute wall lighting (baked lightmap + dynamic point lights + flashlight)
|
|
invoke ComputeWallLightLevel, screenX, mapX, mapY, sideHit, stepX, stepY,\
|
|
rawDist, perpWallDist, rayAngle, rayDirX, rayDirY
|
|
mov colLightLevel, eax
|
|
|
|
; --------------------------------------------------------------------------
|
|
; Draw Vertical Wall Stripe
|
|
; --------------------------------------------------------------------------
|
|
; Screen pixel address = screenBuffer + (drawStart * SCREEN_PITCH) + (screenX * 4)
|
|
mov eax, drawStart
|
|
imul eax, SCREEN_PITCH
|
|
add eax, screenX
|
|
add eax, screenX
|
|
add eax, screenX
|
|
add eax, screenX ; screenX * 4
|
|
add eax, OFFSET screenBuffer
|
|
mov pScreenPixel, eax
|
|
|
|
mov ecx, drawStart
|
|
wall_pixel_loop:
|
|
cmp ecx, drawEnd
|
|
jg end_wall_stripe
|
|
|
|
; Sample Texture: texY = (texPos >> 16) & TEX_MASK
|
|
mov eax, texPos
|
|
shr eax, 16
|
|
and eax, TEX_MASK
|
|
shl eax, 7 ; texY * 128
|
|
add eax, texX
|
|
shl eax, 2 ; * 4 bytes
|
|
add eax, texPtr
|
|
mov edx, [eax] ; Sampled Color (BGRA)
|
|
|
|
cmp lightingMode, LIGHTING_MODE_NONE
|
|
jne apply_col_light
|
|
|
|
; Wall shading: Y-facing walls darkened 50% for 3D depth perception
|
|
cmp sideHit, 1
|
|
jne apply_distance_fog
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
|
|
apply_distance_fog:
|
|
; Distance Fog: if perpWallDist > 250, blend towards darkness
|
|
mov eax, perpWallDist
|
|
cmp eax, 350
|
|
jl write_wall_pixel
|
|
cmp eax, 700
|
|
jge make_pitch_black
|
|
; Moderate distance dimming
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
jmp write_wall_pixel
|
|
|
|
make_pitch_black:
|
|
mov edx, 00111111h
|
|
jmp write_wall_pixel
|
|
|
|
apply_col_light:
|
|
call ShadeLightPixel
|
|
|
|
write_wall_pixel:
|
|
mov eax, pScreenPixel
|
|
mov [eax], edx
|
|
|
|
; Advance texture position and screen pointer
|
|
mov eax, texPos
|
|
add eax, texStep
|
|
mov texPos, eax
|
|
|
|
add pScreenPixel, SCREEN_PITCH
|
|
inc ecx
|
|
jmp wall_pixel_loop
|
|
|
|
end_wall_stripe:
|
|
mov ecx, screenX
|
|
cmp hasGlassHit, 0
|
|
je no_glass_in_col
|
|
mov eax, glassPerpDist
|
|
mov [glassDistBuffer + ecx*4], eax
|
|
mov eax, glassDrawStart
|
|
mov [glassDrawStartBuf + ecx*4], eax
|
|
mov eax, glassDrawEnd
|
|
mov [glassDrawEndBuf + ecx*4], eax
|
|
mov eax, glassLineHeight
|
|
mov [glassLineHeightBuf + ecx*4], eax
|
|
mov eax, glassTexX
|
|
mov [glassTexXBuf + ecx*4], eax
|
|
mov eax, glassSideHit
|
|
mov [glassSideHitBuf + ecx*4], eax
|
|
jmp col_glass_saved
|
|
no_glass_in_col:
|
|
mov DWORD PTR [glassDistBuffer + ecx*4], 0
|
|
col_glass_saved:
|
|
inc screenX
|
|
cmp screenX, SCREEN_W
|
|
jl ray_column_loop
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 3. Render Sprites (Behind Glass & Normal Columns)
|
|
; --------------------------------------------------------------------------
|
|
invoke RenderSprites, 1
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 4. Render Glass (Semi-Transparent Wall Pass)
|
|
; --------------------------------------------------------------------------
|
|
call RenderGlass
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 5. Render Sprites (In Front of Glass)
|
|
; --------------------------------------------------------------------------
|
|
invoke RenderSprites, 2
|
|
|
|
ret
|
|
Render3DScene ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; RenderSprites: Projects and renders 3D billboard sprites with depth testing
|
|
; Pipeline (same as KAZIK casino3d):
|
|
; 1. Distance pass - squared dist per sprite, fill spriteOrder
|
|
; 2. Sort - insertion sort far-to-near (painter's algorithm)
|
|
; 3. Raster pass - camera transform, projection, textured stripes,
|
|
; per-column depth test vs depthBuffer, chroma key
|
|
; Sprite x/y are RAW world units (1 cell = 64 units), same scale as playerX/Y.
|
|
; ------------------------------------------------------------------------------
|
|
RenderSprites PROC uses ebx ecx edx esi edi passFilter:DWORD
|
|
LOCAL i:DWORD, j:DWORD, sprIdx:DWORD
|
|
LOCAL spriteX:DWORD, spriteY:DWORD
|
|
LOCAL transX:DWORD, transY:DWORD
|
|
LOCAL sprScreenX:DWORD, sprSize:DWORD, baseSize:DWORD
|
|
LOCAL origStartX:DWORD, origStartY:DWORD
|
|
LOCAL drawStartX:DWORD, drawEndX:DWORD
|
|
LOCAL drawStartY:DWORD, drawEndY:DWORD
|
|
LOCAL stripe:DWORD, texX:DWORD
|
|
LOCAL texStepX:DWORD, texStepY:DWORD, texPosY:DWORD
|
|
LOCAL texPtr:DWORD, pPixel:DWORD
|
|
LOCAL camCos:DWORD, camSin:DWORD
|
|
LOCAL keyIdx:DWORD, keyDist:DWORD
|
|
LOCAL sprLightLevel:DWORD
|
|
|
|
mov eax, spriteCount
|
|
test eax, eax
|
|
jz sprites_done
|
|
|
|
; Camera basis for -playerAngle (rotate world into camera space)
|
|
mov eax, ANGLE_512
|
|
sub eax, viewAngle
|
|
and eax, ANGLE_MASK
|
|
mov ecx, [tableCos + eax*4]
|
|
mov camCos, ecx
|
|
mov edx, [tableSin + eax*4]
|
|
mov camSin, edx
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 1. Distance pass: dist = dx^2 + dy^2 (raw units^2), spriteOrder[i] = i
|
|
; --------------------------------------------------------------------------
|
|
mov i, 0
|
|
dist_loop:
|
|
mov eax, i
|
|
cmp eax, spriteCount
|
|
jge dist_done
|
|
|
|
mov ebx, i
|
|
imul ebx, sizeof SpriteStruct
|
|
add ebx, OFFSET spriteList
|
|
|
|
mov eax, [ebx].SpriteStruct.x
|
|
sub eax, viewX ; dx
|
|
imul eax, eax
|
|
mov ecx, eax
|
|
|
|
mov eax, [ebx].SpriteStruct.y
|
|
sub eax, viewY ; dy
|
|
imul eax, eax
|
|
add eax, ecx
|
|
mov [ebx].SpriteStruct.dist, eax
|
|
|
|
mov ecx, i
|
|
mov [spriteOrder + ecx*4], ecx
|
|
|
|
inc i
|
|
jmp dist_loop
|
|
dist_done:
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 2. Insertion sort spriteOrder by dist, far-to-near (descending)
|
|
; --------------------------------------------------------------------------
|
|
mov i, 1
|
|
sort_outer:
|
|
mov eax, i
|
|
cmp eax, spriteCount
|
|
jge sort_done
|
|
|
|
mov ecx, i
|
|
mov eax, [spriteOrder + ecx*4]
|
|
mov keyIdx, eax
|
|
; keyDist = spriteList[keyIdx].dist
|
|
mov ebx, eax
|
|
imul ebx, sizeof SpriteStruct
|
|
add ebx, OFFSET spriteList
|
|
mov eax, [ebx].SpriteStruct.dist
|
|
mov keyDist, eax
|
|
|
|
mov eax, i
|
|
dec eax
|
|
mov j, eax
|
|
|
|
sort_inner:
|
|
mov eax, j
|
|
test eax, eax
|
|
js sort_insert ; j < 0 -> insert at 0
|
|
|
|
; Compare spriteList[spriteOrder[j]].dist with keyDist
|
|
mov ecx, j
|
|
mov ebx, [spriteOrder + ecx*4]
|
|
imul ebx, sizeof SpriteStruct
|
|
add ebx, OFFSET spriteList
|
|
mov eax, [ebx].SpriteStruct.dist
|
|
cmp eax, keyDist
|
|
jae sort_insert ; dist[j] >= key -> keep far-to-near order
|
|
|
|
; Shift spriteOrder[j] -> spriteOrder[j+1]
|
|
mov ecx, j
|
|
mov eax, [spriteOrder + ecx*4]
|
|
mov [spriteOrder + ecx*4 + 4], eax
|
|
dec j
|
|
jmp sort_inner
|
|
|
|
sort_insert:
|
|
mov ecx, j
|
|
inc ecx
|
|
mov eax, keyIdx
|
|
mov [spriteOrder + ecx*4], eax
|
|
|
|
inc i
|
|
jmp sort_outer
|
|
sort_done:
|
|
|
|
; --------------------------------------------------------------------------
|
|
; 3. Raster pass (far to near)
|
|
; --------------------------------------------------------------------------
|
|
mov i, 0
|
|
sprite_loop:
|
|
mov eax, i
|
|
cmp eax, spriteCount
|
|
jge sprites_done
|
|
|
|
mov ecx, i
|
|
mov eax, [spriteOrder + ecx*4]
|
|
mov sprIdx, eax
|
|
mov ebx, eax
|
|
imul ebx, sizeof SpriteStruct
|
|
add ebx, OFFSET spriteList
|
|
|
|
; Sprite position relative to player (raw units)
|
|
mov eax, [ebx].SpriteStruct.x
|
|
sub eax, viewX
|
|
mov spriteX, eax
|
|
|
|
mov eax, [ebx].SpriteStruct.y
|
|
sub eax, viewY
|
|
mov spriteY, eax
|
|
|
|
; Transform to camera space:
|
|
; transY (depth) = spriteX * camCos - spriteY * camSin
|
|
; transX = spriteX * camSin + spriteY * camCos
|
|
mov eax, spriteX
|
|
imul camCos
|
|
shrd eax, edx, 16
|
|
mov ecx, eax
|
|
mov eax, spriteY
|
|
imul camSin
|
|
shrd eax, edx, 16
|
|
sub ecx, eax
|
|
mov transY, ecx
|
|
|
|
; Behind camera / too close -> skip
|
|
cmp transY, 8
|
|
jle next_sprite
|
|
|
|
mov eax, spriteX
|
|
imul camSin
|
|
shrd eax, edx, 16
|
|
mov ecx, eax
|
|
mov eax, spriteY
|
|
imul camCos
|
|
shrd eax, edx, 16
|
|
add ecx, eax
|
|
mov transX, ecx
|
|
|
|
; Screen X of sprite center
|
|
mov eax, transX
|
|
imul eax, PROJ_DIST
|
|
cdq
|
|
idiv transY
|
|
add eax, SCREEN_HALF_W
|
|
mov sprScreenX, eax
|
|
|
|
; Projected size: (64 * PROJ_DIST / transY) * scale (16.16)
|
|
mov eax, (64 * PROJ_DIST)
|
|
xor edx, edx
|
|
div transY
|
|
mov baseSize, eax
|
|
mov ecx, [ebx].SpriteStruct.scale
|
|
test ecx, ecx
|
|
jnz @F
|
|
mov ecx, FIXED_ONE ; scale 0 treated as 1.0
|
|
@@:
|
|
imul ecx
|
|
shrd eax, edx, 16
|
|
test eax, eax
|
|
jg @F
|
|
mov eax, 1
|
|
@@:
|
|
mov sprSize, eax
|
|
|
|
; Unclipped bounds (needed for texture mapping)
|
|
mov eax, sprScreenX
|
|
mov edx, sprSize
|
|
shr edx, 1
|
|
sub eax, edx
|
|
mov origStartX, eax
|
|
|
|
; Floor-anchored: sprite bottom sits at the floor line of its depth
|
|
; (wall bottom = horizon + size/2), top = bottom - size
|
|
mov eax, SCREEN_HALF_H
|
|
mov edx, baseSize
|
|
shr edx, 1
|
|
add eax, edx
|
|
mov drawEndY, eax
|
|
sub eax, sprSize
|
|
mov origStartY, eax
|
|
|
|
; Clipped bounds
|
|
mov eax, origStartX
|
|
mov drawStartX, eax
|
|
cmp drawStartX, 0
|
|
jge @F
|
|
mov drawStartX, 0
|
|
@@:
|
|
mov eax, origStartX
|
|
add eax, sprSize
|
|
dec eax
|
|
mov drawEndX, eax
|
|
cmp drawEndX, SCREEN_W
|
|
jl @F
|
|
mov drawEndX, SCREEN_W - 1
|
|
@@:
|
|
mov eax, drawStartX
|
|
cmp eax, drawEndX
|
|
jg next_sprite ; fully off-screen horizontally
|
|
|
|
mov eax, origStartY
|
|
mov drawStartY, eax
|
|
cmp drawStartY, 0
|
|
jge @F
|
|
mov drawStartY, 0
|
|
@@:
|
|
cmp drawEndY, SCREEN_H
|
|
jl @F
|
|
mov drawEndY, SCREEN_H - 1
|
|
@@:
|
|
mov eax, drawStartY
|
|
cmp eax, drawEndY
|
|
jg next_sprite ; fully off-screen vertically
|
|
|
|
; Texture pointer: slot 0 = solid marker color (no texture)
|
|
mov ebx, sprIdx
|
|
imul ebx, sizeof SpriteStruct
|
|
add ebx, OFFSET spriteList
|
|
mov eax, [ebx].SpriteStruct.textureId
|
|
and eax, 15
|
|
shl eax, 16 ; * TEX_SIZE_BYTES
|
|
add eax, OFFSET textureBuffer
|
|
mov texPtr, eax
|
|
|
|
; Texture stepping (16.16)
|
|
mov eax, (128 shl 16)
|
|
xor edx, edx
|
|
div sprSize
|
|
mov texStepX, eax
|
|
mov texStepY, eax
|
|
|
|
; Render vertical stripes
|
|
invoke ComputeSpriteLight, spriteX, spriteY, transY
|
|
mov sprLightLevel, eax
|
|
|
|
mov eax, drawStartX
|
|
mov stripe, eax
|
|
|
|
stripe_loop:
|
|
mov eax, stripe
|
|
cmp eax, drawEndX
|
|
jg next_sprite
|
|
|
|
; Filter by glass pass
|
|
mov ecx, stripe
|
|
mov edx, [glassDistBuffer + ecx*4]
|
|
|
|
cmp passFilter, 1
|
|
jne check_pass_front
|
|
|
|
; Pass 1 (behind glass or columns without glass):
|
|
test edx, edx
|
|
jz normal_sprite_depth_test
|
|
cmp transY, edx
|
|
jl advance_stripe ; Sprite is in front of glass -> defer to Pass 2
|
|
jmp normal_sprite_depth_test
|
|
|
|
check_pass_front:
|
|
cmp passFilter, 2
|
|
jne normal_sprite_depth_test
|
|
; Pass 2 (in front of glass only):
|
|
test edx, edx
|
|
jz advance_stripe ; No glass in this column -> already drawn
|
|
cmp transY, edx
|
|
jge advance_stripe ; Sprite is behind glass -> already drawn
|
|
|
|
normal_sprite_depth_test:
|
|
; Depth test: sprite visible only if closer than wall in this column
|
|
mov eax, transY
|
|
mov ecx, stripe
|
|
cmp eax, [depthBuffer + ecx*4]
|
|
jge advance_stripe
|
|
|
|
; texX = (stripe - origStartX) * 128 / sprSize
|
|
mov eax, stripe
|
|
sub eax, origStartX
|
|
imul eax, texStepX
|
|
shrd eax, edx, 16
|
|
and eax, TEX_MASK
|
|
mov texX, eax
|
|
|
|
; texPosY starts at the clipped top
|
|
mov eax, drawStartY
|
|
sub eax, origStartY
|
|
imul eax, texStepY
|
|
mov texPosY, eax
|
|
|
|
; Pixel pointer at (stripe, drawStartY)
|
|
mov eax, drawStartY
|
|
imul eax, SCREEN_PITCH
|
|
mov ecx, stripe
|
|
shl ecx, 2
|
|
add eax, ecx
|
|
add eax, OFFSET screenBuffer
|
|
mov pPixel, eax
|
|
|
|
mov ecx, drawStartY
|
|
sprite_pixel_y:
|
|
cmp ecx, drawEndY
|
|
jg advance_stripe
|
|
|
|
; Sample texture texel (texX, texY)
|
|
mov eax, texPosY
|
|
shr eax, 16
|
|
and eax, TEX_MASK
|
|
shl eax, 7 ; texY * 128
|
|
add eax, texX
|
|
shl eax, 2
|
|
add eax, texPtr
|
|
mov edx, [eax]
|
|
|
|
; Chroma key: magenta texels are transparent
|
|
cmp edx, COLOR_CHROMA_KEY
|
|
je skip_sprite_pixel
|
|
|
|
cmp lightingMode, LIGHTING_MODE_NONE
|
|
jne apply_spr_lighting
|
|
|
|
; Distance fog (same thresholds as walls)
|
|
mov eax, transY
|
|
cmp eax, 350
|
|
jl write_sprite_pixel
|
|
cmp eax, 700
|
|
jge sprite_pixel_dark
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
jmp write_sprite_pixel
|
|
sprite_pixel_dark:
|
|
mov edx, 00111111h
|
|
jmp write_sprite_pixel
|
|
|
|
apply_spr_lighting:
|
|
call ShadeLightPixel
|
|
|
|
write_sprite_pixel:
|
|
mov eax, pPixel
|
|
mov [eax], edx
|
|
|
|
skip_sprite_pixel:
|
|
mov eax, texPosY
|
|
add eax, texStepY
|
|
mov texPosY, eax
|
|
|
|
add pPixel, SCREEN_PITCH
|
|
inc ecx
|
|
jmp sprite_pixel_y
|
|
|
|
advance_stripe:
|
|
inc stripe
|
|
jmp stripe_loop
|
|
|
|
next_sprite:
|
|
inc i
|
|
jmp sprite_loop
|
|
|
|
sprites_done:
|
|
ret
|
|
RenderSprites ENDP
|
|
|
|
; ------------------------------------------------------------------------------
|
|
; RenderGlass: Renders semi-transparent glass wall pass over background & sprites
|
|
; ------------------------------------------------------------------------------
|
|
RenderGlass PROC uses ebx ecx edx esi edi
|
|
LOCAL screenX:DWORD
|
|
LOCAL drawStart:DWORD, drawEnd:DWORD, lineHeight:DWORD
|
|
LOCAL texX:DWORD, texPos:DWORD, texStep:DWORD
|
|
LOCAL texPtr:DWORD, pScreenPixel:DWORD
|
|
LOCAL sideHit:DWORD, perpDist:DWORD
|
|
|
|
mov eax, CELL_GLASS
|
|
shl eax, 16 ; CELL_GLASS * TEX_SIZE_BYTES (8 * 65536)
|
|
add eax, OFFSET textureBuffer
|
|
mov texPtr, eax
|
|
|
|
mov screenX, 0
|
|
glass_col_loop:
|
|
mov ecx, screenX
|
|
cmp ecx, SCREEN_W
|
|
jge glass_done
|
|
|
|
mov eax, [glassDistBuffer + ecx*4]
|
|
test eax, eax
|
|
jz next_glass_col
|
|
|
|
mov perpDist, eax
|
|
mov eax, [glassDrawStartBuf + ecx*4]
|
|
mov drawStart, eax
|
|
mov eax, [glassDrawEndBuf + ecx*4]
|
|
mov drawEnd, eax
|
|
mov eax, [glassLineHeightBuf + ecx*4]
|
|
mov lineHeight, eax
|
|
mov eax, [glassTexXBuf + ecx*4]
|
|
mov texX, eax
|
|
mov eax, [glassSideHitBuf + ecx*4]
|
|
mov sideHit, eax
|
|
|
|
; Texture vertical step: texStep = (128 << 16) / lineHeight
|
|
mov eax, (128 shl 16)
|
|
xor edx, edx
|
|
div lineHeight
|
|
mov texStep, eax
|
|
|
|
; texPos = (drawStart - SCREEN_HALF_H + lineHeight / 2) * texStep
|
|
mov eax, drawStart
|
|
sub eax, SCREEN_HALF_H
|
|
mov ebx, lineHeight
|
|
shr ebx, 1
|
|
add eax, ebx
|
|
imul eax, texStep
|
|
mov texPos, eax
|
|
|
|
; Screen pixel address: screenBuffer + (drawStart * SCREEN_PITCH) + (screenX * 4)
|
|
mov eax, drawStart
|
|
imul eax, SCREEN_PITCH
|
|
mov ecx, screenX
|
|
shl ecx, 2
|
|
add eax, ecx
|
|
add eax, OFFSET screenBuffer
|
|
mov pScreenPixel, eax
|
|
|
|
mov ecx, drawStart
|
|
glass_pixel_loop:
|
|
cmp ecx, drawEnd
|
|
jg next_glass_col
|
|
|
|
; Sample texture: texY = (texPos >> 16) & TEX_MASK
|
|
mov eax, texPos
|
|
shr eax, 16
|
|
and eax, TEX_MASK
|
|
mov ebx, eax ; ebx = texY
|
|
|
|
shl eax, 7 ; texY * 128
|
|
add eax, texX
|
|
shl eax, 2
|
|
add eax, texPtr
|
|
mov edx, [eax] ; edx = sampled texel (00RRGGBBh)
|
|
|
|
; Wall shading: Y-facing walls darkened 50% for 3D depth perception
|
|
cmp sideHit, 1
|
|
jne @F
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
@@:
|
|
; Distance fog for glass
|
|
mov eax, perpDist
|
|
cmp eax, 350
|
|
jl glass_blend
|
|
cmp eax, 700
|
|
jge glass_fog_black
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
jmp glass_blend
|
|
glass_fog_black:
|
|
mov edx, 00111111h
|
|
|
|
glass_blend:
|
|
; Check if this texel is the solid metal frame:
|
|
; Outer 6 pixels: texX < 6 || texX >= 122 || texY < 6 || texY >= 122
|
|
cmp texX, 6
|
|
jl write_solid_frame
|
|
cmp texX, 122
|
|
jge write_solid_frame
|
|
cmp ebx, 6
|
|
jl write_solid_frame
|
|
cmp ebx, 122
|
|
jge write_solid_frame
|
|
|
|
; Inside the glass pane: SEMI-TRANSPARENT BLEND!
|
|
; Background pixel from screenBuffer
|
|
mov eax, pScreenPixel
|
|
mov eax, [eax]
|
|
|
|
; Check diagonal reflection streak:
|
|
mov esi, texX
|
|
add esi, ebx
|
|
and esi, 63
|
|
cmp esi, 4
|
|
jl glass_shine_streak
|
|
cmp esi, 24
|
|
jl glass_pane_blend
|
|
cmp esi, 28
|
|
jl glass_shine_streak
|
|
|
|
glass_pane_blend:
|
|
; 50% background + 50% glass tint:
|
|
and eax, 00FEFEFEh
|
|
shr eax, 1
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
add eax, edx
|
|
jmp write_glass_out
|
|
|
|
glass_shine_streak:
|
|
; Specular reflection: 50% background + 50% glass + specular glare
|
|
and eax, 00FEFEFEh
|
|
shr eax, 1
|
|
and edx, 00FEFEFEh
|
|
shr edx, 1
|
|
add eax, edx
|
|
; Add specular highlight:
|
|
add eax, 00203040h
|
|
|
|
write_glass_out:
|
|
mov edx, pScreenPixel
|
|
mov [edx], eax
|
|
jmp next_glass_pixel
|
|
|
|
write_solid_frame:
|
|
mov eax, pScreenPixel
|
|
mov [eax], edx
|
|
|
|
next_glass_pixel:
|
|
mov eax, texPos
|
|
add eax, texStep
|
|
mov texPos, eax
|
|
|
|
add pScreenPixel, SCREEN_PITCH
|
|
inc ecx
|
|
jmp glass_pixel_loop
|
|
|
|
next_glass_col:
|
|
inc screenX
|
|
jmp glass_col_loop
|
|
|
|
glass_done:
|
|
ret
|
|
RenderGlass ENDP
|