using System; using System.Collections.Generic; using System.Drawing; namespace VL.Pipeline.Rendering { public class SectorRenderer { public float playerx = 0; public float playery = 0; public float angle = 0; public float height = 32; public readonly int w; public readonly int h; private readonly float scale; public readonly List world = new(); private readonly TextureManager textureManager; private float cosAngle, sinAngle; private bool trigDirty = true; private int[] ceilEnd; private int[] floorStart; private readonly float floorOriginX = 0f; private readonly float floorOriginY = 0f; private float[] depthBuffer; public SectorRenderer(int screenw, int screenh, TextureManager textureMgr = null) { w = screenw; h = screenh; scale = w / 2f; textureManager = textureMgr ?? new TextureManager(); ceilEnd = new int[w]; floorStart = new int[w]; depthBuffer = new float[w]; } public TextureManager TextureManager => textureManager; public void move(float dx, float dy) { playerx += dx; playery += dy; } public void rotate(float deg) { angle += deg * (MathF.PI / 180f); while (angle > MathF.PI) angle -= 2 * MathF.PI; while (angle < -MathF.PI) angle += 2 * MathF.PI; trigDirty = true; } private void updateTrig() { if (trigDirty) { cosAngle = MathF.Cos(-angle); sinAngle = MathF.Sin(-angle); trigDirty = false; } } //the enter render function public IEnumerable render() { updateTrig(); for (int x = 0; x < w; x++) { ceilEnd[x] = 0; floorStart[x] = h - 1; } for (int i = 0; i < w; i++) { depthBuffer[i] = float.MaxValue; } foreach (var s in world) { //this is my main wall handler foreach (var wall in s.walls) { var (sx, sy) = transform(wall.start); var (ex, ey) = transform(wall.end); const float near_clip = 1.0f; if (sy <= near_clip && ey <= near_clip) { continue; } clip(ref sx, ref sy, ref ex, ref ey, near_clip); if (sy <= near_clip && ey <= near_clip) { continue; } sy = Math.Max(near_clip, sy); ey = Math.Max(near_clip, ey); float sxproj = (sx / sy) * scale + this.w / 2.0f; float exproj = (ex / ey) * scale + this.w / 2.0f; int x0 = (int)sxproj; int x1 = (int)exproj; bool flipped = false; if (x0 > x1) { (x0, x1) = (x1, x0); (sxproj, exproj) = (exproj, sxproj); (sx, ex) = (ex, sx); (sy, ey) = (ey, sy); flipped = true; } if (x1 < 0 || x0 >= this.w) continue; x0 = Math.Max(0, x0); x1 = Math.Min(this.w - 1, x1); if (x1 <= x0) continue; float top_s = h / 2f - ((s.ceil - height) / sy) * scale; float bot_s = h / 2f - ((s.floor - height) / sy) * scale; float top_e = h / 2f - ((s.ceil - height) / ey) * scale; float bot_e = h / 2f - ((s.floor - height) / ey) * scale; float wallLength = MathF.Sqrt((wall.end.x - wall.start.x) * (wall.end.x - wall.start.x) + (wall.end.y - wall.start.y) * (wall.end.y - wall.start.y)); for (int x = x0; x <= x1; x++) { float t = (exproj - sxproj != 0) ? (x - sxproj) / (exproj - sxproj) : 0; t = Math.Max(0, Math.Min(1, t)); float top = lerp(top_s, top_e, t); float bot = lerp(bot_s, bot_e, t); int wallTop = Math.Max(0, (int)Math.Ceiling(top)); int wallBot = Math.Min(h - 1, (int)Math.Floor(bot)); if (wallTop > ceilEnd[x]) ceilEnd[x] = wallTop; if (wallBot < floorStart[x]) floorStart[x] = wallBot; float invDepthS = 1.0f / sy; float invDepthE = 1.0f / ey; float invDepth = lerp(invDepthS, invDepthE, t); const float min_dist = 0.01f; float distance = Math.Max(1.0f / invDepth, min_dist); if (distance >= depthBuffer[x]) continue; depthBuffer[x] = distance; float worldX = lerp(wall.start.x * invDepthS, wall.end.x * invDepthE, t) * distance; float worldY = lerp(wall.start.y * invDepthS, wall.end.y * invDepthE, t) * distance; float texCoordStart = flipped ? wallLength : 0.0f; float texCoordEnd = flipped ? 0.0f : wallLength; float texCoord = lerp(texCoordStart * invDepthS, texCoordEnd * invDepthE, t) * distance; float u = (texCoord * wall.uScale) / 128.0f; u = u - MathF.Floor(u); if (u < 0) u += 1.0f; yield return new WallRenderInfo { x = x, top = wallTop, bottom = wallBot, textureId = wall.textureId, wallX = worldX, wallY = worldY, distance = distance, u = u }; } } } //this is for floors and roof's for (int x = 0; x < w; x++) { float screenX = (x - w / 2.0f) / scale; float rayDirX = MathF.Cos(angle) + screenX * MathF.Sin(angle); float rayDirY = MathF.Sin(angle) - screenX * MathF.Cos(angle); float rayLength = MathF.Sqrt(rayDirX * rayDirX + rayDirY * rayDirY); if (rayLength > 0.001f) { rayDirX /= rayLength; rayDirY /= rayLength; } int floorStartY = Math.Max(h / 2 + 1, floorStart[x]); int floorEndY = h; if (floorStartY < floorEndY) { FlatPiece floorPiece = null; float floorHeight = float.MinValue; foreach (var s in world) { foreach (var piece in s.flatPieces) { if (piece.start.y < height && piece.start.y > floorHeight) { floorPiece = piece; floorHeight = piece.start.y; } } } if (floorPiece != null) { float heightDiff = height - floorHeight; if (heightDiff > 0.001f) { for (int y = floorStartY; y < floorEndY; y++) { float projDistance = (y - h / 2f) / scale; if (projDistance > 0.001f) { float distance = heightDiff / projDistance; if (distance > 0.001f) { float worldX = playerx + distance * rayDirX; float worldY = playery + distance * rayDirY; float u = (worldX / 64.0f); float v = (worldY / 64.0f); u = u - MathF.Floor(u); v = v - MathF.Floor(v); if (u < 0) u += 1.0f; if (v < 0) v += 1.0f; yield return new WallRenderInfo { x = x, top = y, bottom = y + 1, textureId = floorPiece.textureId, wallX = worldX, wallY = worldY, distance = distance, u = u, v = v }; } } } } } } int ceilStartY = 0; int ceilEndY = Math.Min(h / 2, ceilEnd[x]); if (ceilStartY < ceilEndY) { FlatPiece ceilPiece = null; float ceilHeight = float.MaxValue; foreach (var s in world) { foreach (var piece in s.flatPieces) { if (piece.start.y > height && piece.start.y < ceilHeight) { ceilPiece = piece; ceilHeight = piece.start.y; } } } if (ceilPiece != null) { float heightDiff = ceilHeight - height; if (heightDiff > 0.001f) { for (int y = ceilStartY; y < ceilEndY; y++) { float projDistance = (h / 2f - y) / scale; if (projDistance > 0.001f) { float distance = heightDiff / projDistance; if (distance > 0.001f) { float worldX = playerx + distance * rayDirX; float worldY = playery + distance * rayDirY; float u = (worldX / 64.0f); float v = (worldY / 64.0f); u = u - MathF.Floor(u); v = v - MathF.Floor(v); if (u < 0) u += 1.0f; if (v < 0) v += 1.0f; yield return new WallRenderInfo { x = x, top = y, bottom = y + 1, textureId = ceilPiece.textureId, wallX = worldX, wallY = worldY, distance = distance, u = u, v = v }; } } } } } } } } private (float, float) transform(Vertex v) { float dx = v.x - playerx; float dy = v.y - playery; float rx = dx * cosAngle - dy * sinAngle; float ry = dx * sinAngle + dy * cosAngle; return (rx, ry); } //quick clipping check private void clip(ref float sx, ref float sy, ref float ex, ref float ey, float nearClip) { if (sy <= nearClip && ey > nearClip) { float t = (nearClip - sy) / (ey - sy); sx = sx + (ex - sx) * t; sy = nearClip; } else if (ey <= nearClip && sy > nearClip) { float t = (nearClip - ey) / (sy - ey); ex = ex + (sx - ex) * t; ey = nearClip; } sy = Math.Max(nearClip, sy); ey = Math.Max(nearClip, ey); } private float lerp(float a, float b, float t) => a + (b - a) * t; public struct Vertex { public float x; public float y; public Vertex(float x, float y) { this.x = x; this.y = y; } } //wall constructer public struct Wall { public Vertex start; public Vertex end; public int textureId; public float uScale; public float vScale; public Wall(Vertex start, Vertex end, int textureId = 0, float uScale = 1.0f, float vScale = 1.0f) { this.start = start; this.end = end; this.textureId = textureId; this.uScale = uScale; this.vScale = vScale; } } // public class Sector { public List walls = new(); public List flatPieces = new(); public int floor = 0; public int ceil = 64; public void UpdateBounds() { Console.WriteLine("update bounds got called"); } //this used to be a helper method but not really that important anymore. public void AddFlatPiece(float yPosition, int textureId) { flatPieces.Add(new FlatPiece( new Vertex(-150, yPosition), new Vertex(150, yPosition), textureId )); } //gets the close point from a line (like for collison for smth) public SectorRenderer.Vertex ClosestPointOnLine(SectorRenderer.Vertex a, SectorRenderer.Vertex b, float px, float py) { float ax = a.x; float ay = a.y; float bx = b.x; float by = b.y; float abx = bx - ax; float aby = by - ay; float denominator = abx * abx + aby * aby; if (denominator < 0.0001f) return a; float t = ((px - ax) * abx + (py - ay) * aby) / denominator; t = MathF.Max(0, MathF.Min(1, t)); return new SectorRenderer.Vertex(ax + abx * t, ay + aby * t); } } public struct WallRenderInfo { public int x; public int top; public int bottom; public int textureId; public float wallX; public float wallY; public float distance; public float u; public float v; } //roof/ floor constructor public class FlatPiece { public Vertex start; public Vertex end; public int textureId; public FlatPiece(Vertex start, Vertex end, int textureId) { this.start = start; this.end = end; this.textureId = textureId; } } public int GetWallPixelColor(WallRenderInfo wallInfo, int y, int screenHeight) { float wallHeight = wallInfo.bottom - wallInfo.top; if (wallHeight <= 0) { return Color.Red.ToArgb(); } float v; if (wallHeight == 1) { v = wallInfo.v; } else { v = (float)(y - wallInfo.top) / wallHeight; v = Math.Max(0, Math.Min(1, v)); } if (!TextureManagerFast.sizeCache.TryGetValue(wallInfo.textureId, out var size)) { var tempBitmap = textureManager.GetTexture(wallInfo.textureId); if (tempBitmap == null) { return Color.Blue.ToArgb(); } size = (tempBitmap.Width, tempBitmap.Height); TextureManagerFast.sizeCache[wallInfo.textureId] = size; } if (size.width <= 0 || size.height <= 0) { return Color.Yellow.ToArgb(); } float clampedU = wallInfo.u; float clampedV = v; if (float.IsNaN(clampedU) || float.IsInfinity(clampedU)) clampedU = 0.0f; if (float.IsNaN(clampedV) || float.IsInfinity(clampedV)) clampedV = 0.0f; clampedU = clampedU - MathF.Floor(clampedU); clampedV = clampedV - MathF.Floor(clampedV); if (clampedU < 0) clampedU += 1.0f; if (clampedV < 0) clampedV += 1.0f; int uPixel = (int)(clampedU * size.width); int vPixel = (int)(clampedV * size.height); uPixel = Math.Max(0, Math.Min(size.width - 1, uPixel)); vPixel = Math.Max(0, Math.Min(size.height - 1, vPixel)); try { int color = textureManager.GetPixelFaster(wallInfo.textureId, uPixel, vPixel); Color c = Color.FromArgb(color); if (c.R > 200 && c.G < 50 && c.B > 200) { return Color.Gray.ToArgb(); } return color; } catch (Exception ex) { Console.WriteLine($"Texture sampling error: {ex.Message}"); return Color.Green.ToArgb(); } } public Color GetWallColor(WallRenderInfo wallInfo) { return textureManager.GetTextureColor(wallInfo.textureId); } } }