diff --git a/src/dusk/asset/assetfile.c b/src/dusk/asset/assetfile.c index 3f9a9ec8..8ae31c5e 100644 --- a/src/dusk/asset/assetfile.c +++ b/src/dusk/asset/assetfile.c @@ -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), diff --git a/src/dusk/display/display.c b/src/dusk/display/display.c index e66386cb..0cab3c60 100644 --- a/src/dusk/display/display.c +++ b/src/dusk/display/display.c @@ -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()); diff --git a/src/dusk/display/mesh/CMakeLists.txt b/src/dusk/display/mesh/CMakeLists.txt index 872055e9..b641eea9 100644 --- a/src/dusk/display/mesh/CMakeLists.txt +++ b/src/dusk/display/mesh/CMakeLists.txt @@ -9,4 +9,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} mesh.c quad.c cube.c + sphere.c + plane.c + capsule.c + triprism.c ) \ No newline at end of file diff --git a/src/dusk/display/mesh/capsule.c b/src/dusk/display/mesh/capsule.c new file mode 100644 index 00000000..b6587f32 --- /dev/null +++ b/src/dusk/display/mesh/capsule.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 +} diff --git a/src/dusk/display/mesh/capsule.h b/src/dusk/display/mesh/capsule.h new file mode 100644 index 00000000..e078c60a --- /dev/null +++ b/src/dusk/display/mesh/capsule.h @@ -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 +); diff --git a/src/dusk/display/mesh/cube.c b/src/dusk/display/mesh/cube.c index 14c5e2c1..c2e7efe9 100644 --- a/src/dusk/display/mesh/cube.c +++ b/src/dusk/display/mesh/cube.c @@ -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"); diff --git a/src/dusk/display/mesh/cube.h b/src/dusk/display/mesh/cube.h index 28c85c85..6aa4f015 100644 --- a/src/dusk/display/mesh/cube.h +++ b/src/dusk/display/mesh/cube.h @@ -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 ); diff --git a/src/dusk/display/mesh/plane.c b/src/dusk/display/mesh/plane.c new file mode 100644 index 00000000..153ed065 --- /dev/null +++ b/src/dusk/display/mesh/plane.c @@ -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 +} diff --git a/src/dusk/display/mesh/plane.h b/src/dusk/display/mesh/plane.h new file mode 100644 index 00000000..22fac881 --- /dev/null +++ b/src/dusk/display/mesh/plane.h @@ -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 +); diff --git a/src/dusk/display/mesh/quad.c b/src/dusk/display/mesh/quad.c index dc3fa265..5c759a5f 100644 --- a/src/dusk/display/mesh/quad.c +++ b/src/dusk/display/mesh/quad.c @@ -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"); diff --git a/src/dusk/display/mesh/quad.h b/src/dusk/display/mesh/quad.h index cf60d638..5688a7a8 100644 --- a/src/dusk/display/mesh/quad.h +++ b/src/dusk/display/mesh/quad.h @@ -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 ); \ No newline at end of file diff --git a/src/dusk/display/mesh/sphere.c b/src/dusk/display/mesh/sphere.c new file mode 100644 index 00000000..35278fda --- /dev/null +++ b/src/dusk/display/mesh/sphere.c @@ -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++; + } + } +} diff --git a/src/dusk/display/mesh/sphere.h b/src/dusk/display/mesh/sphere.h new file mode 100644 index 00000000..88b4e5cb --- /dev/null +++ b/src/dusk/display/mesh/sphere.h @@ -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 +); diff --git a/src/dusk/display/mesh/triprism.c b/src/dusk/display/mesh/triprism.c new file mode 100644 index 00000000..9a0ded21 --- /dev/null +++ b/src/dusk/display/mesh/triprism.c @@ -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 +} diff --git a/src/dusk/display/mesh/triprism.h b/src/dusk/display/mesh/triprism.h new file mode 100644 index 00000000..1230be32 --- /dev/null +++ b/src/dusk/display/mesh/triprism.h @@ -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 +); diff --git a/src/dusk/display/screen/screen.c b/src/dusk/display/screen/screen.c index 2acab6b6..3c45fe9b 100644 --- a/src/dusk/display/screen/screen.c +++ b/src/dusk/display/screen/screen.c @@ -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( diff --git a/src/dusk/display/spritebatch/spritebatch.c b/src/dusk/display/spritebatch/spritebatch.c index 2e62bacb..1bff51f6 100644 --- a/src/dusk/display/spritebatch/spritebatch.c +++ b/src/dusk/display/spritebatch/spritebatch.c @@ -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(); diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 94324b45..50f58f3c 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -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; diff --git a/src/dusk/entity/component/display/entityposition.c b/src/dusk/entity/component/display/entityposition.c index 20dd72f2..13b698d3 100644 --- a/src/dusk/entity/component/display/entityposition.c +++ b/src/dusk/entity/component/display/entityposition.c @@ -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 { diff --git a/tools/editor/common/dtf.js b/tools/editor/common/dtf.js index ecd1144a..3017b819 100644 --- a/tools/editor/common/dtf.js +++ b/tools/editor/common/dtf.js @@ -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: { diff --git a/tools/editor/common/png.js b/tools/editor/common/png.js index 54d6100f..fcaecce0 100644 --- a/tools/editor/common/png.js +++ b/tools/editor/common/png.js @@ -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); diff --git a/tools/editor/texture-padder/texture-padder.js b/tools/editor/texture-padder/texture-padder.js index 193da80d..b8ed1aa4 100644 --- a/tools/editor/texture-padder/texture-padder.js +++ b/tools/editor/texture-padder/texture-padder.js @@ -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; diff --git a/tools/editor/texture/texture.js b/tools/editor/texture/texture.js index 34dc7ee2..84776685 100644 --- a/tools/editor/texture/texture.js +++ b/tools/editor/texture/texture.js @@ -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;