summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-07 04:47:14 +0800
committerKonAki <konaki@anokonaki.org>2026-10-07 04:47:14 +0800
commit6605b450aef9c68067f3749c3f5780101242f261 (patch)
treefab9d0dc2b1b331440090f3f9269f375cc35dbb3
parent5e6722e9ec3031c6e85a40f63291ea10cb7f1978 (diff)
Add konrender.h: camera, 3D line drawing with near-plane clipping, wire meshes
-rw-r--r--konrender.h181
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