Add a few more mesh types
This commit is contained in:
@@ -165,7 +165,7 @@ static errorret_t assetFileLineReaderAppend(
|
||||
}
|
||||
|
||||
/* reserve room for optional NUL terminator */
|
||||
if (reader->lineLength + srcLength >= reader->outBufferSize) {
|
||||
if(reader->lineLength + srcLength >= reader->outBufferSize) {
|
||||
errorThrow("Line length exceeds output buffer size.");
|
||||
}
|
||||
|
||||
@@ -187,8 +187,8 @@ static ssize_t assetFileLineReaderFindNewline(const assetfilelinereader_t *reade
|
||||
assertNotNull(reader, "Reader cannot be NULL.");
|
||||
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
||||
|
||||
for (i = reader->bufferStart; i < reader->bufferEnd; ++i) {
|
||||
if (reader->readBuffer[i] == '\n') {
|
||||
for(i = reader->bufferStart; i < reader->bufferEnd; ++i) {
|
||||
if(reader->readBuffer[i] == '\n') {
|
||||
return (ssize_t)i;
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ errorret_t assetFileLineReaderFill(assetfilelinereader_t *reader) {
|
||||
size_t unreadBytes = assetFileLineReaderUnreadBytes(reader);
|
||||
|
||||
/* If buffer is fully consumed, refill from start. */
|
||||
if (unreadBytes == 0) {
|
||||
if(unreadBytes == 0) {
|
||||
reader->bufferStart = 0;
|
||||
reader->bufferEnd = 0;
|
||||
|
||||
@@ -276,15 +276,15 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
|
||||
|
||||
reader->lineLength = 0;
|
||||
|
||||
for (;;) {
|
||||
for(;;) {
|
||||
ssize_t newlineIndex = assetFileLineReaderFindNewline(reader);
|
||||
|
||||
if (newlineIndex >= 0) {
|
||||
if(newlineIndex >= 0) {
|
||||
size_t chunkLength = (size_t)newlineIndex - reader->bufferStart;
|
||||
errorret_t ret;
|
||||
|
||||
/* strip CR in CRLF */
|
||||
if (chunkLength > 0 && reader->readBuffer[(size_t)newlineIndex - 1] == '\r') {
|
||||
if(chunkLength > 0 && reader->readBuffer[(size_t)newlineIndex - 1] == '\r') {
|
||||
chunkLength--;
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if (assetFileLineReaderUnreadBytes(reader) > 0) {
|
||||
if(assetFileLineReaderUnreadBytes(reader) > 0) {
|
||||
errorChain(assetFileLineReaderAppend(
|
||||
reader,
|
||||
assetFileLineReaderUnreadPtr(reader),
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/sphere.h"
|
||||
#include "display/mesh/plane.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "display/mesh/triprism.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "ui/ui.h"
|
||||
#include "display/text/text.h"
|
||||
@@ -35,8 +39,15 @@ errorret_t displayInit(void) {
|
||||
&TEXTURE_WHITE, 4, 4,
|
||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
||||
));
|
||||
|
||||
// Standard meshes
|
||||
errorChain(quadInit());
|
||||
errorChain(cubeInit());
|
||||
errorChain(sphereInit());
|
||||
errorChain(planeInit());
|
||||
errorChain(capsuleInit());
|
||||
errorChain(triPrismInit());
|
||||
|
||||
errorChain(frameBufferInitBackBuffer());
|
||||
errorChain(spriteBatchInit());
|
||||
errorChain(textInit());
|
||||
|
||||
@@ -9,4 +9,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
mesh.c
|
||||
quad.c
|
||||
cube.c
|
||||
sphere.c
|
||||
plane.c
|
||||
capsule.c
|
||||
triprism.c
|
||||
)
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "capsule.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t CAPSULE_MESH_SIMPLE;
|
||||
meshvertex_t CAPSULE_MESH_SIMPLE_VERTICES[CAPSULE_VERTEX_COUNT];
|
||||
|
||||
errorret_t capsuleInit() {
|
||||
vec3 center = { 0.0f, 0.0f, 0.0f };
|
||||
capsuleBuffer(
|
||||
CAPSULE_MESH_SIMPLE_VERTICES,
|
||||
center,
|
||||
0.5f,
|
||||
0.5f,
|
||||
CAPSULE_CAP_RINGS,
|
||||
CAPSULE_SECTORS
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&CAPSULE_MESH_SIMPLE,
|
||||
CAPSULE_PRIMITIVE_TYPE,
|
||||
CAPSULE_VERTEX_COUNT,
|
||||
CAPSULE_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void capsuleBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 center,
|
||||
const float_t radius,
|
||||
const float_t halfHeight,
|
||||
const int32_t capRings,
|
||||
const int32_t sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertNotNull(center, "Center vector cannot be NULL");
|
||||
|
||||
const float_t cx = center[0];
|
||||
const float_t cy = center[1];
|
||||
const float_t cz = center[2];
|
||||
const float_t sectorStep = 2.0f * (float_t)GLM_PI / (float_t)sectors;
|
||||
int32_t vi = 0;
|
||||
|
||||
/* Helper macro: write one vertex. */
|
||||
#if MESH_ENABLE_COLOR
|
||||
#define CAP_VERT(px, py, pz, u, v) \
|
||||
vertices[vi].color = color; \
|
||||
vertices[vi].pos[0] = (px); \
|
||||
vertices[vi].pos[1] = (py); \
|
||||
vertices[vi].pos[2] = (pz); \
|
||||
vertices[vi].uv[0] = (u); \
|
||||
vertices[vi].uv[1] = (v); \
|
||||
vi++;
|
||||
#else
|
||||
#define CAP_VERT(px, py, pz, u, v) \
|
||||
vertices[vi].pos[0] = (px); \
|
||||
vertices[vi].pos[1] = (py); \
|
||||
vertices[vi].pos[2] = (pz); \
|
||||
vertices[vi].uv[0] = (u); \
|
||||
vertices[vi].uv[1] = (v); \
|
||||
vi++;
|
||||
#endif
|
||||
|
||||
/* ---- Top hemisphere ---- */
|
||||
/* phi ranges from PI/2 (top pole) down to 0 (equator). */
|
||||
const float_t capStep = (float_t)GLM_PI_2 / (float_t)capRings;
|
||||
for(int32_t i = 0; i < capRings; i++) {
|
||||
const float_t phi1 = (float_t)GLM_PI_2 - (float_t)i * capStep;
|
||||
const float_t phi2 = (float_t)GLM_PI_2 - (float_t)(i + 1) * capStep;
|
||||
|
||||
const float_t ly1 = radius * sinf(phi1);
|
||||
const float_t ly2 = radius * sinf(phi2);
|
||||
const float_t lxz1 = radius * cosf(phi1);
|
||||
const float_t lxz2 = radius * cosf(phi2);
|
||||
|
||||
/* UV: top cap occupies v in [0.5 + halfHeightFrac .. 1.0] — we use a
|
||||
* simple per-band normalisation against the full height. */
|
||||
const float_t v1 = 1.0f - (float_t)i / (float_t)(2 * capRings + 1);
|
||||
const float_t v2 = 1.0f - (float_t)(i + 1) / (float_t)(2 * capRings + 1);
|
||||
|
||||
for(int32_t j = 0; j < sectors; j++) {
|
||||
const float_t t1 = (float_t)j * sectorStep;
|
||||
const float_t t2 = (float_t)(j + 1) * sectorStep;
|
||||
|
||||
const float_t u1 = (float_t)j / (float_t)sectors;
|
||||
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
|
||||
|
||||
const float_t x11 = lxz1 * cosf(t1), z11 = lxz1 * sinf(t1);
|
||||
const float_t x12 = lxz1 * cosf(t2), z12 = lxz1 * sinf(t2);
|
||||
const float_t x21 = lxz2 * cosf(t1), z21 = lxz2 * sinf(t1);
|
||||
const float_t x22 = lxz2 * cosf(t2), z22 = lxz2 * sinf(t2);
|
||||
|
||||
const float_t y1off = cy + halfHeight + ly1;
|
||||
const float_t y2off = cy + halfHeight + ly2;
|
||||
|
||||
CAP_VERT(cx+x11, y1off, cz+z11, u1, v1)
|
||||
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
|
||||
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
|
||||
|
||||
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
|
||||
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
|
||||
CAP_VERT(cx+x22, y2off, cz+z22, u2, v2)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Cylindrical body ---- */
|
||||
{
|
||||
const float_t yTop = cy + halfHeight;
|
||||
const float_t yBot = cy - halfHeight;
|
||||
const float_t vTop = 1.0f - (float_t)capRings / (float_t)(2 * capRings + 1);
|
||||
const float_t vBot = 1.0f - (float_t)(capRings + 1) / (float_t)(2 * capRings + 1);
|
||||
|
||||
for(int32_t j = 0; j < sectors; j++) {
|
||||
const float_t t1 = (float_t)j * sectorStep;
|
||||
const float_t t2 = (float_t)(j + 1) * sectorStep;
|
||||
|
||||
const float_t u1 = (float_t)j / (float_t)sectors;
|
||||
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
|
||||
|
||||
const float_t x1 = radius * cosf(t1), z1 = radius * sinf(t1);
|
||||
const float_t x2 = radius * cosf(t2), z2 = radius * sinf(t2);
|
||||
|
||||
CAP_VERT(cx+x1, yTop, cz+z1, u1, vTop)
|
||||
CAP_VERT(cx+x1, yBot, cz+z1, u1, vBot)
|
||||
CAP_VERT(cx+x2, yTop, cz+z2, u2, vTop)
|
||||
|
||||
CAP_VERT(cx+x2, yTop, cz+z2, u2, vTop)
|
||||
CAP_VERT(cx+x1, yBot, cz+z1, u1, vBot)
|
||||
CAP_VERT(cx+x2, yBot, cz+z2, u2, vBot)
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom hemisphere
|
||||
for(int32_t i = 0; i < capRings; i++) {
|
||||
const float_t phi1 = -(float_t)i * capStep;
|
||||
const float_t phi2 = -(float_t)(i + 1) * capStep;
|
||||
|
||||
const float_t ly1 = radius * sinf(phi1);
|
||||
const float_t ly2 = radius * sinf(phi2);
|
||||
const float_t lxz1 = radius * cosf(phi1);
|
||||
const float_t lxz2 = radius * cosf(phi2);
|
||||
|
||||
const float_t v1 = 1.0f - (float_t)(capRings + 1 + i) / (float_t)(2 * capRings + 1);
|
||||
const float_t v2 = 1.0f - (float_t)(capRings + 1 + i + 1) / (float_t)(2 * capRings + 1);
|
||||
|
||||
for(int32_t j = 0; j < sectors; j++) {
|
||||
const float_t t1 = (float_t)j * sectorStep;
|
||||
const float_t t2 = (float_t)(j + 1) * sectorStep;
|
||||
|
||||
const float_t u1 = (float_t)j / (float_t)sectors;
|
||||
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
|
||||
|
||||
const float_t x11 = lxz1 * cosf(t1), z11 = lxz1 * sinf(t1);
|
||||
const float_t x12 = lxz1 * cosf(t2), z12 = lxz1 * sinf(t2);
|
||||
const float_t x21 = lxz2 * cosf(t1), z21 = lxz2 * sinf(t1);
|
||||
const float_t x22 = lxz2 * cosf(t2), z22 = lxz2 * sinf(t2);
|
||||
|
||||
const float_t y1off = cy - halfHeight + ly1;
|
||||
const float_t y2off = cy - halfHeight + ly2;
|
||||
|
||||
CAP_VERT(cx+x11, y1off, cz+z11, u1, v1)
|
||||
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
|
||||
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
|
||||
|
||||
CAP_VERT(cx+x12, y1off, cz+z12, u2, v1)
|
||||
CAP_VERT(cx+x21, y2off, cz+z21, u1, v2)
|
||||
CAP_VERT(cx+x22, y2off, cz+z22, u2, v2)
|
||||
}
|
||||
}
|
||||
|
||||
#undef CAP_VERT
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#define CAPSULE_CAP_RINGS 4
|
||||
#define CAPSULE_SECTORS 16
|
||||
#define CAPSULE_VERTEX_COUNT ((2 * CAPSULE_CAP_RINGS + 1) * CAPSULE_SECTORS * 6)
|
||||
#define CAPSULE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
extern mesh_t CAPSULE_MESH_SIMPLE;
|
||||
extern meshvertex_t CAPSULE_MESH_SIMPLE_VERTICES[CAPSULE_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the simple unit capsule mesh centered at (0,0,0) with radius 0.5
|
||||
* and a cylindrical half-height of 0.5 (total height 2.0).
|
||||
*
|
||||
* @return Error for initialization of the capsule mesh.
|
||||
*/
|
||||
errorret_t capsuleInit();
|
||||
|
||||
/**
|
||||
* Buffers a capsule (cylinder + two hemisphere caps) into the provided vertex
|
||||
* array. The capsule's long axis is Y. Total vertex count is
|
||||
* (2*capRings + 1) * sectors * 6.
|
||||
*
|
||||
* @param vertices Vertex array to write into.
|
||||
* @param center Center position of the capsule.
|
||||
* @param radius Radius of the cylinder and hemisphere caps.
|
||||
* @param halfHeight Half the height of the cylindrical section only (caps
|
||||
* extend an additional radius above/below).
|
||||
* @param capRings Number of latitude rings per hemisphere cap.
|
||||
* @param sectors Number of longitude segments around the circumference.
|
||||
* @param color Color applied to all vertices.
|
||||
*/
|
||||
void capsuleBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 center,
|
||||
const float_t radius,
|
||||
const float_t halfHeight,
|
||||
const int32_t capRings,
|
||||
const int32_t sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -14,7 +14,12 @@ meshvertex_t CUBE_MESH_SIMPLE_VERTICES[CUBE_VERTEX_COUNT];
|
||||
errorret_t cubeInit() {
|
||||
vec3 min = { 0.0f, 0.0f, 0.0f };
|
||||
vec3 max = { 1.0f, 1.0f, 1.0f };
|
||||
cubeBuffer(CUBE_MESH_SIMPLE_VERTICES, min, max, COLOR_WHITE_4B);
|
||||
cubeBuffer(
|
||||
CUBE_MESH_SIMPLE_VERTICES, min, max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&CUBE_MESH_SIMPLE,
|
||||
CUBE_PRIMITIVE_TYPE,
|
||||
@@ -45,8 +50,10 @@ errorret_t cubeInit() {
|
||||
void cubeBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max,
|
||||
const color_t color
|
||||
const vec3 max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertNotNull(min, "Min vector cannot be NULL");
|
||||
|
||||
@@ -36,6 +36,8 @@ errorret_t cubeInit();
|
||||
void cubeBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max,
|
||||
const color_t color
|
||||
const vec3 max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "plane.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t PLANE_MESH_SIMPLE;
|
||||
meshvertex_t PLANE_MESH_SIMPLE_VERTICES[PLANE_VERTEX_COUNT];
|
||||
|
||||
errorret_t planeInit() {
|
||||
vec3 min = { 0.0f, 0.0f, 0.0f };
|
||||
vec3 max = { 1.0f, 0.0f, 1.0f };
|
||||
vec2 uvMin = { 0.0f, 0.0f };
|
||||
vec2 uvMax = { 1.0f, 1.0f };
|
||||
planeBuffer(
|
||||
PLANE_MESH_SIMPLE_VERTICES,
|
||||
PLANE_AXIS_XZ,
|
||||
min,
|
||||
max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
, uvMin,
|
||||
uvMax
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&PLANE_MESH_SIMPLE,
|
||||
PLANE_PRIMITIVE_TYPE,
|
||||
PLANE_VERTEX_COUNT,
|
||||
PLANE_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
/* Helper macro to write one vertex. */
|
||||
#if MESH_ENABLE_COLOR
|
||||
#define PLANE_VERT(i, px, py, pz, u, v) \
|
||||
vertices[i].color = color; \
|
||||
vertices[i].pos[0] = (px); \
|
||||
vertices[i].pos[1] = (py); \
|
||||
vertices[i].pos[2] = (pz); \
|
||||
vertices[i].uv[0] = (u); \
|
||||
vertices[i].uv[1] = (v);
|
||||
#else
|
||||
#define PLANE_VERT(i, px, py, pz, u, v) \
|
||||
vertices[i].pos[0] = (px); \
|
||||
vertices[i].pos[1] = (py); \
|
||||
vertices[i].pos[2] = (pz); \
|
||||
vertices[i].uv[0] = (u); \
|
||||
vertices[i].uv[1] = (v);
|
||||
#endif
|
||||
|
||||
void planeBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const planeaxis_t axis,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
, const vec2 uvMin,
|
||||
const vec2 uvMax
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertNotNull(min, "Min vector cannot be NULL");
|
||||
assertNotNull(max, "Max vector cannot be NULL");
|
||||
assertNotNull(uvMin, "uvMin cannot be NULL");
|
||||
assertNotNull(uvMax, "uvMax cannot be NULL");
|
||||
|
||||
const float_t u0 = uvMin[0], u1 = uvMax[0];
|
||||
const float_t v0 = uvMin[1], v1 = uvMax[1];
|
||||
|
||||
switch (axis) {
|
||||
case PLANE_AXIS_XY: {
|
||||
/* Flat in XY at z = min[2]; spans X and Y. */
|
||||
const float_t z = min[2];
|
||||
PLANE_VERT(0, min[0], min[1], z, u0, v0)
|
||||
PLANE_VERT(1, max[0], min[1], z, u1, v0)
|
||||
PLANE_VERT(2, max[0], max[1], z, u1, v1)
|
||||
PLANE_VERT(3, min[0], min[1], z, u0, v0)
|
||||
PLANE_VERT(4, max[0], max[1], z, u1, v1)
|
||||
PLANE_VERT(5, min[0], max[1], z, u0, v1)
|
||||
break;
|
||||
}
|
||||
|
||||
case PLANE_AXIS_XZ: {
|
||||
/* Flat in XZ at y = min[1]; spans X and Z. */
|
||||
const float_t y = min[1];
|
||||
PLANE_VERT(0, min[0], y, min[2], u0, v0)
|
||||
PLANE_VERT(1, max[0], y, min[2], u1, v0)
|
||||
PLANE_VERT(2, max[0], y, max[2], u1, v1)
|
||||
PLANE_VERT(3, min[0], y, min[2], u0, v0)
|
||||
PLANE_VERT(4, max[0], y, max[2], u1, v1)
|
||||
PLANE_VERT(5, min[0], y, max[2], u0, v1)
|
||||
break;
|
||||
}
|
||||
|
||||
case PLANE_AXIS_YZ: {
|
||||
/* Flat in YZ at x = min[0]; spans Y and Z. */
|
||||
const float_t x = min[0];
|
||||
PLANE_VERT(0, x, min[1], min[2], u0, v0)
|
||||
PLANE_VERT(1, x, max[1], min[2], u1, v0)
|
||||
PLANE_VERT(2, x, max[1], max[2], u1, v1)
|
||||
PLANE_VERT(3, x, min[1], min[2], u0, v0)
|
||||
PLANE_VERT(4, x, max[1], max[2], u1, v1)
|
||||
PLANE_VERT(5, x, min[1], max[2], u0, v1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef PLANE_VERT
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#define PLANE_VERTEX_COUNT 6
|
||||
#define PLANE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
/** Which axis the plane's normal points along. */
|
||||
typedef enum {
|
||||
PLANE_AXIS_XY = 0, /**< Flat in XY, normal along +Z (billboard/wall face). */
|
||||
PLANE_AXIS_XZ = 1, /**< Flat in XZ, normal along +Y (ground/floor plane). */
|
||||
PLANE_AXIS_YZ = 2, /**< Flat in YZ, normal along +X (side wall). */
|
||||
} planeaxis_t;
|
||||
|
||||
extern mesh_t PLANE_MESH_SIMPLE;
|
||||
extern meshvertex_t PLANE_MESH_SIMPLE_VERTICES[PLANE_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the simple unit XZ plane mesh (ground plane) from (0,0,0) to
|
||||
* (1,0,1).
|
||||
*
|
||||
* @return Error for initialization of the plane mesh.
|
||||
*/
|
||||
errorret_t planeInit();
|
||||
|
||||
/**
|
||||
* Buffers an axis-aligned plane into the provided vertex array.
|
||||
* Writes PLANE_VERTEX_COUNT (6) vertices (two triangles, CCW winding).
|
||||
*
|
||||
* The min/max corners fully describe the plane in 3D space. The axis enum
|
||||
* controls which dimension is treated as the "depth" (normal) axis:
|
||||
* PLANE_AXIS_XY — spans X and Y, depth from min[2]/max[2] (uses min[2])
|
||||
* PLANE_AXIS_XZ — spans X and Z, depth from min[1]/max[1] (uses min[1])
|
||||
* PLANE_AXIS_YZ — spans Y and Z, depth from min[0]/max[0] (uses min[0])
|
||||
*
|
||||
* @param vertices Vertex array to write into (must hold PLANE_VERTEX_COUNT).
|
||||
* @param axis Which axis the plane's normal points along.
|
||||
* @param min Minimum XYZ corner.
|
||||
* @param max Maximum XYZ corner.
|
||||
* @param color Color applied to all vertices.
|
||||
* @param uvMin Minimum UV coordinates.
|
||||
* @param uvMax Maximum UV coordinates.
|
||||
*/
|
||||
void planeBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const planeaxis_t axis,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
, const vec2 uvMin,
|
||||
const vec2 uvMax
|
||||
);
|
||||
@@ -77,13 +77,13 @@ void quadBuffer(
|
||||
const float_t minY,
|
||||
const float_t maxX,
|
||||
const float_t maxY,
|
||||
#if MESH_ENABLE_COLOR
|
||||
const color_t color,
|
||||
#endif
|
||||
const float_t u0,
|
||||
const float_t v0,
|
||||
const float_t u1,
|
||||
const float_t v1
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
const float_t z = 0.0f; // Z coordinate for 2D rendering
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
@@ -149,11 +149,11 @@ void quadBuffer3D(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max,
|
||||
#if MESH_ENABLE_COLOR
|
||||
const color_t color,
|
||||
#endif
|
||||
const vec2 uvMin,
|
||||
const vec2 uvMax
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertNotNull(min, "Min vector cannot be NULL");
|
||||
|
||||
@@ -42,13 +42,13 @@ void quadBuffer(
|
||||
const float_t minY,
|
||||
const float_t maxX,
|
||||
const float_t maxY,
|
||||
#if MESH_ENABLE_COLOR
|
||||
const color_t color,
|
||||
#endif
|
||||
const float_t u0,
|
||||
const float_t v0,
|
||||
const float_t u1,
|
||||
const float_t v1
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -65,9 +65,9 @@ void quadBuffer3D(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max,
|
||||
#if MESH_ENABLE_COLOR
|
||||
const color_t color,
|
||||
#endif
|
||||
const vec2 uvMin,
|
||||
const vec2 uvMax
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "sphere.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t SPHERE_MESH_SIMPLE;
|
||||
meshvertex_t SPHERE_MESH_SIMPLE_VERTICES[SPHERE_VERTEX_COUNT];
|
||||
|
||||
errorret_t sphereInit() {
|
||||
vec3 center = { 0.0f, 0.0f, 0.0f };
|
||||
sphereBuffer(
|
||||
SPHERE_MESH_SIMPLE_VERTICES,
|
||||
center,
|
||||
0.5f,
|
||||
SPHERE_STACKS,
|
||||
SPHERE_SECTORS
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&SPHERE_MESH_SIMPLE,
|
||||
SPHERE_PRIMITIVE_TYPE,
|
||||
SPHERE_VERTEX_COUNT,
|
||||
SPHERE_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void sphereBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 center,
|
||||
const float_t radius,
|
||||
const int32_t stacks,
|
||||
const int32_t sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertNotNull(center, "Center vector cannot be NULL");
|
||||
|
||||
const float_t stackStep = (float_t)GLM_PI / (float_t)stacks;
|
||||
const float_t sectorStep = 2.0f * (float_t)GLM_PI / (float_t)sectors;
|
||||
int32_t vi = 0;
|
||||
|
||||
for(int32_t i = 0; i < stacks; i++) {
|
||||
/* Latitude angles: top of band -> bottom of band */
|
||||
const float_t phi1 = (float_t)GLM_PI_2 - (float_t)i * stackStep;
|
||||
const float_t phi2 = (float_t)GLM_PI_2 - (float_t)(i + 1) * stackStep;
|
||||
|
||||
const float_t y1 = radius * sinf(phi1);
|
||||
const float_t y2 = radius * sinf(phi2);
|
||||
const float_t xz1 = radius * cosf(phi1);
|
||||
const float_t xz2 = radius * cosf(phi2);
|
||||
|
||||
const float_t v1 = 1.0f - (float_t)i / (float_t)stacks;
|
||||
const float_t v2 = 1.0f - (float_t)(i + 1) / (float_t)stacks;
|
||||
|
||||
for(int32_t j = 0; j < sectors; j++) {
|
||||
const float_t theta1 = (float_t)j * sectorStep;
|
||||
const float_t theta2 = (float_t)(j + 1) * sectorStep;
|
||||
|
||||
const float_t x11 = xz1 * cosf(theta1);
|
||||
const float_t z11 = xz1 * sinf(theta1);
|
||||
const float_t x12 = xz1 * cosf(theta2);
|
||||
const float_t z12 = xz1 * sinf(theta2);
|
||||
|
||||
const float_t x21 = xz2 * cosf(theta1);
|
||||
const float_t z21 = xz2 * sinf(theta1);
|
||||
const float_t x22 = xz2 * cosf(theta2);
|
||||
const float_t z22 = xz2 * sinf(theta2);
|
||||
|
||||
const float_t u1 = (float_t)j / (float_t)sectors;
|
||||
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
|
||||
|
||||
/* Triangle 1: top-left, bottom-left, top-right */
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x11;
|
||||
vertices[vi].pos[1] = center[1] + y1;
|
||||
vertices[vi].pos[2] = center[2] + z11;
|
||||
vertices[vi].uv[0] = u1;
|
||||
vertices[vi].uv[1] = v1;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x21;
|
||||
vertices[vi].pos[1] = center[1] + y2;
|
||||
vertices[vi].pos[2] = center[2] + z21;
|
||||
vertices[vi].uv[0] = u1;
|
||||
vertices[vi].uv[1] = v2;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x12;
|
||||
vertices[vi].pos[1] = center[1] + y1;
|
||||
vertices[vi].pos[2] = center[2] + z12;
|
||||
vertices[vi].uv[0] = u2;
|
||||
vertices[vi].uv[1] = v1;
|
||||
vi++;
|
||||
|
||||
/* Triangle 2: top-right, bottom-left, bottom-right */
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x12;
|
||||
vertices[vi].pos[1] = center[1] + y1;
|
||||
vertices[vi].pos[2] = center[2] + z12;
|
||||
vertices[vi].uv[0] = u2;
|
||||
vertices[vi].uv[1] = v1;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x21;
|
||||
vertices[vi].pos[1] = center[1] + y2;
|
||||
vertices[vi].pos[2] = center[2] + z21;
|
||||
vertices[vi].uv[0] = u1;
|
||||
vertices[vi].uv[1] = v2;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x22;
|
||||
vertices[vi].pos[1] = center[1] + y2;
|
||||
vertices[vi].pos[2] = center[2] + z22;
|
||||
vertices[vi].uv[0] = u2;
|
||||
vertices[vi].uv[1] = v2;
|
||||
vi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#define SPHERE_STACKS 8
|
||||
#define SPHERE_SECTORS 16
|
||||
#define SPHERE_VERTEX_COUNT (SPHERE_STACKS * SPHERE_SECTORS * 6)
|
||||
#define SPHERE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
extern mesh_t SPHERE_MESH_SIMPLE;
|
||||
extern meshvertex_t SPHERE_MESH_SIMPLE_VERTICES[SPHERE_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the simple unit sphere mesh centered at (0,0,0) with radius 0.5.
|
||||
*
|
||||
* @return Error for initialization of the sphere mesh.
|
||||
*/
|
||||
errorret_t sphereInit();
|
||||
|
||||
/**
|
||||
* Buffers a UV sphere into the provided vertex array.
|
||||
* Writes stacks * sectors * 6 vertices (CCW winding).
|
||||
*
|
||||
* @param vertices Vertex array to write into (must hold stacks*sectors*6).
|
||||
* @param center Center position of the sphere.
|
||||
* @param radius Radius of the sphere.
|
||||
* @param stacks Number of horizontal rings (latitude bands).
|
||||
* @param sectors Number of vertical segments (longitude slices).
|
||||
* @param color Color applied to all vertices.
|
||||
*/
|
||||
void sphereBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 center,
|
||||
const float_t radius,
|
||||
const int32_t stacks,
|
||||
const int32_t sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "triprism.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t TRIPRISM_MESH_SIMPLE;
|
||||
meshvertex_t TRIPRISM_MESH_SIMPLE_VERTICES[TRIPRISM_VERTEX_COUNT];
|
||||
|
||||
errorret_t triPrismInit() {
|
||||
triPrismBuffer(
|
||||
TRIPRISM_MESH_SIMPLE_VERTICES,
|
||||
0.0f, 0.0f, /* p0: bottom-left */
|
||||
1.0f, 0.0f, /* p1: bottom-right */
|
||||
0.5f, 1.0f, /* p2: apex */
|
||||
0.0f, 1.0f /* minZ, maxZ */
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&TRIPRISM_MESH_SIMPLE,
|
||||
TRIPRISM_PRIMITIVE_TYPE,
|
||||
TRIPRISM_VERTEX_COUNT,
|
||||
TRIPRISM_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void triPrismBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t x0, const float_t y0,
|
||||
const float_t x1, const float_t y1,
|
||||
const float_t x2, const float_t y2,
|
||||
const float_t minZ,
|
||||
const float_t maxZ
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
|
||||
/* Helper macro: write one vertex then advance index. */
|
||||
int32_t vi = 0;
|
||||
#if MESH_ENABLE_COLOR
|
||||
#define PRISM_VERT(px, py, pz, u, v) \
|
||||
vertices[vi].color = color; \
|
||||
vertices[vi].pos[0] = (px); \
|
||||
vertices[vi].pos[1] = (py); \
|
||||
vertices[vi].pos[2] = (pz); \
|
||||
vertices[vi].uv[0] = (u); \
|
||||
vertices[vi].uv[1] = (v); \
|
||||
vi++;
|
||||
#else
|
||||
#define PRISM_VERT(px, py, pz, u, v) \
|
||||
vertices[vi].pos[0] = (px); \
|
||||
vertices[vi].pos[1] = (py); \
|
||||
vertices[vi].pos[2] = (pz); \
|
||||
vertices[vi].uv[0] = (u); \
|
||||
vertices[vi].uv[1] = (v); \
|
||||
vi++;
|
||||
#endif
|
||||
|
||||
/* --- Front face (z = maxZ), CCW from +Z --- */
|
||||
PRISM_VERT(x0, y0, maxZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 0.5f, 1.0f)
|
||||
|
||||
/* --- Back face (z = minZ), CCW from -Z (reverse winding) --- */
|
||||
PRISM_VERT(x2, y2, minZ, 0.5f, 1.0f)
|
||||
PRISM_VERT(x1, y1, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
|
||||
|
||||
/* --- Side face 0: edge p0->p1 --- */
|
||||
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 1.0f, 1.0f)
|
||||
|
||||
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 1.0f, 1.0f)
|
||||
PRISM_VERT(x0, y0, maxZ, 0.0f, 1.0f)
|
||||
|
||||
/* --- Side face 1: edge p1->p2 --- */
|
||||
PRISM_VERT(x1, y1, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 1.0f, 1.0f)
|
||||
|
||||
PRISM_VERT(x1, y1, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 1.0f, 1.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 0.0f, 1.0f)
|
||||
|
||||
/* --- Side face 2: edge p2->p0 --- */
|
||||
PRISM_VERT(x2, y2, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, maxZ, 1.0f, 1.0f)
|
||||
|
||||
PRISM_VERT(x2, y2, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, maxZ, 1.0f, 1.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 0.0f, 1.0f)
|
||||
|
||||
#undef PRISM_VERT
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/color.h"
|
||||
|
||||
/**
|
||||
* Vertex layout:
|
||||
* 2 triangular end-caps (3 verts each) = 6
|
||||
* 3 rectangular side faces (6 verts each) = 18
|
||||
* Total = 24
|
||||
*/
|
||||
#define TRIPRISM_VERTEX_COUNT 24
|
||||
#define TRIPRISM_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
extern mesh_t TRIPRISM_MESH_SIMPLE;
|
||||
extern meshvertex_t TRIPRISM_MESH_SIMPLE_VERTICES[TRIPRISM_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the simple unit triangular prism. The cross-section triangle has
|
||||
* vertices (0,0), (1,0), (0.5,1) in XY, extruded from z=0 to z=1.
|
||||
*
|
||||
* @return Error for initialization of the triangular prism mesh.
|
||||
*/
|
||||
errorret_t triPrismInit();
|
||||
|
||||
/**
|
||||
* Buffers a triangular prism into the provided vertex array.
|
||||
* The triangular cross-section is defined by three 2D points in the XY plane;
|
||||
* the prism is extruded along the Z axis between minZ and maxZ.
|
||||
* Writes TRIPRISM_VERTEX_COUNT (24) vertices (CCW winding).
|
||||
*
|
||||
* @param vertices Vertex array to write into (must hold TRIPRISM_VERTEX_COUNT).
|
||||
* @param x0,y0 First triangle vertex (XY).
|
||||
* @param x1,y1 Second triangle vertex (XY).
|
||||
* @param x2,y2 Third triangle vertex (XY).
|
||||
* @param minZ Near Z extent of the prism.
|
||||
* @param maxZ Far Z extent of the prism.
|
||||
* @param color Color applied to all vertices.
|
||||
*/
|
||||
void triPrismBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t x0, const float_t y0,
|
||||
const float_t x1, const float_t y1,
|
||||
const float_t x2, const float_t y2,
|
||||
const float_t minZ,
|
||||
const float_t maxZ
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -26,11 +26,11 @@ errorret_t screenInit() {
|
||||
SCREEN.frameBufferMeshVertices,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
#if MESH_ENABLE_COLOR
|
||||
COLOR_WHITE,
|
||||
#endif
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&SCREEN.frameBufferMesh,
|
||||
@@ -363,11 +363,11 @@ errorret_t screenRender() {
|
||||
SCREEN.frameBufferMeshVertices,
|
||||
centerX - fbWidth * 0.5f, centerY + fbHeight * 0.5f, // top-left
|
||||
centerX + fbWidth * 0.5f, centerY - fbHeight * 0.5f, // bottom-right
|
||||
#if MESH_ENABLE_COLOR
|
||||
COLOR_WHITE,
|
||||
#endif
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE
|
||||
#endif
|
||||
);
|
||||
|
||||
frameBufferClear(
|
||||
|
||||
@@ -69,10 +69,10 @@ errorret_t spriteBatchPush3D(
|
||||
quadBuffer3D(
|
||||
&SPRITEBATCH_VERTICES[vertexOffset],
|
||||
min, max,
|
||||
#if MESH_ENABLE_COLOR
|
||||
color,
|
||||
#endif
|
||||
uv0, uv1
|
||||
#if MESH_ENABLE_COLOR
|
||||
, color
|
||||
#endif
|
||||
);
|
||||
SPRITEBATCH.spriteCount++;
|
||||
errorOk();
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
#include "assert/assert.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "game/game.h"
|
||||
#include "display/mesh/quad.h"
|
||||
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "asset/loader/display/assetmeshloader.h"
|
||||
|
||||
engine_t ENGINE;
|
||||
|
||||
@@ -171,7 +171,7 @@ void entityPositionDecompose(entityposition_t *pos) {
|
||||
pos->rotation[1] = asinf(sinBeta);
|
||||
float cosBeta = cosf(pos->rotation[1]);
|
||||
|
||||
if (fabsf(cosBeta) > 1e-6f) {
|
||||
if(fabsf(cosBeta) > 1e-6f) {
|
||||
pos->rotation[0] = atan2f(-r[2][1], r[2][2]);
|
||||
pos->rotation[2] = atan2f(-r[1][0], r[0][0]);
|
||||
} else {
|
||||
|
||||
+10
-10
@@ -35,9 +35,9 @@ const DTF = (() => {
|
||||
// When format is FORMAT_ALPHA and redAsAlpha is true, the red channel is
|
||||
// used as the alpha value instead of the actual alpha channel.
|
||||
function encode(width, height, rgbaData, format, redAsAlpha) {
|
||||
if (format === undefined) format = FORMAT_RGBA;
|
||||
if(format === undefined) format = FORMAT_RGBA;
|
||||
const bpp = BPP[format];
|
||||
if (bpp === undefined) throw new Error(`Unknown DTF format: 0x${format.toString(16)}`);
|
||||
if(bpp === undefined) throw new Error(`Unknown DTF format: 0x${format.toString(16)}`);
|
||||
|
||||
const src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
||||
const buf = new ArrayBuffer(HEADER_SIZE + width * height * bpp);
|
||||
@@ -53,7 +53,7 @@ const DTF = (() => {
|
||||
bytes[12] = format;
|
||||
|
||||
let dst = HEADER_SIZE;
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
for(let i = 0; i < width * height; i++) {
|
||||
const o = i * 4;
|
||||
switch (format) {
|
||||
case FORMAT_ALPHA:
|
||||
@@ -82,13 +82,13 @@ const DTF = (() => {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
if (bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DTF");
|
||||
if (bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] || bytes[2] !== MAGIC[2]) {
|
||||
if(bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DTF");
|
||||
if(bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] || bytes[2] !== MAGIC[2]) {
|
||||
throw new Error("Invalid DTF magic bytes – not a DTF file");
|
||||
}
|
||||
|
||||
const version = bytes[3];
|
||||
if (version !== VERSION) {
|
||||
if(version !== VERSION) {
|
||||
throw new Error(`Unsupported DTF version: 0x${version.toString(16).padStart(2, "0")}`);
|
||||
}
|
||||
|
||||
@@ -97,18 +97,18 @@ const DTF = (() => {
|
||||
const format = bytes[12];
|
||||
const bpp = BPP[format];
|
||||
|
||||
if (bpp === undefined) {
|
||||
if(bpp === undefined) {
|
||||
throw new Error(`Unsupported DTF format: 0x${format.toString(16).padStart(2, "0")}`);
|
||||
}
|
||||
|
||||
const expected = HEADER_SIZE + width * height * bpp;
|
||||
if (bytes.length < expected) {
|
||||
if(bytes.length < expected) {
|
||||
throw new Error(`DTF pixel data truncated (expected ${expected} bytes, got ${bytes.length})`);
|
||||
}
|
||||
|
||||
const rgba = new Uint8ClampedArray(width * height * 4);
|
||||
let src = HEADER_SIZE;
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
for(let i = 0; i < width * height; i++) {
|
||||
const o = i * 4;
|
||||
switch (format) {
|
||||
case FORMAT_ALPHA:
|
||||
@@ -141,7 +141,7 @@ const DTF = (() => {
|
||||
const src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
||||
const out = new Uint8ClampedArray(width * height * 4);
|
||||
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
for(let i = 0; i < width * height; i++) {
|
||||
const o = i * 4;
|
||||
switch (format) {
|
||||
case FORMAT_ALPHA: {
|
||||
|
||||
@@ -11,14 +11,14 @@ const DuskPNG = (() => {
|
||||
// Encode RGBA pixel data into a PNG Buffer via pngjs.
|
||||
// Returns a Uint8Array (Buffer) if pngjs is available, otherwise null.
|
||||
function encode(width, height, rgbaData) {
|
||||
if (!_pngAvailable()) return null;
|
||||
if(!_pngAvailable()) return null;
|
||||
|
||||
const png = new PNG({ width, height });
|
||||
const src = rgbaData instanceof Uint8ClampedArray
|
||||
? rgbaData
|
||||
: new Uint8ClampedArray(rgbaData);
|
||||
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
for(let i = 0; i < src.length; i++) {
|
||||
png.data[i] = src[i];
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const DuskPNG = (() => {
|
||||
function download(filename, width, height, rgbaData) {
|
||||
const buf = encode(width, height, rgbaData);
|
||||
|
||||
if (buf) {
|
||||
if(buf) {
|
||||
// pngjs path
|
||||
const blob = new Blob([buf], { type: "image/png" });
|
||||
_triggerDownload(URL.createObjectURL(blob), filename);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Returns the smallest power of two >= n.
|
||||
function nextPow2(n) {
|
||||
if (n <= 0) return 1;
|
||||
if(n <= 0) return 1;
|
||||
let p = 1;
|
||||
while (p < n) p <<= 1;
|
||||
return p;
|
||||
@@ -57,10 +57,10 @@ function computeOutputSize() {
|
||||
let outW = state.width;
|
||||
let outH = state.height;
|
||||
// Apply minimum-size constraints first so pow2 rounding accounts for them.
|
||||
if (state.minWidth4 && outW < 4) outW = 4;
|
||||
if (state.minHeight4 && outH < 4) outH = 4;
|
||||
if (state.padWidthPow2) outW = nextPow2(outW);
|
||||
if (state.padHeightPow2) outH = nextPow2(outH);
|
||||
if(state.minWidth4 && outW < 4) outW = 4;
|
||||
if(state.minHeight4 && outH < 4) outH = 4;
|
||||
if(state.padWidthPow2) outW = nextPow2(outW);
|
||||
if(state.padHeightPow2) outH = nextPow2(outH);
|
||||
return { outW, outH };
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ function buildPaddedPixels() {
|
||||
const src = state.pixels;
|
||||
// Zero-filled Uint8ClampedArray = fully transparent black padding.
|
||||
const out = new Uint8ClampedArray(outW * outH * 4);
|
||||
for (let y = 0; y < state.height; y++) {
|
||||
for (let x = 0; x < state.width; x++) {
|
||||
for(let y = 0; y < state.height; y++) {
|
||||
for(let x = 0; x < state.width; x++) {
|
||||
const si = (y * state.width + x) * 4;
|
||||
const di = (y * outW + x) * 4;
|
||||
out[di] = src[si];
|
||||
@@ -87,8 +87,8 @@ function buildPaddedPixels() {
|
||||
const CHECKER_CELL = 8;
|
||||
|
||||
function drawCheckerboard(w, h) {
|
||||
for (let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for (let x = 0; x < w; x += CHECKER_CELL) {
|
||||
for(let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for(let x = 0; x < w; x += CHECKER_CELL) {
|
||||
ctx.fillStyle = ((x / CHECKER_CELL + y / CHECKER_CELL) % 2 === 0) ? "#c8c8c8" : "#ffffff";
|
||||
ctx.fillRect(x, y, Math.min(CHECKER_CELL, w - x), Math.min(CHECKER_CELL, h - y));
|
||||
}
|
||||
@@ -96,7 +96,7 @@ function drawCheckerboard(w, h) {
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
|
||||
const { pixels, width, height } = buildPaddedPixels();
|
||||
const cw = width * state.scale;
|
||||
@@ -105,7 +105,7 @@ function render() {
|
||||
canvas.width = cw;
|
||||
canvas.height = ch;
|
||||
|
||||
if (state.bg === "grid") {
|
||||
if(state.bg === "grid") {
|
||||
drawCheckerboard(cw, ch);
|
||||
} else {
|
||||
ctx.fillStyle = state.bg;
|
||||
@@ -122,7 +122,7 @@ function render() {
|
||||
// ─── Info update ─────────────────────────────────────────────────────────────
|
||||
|
||||
function updateInfo() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const { outW, outH } = computeOutputSize();
|
||||
const bytes = DTF.HEADER_SIZE + outW * outH * DTF.BPP[state.format];
|
||||
infoInputSize.textContent = `${state.width} × ${state.height}`;
|
||||
@@ -189,8 +189,8 @@ function applyImageData(width, height, data, filename) {
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
if (!file) return;
|
||||
if (file.name.toLowerCase().endsWith(".dtf")) {
|
||||
if(!file) return;
|
||||
if(file.name.toLowerCase().endsWith(".dtf")) {
|
||||
loadDTF(file);
|
||||
} else {
|
||||
loadStandardImage(file);
|
||||
@@ -200,7 +200,7 @@ function handleFile(file) {
|
||||
// ─── Export ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function exportDTF() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const { pixels, width, height } = buildPaddedPixels();
|
||||
const buf = DTF.encode(width, height, pixels, state.format);
|
||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||
@@ -214,7 +214,7 @@ function exportDTF() {
|
||||
}
|
||||
|
||||
function exportPNG() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const { pixels, width, height } = buildPaddedPixels();
|
||||
DuskPNG.download(`${state.filename}.png`, width, height, pixels);
|
||||
}
|
||||
@@ -232,7 +232,7 @@ scaleInput.addEventListener("input", () => {
|
||||
|
||||
bgSwatches.addEventListener("click", e => {
|
||||
const btn = e.target.closest(".bg-swatch");
|
||||
if (!btn) return;
|
||||
if(!btn) return;
|
||||
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
state.bg = btn.dataset.bg;
|
||||
|
||||
@@ -43,8 +43,8 @@ const redAsAlphaCheck = document.getElementById("red-as-alpha");
|
||||
const CHECKER_CELL = 8;
|
||||
|
||||
function drawCheckerboard(w, h) {
|
||||
for (let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for (let x = 0; x < w; x += CHECKER_CELL) {
|
||||
for(let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for(let x = 0; x < w; x += CHECKER_CELL) {
|
||||
ctx.fillStyle = ((x / CHECKER_CELL + y / CHECKER_CELL) % 2 === 0) ? "#c8c8c8" : "#ffffff";
|
||||
ctx.fillRect(x, y, Math.min(CHECKER_CELL, w - x), Math.min(CHECKER_CELL, h - y));
|
||||
}
|
||||
@@ -52,7 +52,7 @@ function drawCheckerboard(w, h) {
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
|
||||
const { pixels, width, height, scale, bg, format } = state;
|
||||
const cw = width * scale;
|
||||
@@ -62,7 +62,7 @@ function render() {
|
||||
canvas.height = ch;
|
||||
|
||||
// 1. Background
|
||||
if (bg === "grid") {
|
||||
if(bg === "grid") {
|
||||
drawCheckerboard(cw, ch);
|
||||
} else {
|
||||
ctx.fillStyle = bg;
|
||||
@@ -128,17 +128,17 @@ function loadDTF(file) {
|
||||
function updateWarnings() {
|
||||
const warnings = [];
|
||||
|
||||
if (state.pixels) {
|
||||
if(state.pixels) {
|
||||
const { width, height } = state;
|
||||
const isPow2 = n => n > 0 && (n & (n - 1)) === 0;
|
||||
|
||||
if (width < 4) warnings.push(`Width is below 4 px (${width})`);
|
||||
if (height < 4) warnings.push(`Height is below 4 px (${height})`);
|
||||
if (!isPow2(width)) warnings.push(`Width is not a power of two (${width})`);
|
||||
if (!isPow2(height)) warnings.push(`Height is not a power of two (${height})`);
|
||||
if(width < 4) warnings.push(`Width is below 4 px (${width})`);
|
||||
if(height < 4) warnings.push(`Height is below 4 px (${height})`);
|
||||
if(!isPow2(width)) warnings.push(`Width is not a power of two (${width})`);
|
||||
if(!isPow2(height)) warnings.push(`Height is not a power of two (${height})`);
|
||||
|
||||
const bytes = DTF.HEADER_SIZE + width * height * DTF.BPP[state.format];
|
||||
if (bytes > 256 * 1024) {
|
||||
if(bytes > 256 * 1024) {
|
||||
warnings.push(`Output exceeds 256 KB (${(bytes / 1024).toFixed(1)} KB)`);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ function updateWarnings() {
|
||||
}
|
||||
|
||||
function updateDtfSize() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const bytes = DTF.HEADER_SIZE + state.width * state.height * DTF.BPP[state.format];
|
||||
infoDtfSize.textContent = `${(bytes / 1024).toFixed(1)} KB`;
|
||||
updateWarnings();
|
||||
@@ -163,7 +163,7 @@ function applyImageData(width, height, data, filename, formatLabel, format) {
|
||||
state.filename = filename.replace(/\.[^/.]+$/, "");
|
||||
|
||||
// Sync format selector when loading an existing DTF
|
||||
if (format !== undefined) {
|
||||
if(format !== undefined) {
|
||||
state.format = format;
|
||||
formatSelect.value = format;
|
||||
redAsAlphaRow.hidden = format !== DTF.FORMAT_ALPHA;
|
||||
@@ -189,8 +189,8 @@ function applyImageData(width, height, data, filename, formatLabel, format) {
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
if (!file) return;
|
||||
if (file.name.toLowerCase().endsWith(".dtf")) {
|
||||
if(!file) return;
|
||||
if(file.name.toLowerCase().endsWith(".dtf")) {
|
||||
loadDTF(file);
|
||||
} else {
|
||||
loadStandardImage(file);
|
||||
@@ -204,7 +204,7 @@ function showError(msg) {
|
||||
// ─── Export ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function exportDTF() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const buf = DTF.encode(state.width, state.height, state.pixels, state.format, state.redAsAlpha);
|
||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -217,7 +217,7 @@ function exportDTF() {
|
||||
}
|
||||
|
||||
function exportPNG() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
DuskPNG.download(`${state.filename}.png`, state.width, state.height, state.pixels);
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ scaleInput.addEventListener("input", () => {
|
||||
|
||||
bgSwatches.addEventListener("click", e => {
|
||||
const btn = e.target.closest(".bg-swatch");
|
||||
if (!btn) return;
|
||||
if(!btn) return;
|
||||
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
state.bg = btn.dataset.bg;
|
||||
|
||||
Reference in New Issue
Block a user