; ============================================================================== ; 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