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 */
|
/* 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.");
|
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, "Reader cannot be NULL.");
|
||||||
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
assertNotNull(reader->readBuffer, "Read buffer cannot be NULL.");
|
||||||
|
|
||||||
for (i = reader->bufferStart; i < reader->bufferEnd; ++i) {
|
for(i = reader->bufferStart; i < reader->bufferEnd; ++i) {
|
||||||
if (reader->readBuffer[i] == '\n') {
|
if(reader->readBuffer[i] == '\n') {
|
||||||
return (ssize_t)i;
|
return (ssize_t)i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ errorret_t assetFileLineReaderFill(assetfilelinereader_t *reader) {
|
|||||||
size_t unreadBytes = assetFileLineReaderUnreadBytes(reader);
|
size_t unreadBytes = assetFileLineReaderUnreadBytes(reader);
|
||||||
|
|
||||||
/* If buffer is fully consumed, refill from start. */
|
/* If buffer is fully consumed, refill from start. */
|
||||||
if (unreadBytes == 0) {
|
if(unreadBytes == 0) {
|
||||||
reader->bufferStart = 0;
|
reader->bufferStart = 0;
|
||||||
reader->bufferEnd = 0;
|
reader->bufferEnd = 0;
|
||||||
|
|
||||||
@@ -276,15 +276,15 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
|
|||||||
|
|
||||||
reader->lineLength = 0;
|
reader->lineLength = 0;
|
||||||
|
|
||||||
for (;;) {
|
for(;;) {
|
||||||
ssize_t newlineIndex = assetFileLineReaderFindNewline(reader);
|
ssize_t newlineIndex = assetFileLineReaderFindNewline(reader);
|
||||||
|
|
||||||
if (newlineIndex >= 0) {
|
if(newlineIndex >= 0) {
|
||||||
size_t chunkLength = (size_t)newlineIndex - reader->bufferStart;
|
size_t chunkLength = (size_t)newlineIndex - reader->bufferStart;
|
||||||
errorret_t ret;
|
errorret_t ret;
|
||||||
|
|
||||||
/* strip CR in CRLF */
|
/* 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--;
|
chunkLength--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ errorret_t assetFileLineReaderNext(assetfilelinereader_t *reader) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assetFileLineReaderUnreadBytes(reader) > 0) {
|
if(assetFileLineReaderUnreadBytes(reader) > 0) {
|
||||||
errorChain(assetFileLineReaderAppend(
|
errorChain(assetFileLineReaderAppend(
|
||||||
reader,
|
reader,
|
||||||
assetFileLineReaderUnreadPtr(reader),
|
assetFileLineReaderUnreadPtr(reader),
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
#include "display/spritebatch/spritebatch.h"
|
#include "display/spritebatch/spritebatch.h"
|
||||||
#include "display/mesh/quad.h"
|
#include "display/mesh/quad.h"
|
||||||
#include "display/mesh/cube.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 "display/screen/screen.h"
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "display/text/text.h"
|
#include "display/text/text.h"
|
||||||
@@ -35,8 +39,15 @@ errorret_t displayInit(void) {
|
|||||||
&TEXTURE_WHITE, 4, 4,
|
&TEXTURE_WHITE, 4, 4,
|
||||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Standard meshes
|
||||||
errorChain(quadInit());
|
errorChain(quadInit());
|
||||||
errorChain(cubeInit());
|
errorChain(cubeInit());
|
||||||
|
errorChain(sphereInit());
|
||||||
|
errorChain(planeInit());
|
||||||
|
errorChain(capsuleInit());
|
||||||
|
errorChain(triPrismInit());
|
||||||
|
|
||||||
errorChain(frameBufferInitBackBuffer());
|
errorChain(frameBufferInitBackBuffer());
|
||||||
errorChain(spriteBatchInit());
|
errorChain(spriteBatchInit());
|
||||||
errorChain(textInit());
|
errorChain(textInit());
|
||||||
|
|||||||
@@ -9,4 +9,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
mesh.c
|
mesh.c
|
||||||
quad.c
|
quad.c
|
||||||
cube.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() {
|
errorret_t cubeInit() {
|
||||||
vec3 min = { 0.0f, 0.0f, 0.0f };
|
vec3 min = { 0.0f, 0.0f, 0.0f };
|
||||||
vec3 max = { 1.0f, 1.0f, 1.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(
|
errorChain(meshInit(
|
||||||
&CUBE_MESH_SIMPLE,
|
&CUBE_MESH_SIMPLE,
|
||||||
CUBE_PRIMITIVE_TYPE,
|
CUBE_PRIMITIVE_TYPE,
|
||||||
@@ -45,8 +50,10 @@ errorret_t cubeInit() {
|
|||||||
void cubeBuffer(
|
void cubeBuffer(
|
||||||
meshvertex_t *vertices,
|
meshvertex_t *vertices,
|
||||||
const vec3 min,
|
const vec3 min,
|
||||||
const vec3 max,
|
const vec3 max
|
||||||
const color_t color
|
#if MESH_ENABLE_COLOR
|
||||||
|
, const color_t color
|
||||||
|
#endif
|
||||||
) {
|
) {
|
||||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||||
assertNotNull(min, "Min vector cannot be NULL");
|
assertNotNull(min, "Min vector cannot be NULL");
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ errorret_t cubeInit();
|
|||||||
void cubeBuffer(
|
void cubeBuffer(
|
||||||
meshvertex_t *vertices,
|
meshvertex_t *vertices,
|
||||||
const vec3 min,
|
const vec3 min,
|
||||||
const vec3 max,
|
const vec3 max
|
||||||
const color_t color
|
#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 minY,
|
||||||
const float_t maxX,
|
const float_t maxX,
|
||||||
const float_t maxY,
|
const float_t maxY,
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
const color_t color,
|
|
||||||
#endif
|
|
||||||
const float_t u0,
|
const float_t u0,
|
||||||
const float_t v0,
|
const float_t v0,
|
||||||
const float_t u1,
|
const float_t u1,
|
||||||
const float_t v1
|
const float_t v1
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
, const color_t color
|
||||||
|
#endif
|
||||||
) {
|
) {
|
||||||
const float_t z = 0.0f; // Z coordinate for 2D rendering
|
const float_t z = 0.0f; // Z coordinate for 2D rendering
|
||||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||||
@@ -149,11 +149,11 @@ void quadBuffer3D(
|
|||||||
meshvertex_t *vertices,
|
meshvertex_t *vertices,
|
||||||
const vec3 min,
|
const vec3 min,
|
||||||
const vec3 max,
|
const vec3 max,
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
const color_t color,
|
|
||||||
#endif
|
|
||||||
const vec2 uvMin,
|
const vec2 uvMin,
|
||||||
const vec2 uvMax
|
const vec2 uvMax
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
, const color_t color
|
||||||
|
#endif
|
||||||
) {
|
) {
|
||||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||||
assertNotNull(min, "Min vector cannot be NULL");
|
assertNotNull(min, "Min vector cannot be NULL");
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ void quadBuffer(
|
|||||||
const float_t minY,
|
const float_t minY,
|
||||||
const float_t maxX,
|
const float_t maxX,
|
||||||
const float_t maxY,
|
const float_t maxY,
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
const color_t color,
|
|
||||||
#endif
|
|
||||||
const float_t u0,
|
const float_t u0,
|
||||||
const float_t v0,
|
const float_t v0,
|
||||||
const float_t u1,
|
const float_t u1,
|
||||||
const float_t v1
|
const float_t v1
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
, const color_t color
|
||||||
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,9 +65,9 @@ void quadBuffer3D(
|
|||||||
meshvertex_t *vertices,
|
meshvertex_t *vertices,
|
||||||
const vec3 min,
|
const vec3 min,
|
||||||
const vec3 max,
|
const vec3 max,
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
const color_t color,
|
|
||||||
#endif
|
|
||||||
const vec2 uvMin,
|
const vec2 uvMin,
|
||||||
const vec2 uvMax
|
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,
|
SCREEN.frameBufferMeshVertices,
|
||||||
0.0f, 0.0f,
|
0.0f, 0.0f,
|
||||||
1.0f, 1.0f,
|
1.0f, 1.0f,
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
COLOR_WHITE,
|
|
||||||
#endif
|
|
||||||
0.0f, 0.0f,
|
0.0f, 0.0f,
|
||||||
1.0f, 1.0f
|
1.0f, 1.0f
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
, COLOR_WHITE
|
||||||
|
#endif
|
||||||
);
|
);
|
||||||
errorChain(meshInit(
|
errorChain(meshInit(
|
||||||
&SCREEN.frameBufferMesh,
|
&SCREEN.frameBufferMesh,
|
||||||
@@ -363,11 +363,11 @@ errorret_t screenRender() {
|
|||||||
SCREEN.frameBufferMeshVertices,
|
SCREEN.frameBufferMeshVertices,
|
||||||
centerX - fbWidth * 0.5f, centerY + fbHeight * 0.5f, // top-left
|
centerX - fbWidth * 0.5f, centerY + fbHeight * 0.5f, // top-left
|
||||||
centerX + fbWidth * 0.5f, centerY - fbHeight * 0.5f, // bottom-right
|
centerX + fbWidth * 0.5f, centerY - fbHeight * 0.5f, // bottom-right
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
COLOR_WHITE,
|
|
||||||
#endif
|
|
||||||
0.0f, 0.0f,
|
0.0f, 0.0f,
|
||||||
1.0f, 1.0f
|
1.0f, 1.0f
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
, COLOR_WHITE
|
||||||
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
frameBufferClear(
|
frameBufferClear(
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ errorret_t spriteBatchPush3D(
|
|||||||
quadBuffer3D(
|
quadBuffer3D(
|
||||||
&SPRITEBATCH_VERTICES[vertexOffset],
|
&SPRITEBATCH_VERTICES[vertexOffset],
|
||||||
min, max,
|
min, max,
|
||||||
#if MESH_ENABLE_COLOR
|
|
||||||
color,
|
|
||||||
#endif
|
|
||||||
uv0, uv1
|
uv0, uv1
|
||||||
|
#if MESH_ENABLE_COLOR
|
||||||
|
, color
|
||||||
|
#endif
|
||||||
);
|
);
|
||||||
SPRITEBATCH.spriteCount++;
|
SPRITEBATCH.spriteCount++;
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "entity/entitymanager.h"
|
#include "entity/entitymanager.h"
|
||||||
#include "game/game.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"
|
#include "asset/loader/display/assetmeshloader.h"
|
||||||
|
|
||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ void entityPositionDecompose(entityposition_t *pos) {
|
|||||||
pos->rotation[1] = asinf(sinBeta);
|
pos->rotation[1] = asinf(sinBeta);
|
||||||
float cosBeta = cosf(pos->rotation[1]);
|
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[0] = atan2f(-r[2][1], r[2][2]);
|
||||||
pos->rotation[2] = atan2f(-r[1][0], r[0][0]);
|
pos->rotation[2] = atan2f(-r[1][0], r[0][0]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+10
-10
@@ -35,9 +35,9 @@ const DTF = (() => {
|
|||||||
// When format is FORMAT_ALPHA and redAsAlpha is true, the red channel is
|
// When format is FORMAT_ALPHA and redAsAlpha is true, the red channel is
|
||||||
// used as the alpha value instead of the actual alpha channel.
|
// used as the alpha value instead of the actual alpha channel.
|
||||||
function encode(width, height, rgbaData, format, redAsAlpha) {
|
function encode(width, height, rgbaData, format, redAsAlpha) {
|
||||||
if (format === undefined) format = FORMAT_RGBA;
|
if(format === undefined) format = FORMAT_RGBA;
|
||||||
const bpp = BPP[format];
|
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 src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
||||||
const buf = new ArrayBuffer(HEADER_SIZE + width * height * bpp);
|
const buf = new ArrayBuffer(HEADER_SIZE + width * height * bpp);
|
||||||
@@ -53,7 +53,7 @@ const DTF = (() => {
|
|||||||
bytes[12] = format;
|
bytes[12] = format;
|
||||||
|
|
||||||
let dst = HEADER_SIZE;
|
let dst = HEADER_SIZE;
|
||||||
for (let i = 0; i < width * height; i++) {
|
for(let i = 0; i < width * height; i++) {
|
||||||
const o = i * 4;
|
const o = i * 4;
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case FORMAT_ALPHA:
|
case FORMAT_ALPHA:
|
||||||
@@ -82,13 +82,13 @@ const DTF = (() => {
|
|||||||
const bytes = new Uint8Array(buffer);
|
const bytes = new Uint8Array(buffer);
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
|
|
||||||
if (bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DTF");
|
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[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] || bytes[2] !== MAGIC[2]) {
|
||||||
throw new Error("Invalid DTF magic bytes – not a DTF file");
|
throw new Error("Invalid DTF magic bytes – not a DTF file");
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = bytes[3];
|
const version = bytes[3];
|
||||||
if (version !== VERSION) {
|
if(version !== VERSION) {
|
||||||
throw new Error(`Unsupported DTF version: 0x${version.toString(16).padStart(2, "0")}`);
|
throw new Error(`Unsupported DTF version: 0x${version.toString(16).padStart(2, "0")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,18 +97,18 @@ const DTF = (() => {
|
|||||||
const format = bytes[12];
|
const format = bytes[12];
|
||||||
const bpp = BPP[format];
|
const bpp = BPP[format];
|
||||||
|
|
||||||
if (bpp === undefined) {
|
if(bpp === undefined) {
|
||||||
throw new Error(`Unsupported DTF format: 0x${format.toString(16).padStart(2, "0")}`);
|
throw new Error(`Unsupported DTF format: 0x${format.toString(16).padStart(2, "0")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const expected = HEADER_SIZE + width * height * bpp;
|
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})`);
|
throw new Error(`DTF pixel data truncated (expected ${expected} bytes, got ${bytes.length})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rgba = new Uint8ClampedArray(width * height * 4);
|
const rgba = new Uint8ClampedArray(width * height * 4);
|
||||||
let src = HEADER_SIZE;
|
let src = HEADER_SIZE;
|
||||||
for (let i = 0; i < width * height; i++) {
|
for(let i = 0; i < width * height; i++) {
|
||||||
const o = i * 4;
|
const o = i * 4;
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case FORMAT_ALPHA:
|
case FORMAT_ALPHA:
|
||||||
@@ -141,7 +141,7 @@ const DTF = (() => {
|
|||||||
const src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
const src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
||||||
const out = new Uint8ClampedArray(width * height * 4);
|
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;
|
const o = i * 4;
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case FORMAT_ALPHA: {
|
case FORMAT_ALPHA: {
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ const DuskPNG = (() => {
|
|||||||
// Encode RGBA pixel data into a PNG Buffer via pngjs.
|
// Encode RGBA pixel data into a PNG Buffer via pngjs.
|
||||||
// Returns a Uint8Array (Buffer) if pngjs is available, otherwise null.
|
// Returns a Uint8Array (Buffer) if pngjs is available, otherwise null.
|
||||||
function encode(width, height, rgbaData) {
|
function encode(width, height, rgbaData) {
|
||||||
if (!_pngAvailable()) return null;
|
if(!_pngAvailable()) return null;
|
||||||
|
|
||||||
const png = new PNG({ width, height });
|
const png = new PNG({ width, height });
|
||||||
const src = rgbaData instanceof Uint8ClampedArray
|
const src = rgbaData instanceof Uint8ClampedArray
|
||||||
? rgbaData
|
? rgbaData
|
||||||
: new 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];
|
png.data[i] = src[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ const DuskPNG = (() => {
|
|||||||
function download(filename, width, height, rgbaData) {
|
function download(filename, width, height, rgbaData) {
|
||||||
const buf = encode(width, height, rgbaData);
|
const buf = encode(width, height, rgbaData);
|
||||||
|
|
||||||
if (buf) {
|
if(buf) {
|
||||||
// pngjs path
|
// pngjs path
|
||||||
const blob = new Blob([buf], { type: "image/png" });
|
const blob = new Blob([buf], { type: "image/png" });
|
||||||
_triggerDownload(URL.createObjectURL(blob), filename);
|
_triggerDownload(URL.createObjectURL(blob), filename);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// Returns the smallest power of two >= n.
|
// Returns the smallest power of two >= n.
|
||||||
function nextPow2(n) {
|
function nextPow2(n) {
|
||||||
if (n <= 0) return 1;
|
if(n <= 0) return 1;
|
||||||
let p = 1;
|
let p = 1;
|
||||||
while (p < n) p <<= 1;
|
while (p < n) p <<= 1;
|
||||||
return p;
|
return p;
|
||||||
@@ -57,10 +57,10 @@ function computeOutputSize() {
|
|||||||
let outW = state.width;
|
let outW = state.width;
|
||||||
let outH = state.height;
|
let outH = state.height;
|
||||||
// Apply minimum-size constraints first so pow2 rounding accounts for them.
|
// Apply minimum-size constraints first so pow2 rounding accounts for them.
|
||||||
if (state.minWidth4 && outW < 4) outW = 4;
|
if(state.minWidth4 && outW < 4) outW = 4;
|
||||||
if (state.minHeight4 && outH < 4) outH = 4;
|
if(state.minHeight4 && outH < 4) outH = 4;
|
||||||
if (state.padWidthPow2) outW = nextPow2(outW);
|
if(state.padWidthPow2) outW = nextPow2(outW);
|
||||||
if (state.padHeightPow2) outH = nextPow2(outH);
|
if(state.padHeightPow2) outH = nextPow2(outH);
|
||||||
return { outW, outH };
|
return { outW, outH };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +69,8 @@ function buildPaddedPixels() {
|
|||||||
const src = state.pixels;
|
const src = state.pixels;
|
||||||
// Zero-filled Uint8ClampedArray = fully transparent black padding.
|
// Zero-filled Uint8ClampedArray = fully transparent black padding.
|
||||||
const out = new Uint8ClampedArray(outW * outH * 4);
|
const out = new Uint8ClampedArray(outW * outH * 4);
|
||||||
for (let y = 0; y < state.height; y++) {
|
for(let y = 0; y < state.height; y++) {
|
||||||
for (let x = 0; x < state.width; x++) {
|
for(let x = 0; x < state.width; x++) {
|
||||||
const si = (y * state.width + x) * 4;
|
const si = (y * state.width + x) * 4;
|
||||||
const di = (y * outW + x) * 4;
|
const di = (y * outW + x) * 4;
|
||||||
out[di] = src[si];
|
out[di] = src[si];
|
||||||
@@ -87,8 +87,8 @@ function buildPaddedPixels() {
|
|||||||
const CHECKER_CELL = 8;
|
const CHECKER_CELL = 8;
|
||||||
|
|
||||||
function drawCheckerboard(w, h) {
|
function drawCheckerboard(w, h) {
|
||||||
for (let y = 0; y < h; y += CHECKER_CELL) {
|
for(let y = 0; y < h; y += CHECKER_CELL) {
|
||||||
for (let x = 0; x < w; x += CHECKER_CELL) {
|
for(let x = 0; x < w; x += CHECKER_CELL) {
|
||||||
ctx.fillStyle = ((x / CHECKER_CELL + y / CHECKER_CELL) % 2 === 0) ? "#c8c8c8" : "#ffffff";
|
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));
|
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() {
|
function render() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
|
|
||||||
const { pixels, width, height } = buildPaddedPixels();
|
const { pixels, width, height } = buildPaddedPixels();
|
||||||
const cw = width * state.scale;
|
const cw = width * state.scale;
|
||||||
@@ -105,7 +105,7 @@ function render() {
|
|||||||
canvas.width = cw;
|
canvas.width = cw;
|
||||||
canvas.height = ch;
|
canvas.height = ch;
|
||||||
|
|
||||||
if (state.bg === "grid") {
|
if(state.bg === "grid") {
|
||||||
drawCheckerboard(cw, ch);
|
drawCheckerboard(cw, ch);
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = state.bg;
|
ctx.fillStyle = state.bg;
|
||||||
@@ -122,7 +122,7 @@ function render() {
|
|||||||
// ─── Info update ─────────────────────────────────────────────────────────────
|
// ─── Info update ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function updateInfo() {
|
function updateInfo() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
const { outW, outH } = computeOutputSize();
|
const { outW, outH } = computeOutputSize();
|
||||||
const bytes = DTF.HEADER_SIZE + outW * outH * DTF.BPP[state.format];
|
const bytes = DTF.HEADER_SIZE + outW * outH * DTF.BPP[state.format];
|
||||||
infoInputSize.textContent = `${state.width} × ${state.height}`;
|
infoInputSize.textContent = `${state.width} × ${state.height}`;
|
||||||
@@ -189,8 +189,8 @@ function applyImageData(width, height, data, filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleFile(file) {
|
function handleFile(file) {
|
||||||
if (!file) return;
|
if(!file) return;
|
||||||
if (file.name.toLowerCase().endsWith(".dtf")) {
|
if(file.name.toLowerCase().endsWith(".dtf")) {
|
||||||
loadDTF(file);
|
loadDTF(file);
|
||||||
} else {
|
} else {
|
||||||
loadStandardImage(file);
|
loadStandardImage(file);
|
||||||
@@ -200,7 +200,7 @@ function handleFile(file) {
|
|||||||
// ─── Export ───────────────────────────────────────────────────────────────────
|
// ─── Export ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function exportDTF() {
|
function exportDTF() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
const { pixels, width, height } = buildPaddedPixels();
|
const { pixels, width, height } = buildPaddedPixels();
|
||||||
const buf = DTF.encode(width, height, pixels, state.format);
|
const buf = DTF.encode(width, height, pixels, state.format);
|
||||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||||
@@ -214,7 +214,7 @@ function exportDTF() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportPNG() {
|
function exportPNG() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
const { pixels, width, height } = buildPaddedPixels();
|
const { pixels, width, height } = buildPaddedPixels();
|
||||||
DuskPNG.download(`${state.filename}.png`, width, height, pixels);
|
DuskPNG.download(`${state.filename}.png`, width, height, pixels);
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ scaleInput.addEventListener("input", () => {
|
|||||||
|
|
||||||
bgSwatches.addEventListener("click", e => {
|
bgSwatches.addEventListener("click", e => {
|
||||||
const btn = e.target.closest(".bg-swatch");
|
const btn = e.target.closest(".bg-swatch");
|
||||||
if (!btn) return;
|
if(!btn) return;
|
||||||
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
||||||
btn.classList.add("active");
|
btn.classList.add("active");
|
||||||
state.bg = btn.dataset.bg;
|
state.bg = btn.dataset.bg;
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ const redAsAlphaCheck = document.getElementById("red-as-alpha");
|
|||||||
const CHECKER_CELL = 8;
|
const CHECKER_CELL = 8;
|
||||||
|
|
||||||
function drawCheckerboard(w, h) {
|
function drawCheckerboard(w, h) {
|
||||||
for (let y = 0; y < h; y += CHECKER_CELL) {
|
for(let y = 0; y < h; y += CHECKER_CELL) {
|
||||||
for (let x = 0; x < w; x += CHECKER_CELL) {
|
for(let x = 0; x < w; x += CHECKER_CELL) {
|
||||||
ctx.fillStyle = ((x / CHECKER_CELL + y / CHECKER_CELL) % 2 === 0) ? "#c8c8c8" : "#ffffff";
|
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));
|
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() {
|
function render() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
|
|
||||||
const { pixels, width, height, scale, bg, format } = state;
|
const { pixels, width, height, scale, bg, format } = state;
|
||||||
const cw = width * scale;
|
const cw = width * scale;
|
||||||
@@ -62,7 +62,7 @@ function render() {
|
|||||||
canvas.height = ch;
|
canvas.height = ch;
|
||||||
|
|
||||||
// 1. Background
|
// 1. Background
|
||||||
if (bg === "grid") {
|
if(bg === "grid") {
|
||||||
drawCheckerboard(cw, ch);
|
drawCheckerboard(cw, ch);
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = bg;
|
ctx.fillStyle = bg;
|
||||||
@@ -128,17 +128,17 @@ function loadDTF(file) {
|
|||||||
function updateWarnings() {
|
function updateWarnings() {
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
|
||||||
if (state.pixels) {
|
if(state.pixels) {
|
||||||
const { width, height } = state;
|
const { width, height } = state;
|
||||||
const isPow2 = n => n > 0 && (n & (n - 1)) === 0;
|
const isPow2 = n => n > 0 && (n & (n - 1)) === 0;
|
||||||
|
|
||||||
if (width < 4) warnings.push(`Width is below 4 px (${width})`);
|
if(width < 4) warnings.push(`Width is below 4 px (${width})`);
|
||||||
if (height < 4) warnings.push(`Height is below 4 px (${height})`);
|
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(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(!isPow2(height)) warnings.push(`Height is not a power of two (${height})`);
|
||||||
|
|
||||||
const bytes = DTF.HEADER_SIZE + width * height * DTF.BPP[state.format];
|
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)`);
|
warnings.push(`Output exceeds 256 KB (${(bytes / 1024).toFixed(1)} KB)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ function updateWarnings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateDtfSize() {
|
function updateDtfSize() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
const bytes = DTF.HEADER_SIZE + state.width * state.height * DTF.BPP[state.format];
|
const bytes = DTF.HEADER_SIZE + state.width * state.height * DTF.BPP[state.format];
|
||||||
infoDtfSize.textContent = `${(bytes / 1024).toFixed(1)} KB`;
|
infoDtfSize.textContent = `${(bytes / 1024).toFixed(1)} KB`;
|
||||||
updateWarnings();
|
updateWarnings();
|
||||||
@@ -163,7 +163,7 @@ function applyImageData(width, height, data, filename, formatLabel, format) {
|
|||||||
state.filename = filename.replace(/\.[^/.]+$/, "");
|
state.filename = filename.replace(/\.[^/.]+$/, "");
|
||||||
|
|
||||||
// Sync format selector when loading an existing DTF
|
// Sync format selector when loading an existing DTF
|
||||||
if (format !== undefined) {
|
if(format !== undefined) {
|
||||||
state.format = format;
|
state.format = format;
|
||||||
formatSelect.value = format;
|
formatSelect.value = format;
|
||||||
redAsAlphaRow.hidden = format !== DTF.FORMAT_ALPHA;
|
redAsAlphaRow.hidden = format !== DTF.FORMAT_ALPHA;
|
||||||
@@ -189,8 +189,8 @@ function applyImageData(width, height, data, filename, formatLabel, format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleFile(file) {
|
function handleFile(file) {
|
||||||
if (!file) return;
|
if(!file) return;
|
||||||
if (file.name.toLowerCase().endsWith(".dtf")) {
|
if(file.name.toLowerCase().endsWith(".dtf")) {
|
||||||
loadDTF(file);
|
loadDTF(file);
|
||||||
} else {
|
} else {
|
||||||
loadStandardImage(file);
|
loadStandardImage(file);
|
||||||
@@ -204,7 +204,7 @@ function showError(msg) {
|
|||||||
// ─── Export ───────────────────────────────────────────────────────────────────
|
// ─── Export ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function exportDTF() {
|
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 buf = DTF.encode(state.width, state.height, state.pixels, state.format, state.redAsAlpha);
|
||||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
@@ -217,7 +217,7 @@ function exportDTF() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportPNG() {
|
function exportPNG() {
|
||||||
if (!state.pixels) return;
|
if(!state.pixels) return;
|
||||||
DuskPNG.download(`${state.filename}.png`, state.width, state.height, state.pixels);
|
DuskPNG.download(`${state.filename}.png`, state.width, state.height, state.pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ scaleInput.addEventListener("input", () => {
|
|||||||
|
|
||||||
bgSwatches.addEventListener("click", e => {
|
bgSwatches.addEventListener("click", e => {
|
||||||
const btn = e.target.closest(".bg-swatch");
|
const btn = e.target.closest(".bg-swatch");
|
||||||
if (!btn) return;
|
if(!btn) return;
|
||||||
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
||||||
btn.classList.add("active");
|
btn.classList.add("active");
|
||||||
state.bg = btn.dataset.bg;
|
state.bg = btn.dataset.bg;
|
||||||
|
|||||||
Reference in New Issue
Block a user