diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-07 04:47:14 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-07 04:47:14 +0800 |
| commit | 6605b450aef9c68067f3749c3f5780101242f261 (patch) | |
| tree | fab9d0dc2b1b331440090f3f9269f375cc35dbb3 | |
| parent | 5e6722e9ec3031c6e85a40f63291ea10cb7f1978 (diff) | |
Add konrender.h: camera, 3D line drawing with near-plane clipping, wire meshes
| -rw-r--r-- | konrender.h | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/konrender.h b/konrender.h new file mode 100644 index 0000000..1e81e3f --- /dev/null +++ b/konrender.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konrender.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 KONRENDER_H +#define KONRENDER_H + +#include "konmath.h" +#include "konsofren.h" + +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; + +/*** 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); + +/*** 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); + +/*** implementation ***/ + +#ifdef KONRENDER_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)); +} + +/*** 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); + } +} + +#endif /* end of KONRENDER_IMPLEMENTATION */ + +#endif |
