EBLAN_ENGINE/include/engine_raycaster.inc

741 lines
18 KiB
PHP

; ==============================================================================
; EBLAN ENGINE - 2.5D Software Raycaster Core & Sprite Rasterizer
; ==============================================================================
.code
; ------------------------------------------------------------------------------
; 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 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
; --------------------------------------------------------------------------
; 1. Fast Background Clear: Ceiling (Top Half) and Floor (Bottom Half)
; --------------------------------------------------------------------------
mov edi, OFFSET screenBuffer
mov eax, mapCeilingColor
mov ecx, (SCREEN_W * SCREEN_HALF_H)
rep stosd
mov eax, mapFloorColor
mov ecx, (SCREEN_W * SCREEN_HALF_H)
rep stosd
; --------------------------------------------------------------------------
; 2. Raycasting: 320 Columns across Field of View
; --------------------------------------------------------------------------
mov screenX, 0
ray_column_loop:
; Calculate Ray Angle: playerAngle - FOV_HALF + (screenX * FOV_ANGLES / 320)
mov eax, screenX
imul eax, FOV_ANGLES
xor edx, edx
mov ecx, SCREEN_W
div ecx ; eax = offset from left of FOV
add eax, playerAngle
sub eax, FOV_HALF
and eax, ANGLE_MASK
mov rayAngle, eax
; 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, playerX
sar eax, 6
mov mapX, eax
mov eax, playerY
sar eax, 6
mov mapY, eax
; Delta Dist X = abs(64 << 16 / rayDirX)
mov eax, rayDirX
test eax, eax
jnz calc_delta_x
mov eax, 1 ; avoid div by 0
calc_delta_x:
cdq
xor eax, edx
sub eax, edx ; eax = abs(rayDirX)
mov ecx, eax
mov eax, (64 shl 16)
xor edx, edx
div ecx
mov deltaDistX, eax
; Delta Dist Y = abs(64 << 16 / rayDirY)
mov eax, rayDirY
test eax, eax
jnz calc_delta_y
mov eax, 1
calc_delta_y:
cdq
xor eax, edx
sub eax, edx ; eax = abs(rayDirY)
mov ecx, eax
mov eax, (64 shl 16)
xor edx, edx
div ecx
mov deltaDistY, eax
; Calculate Step and Initial Side Dist
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, playerX
sub edx, eax
imul edx, deltaDistX
sar edx, 6
mov sideDistX, edx
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, playerX
imul eax, deltaDistX
sar eax, 6
mov sideDistX, eax
check_step_y:
cmp rayDirY, 0
jge ray_dir_y_pos
mov stepY, -1
mov eax, mapY
shl eax, 6
mov edx, playerY
sub edx, eax
imul edx, deltaDistY
sar edx, 6
mov sideDistY, edx
jmp dda_loop
ray_dir_y_pos:
mov stepY, 1
mov eax, mapY
inc eax
shl eax, 6
sub eax, playerY
imul eax, deltaDistY
sar eax, 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
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:
; perpWallDist = dist * cos(rayAngle - playerAngle)
mov ebx, rayAngle
sub ebx, playerAngle
and ebx, ANGLE_MASK
mov ecx, [tableCos + ebx*4]
imul ecx ; edx:eax
shrd eax, edx, 16
cmp eax, 1 ; Prevent division by zero
jg @F
mov eax, 1
@@:
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)
xor edx, edx
div perpWallDist
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)
; --------------------------------------------------------------------------
cmp sideHit, 0
jne tex_hit_y
; wallHit = playerY + perpWallDist * rayDirY
mov eax, perpWallDist
mov ebx, rayDirY
imul ebx
shrd eax, edx, 16
add eax, playerY
jmp compute_tex_x
tex_hit_y:
mov eax, perpWallDist
mov ebx, rayDirX
imul ebx
shrd eax, edx, 16
add eax, playerX
compute_tex_x:
and eax, 63 ; coordinate within 64-unit cell
shl eax, 1 ; scale 0..63 to 0..127 (TEX_W)
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
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
; --------------------------------------------------------------------------
; 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)
; 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
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:
inc screenX
cmp screenX, SCREEN_W
jl ray_column_loop
; --------------------------------------------------------------------------
; 3. Render Sprites
; --------------------------------------------------------------------------
call RenderSprites
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
LOCAL i:DWORD, j:DWORD, sprIdx:DWORD
LOCAL spriteX:DWORD, spriteY:DWORD
LOCAL transX:DWORD, transY:DWORD
LOCAL sprScreenX:DWORD, sprSize: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
mov eax, spriteCount
test eax, eax
jz sprites_done
; Camera basis for -playerAngle (rotate world into camera space)
mov eax, ANGLE_512
sub eax, playerAngle
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, playerX ; dx
imul eax, eax
mov ecx, eax
mov eax, [ebx].SpriteStruct.y
sub eax, playerY ; 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
jbe sort_insert ; dist[j] <= key -> insert after j
; 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, playerX
mov spriteX, eax
mov eax, [ebx].SpriteStruct.y
sub eax, playerY
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 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, sprSize
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
mov eax, drawStartX
mov stripe, eax
stripe_loop:
mov eax, stripe
cmp eax, drawEndX
jg next_sprite
; 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
; 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
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