/* SPDX-License-Identifier: MIT */ /* * konsofren.h * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef KONSOFREN_H #define KONSOFREN_H /* the 3D part (cameras, triangles, meshes, billboards, mipmaps, fog) is only there if you ask for it. define KONSOFREN_3D before this header in every file that includes it, and in the one with the implementation. a 2D game compiles without it */ #include #include #include #include #include "konmath.h" #ifdef __cplusplus extern "C" { #endif #define KON_BACKGROUND_COLOR 0xFF05050A typedef struct kon_framebuffer { int width, height; uint32_t *data; float *depth; /* created the first time something is drawn with KON_RENDER_DEPTH */ } kon_framebuffer_t; typedef kon_framebuffer_t kon_image; typedef enum kon_imageFormat { konFormatRGBA8 = 0, konFormatABGR8, konFormatARGB8, konFormatBGRA8 } kon_imageFormat_t; typedef enum kon_filter { KON_FILTER_NEAREST = 0, KON_FILTER_LINEAR } kon_filter_t; /* how a drawn pixel is mixed with what is already there. its alpha says how much of the effect to apply */ typedef enum kon_blend { KON_BLEND_NORMAL, KON_BLEND_ADD, /* brightens, for glows and light beams */ KON_BLEND_MULTIPLY, /* darkens, for shadows and colouring what is under it */ KON_BLEND_SCREEN /* brightens without going past white */ } kon_blend_t; typedef struct kon_rect { int x, y, w, h; } kon_rect_t; #ifdef __cplusplus static inline kon_rect_t kon_makeRect_(int x, int y, int w, int h) { kon_rect_t r; r.x = x; r.y = y; r.w = w; r.h = h; return r; } #define KON_RECT(x, y, w, h) kon_makeRect_((int)(x), (int)(y), (int)(w), (int)(h)) #else #define KON_RECT(x, y, w, h) ((kon_rect_t){(int)(x), (int)(y), (int)(w), (int)(h)}) #endif /* game is the fixed-size framebuffer you draw into, screen always matches the window */ typedef struct kon_display { kon_framebuffer_t *game; kon_framebuffer_t *screen; int integerOnly; /* where the game image sits inside screen, kept up to date by the functions below */ int viewX, viewY, viewWidth, viewHeight; int *columnMap; int *linearMap; /* x0, x1 and weight for every view column, only built while the filter is linear */ kon_filter_t filter; } kon_display_t; #ifdef KONSOFREN_3D #define KONSOFREN_HAS_3D typedef enum kon_projection { KON_PROJECTION_PERSPECTIVE = 0, KON_PROJECTION_ORTHOGRAPHIC } kon_projection_t; typedef struct kon_camera { kon_vec3_t position; kon_vec3_t target; kon_vec3_t up; kon_projection_t projection; float fov; /* vertical field of view in radians, perspective only */ float orthoSize; /* half the visible height in world units, orthographic only */ float nearZ, farZ; } kon_camera_t; /* edges holds pairs of indices into vertices */ typedef struct kon_wireMesh { const kon_vec3_t *vertices; const uint16_t *edges; int edgeCount; } kon_wireMesh_t; typedef enum kon_renderFlags { KON_RENDER_NONE = 0, KON_RENDER_WIREFRAME = 1 << 0, KON_RENDER_DEPTH = 1 << 1, KON_RENDER_CULL_BACK = 1 << 2, KON_RENDER_FLAT = 1 << 3 /* light each triangle as a whole instead of smoothly across its vertex normals */ } kon_renderFlags_t; #define KON_RENDER_DEFAULT ((kon_renderFlags_t)(KON_RENDER_DEPTH | KON_RENDER_CULL_BACK)) /* normal points away from the surface and is only used for lighting, uv (0, 0) is the top-left of the texture, (1, 1) the bottom-right */ typedef struct kon_vertex { kon_vec3_t position; kon_vec3_t normal; kon_vec2_t uv; } kon_vertex_t; /* smaller copies of a texture, each half the size of the one before, down to 1 pixel. level 0 is the texture itself. far away surfaces use the small ones so they stop shimmering */ typedef struct kon_mipmaps { int levels; kon_image *image[16]; /* image[0] is not owned, the rest are */ } kon_mipmaps_t; /* texture is used when it isn't NULL, otherwise color. mips can stay NULL, the texture is used at full size. with mips the texture still has to be the one they were made from */ typedef struct kon_material { const kon_image *texture; uint32_t color; kon_filter_t filter; const kon_mipmaps_t *mips; } kon_material_t; /* one light from far away, like the sun, plus a base light that reaches everything */ typedef struct kon_light { kon_vec3_t direction; /* the way the light travels, (0, -1, 0) shines straight down */ uint32_t color; /* 0xFFRRGGBB, alpha is ignored */ float intensity; /* strength of the directional part, 0 to 1 */ float ambient; /* strength of the base light, 0 to 1 */ uint32_t fogColor; /* what far away things fade into, usually the sky */ float fogStart; /* distance where the fade begins */ float fogEnd; /* distance where it is all fog, 0 turns fog off */ } kon_light_t; /* front faces are counter-clockwise, indices hold three entries per triangle */ typedef struct kon_mesh { const kon_vertex_t *vertices; const uint16_t *indices; int triangleCount; } kon_mesh_t; #endif /* KONSOFREN_3D */ /*** 2D declarations ***/ /*** framebuffer declarations ***/ kon_framebuffer_t *kon_createFramebuffer(int width, int height); void kon_freeFramebuffer(kon_framebuffer_t *fb); void kon_putPixel(kon_framebuffer_t *fb, int x, int y, uint32_t color); void kon_clearFramebuffer(kon_framebuffer_t *fb, uint32_t color); void kon_resizeFramebuffer(kon_framebuffer_t *fb, int width, int height); void kon_exportPixels(kon_framebuffer_t *fb, kon_imageFormat_t format, uint8_t *out); /*** image declarations ***/ kon_image *kon_loadImage(const uint8_t *pixels, int width, int height, kon_imageFormat_t format); void kon_freeImage(kon_image *image); void kon_drawImage(kon_framebuffer_t *fb, int x, int y, int width, int height, kon_image *image); /* sprite sheets: draws the src rectangle of image at x, y with no scaling */ void kon_drawImageRegion(kon_framebuffer_t *fb, int x, int y, const kon_image *image, kon_rect_t src); /* the rectangle of frame index in a sheet of equally sized frames, counted left to right then top to bottom */ kon_rect_t kon_frameRect(const kon_image *image, int frameWidth, int frameHeight, int index); /* x, y is where the origin point (in src pixels, so w / 2, h / 2 is the centre) lands on fb. angle is in radians, positive turns clockwise on screen. a negative scale flips */ void kon_drawImageEx(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter); /* the same, with every pixel multiplied by tint (0xAARRGGBB, 0xFFFFFFFF changes nothing). the alpha fades the image out, the colour darkens or colours it */ void kon_drawImageTint(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter, uint32_t tint); /* the same, mixed in with blend. positions beyond a few million pixels and values that are not numbers draw nothing */ void kon_drawImageBlend(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter, uint32_t tint, kon_blend_t blend); /*** draw function declarations ***/ void kon_drawRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color); void kon_fillRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color); void kon_drawLine(kon_framebuffer_t *fb, int x0, int y0, int x1, int y1, uint32_t color); void kon_drawCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color); void kon_fillCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color); /*** display declarations ***/ kon_display_t *kon_createDisplay(int gameWidth, int gameHeight, int windowWidth, int windowHeight); void kon_freeDisplay(kon_display_t *display); void kon_setGameResolution(kon_display_t *display, int gameWidth, int gameHeight); void kon_setIntegerScaling(kon_display_t *display, int integerOnly); void kon_resizeDisplay(kon_display_t *display, int windowWidth, int windowHeight); void kon_setDisplayFilter(kon_display_t *display, kon_filter_t filter); /* scales game into screen, then blit display->screen to the window */ void kon_presentDisplay(kon_display_t *display); /* returns 0 if the window position is on a bar, outside the game image */ int kon_windowToGame(const kon_display_t *display, int windowX, int windowY, int *gameX, int *gameY); #ifdef KONSOFREN_3D /*** 3D declarations ***/ /*** camera declarations ***/ kon_camera_t kon_cameraDefault(void); kon_mat4_t kon_cameraView(const kon_camera_t *camera); kon_mat4_t kon_cameraProjection(const kon_camera_t *camera, int width, int height); kon_mat4_t kon_cameraViewProjection(const kon_camera_t *camera, int width, int height); /*** 3D draw declarations ***/ /* returns 0 if the point is behind the camera */ int kon_worldToScreen(const kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vec3_t point, kon_vec2_t *out); void kon_drawLine3D(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vec3_t a, kon_vec3_t b, uint32_t color); void kon_drawWireMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4_t model, const kon_wireMesh_t *mesh, uint32_t color); kon_material_t kon_materialColor(uint32_t color); kon_material_t kon_materialTexture(const kon_image *texture); /* NULL if there is no memory. free it with kon_freeMipmaps, the texture has to outlive it */ kon_mipmaps_t *kon_createMipmaps(const kon_image *texture); void kon_freeMipmaps(kon_mipmaps_t *mips); kon_light_t kon_lightDefault(void); /* light can be NULL for no lighting. the triangle's positions and normals are in world space */ void kon_drawTriangle3D(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vertex_t a, kon_vertex_t b, kon_vertex_t c, const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags); void kon_drawMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4_t model, const kon_mesh_t *mesh, const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags); /* a flat sprite that always faces the camera, for things you can't model. position is the middle of its feet in the world, height and width are in world units, src is the part of image to use (the whole image if it is 0 wide). brightness scales the colors, 1 is as drawn, so you can darken it with distance yourself. pixels with alpha 128 or more are solid, hide behind nearer things with KON_RENDER_DEPTH and write depth, the rest blend on top. returns 0 if nothing was drawn because it is behind the camera */ int kon_drawBillboard(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vec3_t position, float width, float height, const kon_image *image, kon_rect_t src, float brightness, kon_renderFlags_t flags); /*** lights that sit in the world ***/ /* a light with a place. with cone 0 it shines all around, otherwise it is a spot light like a flashlight pointing along direction */ typedef struct kon_pointLight { kon_vec3_t position; uint32_t color; /* 0xFFRRGGBB, alpha is ignored */ float radius; /* it reaches nothing farther than this */ float intensity; /* 1 is full strength, more than that lights things brighter than they were drawn */ kon_vec3_t direction; /* spot lights only, the way it points */ float cone; /* spot lights only, how wide it is from the middle to the edge in radians, 0 for a light that shines all around */ float soft; /* spot lights only, 0 to 1, how much of the cone fades out at the edge */ } kon_pointLight_t; /* lights what is already drawn, after everything with depth is in and before the screen elements. every pixel that has depth gets color * (ambient + the light of every light that reaches it). draw the scene with light.ambient 1 and intensity 0 so it still has its plain colors, this makes it dark and then lights it. it knows where things are but not which way they face, and it has no shadows: light goes through walls, so keep the radius of a light inside its room. it costs about one pass over the screen plus a bit for every light that is close. needs the depth buffer, so draw with KON_RENDER_DEPTH */ void kon_lightPass(kon_framebuffer_t *fb, kon_mat4_t viewProjection, const kon_pointLight_t *lights, int count, float ambient); /* cube from -1 to 1 with every face mapped to the whole texture */ extern const kon_mesh_t kon_cubeMesh; #endif /* KONSOFREN_3D */ /*** implementation ***/ #ifdef __cplusplus } #endif #ifdef KONSOFREN_IMPLEMENTATION /*** private helper ***/ static inline uint32_t kon_blendColor(uint32_t dst, uint32_t src) { uint8_t src_a = (uint8_t)((src >> 24) & 0xFF); uint8_t src_r = (uint8_t)((src >> 16) & 0xFF); uint8_t src_g = (uint8_t)((src >> 8) & 0xFF); uint8_t src_b = (uint8_t)((src >> 0) & 0xFF); if (src_a == 0xFF) { return src; } uint8_t dst_a = (uint8_t)((dst >> 24) & 0xFF); uint8_t dst_r = (uint8_t)((dst >> 16) & 0xFF); uint8_t dst_g = (uint8_t)((dst >> 8) & 0xFF); uint8_t dst_b = (uint8_t)((dst >> 0) & 0xFF); uint8_t inv_a = (uint8_t)(255 - src_a); uint8_t out_r = (uint8_t)((src_r * src_a + dst_r * inv_a) / 255); uint8_t out_g = (uint8_t)((src_g * src_a + dst_g * inv_a) / 255); uint8_t out_b = (uint8_t)((src_b * src_a + dst_b * inv_a) / 255); return ((uint32_t)dst_a << 24) | ((uint32_t)out_r << 16) | ((uint32_t)out_g << 8) | (uint32_t)out_b; } static inline int kon_wrapInt_(int v, int n) { v %= n; return v < 0 ? v + n : v; } /* w is 0 to 255, red/blue and green/alpha are blended together in one multiply each */ static inline uint32_t kon_lerpColor_(uint32_t a, uint32_t b, uint32_t w) { uint32_t rb = ((a & 0x00FF00FFu) * (256u - w) + (b & 0x00FF00FFu) * w) >> 8; uint32_t ga = ((a >> 8) & 0x00FF00FFu) * (256u - w) + ((b >> 8) & 0x00FF00FFu) * w; return (rb & 0x00FF00FFu) | (ga & 0xFF00FF00u); } /* fx, fy are in pixels inside r, texel i covers i to i + 1. wrap repeats r, otherwise the edge pixels are held */ static uint32_t kon_sampleLinear_(const kon_framebuffer_t *image, kon_rect_t r, float fx, float fy, int wrap) { float px = fx - 0.5f, py = fy - 0.5f; int x0 = (int)floorf(px), y0 = (int)floorf(py); int x1 = x0 + 1, y1 = y0 + 1; uint32_t wx = (uint32_t)((px - (float)x0) * 256.0f) & 0xFF; uint32_t wy = (uint32_t)((py - (float)y0) * 256.0f) & 0xFF; if (wrap) { x0 = kon_wrapInt_(x0, r.w); x1 = kon_wrapInt_(x1, r.w); y0 = kon_wrapInt_(y0, r.h); y1 = kon_wrapInt_(y1, r.h); } else { if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; if (x0 > r.w - 1) x0 = r.w - 1; if (x1 > r.w - 1) x1 = r.w - 1; if (y0 > r.h - 1) y0 = r.h - 1; if (y1 > r.h - 1) y1 = r.h - 1; } const uint32_t *row0 = &image->data[(size_t)(r.y + y0) * (size_t)image->width + (size_t)r.x]; const uint32_t *row1 = &image->data[(size_t)(r.y + y1) * (size_t)image->width + (size_t)r.x]; return kon_lerpColor_(kon_lerpColor_(row0[x0], row0[x1], wx), kon_lerpColor_(row1[x0], row1[x1], wx), wy); } /* Cohen-Sutherland: clips the line to the framebuffer, returns 0 if nothing is visible */ static int kon_outCode_(double x, double y, int width, int height) { int code = 0; if (x < 0) code |= 1; else if (x > width - 1) code |= 2; if (y < 0) code |= 4; else if (y > height - 1) code |= 8; return code; } static int kon_clipLine_(int width, int height, int *x0, int *y0, int *x1, int *y1) { double ax = *x0, ay = *y0, bx = *x1, by = *y1; double maxX = width - 1, maxY = height - 1; int ca = kon_outCode_(ax, ay, width, height); int cb = kon_outCode_(bx, by, width, height); while (ca | cb) { if (ca & cb) return 0; int out = ca ? ca : cb; double x, y; if (out & 8) { x = ax + (bx - ax) * (maxY - ay) / (by - ay); y = maxY; } else if (out & 4) { x = ax + (bx - ax) * (0 - ay) / (by - ay); y = 0; } else if (out & 2) { y = ay + (by - ay) * (maxX - ax) / (bx - ax); x = maxX; } else { y = ay + (by - ay) * (0 - ax) / (bx - ax); x = 0; } if (out == ca) { ax = x; ay = y; ca = kon_outCode_(ax, ay, width, height); } else { bx = x; by = y; cb = kon_outCode_(bx, by, width, height); } } *x0 = (int)(ax + 0.5); *y0 = (int)(ay + 0.5); *x1 = (int)(bx + 0.5); *y1 = (int)(by + 0.5); return 1; } /*** framebuffer implementation ***/ kon_framebuffer_t *kon_createFramebuffer(int width, int height) { kon_framebuffer_t *fb = calloc(1, sizeof(kon_framebuffer_t)); if (!fb) return NULL; fb->data = calloc((size_t)width * (size_t)height, sizeof(uint32_t)); if (!fb->data) { free(fb); return NULL; } fb->width = width; fb->height = height; return fb; } void kon_freeFramebuffer(kon_framebuffer_t *fb) { if (!fb) return; free(fb->data); free(fb->depth); free(fb); } void kon_putPixel(kon_framebuffer_t *fb, int x, int y, uint32_t color) { if (!fb) return; if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) return; uint32_t dst = fb->data[y * fb->width + x]; fb->data[y * fb->width + x] = kon_blendColor(dst, color); } void kon_clearFramebuffer(kon_framebuffer_t *fb, uint32_t color) { if (!fb) return; for (int i = 0; i < fb->width * fb->height; i++) { /* did it directly to not check overhead because of the if in bounds check */ fb->data[i] = color; } if (fb->depth) { for (int i = 0; i < fb->width * fb->height; i++) { fb->depth[i] = 1.0f; } } } void kon_resizeFramebuffer(kon_framebuffer_t *fb, int width, int height) { if (!fb) return; uint32_t *tmp = realloc(fb->data, (size_t)width * (size_t)height * sizeof(uint32_t)); if (!tmp) return; fb->data = tmp; fb->width = width; fb->height = height; if (fb->depth) { float *depth = realloc(fb->depth, (size_t)width * (size_t)height * sizeof(float)); if (!depth) free(fb->depth); fb->depth = depth; } kon_clearFramebuffer(fb, KON_BACKGROUND_COLOR); } void kon_exportPixels(kon_framebuffer_t *fb, kon_imageFormat_t format, uint8_t *out) { if (!fb || !out) return; int fbSize = fb->width * fb->height; for (int i = 0; i < fbSize; i++) { uint32_t color = fb->data[i]; uint8_t a = (uint8_t)((color >> 24) & 0xFF); uint8_t r = (uint8_t)((color >> 16) & 0xFF); uint8_t g = (uint8_t)((color >> 8) & 0xFF); uint8_t b = (uint8_t)(color & 0xFF); switch (format) { case konFormatRGBA8: out[i * 4 + 0] = r; out[i * 4 + 1] = g; out[i * 4 + 2] = b; out[i * 4 + 3] = a; break; case konFormatABGR8: out[i * 4 + 0] = a; out[i * 4 + 1] = b; out[i * 4 + 2] = g; out[i * 4 + 3] = r; break; case konFormatARGB8: out[i * 4 + 0] = a; out[i * 4 + 1] = r; out[i * 4 + 2] = g; out[i * 4 + 3] = b; break; case konFormatBGRA8: out[i * 4 + 0] = b; out[i * 4 + 1] = g; out[i * 4 + 2] = r; out[i * 4 + 3] = a; break; default: return; } } } /*** draw functions implementation ***/ void kon_drawRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color) { if (!fb) return; kon_drawLine(fb, x, y, x + width, y, color); kon_drawLine(fb, x, y + height, x + width, y + height, color); kon_drawLine(fb, x, y, x, y + height, color); kon_drawLine(fb, x + width, y, x + width, y + height, color); } void kon_fillRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color) { if (!fb) return; if (width < 0 || height < 0) return; if (x < 0) { width += x; x = 0; } if (y < 0) { height += y; y = 0; } if (x + width > fb->width ) width = fb->width - x; if (y + height > fb->height) height = fb->height - y; if (width <= 0 || height <= 0) return; uint32_t a = color >> 24; if (a == 0) return; if (a == 0xFF) { for (int row = 0; row < height; row++) { uint32_t *out = fb->data + (size_t)(y + row) * fb->width + x; for (int col = 0; col < width; col++) out[col] = color; } return; } /* same result as kon_blendColor, with x / 255 done as shifts */ uint32_t inv = 255 - a; uint32_t sr = ((color >> 16) & 0xFF) * a; uint32_t sg = ((color >> 8) & 0xFF) * a; uint32_t sb = (color & 0xFF) * a; for (int row = 0; row < height; row++) { uint32_t *out = fb->data + (size_t)(y + row) * fb->width + x; for (int col = 0; col < width; col++) { uint32_t dst = out[col]; uint32_t r = sr + ((dst >> 16) & 0xFF) * inv; uint32_t g = sg + ((dst >> 8) & 0xFF) * inv; uint32_t b = sb + (dst & 0xFF) * inv; r = (r + 1 + (r >> 8)) >> 8; g = (g + 1 + (g >> 8)) >> 8; b = (b + 1 + (b >> 8)) >> 8; out[col] = (dst & 0xFF000000u) | (r << 16) | (g << 8) | b; } } } void kon_drawLine(kon_framebuffer_t *fb, int x0, int y0, int x1, int y1, uint32_t color) { if (!fb) return; if (!kon_clipLine_(fb->width, fb->height, &x0, &y0, &x1, &y1)) return; int dx = abs(x1 - x0); int dy = abs(y1 - y0); int sx = (x0 < x1) ? 1 : -1; int sy = (y0 < y1) ? 1 : -1; int err = dx - dy; for (;;) { kon_putPixel(fb, x0, y0, color); if (x0 == x1 && y0 == y1) break; int e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } } void kon_drawCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color) { if (!fb) return; if (radius <= 0) return; int x = radius; int y = 0; int err = 1 - radius; while (x >= y) { kon_putPixel(fb, center_x + x, center_y + y, color); kon_putPixel(fb, center_x + y, center_y + x, color); kon_putPixel(fb, center_x - y, center_y + x, color); kon_putPixel(fb, center_x - x, center_y + y, color); kon_putPixel(fb, center_x - x, center_y - y, color); kon_putPixel(fb, center_x - y, center_y - x, color); kon_putPixel(fb, center_x + y, center_y - x, color); kon_putPixel(fb, center_x + x, center_y - y, color); y += 1; if (err < 0) { err += 2 * y + 1; } else { x -= 1; err += 2 * (y - x) + 1; } } } void kon_fillCircle(kon_framebuffer_t *fb, int center_x, int center_y, int radius, uint32_t color) { if (!fb) return; if (radius <= 0) return; int x = radius; int y = 0; int err = 1 - radius; while (x >= y) { kon_drawLine(fb, center_x - x, center_y + y, center_x + x, center_y + y, color); kon_drawLine(fb, center_x - x, center_y - y, center_x + x, center_y - y, color); kon_drawLine(fb, center_x - y, center_y + x, center_x + y, center_y + x, color); kon_drawLine(fb, center_x - y, center_y - x, center_x + y, center_y - x, color); y += 1; if (err < 0) { err += 2 * y + 1; } else { x -= 1; err += 2 * (y - x) + 1; } } } /*** image implementation ***/ kon_image *kon_loadImage(const uint8_t *pixels, int width, int height, kon_imageFormat_t format) { kon_image *image = calloc(1, sizeof(kon_image)); if (!image) return NULL; image->data = malloc((size_t)width * (size_t)height * sizeof(uint32_t)); if (!image->data) { free(image); return NULL; } image->width = width; image->height = height; for (int i = 0; i < width * height; i++) { uint8_t r, g, b, a; switch(format) { case konFormatRGBA8: r = pixels[i * 4 + 0]; g = pixels[i * 4 + 1]; b = pixels[i * 4 + 2]; a = pixels[i * 4 + 3]; break; case konFormatABGR8: a = pixels[i * 4 + 0]; b = pixels[i * 4 + 1]; g = pixels[i * 4 + 2]; r = pixels[i * 4 + 3]; break; case konFormatARGB8: a = pixels[i * 4 + 0]; r = pixels[i * 4 + 1]; g = pixels[i * 4 + 2]; b = pixels[i * 4 + 3]; break; case konFormatBGRA8: b = pixels[i * 4 + 0]; g = pixels[i * 4 + 1]; r = pixels[i * 4 + 2]; a = pixels[i * 4 + 3]; break; default: free(image->data); free(image); return NULL; } image->data[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; } return image; } void kon_freeImage(kon_image *image) { kon_freeFramebuffer(image); } void kon_drawImage(kon_framebuffer_t *fb, int x, int y, int width, int height, kon_image *image) { if (!fb || !image) return; /* just trying to not draw when it's unnecessary */ if (image->width <= 0 || image->height <= 0) return; if (width <= 0 || height <= 0) return; if (x >= fb->width || y >= fb->height) return; /* using this loop to make sure we only the parts of the image that are on screen */ for (int iy = 0; iy < height; iy++) { for (int ix = 0; ix < width; ix++) { int src_x = ix * image->width / width; int src_y = iy * image->height / height; uint32_t color = image->data[src_y * image->width + src_x]; kon_putPixel(fb, x+ix, y+iy, color); } } } static int kon_clipRegion_(const kon_image *image, kon_rect_t *src) { if (src->x < 0) { src->w += src->x; src->x = 0; } if (src->y < 0) { src->h += src->y; src->y = 0; } if (src->x + src->w > image->width) src->w = image->width - src->x; if (src->y + src->h > image->height) src->h = image->height - src->y; return src->w > 0 && src->h > 0; } void kon_drawImageRegion(kon_framebuffer_t *fb, int x, int y, const kon_image *image, kon_rect_t src) { if (!fb || !image) return; if (!kon_clipRegion_(image, &src)) return; int startX = 0, startY = 0, endX = src.w, endY = src.h; if (x < 0) startX = -x; if (y < 0) startY = -y; if (x + endX > fb->width) endX = fb->width - x; if (y + endY > fb->height) endY = fb->height - y; for (int iy = startY; iy < endY; iy++) { const uint32_t *in = &image->data[(size_t)(src.y + iy) * (size_t)image->width + (size_t)src.x]; uint32_t *out = &fb->data[(size_t)(y + iy) * (size_t)fb->width + (size_t)x]; for (int ix = startX; ix < endX; ix++) { uint32_t color = in[ix]; uint32_t alpha = color >> 24; if (alpha == 0xFF) out[ix] = color; else if (alpha != 0) out[ix] = kon_blendColor(out[ix], color); } } } kon_rect_t kon_frameRect(const kon_image *image, int frameWidth, int frameHeight, int index) { kon_rect_t r = {0, 0, 0, 0}; if (!image || frameWidth <= 0 || frameHeight <= 0 || index < 0) return r; int columns = image->width / frameWidth; if (columns <= 0) return r; r.x = (index % columns) * frameWidth; r.y = (index / columns) * frameHeight; r.w = frameWidth; r.h = frameHeight; return r; } static uint32_t kon_tintColor_(uint32_t color, uint32_t tint) { uint32_t a = ((color >> 24) * (tint >> 24) + 127) / 255; uint32_t r = (((color >> 16) & 0xFF) * ((tint >> 16) & 0xFF) + 127) / 255; uint32_t g = (((color >> 8) & 0xFF) * ((tint >> 8) & 0xFF) + 127) / 255; uint32_t b = ((color & 0xFF) * (tint & 0xFF) + 127) / 255; return (a << 24) | (r << 16) | (g << 8) | b; } void kon_drawImageEx(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter) { kon_drawImageTint(fb, image, src, x, y, originX, originY, scaleX, scaleY, angle, filter, 0xFFFFFFFFu); } static uint32_t kon_blendMode_(uint32_t dst, uint32_t src, kon_blend_t mode) { uint32_t a = src >> 24, out = dst & 0xFF000000u; int shift; for (shift = 0; shift < 24; shift += 8) { uint32_t d = (dst >> shift) & 0xFF, c = (src >> shift) & 0xFF, r; switch (mode) { case KON_BLEND_ADD: r = d + c; if (r > 255) r = 255; break; case KON_BLEND_MULTIPLY: r = (d * c + 127) / 255; break; case KON_BLEND_SCREEN: r = 255 - ((255 - d) * (255 - c) + 127) / 255; break; case KON_BLEND_NORMAL: default: r = c; break; } out |= ((d * (255 - a) + r * a + 127) / 255) << shift; } return out; } void kon_drawImageTint(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter, uint32_t tint) { kon_drawImageBlend(fb, image, src, x, y, originX, originY, scaleX, scaleY, angle, filter, tint, KON_BLEND_NORMAL); } void kon_drawImageBlend(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter, uint32_t tint, kon_blend_t blend) { if (!fb || !image) return; if (!kon_clipRegion_(image, &src)) return; if (scaleX == 0.0f || scaleY == 0.0f) return; if (!isfinite(x) || !isfinite(y) || !isfinite(originX) || !isfinite(originY) || !isfinite(scaleX) || !isfinite(scaleY) || !isfinite(angle)) return; if (fabsf(x) > 4e6f || fabsf(y) > 4e6f || fabsf(originX) > 4e6f || fabsf(originY) > 4e6f || fabsf(scaleX) > 4e4f || fabsf(scaleY) > 4e4f) return; if (tint >> 24 == 0) return; if (blend == KON_BLEND_NORMAL && angle == 0.0f && scaleX == 1.0f && scaleY == 1.0f && filter == KON_FILTER_NEAREST && tint == 0xFFFFFFFFu) { kon_drawImageRegion(fb, (int)floorf(x - originX + 0.5f), (int)floorf(y - originY + 0.5f), image, src); return; } float c = cosf(angle), s = sinf(angle); float cornerX[4], cornerY[4]; const float lx[4] = {0.0f, (float)src.w, (float)src.w, 0.0f}; const float ly[4] = {0.0f, 0.0f, (float)src.h, (float)src.h}; float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f; for (int i = 0; i < 4; i++) { float ox = (lx[i] - originX) * scaleX; float oy = (ly[i] - originY) * scaleY; cornerX[i] = x + c * ox - s * oy; cornerY[i] = y + s * ox + c * oy; if (i == 0 || cornerX[i] < minX) minX = cornerX[i]; if (i == 0 || cornerX[i] > maxX) maxX = cornerX[i]; if (i == 0 || cornerY[i] < minY) minY = cornerY[i]; if (i == 0 || cornerY[i] > maxY) maxY = cornerY[i]; } int x0 = (int)floorf(minX), x1 = (int)ceilf(maxX); int y0 = (int)floorf(minY), y1 = (int)ceilf(maxY); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > fb->width) x1 = fb->width; if (y1 > fb->height) y1 = fb->height; float stepXx = c / scaleX, stepXy = -s / scaleY; for (int py = y0; py < y1; py++) { float dx = (float)x0 + 0.5f - x, dy = (float)py + 0.5f - y; float sx = (c * dx + s * dy) / scaleX + originX; float sy = (-s * dx + c * dy) / scaleY + originY; uint32_t *out = &fb->data[(size_t)py * (size_t)fb->width]; for (int px = x0; px < x1; px++, sx += stepXx, sy += stepXy) { if (sx < 0.0f || sy < 0.0f || sx >= (float)src.w || sy >= (float)src.h) continue; uint32_t color; if (filter == KON_FILTER_LINEAR) { color = kon_sampleLinear_(image, src, sx, sy, 0); } else { color = image->data[(size_t)(src.y + (int)sy) * (size_t)image->width + (size_t)(src.x + (int)sx)]; } if (tint != 0xFFFFFFFFu) color = kon_tintColor_(color, tint); uint32_t alpha = color >> 24; if (blend != KON_BLEND_NORMAL) { if (alpha != 0) out[px] = kon_blendMode_(out[px], color, blend); } else if (alpha == 0xFF) { out[px] = color; } else if (alpha != 0) { out[px] = kon_blendColor(out[px], color); } } } } /*** display implementation ***/ static void kon_buildLinearMap_(kon_display_t *display) { if (display->filter != KON_FILTER_LINEAR || display->viewWidth <= 0) return; int *map = realloc(display->linearMap, (size_t)display->viewWidth * 3 * sizeof(int)); if (!map) return; display->linearMap = map; int gameWidth = display->game->width; for (int x = 0; x < display->viewWidth; x++) { float fx = ((float)x + 0.5f) * (float)gameWidth / (float)display->viewWidth - 0.5f; int x0 = (int)floorf(fx); int weight = (int)((fx - (float)x0) * 256.0f) & 0xFF; int x1 = x0 + 1; if (x0 < 0) x0 = 0; if (x1 < 0) x1 = 0; if (x0 > gameWidth - 1) x0 = gameWidth - 1; if (x1 > gameWidth - 1) x1 = gameWidth - 1; map[x * 3 + 0] = x0; map[x * 3 + 1] = x1; map[x * 3 + 2] = weight; } } static void kon_updateView_(kon_display_t *display) { int gameWidth = display->game->width, gameHeight = display->game->height; int windowWidth = display->screen->width, windowHeight = display->screen->height; int scaleX = windowWidth / gameWidth; int scaleY = windowHeight / gameHeight; int scale = (scaleX < scaleY) ? scaleX : scaleY; if (display->integerOnly && scale >= 1) { display->viewWidth = gameWidth * scale; display->viewHeight = gameHeight * scale; } else if ((int64_t)windowWidth * gameHeight <= (int64_t)windowHeight * gameWidth) { /* window is narrower than the game: bars on top and bottom */ display->viewWidth = windowWidth; display->viewHeight = (int)((int64_t)windowWidth * gameHeight / gameWidth); } else { /* window is wider than the game: bars on the sides */ display->viewHeight = windowHeight; display->viewWidth = (int)((int64_t)windowHeight * gameWidth / gameHeight); } display->viewX = (windowWidth - display->viewWidth) / 2; display->viewY = (windowHeight - display->viewHeight) / 2; /* which game column each view column shows, worked out once instead of for every pixel */ int *map = realloc(display->columnMap, (size_t)windowWidth * sizeof(int)); if (!map) { display->viewWidth = 0; return; } display->columnMap = map; for (int x = 0; x < display->viewWidth; x++) { map[x] = (int)((int64_t)x * gameWidth / display->viewWidth); } kon_buildLinearMap_(display); } kon_display_t *kon_createDisplay(int gameWidth, int gameHeight, int windowWidth, int windowHeight) { if (gameWidth <= 0 || gameHeight <= 0 || windowWidth <= 0 || windowHeight <= 0) return NULL; kon_display_t *display = calloc(1, sizeof(kon_display_t)); if (!display) return NULL; display->game = kon_createFramebuffer(gameWidth, gameHeight); display->screen = kon_createFramebuffer(windowWidth, windowHeight); if (!display->game || !display->screen) { kon_freeDisplay(display); return NULL; } kon_clearFramebuffer(display->game, KON_BACKGROUND_COLOR); kon_updateView_(display); return display; } void kon_freeDisplay(kon_display_t *display) { if (!display) return; kon_freeFramebuffer(display->game); kon_freeFramebuffer(display->screen); free(display->columnMap); free(display->linearMap); free(display); } void kon_setGameResolution(kon_display_t *display, int gameWidth, int gameHeight) { if (!display || gameWidth <= 0 || gameHeight <= 0) return; kon_resizeFramebuffer(display->game, gameWidth, gameHeight); kon_updateView_(display); } void kon_setIntegerScaling(kon_display_t *display, int integerOnly) { if (!display) return; display->integerOnly = integerOnly; kon_updateView_(display); } void kon_resizeDisplay(kon_display_t *display, int windowWidth, int windowHeight) { if (!display || windowWidth <= 0 || windowHeight <= 0) return; kon_resizeFramebuffer(display->screen, windowWidth, windowHeight); kon_updateView_(display); } void kon_setDisplayFilter(kon_display_t *display, kon_filter_t filter) { if (!display) return; display->filter = filter; kon_buildLinearMap_(display); } void kon_presentDisplay(kon_display_t *display) { if (!display) return; const uint32_t barColor = 0xFF000000; kon_framebuffer_t *game = display->game; kon_framebuffer_t *screen = display->screen; int rightX = display->viewX + display->viewWidth; for (int y = 0; y < screen->height; y++) { uint32_t *row = &screen->data[(size_t)y * (size_t)screen->width]; if (display->viewWidth <= 0 || y < display->viewY || y >= display->viewY + display->viewHeight) { for (int x = 0; x < screen->width; x++) row[x] = barColor; continue; } if (display->filter == KON_FILTER_LINEAR && display->linearMap) { float fy = ((float)(y - display->viewY) + 0.5f) * (float)game->height / (float)display->viewHeight - 0.5f; int y0 = (int)floorf(fy), y1 = y0 + 1; uint32_t wy = (uint32_t)((fy - (float)y0) * 256.0f) & 0xFF; if (y0 < 0) y0 = 0; if (y1 < 0) y1 = 0; if (y0 > game->height - 1) y0 = game->height - 1; if (y1 > game->height - 1) y1 = game->height - 1; const uint32_t *row0 = &game->data[(size_t)y0 * (size_t)game->width]; const uint32_t *row1 = &game->data[(size_t)y1 * (size_t)game->width]; const int *lin = display->linearMap; for (int x = 0; x < display->viewX; x++) row[x] = barColor; for (int x = 0; x < display->viewWidth; x++, lin += 3) { uint32_t wx = (uint32_t)lin[2]; row[display->viewX + x] = kon_lerpColor_(kon_lerpColor_(row0[lin[0]], row0[lin[1]], wx), kon_lerpColor_(row1[lin[0]], row1[lin[1]], wx), wy); } for (int x = rightX; x < screen->width; x++) row[x] = barColor; continue; } int srcY = (int)((int64_t)(y - display->viewY) * game->height / display->viewHeight); const uint32_t *srcRow = &game->data[(size_t)srcY * (size_t)game->width]; for (int x = 0; x < display->viewX; x++) row[x] = barColor; for (int x = 0; x < display->viewWidth; x++) row[display->viewX + x] = srcRow[display->columnMap[x]]; for (int x = rightX; x < screen->width; x++) row[x] = barColor; } } int kon_windowToGame(const kon_display_t *display, int windowX, int windowY, int *gameX, int *gameY) { if (!display || display->viewWidth <= 0 || display->viewHeight <= 0) return 0; int localX = windowX - display->viewX; int localY = windowY - display->viewY; if (localX < 0 || localX >= display->viewWidth || localY < 0 || localY >= display->viewHeight) return 0; if (gameX) *gameX = (int)((int64_t)localX * display->game->width / display->viewWidth); if (gameY) *gameY = (int)((int64_t)localY * display->game->height / display->viewHeight); return 1; } #ifdef KONSOFREN_3D /*** 3D implementation ***/ /*** private helper ***/ static kon_vec2_t kon_clipToScreen_(const kon_framebuffer_t *fb, kon_vec4_t clip) { /* clamped so the later float to int conversion can't overflow */ float ndcX = kon_clamp(clip.x / clip.w, -1.0e5f, 1.0e5f); float ndcY = kon_clamp(clip.y / clip.w, -1.0e5f, 1.0e5f); /* screen +Y points down, NDC +Y points up */ return KON_VEC2((ndcX + 1.0f) * 0.5f * (float)fb->width, (1.0f - ndcY) * 0.5f * (float)fb->height); } /*** camera implementation ***/ kon_camera_t kon_cameraDefault(void) { kon_camera_t camera; camera.position = KON_VEC3(0, 0, 5); camera.target = KON_VEC3_ZERO; camera.up = KON_VEC3_UP; camera.projection = KON_PROJECTION_PERSPECTIVE; camera.fov = KON_DEG2RAD(60); camera.orthoSize = 5.0f; camera.nearZ = 0.1f; camera.farZ = 100.0f; return camera; } kon_mat4_t kon_cameraView(const kon_camera_t *camera) { return kon_mat4LookAt(camera->position, camera->target, camera->up); } kon_mat4_t kon_cameraProjection(const kon_camera_t *camera, int width, int height) { float aspect = (height > 0) ? (float)width / (float)height : 1.0f; if (camera->projection == KON_PROJECTION_ORTHOGRAPHIC) { float halfH = camera->orthoSize; float halfW = halfH * aspect; return kon_mat4Ortho(-halfW, halfW, -halfH, halfH, camera->nearZ, camera->farZ); } return kon_mat4Perspective(camera->fov, aspect, camera->nearZ, camera->farZ); } kon_mat4_t kon_cameraViewProjection(const kon_camera_t *camera, int width, int height) { return kon_mat4Mul(kon_cameraProjection(camera, width, height), kon_cameraView(camera)); } /*** 3D draw implementation ***/ int kon_worldToScreen(const kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vec3_t point, kon_vec2_t *out) { if (!fb || !out) return 0; kon_vec4_t clip = kon_mat4MulVec4(viewProjection, KON_VEC4(point.x, point.y, point.z, 1)); /* in front of the near plane when z >= -w */ if (clip.z < -clip.w || clip.w <= 0.0f) return 0; *out = kon_clipToScreen_(fb, clip); return 1; } void kon_drawLine3D(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vec3_t a, kon_vec3_t b, uint32_t color) { if (!fb) return; kon_vec4_t ca = kon_mat4MulVec4(viewProjection, KON_VEC4(a.x, a.y, a.z, 1)); kon_vec4_t cb = kon_mat4MulVec4(viewProjection, KON_VEC4(b.x, b.y, b.z, 1)); /* clip against the near plane (z + w >= 0) before dividing, so points behind the camera never get projected */ float da = ca.z + ca.w; float db = cb.z + cb.w; if (da < 0.0f && db < 0.0f) return; if (da < 0.0f || db < 0.0f) { float t = da / (da - db); kon_vec4_t hit = KON_VEC4( ca.x + (cb.x - ca.x) * t, ca.y + (cb.y - ca.y) * t, ca.z + (cb.z - ca.z) * t, ca.w + (cb.w - ca.w) * t ); if (da < 0.0f) ca = hit; else cb = hit; } if (ca.w <= 0.0f || cb.w <= 0.0f) return; kon_vec2_t sa = kon_clipToScreen_(fb, ca); kon_vec2_t sb = kon_clipToScreen_(fb, cb); kon_drawLine(fb, (int)sa.x, (int)sa.y, (int)sb.x, (int)sb.y, color); } void kon_drawWireMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4_t model, const kon_wireMesh_t *mesh, uint32_t color) { if (!fb || !mesh) return; kon_mat4_t mvp = kon_mat4Mul(viewProjection, model); for (int i = 0; i < mesh->edgeCount; i++) { kon_vec3_t a = mesh->vertices[mesh->edges[i * 2 + 0]]; kon_vec3_t b = mesh->vertices[mesh->edges[i * 2 + 1]]; kon_drawLine3D(fb, mvp, a, b, color); } } kon_material_t kon_materialColor(uint32_t color) { kon_material_t material = { NULL, color, KON_FILTER_NEAREST, NULL }; return material; } kon_material_t kon_materialTexture(const kon_image *texture) { kon_material_t material = { texture, 0xFFFFFFFF, KON_FILTER_NEAREST, NULL }; return material; } kon_light_t kon_lightDefault(void) { kon_light_t light; light.direction = KON_VEC3(-0.5f, -1.0f, -0.3f); light.color = 0xFFFFFFFF; light.intensity = 0.8f; light.ambient = 0.3f; light.fogColor = 0xFF000000; light.fogStart = 0.0f; light.fogEnd = 0.0f; return light; } kon_mipmaps_t *kon_createMipmaps(const kon_image *texture) { if (!texture || texture->width < 1 || texture->height < 1) return NULL; kon_mipmaps_t *mips = calloc(1, sizeof(kon_mipmaps_t)); if (!mips) return NULL; mips->image[0] = (kon_image *)texture; mips->levels = 1; while (mips->levels < 16) { const kon_image *from = mips->image[mips->levels - 1]; if (from->width == 1 && from->height == 1) break; int w = from->width > 1 ? from->width / 2 : 1; int h = from->height > 1 ? from->height / 2 : 1; kon_image *to = kon_createFramebuffer(w, h); if (!to) { kon_freeMipmaps(mips); return NULL; } for (int y = 0; y < h; y++) { int y0 = y * 2, y1 = y0 + 1 < from->height ? y0 + 1 : y0; for (int x = 0; x < w; x++) { int x0 = x * 2, x1 = x0 + 1 < from->width ? x0 + 1 : x0; uint32_t p[4] = { from->data[(size_t)y0 * (size_t)from->width + (size_t)x0], from->data[(size_t)y0 * (size_t)from->width + (size_t)x1], from->data[(size_t)y1 * (size_t)from->width + (size_t)x0], from->data[(size_t)y1 * (size_t)from->width + (size_t)x1] }; uint32_t out = 0; for (int shift = 0; shift < 32; shift += 8) { uint32_t sum = ((p[0] >> shift) & 0xFF) + ((p[1] >> shift) & 0xFF) + ((p[2] >> shift) & 0xFF) + ((p[3] >> shift) & 0xFF); out |= ((sum + 2) >> 2) << shift; } to->data[(size_t)y * (size_t)w + (size_t)x] = out; } } mips->image[mips->levels++] = to; } return mips; } void kon_freeMipmaps(kon_mipmaps_t *mips) { if (!mips) return; for (int i = 1; i < mips->levels; i++) kon_freeFramebuffer(mips->image[i]); free(mips); } /* how bright a surface with this normal is, 0 to 1. toLight points at the light and has length 1 */ static double kon_lightFactor_(const kon_light_t *light, kon_vec3_t toLight, kon_vec3_t normal) { double diffuse = (double)kon_vec3Dot(kon_vec3Normalize(normal), toLight); if (diffuse < 0.0) diffuse = 0.0; double factor = (double)light->ambient + (double)light->intensity * diffuse; return factor > 1.0 ? 1.0 : (factor < 0.0 ? 0.0 : factor); } static uint32_t kon_applyLight_(uint32_t color, double factor, const double rgb[3]) { uint32_t mr = (uint32_t)(rgb[0] * factor * 65536.0 + 0.5); uint32_t mg = (uint32_t)(rgb[1] * factor * 65536.0 + 0.5); uint32_t mb = (uint32_t)(rgb[2] * factor * 65536.0 + 0.5); uint32_t r = (((color >> 16) & 0xFF) * mr + 32768) >> 16; uint32_t g = (((color >> 8) & 0xFF) * mg + 32768) >> 16; uint32_t b = ((color & 0xFF) * mb + 32768) >> 16; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; return (color & 0xFF000000u) | (r << 16) | (g << 8) | b; } /* the triangle rasterizer works in doubles so huge coordinates near the camera don't lose precision */ typedef struct kon_rasterVertex { double x, y, z, w; /* clip space */ double u, v; double light; } kon_rasterVertex_t; static kon_rasterVertex_t kon_lerpRasterVertex_(kon_rasterVertex_t a, kon_rasterVertex_t b, double t) { kon_rasterVertex_t r; r.x = a.x + (b.x - a.x) * t; r.y = a.y + (b.y - a.y) * t; r.z = a.z + (b.z - a.z) * t; r.w = a.w + (b.w - a.w) * t; r.u = a.u + (b.u - a.u) * t; r.v = a.v + (b.v - a.v) * t; r.light = a.light + (b.light - a.light) * t; return r; } /* keeps the part of the triangle in front of the near plane (z + w >= 0), returns 0 to 4 vertices */ static int kon_clipNear_(const kon_rasterVertex_t in[3], kon_rasterVertex_t out[4]) { int count = 0; for (int i = 0; i < 3; i++) { kon_rasterVertex_t current = in[i]; kon_rasterVertex_t next = in[(i + 1) % 3]; double dc = current.z + current.w; double dn = next.z + next.w; if (dc >= 0.0) out[count++] = current; if ((dc >= 0.0) != (dn >= 0.0)) out[count++] = kon_lerpRasterVertex_(current, next, dc / (dc - dn)); } return count; } static double kon_edge_(double ax, double ay, double bx, double by, double px, double py) { return (bx - ax) * (py - ay) - (by - ay) * (px - ax); } /* x - floor(x) without a call to floor, which is not a single instruction without SSE4 */ static inline double kon_fraction_(double x) { x -= (double)(long long)x; return x < 0.0 ? x + 1.0 : x; } /* mixes the color toward fog, amount 0 is none and 256 is all fog. alpha stays as it was */ static inline uint32_t kon_applyFog_(uint32_t color, uint32_t fog, uint32_t amount) { uint32_t keep = 256 - amount; uint32_t r = ((((color >> 16) & 0xFF) * keep) + (((fog >> 16) & 0xFF) * amount) + 128) >> 8; uint32_t g = ((((color >> 8) & 0xFF) * keep) + (((fog >> 8) & 0xFF) * amount) + 128) >> 8; uint32_t b = (((color & 0xFF) * keep) + ((fog & 0xFF) * amount) + 128) >> 8; return (color & 0xFF000000u) | (r << 16) | (g << 8) | b; } /* which mip level fits a pixel that covers rho2 texels squared. the exponent of the float is the log2, level 1 starts at 2 texels */ static inline int kon_mipLevel_(double rho2, int levels) { float f = (float)rho2; uint32_t bits; memcpy(&bits, &f, sizeof(bits)); int e = (int)((bits >> 23) & 0xFF) - 127; int level = e < 0 ? 0 : (e + 1) >> 1; return level < levels ? level : levels - 1; } static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex_t *a, const kon_rasterVertex_t *b, const kon_rasterVertex_t *c, const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags) { const kon_rasterVertex_t *in[3] = {a, b, c}; double sx[3], sy[3], sz[3], invW[3], uw[3], vw[3], lw[3]; for (int i = 0; i < 3; i++) { if (in[i]->w <= 1.0e-12) return; invW[i] = 1.0 / in[i]->w; sx[i] = (in[i]->x * invW[i] + 1.0) * 0.5 * fb->width; sy[i] = (1.0 - in[i]->y * invW[i]) * 0.5 * fb->height; sz[i] = in[i]->z * invW[i]; uw[i] = in[i]->u * invW[i]; vw[i] = in[i]->v * invW[i]; lw[i] = in[i]->light * invW[i]; } /* screen +Y points down, so a counter-clockwise (front) triangle has a negative area here */ double area = kon_edge_(sx[0], sy[0], sx[1], sy[1], sx[2], sy[2]); if (area == 0.0) return; if (area > 0.0) { if (flags & KON_RENDER_CULL_BACK) return; /* back face with culling off: swap two vertices so the math below only deals with one winding */ double t; t = sx[1]; sx[1] = sx[2]; sx[2] = t; t = sy[1]; sy[1] = sy[2]; sy[2] = t; t = sz[1]; sz[1] = sz[2]; sz[2] = t; t = invW[1]; invW[1] = invW[2]; invW[2] = t; t = uw[1]; uw[1] = uw[2]; uw[2] = t; t = vw[1]; vw[1] = vw[2]; vw[2] = t; t = lw[1]; lw[1] = lw[2]; lw[2] = t; area = -area; } double minX = sx[0], maxX = sx[0], minY = sy[0], maxY = sy[0]; for (int i = 1; i < 3; i++) { if (sx[i] < minX) minX = sx[i]; if (sx[i] > maxX) maxX = sx[i]; if (sy[i] < minY) minY = sy[i]; if (sy[i] > maxY) maxY = sy[i]; } /* pixel centers sit at +0.5, clamped to the framebuffer before turning into ints */ minX = ceil(minX - 0.5); maxX = floor(maxX - 0.5); minY = ceil(minY - 0.5); maxY = floor(maxY - 0.5); if (minX < 0) minX = 0; if (minY < 0) minY = 0; if (maxX > fb->width - 1) maxX = fb->width - 1; if (maxY > fb->height - 1) maxY = fb->height - 1; if (minX > maxX || minY > maxY) return; int x0 = (int)minX, x1 = (int)maxX, y0 = (int)minY, y1 = (int)maxY; double px = x0 + 0.5, py = y0 + 0.5; /* edge i is opposite vertex i, inside means all three are <= 0 for this winding */ double e0Row = kon_edge_(sx[1], sy[1], sx[2], sy[2], px, py); double e1Row = kon_edge_(sx[2], sy[2], sx[0], sy[0], px, py); double e2Row = kon_edge_(sx[0], sy[0], sx[1], sy[1], px, py); double e0Dx = -(sy[2] - sy[1]), e0Dy = sx[2] - sx[1]; double e1Dx = -(sy[0] - sy[2]), e1Dy = sx[0] - sx[2]; double e2Dx = -(sy[1] - sy[0]), e2Dy = sx[1] - sx[0]; double invArea = 1.0 / area; int useDepth = (flags & KON_RENDER_DEPTH) != 0; const kon_image *texture = material->texture; /* how fast u and v change across the screen, only worked out when there are mips to pick from */ const kon_mipmaps_t *mips = (texture && material->mips && material->mips->levels > 1) ? material->mips : NULL; double dInvWx = 0.0, dInvWy = 0.0, dUx = 0.0, dUy = 0.0, dVx = 0.0, dVy = 0.0; if (mips) { dInvWx = (invW[0] * e0Dx + invW[1] * e1Dx + invW[2] * e2Dx) * invArea; dInvWy = (invW[0] * e0Dy + invW[1] * e1Dy + invW[2] * e2Dy) * invArea; dUx = (uw[0] * e0Dx + uw[1] * e1Dx + uw[2] * e2Dx) * invArea; dUy = (uw[0] * e0Dy + uw[1] * e1Dy + uw[2] * e2Dy) * invArea; dVx = (vw[0] * e0Dx + vw[1] * e1Dx + vw[2] * e2Dx) * invArea; dVy = (vw[0] * e0Dy + vw[1] * e1Dy + vw[2] * e2Dy) * invArea; } int useFog = light && light->fogEnd > light->fogStart; double fogStart = useFog ? (double)light->fogStart : 0.0; double fogScale = useFog ? 1.0 / ((double)light->fogEnd - (double)light->fogStart) : 0.0; uint32_t fogColor = useFog ? light->fogColor : 0; double lightRgb[3] = {1.0, 1.0, 1.0}; if (light) { lightRgb[0] = (double)((light->color >> 16) & 0xFF) / 255.0; lightRgb[1] = (double)((light->color >> 8) & 0xFF) / 255.0; lightRgb[2] = (double)((light->color >> 0) & 0xFF) / 255.0; } for (int y = y0; y <= y1; y++) { double e0 = e0Row, e1 = e1Row, e2 = e2Row; int mipPick = -1; for (int x = x0; x <= x1; x++) { if (e0 <= 0.0 && e1 <= 0.0 && e2 <= 0.0) { double l0 = e0 * invArea, l1 = e1 * invArea, l2 = e2 * invArea; size_t index = (size_t)y * (size_t)fb->width + (size_t)x; double z = l0 * sz[0] + l1 * sz[1] + l2 * sz[2]; if (!useDepth || z < (double)fb->depth[index]) { uint32_t texel = material->color; double iw = l0 * invW[0] + l1 * invW[1] + l2 * invW[2]; double w = 1.0 / iw; if (texture) { /* uv / w is what varies linearly on screen, dividing by 1 / w again undoes the perspective */ double u = (l0 * uw[0] + l1 * uw[1] + l2 * uw[2]) * w; double v = (l0 * vw[0] + l1 * vw[1] + l2 * vw[2]) * w; const kon_image *level = texture; if (mips) { /* the level hardly changes from one pixel to the next, so it is only worked out every fourth one */ if (mipPick < 0 || (x & 3) == 0) { double ux = (dUx - u * dInvWx) * w * texture->width, uy = (dUy - u * dInvWy) * w * texture->width; double vx = (dVx - v * dInvWx) * w * texture->height, vy = (dVy - v * dInvWy) * w * texture->height; double rx = ux * ux + vx * vx, ry = uy * uy + vy * vy; mipPick = kon_mipLevel_(rx > ry ? rx : ry, mips->levels); } level = mips->image[mipPick]; } u = kon_fraction_(u); v = kon_fraction_(v); if (material->filter == KON_FILTER_LINEAR) { texel = kon_sampleLinear_(level, KON_RECT(0, 0, level->width, level->height), (float)(u * level->width), (float)(v * level->height), 1); } else { int tx = (int)(u * level->width); int ty = (int)(v * level->height); if (tx > level->width - 1) tx = level->width - 1; if (ty > level->height - 1) ty = level->height - 1; texel = level->data[(size_t)ty * (size_t)level->width + (size_t)tx]; } } if (light) { texel = kon_applyLight_(texel, (l0 * lw[0] + l1 * lw[1] + l2 * lw[2]) * w, lightRgb); } if (useFog && w > fogStart) { double f = (w - fogStart) * fogScale; texel = kon_applyFog_(texel, fogColor, f >= 1.0 ? 256u : (uint32_t)(f * 256.0)); } uint32_t alpha = texel >> 24; if (alpha == 0xFF) { fb->data[index] = texel; if (useDepth) fb->depth[index] = (float)z; } else if (alpha != 0) { fb->data[index] = kon_blendColor(fb->data[index], texel); } } } e0 += e0Dx; e1 += e1Dx; e2 += e2Dx; } e0Row += e0Dy; e1Row += e1Dy; e2Row += e2Dy; } } /* projects, clips and rasterizes one triangle, brightness holds the light factor of each vertex */ static void kon_submitTriangle_(kon_framebuffer_t *fb, kon_mat4_t mvp, const kon_vertex_t *source[3], const double brightness[3], const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags) { if ((flags & KON_RENDER_DEPTH) && !fb->depth) { fb->depth = malloc((size_t)fb->width * (size_t)fb->height * sizeof(float)); if (!fb->depth) return; for (int i = 0; i < fb->width * fb->height; i++) { fb->depth[i] = 1.0f; } } kon_rasterVertex_t in[3], clipped[4]; for (int i = 0; i < 3; i++) { kon_vec4_t clip = kon_mat4MulVec4(mvp, KON_VEC4(source[i]->position.x, source[i]->position.y, source[i]->position.z, 1)); in[i].x = clip.x; in[i].y = clip.y; in[i].z = clip.z; in[i].w = clip.w; in[i].u = source[i]->uv.x; in[i].v = source[i]->uv.y; in[i].light = brightness[i]; } int count = kon_clipNear_(in, clipped); for (int i = 1; i + 1 < count; i++) { kon_rasterizeTriangle_(fb, &clipped[0], &clipped[i], &clipped[i + 1], material, light, flags); } } static kon_vec3_t kon_faceNormal_(kon_vec3_t a, kon_vec3_t b, kon_vec3_t c) { return kon_vec3Normalize(kon_vec3Cross(kon_vec3Sub(b, a), kon_vec3Sub(c, a))); } void kon_drawTriangle3D(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vertex_t a, kon_vertex_t b, kon_vertex_t c, const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags) { if (!fb || !material) return; if (flags & KON_RENDER_WIREFRAME) { kon_drawLine3D(fb, viewProjection, a.position, b.position, material->color); kon_drawLine3D(fb, viewProjection, b.position, c.position, material->color); kon_drawLine3D(fb, viewProjection, c.position, a.position, material->color); return; } const kon_vertex_t *source[3] = {&a, &b, &c}; double brightness[3] = {1.0, 1.0, 1.0}; if (light) { kon_vec3_t toLight = kon_vec3Scale(kon_vec3Normalize(light->direction), -1.0f); if (flags & KON_RENDER_FLAT) { double factor = kon_lightFactor_(light, toLight, kon_faceNormal_(a.position, b.position, c.position)); brightness[0] = brightness[1] = brightness[2] = factor; } else { for (int i = 0; i < 3; i++) brightness[i] = kon_lightFactor_(light, toLight, source[i]->normal); } } kon_submitTriangle_(fb, viewProjection, source, brightness, material, light, flags); } void kon_drawMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4_t model, const kon_mesh_t *mesh, const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags) { if (!fb || !mesh || !material) return; kon_mat4_t mvp = kon_mat4Mul(viewProjection, model); if (flags & KON_RENDER_WIREFRAME) light = NULL; kon_vec3_t toLight = KON_VEC3_ZERO; kon_vec3_t normalMatrix[3] = {KON_VEC3_ZERO, KON_VEC3_ZERO, KON_VEC3_ZERO}; if (light) { toLight = kon_vec3Scale(kon_vec3Normalize(light->direction), -1.0f); /* the cofactor matrix keeps normals perpendicular to the surface even when the model is scaled unevenly */ kon_vec3_t c0 = KON_VEC3(model.m[0], model.m[1], model.m[2]); kon_vec3_t c1 = KON_VEC3(model.m[4], model.m[5], model.m[6]); kon_vec3_t c2 = KON_VEC3(model.m[8], model.m[9], model.m[10]); float mirror = (kon_vec3Dot(c0, kon_vec3Cross(c1, c2)) < 0.0f) ? -1.0f : 1.0f; normalMatrix[0] = kon_vec3Scale(kon_vec3Cross(c1, c2), mirror); normalMatrix[1] = kon_vec3Scale(kon_vec3Cross(c2, c0), mirror); normalMatrix[2] = kon_vec3Scale(kon_vec3Cross(c0, c1), mirror); } for (int i = 0; i < mesh->triangleCount; i++) { const kon_vertex_t *source[3] = { &mesh->vertices[mesh->indices[i * 3 + 0]], &mesh->vertices[mesh->indices[i * 3 + 1]], &mesh->vertices[mesh->indices[i * 3 + 2]] }; double brightness[3] = {1.0, 1.0, 1.0}; if (flags & KON_RENDER_WIREFRAME) { kon_drawTriangle3D(fb, mvp, *source[0], *source[1], *source[2], material, NULL, flags); continue; } if (light) { if (flags & KON_RENDER_FLAT) { kon_vec3_t world[3]; for (int j = 0; j < 3; j++) world[j] = kon_mat4MulPoint(model, source[j]->position); double factor = kon_lightFactor_(light, toLight, kon_faceNormal_(world[0], world[1], world[2])); brightness[0] = brightness[1] = brightness[2] = factor; } else { for (int j = 0; j < 3; j++) { kon_vec3_t n = source[j]->normal; kon_vec3_t worldNormal = kon_vec3Add(kon_vec3Add(kon_vec3Scale(normalMatrix[0], n.x), kon_vec3Scale(normalMatrix[1], n.y)), kon_vec3Scale(normalMatrix[2], n.z)); brightness[j] = kon_lightFactor_(light, toLight, worldNormal); } } } kon_submitTriangle_(fb, mvp, source, brightness, material, light, flags); } } /*** built-in meshes ***/ static const kon_vertex_t kon_cubeVertices_[24] = { /* +Z */ {{-1, -1, 1}, {0, 0, 1}, {0, 1}}, {{ 1, -1, 1}, {0, 0, 1}, {1, 1}}, {{ 1, 1, 1}, {0, 0, 1}, {1, 0}}, {{-1, 1, 1}, {0, 0, 1}, {0, 0}}, /* -Z */ {{ 1, -1, -1}, {0, 0, -1}, {0, 1}}, {{-1, -1, -1}, {0, 0, -1}, {1, 1}}, {{-1, 1, -1}, {0, 0, -1}, {1, 0}}, {{ 1, 1, -1}, {0, 0, -1}, {0, 0}}, /* +X */ {{ 1, -1, 1}, {1, 0, 0}, {0, 1}}, {{ 1, -1, -1}, {1, 0, 0}, {1, 1}}, {{ 1, 1, -1}, {1, 0, 0}, {1, 0}}, {{ 1, 1, 1}, {1, 0, 0}, {0, 0}}, /* -X */ {{-1, -1, -1}, {-1, 0, 0}, {0, 1}}, {{-1, -1, 1}, {-1, 0, 0}, {1, 1}}, {{-1, 1, 1}, {-1, 0, 0}, {1, 0}}, {{-1, 1, -1}, {-1, 0, 0}, {0, 0}}, /* +Y */ {{-1, 1, 1}, {0, 1, 0}, {0, 1}}, {{ 1, 1, 1}, {0, 1, 0}, {1, 1}}, {{ 1, 1, -1}, {0, 1, 0}, {1, 0}}, {{-1, 1, -1}, {0, 1, 0}, {0, 0}}, /* -Y */ {{-1, -1, -1}, {0, -1, 0}, {0, 1}}, {{ 1, -1, -1}, {0, -1, 0}, {1, 1}}, {{ 1, -1, 1}, {0, -1, 0}, {1, 0}}, {{-1, -1, 1}, {0, -1, 0}, {0, 0}} }; static const uint16_t kon_cubeIndices_[36] = { 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 }; const kon_mesh_t kon_cubeMesh = { kon_cubeVertices_, kon_cubeIndices_, 12 }; int kon_drawBillboard(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_vec3_t position, float width, float height, const kon_image *image, kon_rect_t src, float brightness, kon_renderFlags_t flags) { if (!fb || !image || width <= 0.0f || height <= 0.0f) return 0; if (src.w <= 0 || src.h <= 0) src = KON_RECT(0, 0, image->width, image->height); if (src.x < 0 || src.y < 0 || src.x + src.w > image->width || src.y + src.h > image->height) return 0; kon_vec4_t foot = kon_mat4MulVec4(viewProjection, KON_VEC4(position.x, position.y, position.z, 1)); kon_vec4_t head = kon_mat4MulVec4(viewProjection, KON_VEC4(position.x, position.y + height, position.z, 1)); kon_vec4_t mid = kon_mat4MulVec4(viewProjection, KON_VEC4(position.x, position.y + height * 0.5f, position.z, 1)); if (foot.w <= 0.0f || head.w <= 0.0f || mid.w <= 0.0f || mid.z < -mid.w) return 0; int useDepth = (flags & KON_RENDER_DEPTH) != 0; if (useDepth && !fb->depth) { fb->depth = malloc((size_t)fb->width * (size_t)fb->height * sizeof(float)); if (!fb->depth) return 0; for (int i = 0; i < fb->width * fb->height; i++) fb->depth[i] = 1.0f; } kon_vec2_t bottom = kon_clipToScreen_(fb, foot); kon_vec2_t top = kon_clipToScreen_(fb, head); float heightPx = bottom.y - top.y; float widthPx = heightPx * width / height; if (heightPx < 1.0f || widthPx < 1.0f) return 0; float left = bottom.x - widthPx * 0.5f; int x0 = (int)ceilf(left - 0.5f), x1 = (int)floorf(left + widthPx - 0.5f); int y0 = (int)ceilf(top.y - 0.5f), y1 = (int)floorf(bottom.y - 0.5f); float z = mid.z / mid.w; if (brightness < 0.0f) brightness = 0.0f; int scale = (int)(brightness * 256.0f); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 > fb->width - 1) x1 = fb->width - 1; if (y1 > fb->height - 1) y1 = fb->height - 1; for (int y = y0; y <= y1; y++) { int sy = src.y + (int)(((float)y + 0.5f - top.y) / heightPx * (float)src.h); if (sy < src.y) sy = src.y; if (sy > src.y + src.h - 1) sy = src.y + src.h - 1; for (int x = x0; x <= x1; x++) { size_t index = (size_t)y * (size_t)fb->width + (size_t)x; if (useDepth && z >= fb->depth[index]) continue; int sx = src.x + (int)(((float)x + 0.5f - left) / widthPx * (float)src.w); if (sx < src.x) sx = src.x; if (sx > src.x + src.w - 1) sx = src.x + src.w - 1; uint32_t texel = image->data[(size_t)sy * (size_t)image->width + (size_t)sx]; uint32_t alpha = texel >> 24; if (alpha == 0) continue; if (scale != 256) { uint32_t r = ((texel >> 16) & 0xFF) * (uint32_t)scale >> 8, g = ((texel >> 8) & 0xFF) * (uint32_t)scale >> 8, b = (texel & 0xFF) * (uint32_t)scale >> 8; texel = (alpha << 24) | ((r > 255 ? 255 : r) << 16) | ((g > 255 ? 255 : g) << 8) | (b > 255 ? 255 : b); } if (alpha >= 128) { fb->data[index] = texel | 0xFF000000u; if (useDepth) fb->depth[index] = z; } else { fb->data[index] = kon_blendColor(fb->data[index], texel); } } } return 1; } /*** light pass ***/ typedef struct kon_lightSetup_ { float x, y, z, radius, invRadius, intensity, r, g, b; float dx, dy, dz, cosOuter, cosRange; int spot; } kon_lightSetup_t; void kon_lightPass(kon_framebuffer_t *fb, kon_mat4_t viewProjection, const kon_pointLight_t *lights, int count, float ambient) { enum { MAX_LIGHTS = 32 }; kon_lightSetup_t set[MAX_LIGHTS]; kon_mat4_t inv; int used = 0, i, ok, px, py; float ndcStepX, ndcStepY; if (!fb || !fb->depth || !lights) return; inv = kon_mat4Inverse(viewProjection, &ok); if (!ok) return; if (ambient < 0.0f) ambient = 0.0f; for (i = 0; i < count && used < MAX_LIGHTS; i++) { const kon_pointLight_t *l = &lights[i]; kon_lightSetup_t *s = &set[used]; if (l->radius <= 0.0f || l->intensity <= 0.0f) continue; s->x = l->position.x; s->y = l->position.y; s->z = l->position.z; s->radius = l->radius; s->invRadius = 1.0f / l->radius; s->intensity = l->intensity; s->r = (float)((l->color >> 16) & 255) / 255.0f; s->g = (float)((l->color >> 8) & 255) / 255.0f; s->b = (float)(l->color & 255) / 255.0f; s->spot = l->cone > 0.0f; if (s->spot) { float len = sqrtf(l->direction.x * l->direction.x + l->direction.y * l->direction.y + l->direction.z * l->direction.z); float soft = kon_clamp(l->soft, 0.0f, 1.0f), inner = l->cone * (1.0f - soft); if (len < 1.0e-6f) continue; s->dx = l->direction.x / len; s->dy = l->direction.y / len; s->dz = l->direction.z / len; s->cosOuter = cosf(l->cone); s->cosRange = cosf(inner) - s->cosOuter; if (s->cosRange < 1.0e-4f) s->cosRange = 1.0e-4f; } used++; } ndcStepX = 2.0f / (float)fb->width; ndcStepY = 2.0f / (float)fb->height; for (py = 0; py < fb->height; py++) { float ndcY = 1.0f - ((float)py + 0.5f) * ndcStepY; for (px = 0; px < fb->width; px++) { int index = py * fb->width + px; float z = fb->depth[index], ndcX, wx, wy, wz, ww, level[3], inverseW; uint32_t c; int k, r, g, b; if (z >= 1.0f) continue; ndcX = ((float)px + 0.5f) * ndcStepX - 1.0f; wx = inv.m[0] * ndcX + inv.m[4] * ndcY + inv.m[8] * z + inv.m[12]; wy = inv.m[1] * ndcX + inv.m[5] * ndcY + inv.m[9] * z + inv.m[13]; wz = inv.m[2] * ndcX + inv.m[6] * ndcY + inv.m[10] * z + inv.m[14]; ww = inv.m[3] * ndcX + inv.m[7] * ndcY + inv.m[11] * z + inv.m[15]; inverseW = 1.0f / ww; wx *= inverseW; wy *= inverseW; wz *= inverseW; level[0] = level[1] = level[2] = ambient; for (k = 0; k < used; k++) { const kon_lightSetup_t *s = &set[k]; float dx = wx - s->x, dy = wy - s->y, dz = wz - s->z, d2 = dx * dx + dy * dy + dz * dz, d, fall, amount; if (d2 >= s->radius * s->radius) continue; d = sqrtf(d2); fall = 1.0f - d * s->invRadius; amount = fall * fall * s->intensity; if (s->spot) { float along = d > 1.0e-6f ? (dx * s->dx + dy * s->dy + dz * s->dz) / d : 1.0f, edge = (along - s->cosOuter) / s->cosRange; if (edge <= 0.0f) continue; if (edge < 1.0f) amount *= edge * edge * (3.0f - 2.0f * edge); } level[0] += amount * s->r; level[1] += amount * s->g; level[2] += amount * s->b; } c = fb->data[index]; r = (int)((float)((c >> 16) & 255) * level[0]); g = (int)((float)((c >> 8) & 255) * level[1]); b = (int)((float)(c & 255) * level[2]); fb->data[index] = 0xFF000000u | (uint32_t)(r > 255 ? 255 : r) << 16 | (uint32_t)(g > 255 ? 255 : g) << 8 | (uint32_t)(b > 255 ? 255 : b); } } } #endif /* KONSOFREN_3D */ #endif /* end of KONSOFREN_IMPLEMENTATION */ #endif