prog
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
display.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(framebuffer)
|
||||
add_subdirectory(mesh)
|
||||
add_subdirectory(screen)
|
||||
add_subdirectory(shader)
|
||||
add_subdirectory(spritebatch)
|
||||
add_subdirectory(text)
|
||||
add_subdirectory(texture)
|
||||
|
||||
# Color definitions
|
||||
dusk_run_python(
|
||||
dusk_color_defs
|
||||
tools.color.csv
|
||||
--csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv
|
||||
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h
|
||||
)
|
||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_color_defs)
|
||||
@@ -0,0 +1,23 @@
|
||||
name,r,g,b,a
|
||||
black,0,0,0,1
|
||||
white,1,1,1,1
|
||||
red,1,0,0,1
|
||||
green,0,1,0,1
|
||||
blue,0,0,1,1
|
||||
yellow,1,1,0,1
|
||||
cyan,0,1,1,1
|
||||
magenta,1,0,1,1
|
||||
transparent,0,0,0,0
|
||||
transparent_white,1,1,1,0
|
||||
transparent_black,0,0,0,0
|
||||
gray,0.5,0.5,0.5,1
|
||||
light_gray,0.75,0.75,0.75,1
|
||||
dark_gray,0.25,0.25,0.25,1
|
||||
orange,1,0.65,0,1
|
||||
purple,0.5,0,0.5,1
|
||||
brown,0.6,0.4,0.2,1
|
||||
pink,1,0.75,0.8,1
|
||||
lime,0.75,1,0,1
|
||||
navy,0,0,0.5,1
|
||||
teal,0,0.5,0.5,1
|
||||
cornflower_blue,0.39,0.58,0.93,1
|
||||
|
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "display/display.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
#include "scene/scene.h"
|
||||
#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"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "asset/asset.h"
|
||||
#include "display/shader/shaderlist.h"
|
||||
#include "time/time.h"
|
||||
|
||||
display_t DISPLAY = { 0 };
|
||||
|
||||
errorret_t displayInit(void) {
|
||||
memoryZero(&DISPLAY, sizeof(DISPLAY));
|
||||
|
||||
#ifdef displayPlatformInit
|
||||
errorChain(displayPlatformInit());
|
||||
#endif
|
||||
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
|
||||
errorChain(textureInit(
|
||||
&TEXTURE_WHITE, 4, 4,
|
||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
|
||||
));
|
||||
errorChain(textureInit(
|
||||
&TEXTURE_TEST, 4, 4,
|
||||
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
|
||||
));
|
||||
|
||||
// Standard meshes
|
||||
errorChain(quadInit());
|
||||
errorChain(cubeInit());
|
||||
errorChain(sphereInit());
|
||||
errorChain(planeInit());
|
||||
errorChain(capsuleInit());
|
||||
errorChain(triPrismInit());
|
||||
|
||||
errorChain(frameBufferInitBackBuffer());
|
||||
errorChain(spriteBatchInit());
|
||||
errorChain(textInit());
|
||||
errorChain(screenInit());
|
||||
|
||||
// Setup initial shader with default values
|
||||
|
||||
errorChain(shaderListInit());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displayUpdate(void) {
|
||||
#ifdef displayPlatformUpdate
|
||||
errorChain(displayPlatformUpdate());
|
||||
#endif
|
||||
|
||||
// Reset state
|
||||
spriteBatchClear();
|
||||
errorChain(frameBufferBind(NULL));
|
||||
|
||||
// Bind screen and render scene
|
||||
errorChain(screenBind());
|
||||
frameBufferClear(
|
||||
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
|
||||
SCREEN.background
|
||||
);
|
||||
|
||||
errorChain(sceneRender());
|
||||
|
||||
// Finish up
|
||||
screenUnbind();
|
||||
screenRender();
|
||||
|
||||
// Swap and return.
|
||||
#ifdef displayPlatformSwap
|
||||
errorChain(displayPlatformSwap());
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displaySetState(displaystate_t state) {
|
||||
#ifdef displayPlatformSetState
|
||||
errorChain(displayPlatformSetState(state));
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t displayDispose(void) {
|
||||
errorChain(shaderListDispose());
|
||||
errorChain(spriteBatchDispose());
|
||||
screenDispose();
|
||||
errorChain(textDispose());
|
||||
errorChain(textureDispose(&TEXTURE_WHITE));
|
||||
errorChain(textureDispose(&TEXTURE_TEST));
|
||||
|
||||
#ifdef displayPlatformDispose
|
||||
displayPlatformDispose();
|
||||
#endif
|
||||
|
||||
// For now, we just return an OK error.
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/displayplatform.h"
|
||||
|
||||
// Expecting some definitions to be provided
|
||||
#ifndef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
#ifndef DUSK_DISPLAY_WIDTH
|
||||
#error "DUSK_DISPLAY_WIDTH must be defined."
|
||||
#endif
|
||||
#ifndef DUSK_DISPLAY_HEIGHT
|
||||
#error "DUSK_DISPLAY_HEIGHT must be defined"
|
||||
#endif
|
||||
#define DUSK_DISPLAY_WIDTH_DEFAULT DUSK_DISPLAY_WIDTH
|
||||
#define DUSK_DISPLAY_HEIGHT_DEFAULT DUSK_DISPLAY_HEIGHT
|
||||
#else
|
||||
#ifndef DUSK_DISPLAY_WIDTH_DEFAULT
|
||||
#error "DUSK_DISPLAY_WIDTH_DEFAULT must be defined."
|
||||
#endif
|
||||
#ifndef DUSK_DISPLAY_HEIGHT_DEFAULT
|
||||
#error "DUSK_DISPLAY_HEIGHT_DEFAULT must be defined."
|
||||
#endif
|
||||
#ifdef DUSK_DISPLAY_WIDTH
|
||||
#error "DUSK_DISPLAY_WIDTH should not be defined."
|
||||
#endif
|
||||
#ifdef DUSK_DISPLAY_HEIGHT
|
||||
#error "DUSK_DISPLAY_HEIGHT should not be defined."
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Main Display Struct, platform-speicifc
|
||||
typedef displayplatform_t display_t;
|
||||
|
||||
extern display_t DISPLAY;
|
||||
|
||||
/**
|
||||
* Initializes the display system.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
errorret_t displayInit(void);
|
||||
|
||||
/**
|
||||
* Tells the display system to actually draw the frame.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
errorret_t displayUpdate(void);
|
||||
|
||||
/**
|
||||
* Sets the display state.
|
||||
*
|
||||
* @param state The state to set.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
errorret_t displaySetState(displaystate_t state);
|
||||
|
||||
/**
|
||||
* Disposes of the display system.
|
||||
* @return An errorret_t indicating success or failure.
|
||||
*/
|
||||
errorret_t displayDispose(void);
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define DISPLAY_STATE_FLAG_CULL (1 << 0)
|
||||
#define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1)
|
||||
#define DISPLAY_STATE_FLAG_BLEND (1 << 2)
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
} displaystate_t;
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
framebuffer.c
|
||||
)
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "display/display.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
framebuffer_t FRAMEBUFFER_BACKBUFFER = {0};
|
||||
const framebuffer_t *FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER;
|
||||
|
||||
errorret_t frameBufferInitBackBuffer() {
|
||||
memoryZero(&FRAMEBUFFER_BACKBUFFER, sizeof(framebuffer_t));
|
||||
errorChain(frameBufferPlatformInitBackBuffer());
|
||||
FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t frameBufferBind(framebuffer_t *framebuffer) {
|
||||
if(framebuffer == NULL) {
|
||||
frameBufferBind(&FRAMEBUFFER_BACKBUFFER);
|
||||
FRAMEBUFFER_BOUND = &FRAMEBUFFER_BACKBUFFER;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorChain(frameBufferPlatformBind(framebuffer));
|
||||
FRAMEBUFFER_BOUND = framebuffer;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
uint32_t frameBufferGetWidth(const framebuffer_t *framebuffer) {
|
||||
return frameBufferPlatformGetWidth(framebuffer);
|
||||
}
|
||||
|
||||
uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer) {
|
||||
return frameBufferPlatformGetHeight(framebuffer);
|
||||
}
|
||||
|
||||
float_t frameBufferGetAspect(const framebuffer_t *framebuffer) {
|
||||
#ifdef frameBufferPlatformGetAspect
|
||||
return frameBufferPlatformGetAspect(framebuffer);
|
||||
#endif
|
||||
|
||||
uint32_t width = frameBufferGetWidth(framebuffer);
|
||||
uint32_t height = frameBufferGetHeight(framebuffer);
|
||||
if(height == 0) return 1.0f; // Avoid divide by zero, just return 1:1 aspect.
|
||||
return (float_t)width / (float_t)height;
|
||||
}
|
||||
|
||||
void frameBufferClear(const uint8_t flags, const color_t color) {
|
||||
frameBufferPlatformClear(flags, color);
|
||||
}
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
errorret_t frameBufferInit(
|
||||
framebuffer_t *fb,
|
||||
const uint32_t width,
|
||||
const uint32_t height
|
||||
) {
|
||||
assertNotNull(fb, "Framebuffer cannot be NULL");
|
||||
assertTrue(width > 0 && height > 0, "W/H must be greater than 0");
|
||||
|
||||
memoryZero(fb, sizeof(framebuffer_t));
|
||||
errorChain(frameBufferPlatformInit(fb, width, height));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t frameBufferDispose(framebuffer_t *framebuffer) {
|
||||
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
|
||||
errorChain(frameBufferPlatformDispose(framebuffer));
|
||||
errorOk();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/framebuffer/framebufferplatform.h"
|
||||
#include "display/texture/texture.h"
|
||||
|
||||
// Expected defs.
|
||||
#ifndef frameBufferPlatformInitBackBuffer
|
||||
#error "frameBufferPlatformInitBackBuffer not defined for this platform"
|
||||
#endif
|
||||
#ifndef frameBufferPlatformBind
|
||||
#error "frameBufferPlatformBind not defined for this platform"
|
||||
#endif
|
||||
#ifndef frameBufferPlatformGetWidth
|
||||
#error "frameBufferPlatformGetWidth not defined for this platform"
|
||||
#endif
|
||||
#ifndef frameBufferPlatformGetHeight
|
||||
#error "frameBufferPlatformGetHeight not defined for this platform"
|
||||
#endif
|
||||
#ifndef frameBufferPlatformClear
|
||||
#error "frameBufferPlatformClear not defined for this platform"
|
||||
#endif
|
||||
|
||||
#define FRAMEBUFFER_CLEAR_COLOR (1 << 0)
|
||||
#define FRAMEBUFFER_CLEAR_DEPTH (1 << 1)
|
||||
|
||||
typedef framebufferplatform_t framebuffer_t;
|
||||
|
||||
extern framebuffer_t FRAMEBUFFER_BACKBUFFER;
|
||||
extern const framebuffer_t *FRAMEBUFFER_BOUND;
|
||||
|
||||
/**
|
||||
* Initializes the backbuffer framebuffer.
|
||||
*
|
||||
* @return Error for initialization of the backbuffer.
|
||||
*/
|
||||
errorret_t frameBufferInitBackBuffer(void);
|
||||
|
||||
/**
|
||||
* Gets the width of the framebuffer.
|
||||
*
|
||||
* @param framebuffer The framebuffer to get the width of.
|
||||
* @return The width of the framebuffer, or 0 if the framebuffer is NULL.
|
||||
*/
|
||||
uint32_t frameBufferGetWidth(const framebuffer_t *framebuffer);
|
||||
|
||||
/**
|
||||
* Gets the height of the framebuffer.
|
||||
*
|
||||
* @param framebuffer The framebuffer to get the height of.
|
||||
* @return The height of the framebuffer, or 0 if the framebuffer is NULL.
|
||||
*/
|
||||
uint32_t frameBufferGetHeight(const framebuffer_t *framebuffer);
|
||||
|
||||
/**
|
||||
* Returns the aspect ratio of the framebuffer. This is ALMOST always just
|
||||
* the width / height, however some platforms may choose to override this if
|
||||
* they have stretched styled back buffers, e.g. 640x480 stretched.
|
||||
*
|
||||
* @param framebuffer The framebuffer to get the aspect ratio of.
|
||||
* @return The aspect ratio of the framebuffer.
|
||||
*/
|
||||
float_t frameBufferGetAspect(const framebuffer_t *framebuffer);
|
||||
|
||||
/**
|
||||
* Binds the framebuffer for rendering, or the backbuffer if the framebuffer
|
||||
* provided is NULL.
|
||||
*
|
||||
* @param framebuffer The framebuffer to bind, or NULL to bind the backbuffer.
|
||||
* @return Error for binding the framebuffer.
|
||||
*/
|
||||
errorret_t frameBufferBind(framebuffer_t *framebuffer);
|
||||
|
||||
/**
|
||||
* Clears the currently bound framebuffer.
|
||||
*
|
||||
* @param flags The clear flags.
|
||||
* @param color The color to clear the color buffer to (if clearing color).
|
||||
*/
|
||||
void frameBufferClear(uint8_t flags, color_t color);
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
#ifndef frameBufferPlatformInit
|
||||
#error "frameBufferPlatformInit not defined for this platform"
|
||||
#endif
|
||||
|
||||
#ifndef frameBufferPlatformDispose
|
||||
#error "frameBufferPlatformDispose not defined for this platform"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes a framebuffer.
|
||||
*
|
||||
* @param framebuffer The framebuffer to initialize.
|
||||
* @param width The width of the framebuffer.
|
||||
* @param height The height of the framebuffer.
|
||||
* @return Error for initialization of the framebuffer.
|
||||
*/
|
||||
errorret_t frameBufferInit(
|
||||
framebuffer_t *framebuffer,
|
||||
const uint32_t width,
|
||||
const uint32_t height
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes of the framebuffer. Will also be used for request disposing of the
|
||||
* backbuffer.
|
||||
*
|
||||
* @param framebuffer The framebuffer to dispose of.
|
||||
*/
|
||||
errorret_t frameBufferDispose(framebuffer_t *framebuffer);
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
mesh.c
|
||||
quad.c
|
||||
cube.c
|
||||
sphere.c
|
||||
plane.c
|
||||
capsule.c
|
||||
triprism.c
|
||||
)
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cube.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t CUBE_MESH_SIMPLE;
|
||||
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
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&CUBE_MESH_SIMPLE,
|
||||
CUBE_PRIMITIVE_TYPE,
|
||||
CUBE_VERTEX_COUNT,
|
||||
CUBE_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Helper macro: set one vertex position, UV and color.
|
||||
#if MESH_ENABLE_COLOR
|
||||
#define CUBE_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 CUBE_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 cubeBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
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");
|
||||
assertNotNull(max, "Max vector cannot be NULL");
|
||||
|
||||
const float_t x0 = min[0], y0 = min[1], z0 = min[2];
|
||||
const float_t x1 = max[0], y1 = max[1], z1 = max[2];
|
||||
|
||||
// Front face (+Z normal): CCW when viewed from +Z
|
||||
CUBE_VERT( 0, x0, y0, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT( 1, x1, y0, z1, 1.0f, 0.0f);
|
||||
CUBE_VERT( 2, x1, y1, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT( 3, x0, y0, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT( 4, x1, y1, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT( 5, x0, y1, z1, 0.0f, 1.0f);
|
||||
|
||||
// Back face (-Z normal): CCW when viewed from -Z
|
||||
CUBE_VERT( 6, x1, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT( 7, x0, y0, z0, 1.0f, 0.0f);
|
||||
CUBE_VERT( 8, x0, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT( 9, x1, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(10, x0, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(11, x1, y1, z0, 0.0f, 1.0f);
|
||||
|
||||
// Right face (+X normal): CCW when viewed from +X
|
||||
CUBE_VERT(12, x1, y0, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT(13, x1, y0, z0, 1.0f, 0.0f);
|
||||
CUBE_VERT(14, x1, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(15, x1, y0, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT(16, x1, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(17, x1, y1, z1, 0.0f, 1.0f);
|
||||
|
||||
// Left face (-X normal): CCW when viewed from -X
|
||||
CUBE_VERT(18, x0, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(19, x0, y0, z1, 1.0f, 0.0f);
|
||||
CUBE_VERT(20, x0, y1, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT(21, x0, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(22, x0, y1, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT(23, x0, y1, z0, 0.0f, 1.0f);
|
||||
|
||||
// Top face (+Y normal): CCW when viewed from +Y
|
||||
CUBE_VERT(24, x0, y1, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT(25, x1, y1, z1, 1.0f, 0.0f);
|
||||
CUBE_VERT(26, x1, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(27, x0, y1, z1, 0.0f, 0.0f);
|
||||
CUBE_VERT(28, x1, y1, z0, 1.0f, 1.0f);
|
||||
CUBE_VERT(29, x0, y1, z0, 0.0f, 1.0f);
|
||||
|
||||
// Bottom face (-Y normal): CCW when viewed from -Y
|
||||
CUBE_VERT(30, x0, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(31, x1, y0, z0, 1.0f, 0.0f);
|
||||
CUBE_VERT(32, x1, y0, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT(33, x0, y0, z0, 0.0f, 0.0f);
|
||||
CUBE_VERT(34, x1, y0, z1, 1.0f, 1.0f);
|
||||
CUBE_VERT(35, x0, y0, z1, 0.0f, 1.0f);
|
||||
|
||||
#undef CUBE_VERT
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 CUBE_FACE_COUNT 6
|
||||
#define CUBE_VERTICES_PER_FACE 6
|
||||
#define CUBE_VERTEX_COUNT (CUBE_FACE_COUNT * CUBE_VERTICES_PER_FACE)
|
||||
#define CUBE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
extern mesh_t CUBE_MESH_SIMPLE;
|
||||
extern meshvertex_t CUBE_MESH_SIMPLE_VERTICES[CUBE_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the simple unit cube mesh (0,0,0) to (1,1,1).
|
||||
*
|
||||
* @return Error for initialization of the cube mesh.
|
||||
*/
|
||||
errorret_t cubeInit();
|
||||
|
||||
/**
|
||||
* Buffers a 3D axis-aligned cube into the provided vertex array.
|
||||
* Writes CUBE_VERTEX_COUNT vertices (6 faces x 6 vertices, CCW winding).
|
||||
*
|
||||
* @param vertices The vertex array to buffer into.
|
||||
* @param min The minimum XYZ corner of the cube.
|
||||
* @param max The maximum XYZ corner of the cube.
|
||||
* @param color The color applied to all vertices.
|
||||
*/
|
||||
void cubeBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "mesh.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/math.h"
|
||||
|
||||
errorret_t meshInit(
|
||||
mesh_t *mesh,
|
||||
const meshprimitivetype_t primitiveType,
|
||||
const int32_t vertexCount,
|
||||
const meshvertex_t *vertices
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertTrue(vertexCount > 0, "Vertex count must be greater than 0");
|
||||
|
||||
memoryZero(mesh, sizeof(mesh_t));
|
||||
|
||||
errorChain(meshInitPlatform(mesh, primitiveType, vertexCount, vertices));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t meshFlush(
|
||||
mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
) {
|
||||
#ifdef meshFlushPlatform
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative.");
|
||||
assertTrue(
|
||||
vertexCount == -1 || vertexCount > 0, "Vertex count incorrect."
|
||||
);
|
||||
|
||||
int32_t vertCount = meshGetVertexCount(mesh);
|
||||
assertTrue(
|
||||
vertexOffset < (vertCount - 1), "Need at least one vert to draw"
|
||||
);
|
||||
|
||||
int32_t drawCount = vertexCount;
|
||||
if(vertexCount == -1) {
|
||||
drawCount = vertCount - vertexOffset;
|
||||
}
|
||||
|
||||
errorChain(meshFlushPlatform(mesh, vertexOffset, vertexCount));
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t meshDraw(
|
||||
const mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
assertTrue(vertexOffset >= 0, "Vertex offset must be non-negative");
|
||||
assertTrue(vertexCount == -1 || vertexCount > 0, "Incorrect vert count");
|
||||
|
||||
int32_t vertDrawCount = vertexCount;
|
||||
if(vertexCount == -1) {
|
||||
const int32_t totalVertices = meshGetVertexCount(mesh);
|
||||
vertDrawCount = totalVertices - vertexOffset;
|
||||
}
|
||||
|
||||
if(vertDrawCount == 0) {
|
||||
errorOk();
|
||||
}
|
||||
assertTrue(
|
||||
vertexOffset + vertDrawCount <= meshGetVertexCount(mesh),
|
||||
"Vertex offset and count must be within vertex count bounds"
|
||||
);
|
||||
|
||||
errorChain(meshDrawPlatform(mesh, vertexOffset, vertDrawCount));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void meshGetBounds(
|
||||
const mesh_t *mesh,
|
||||
vec3 outMin,
|
||||
vec3 outMax
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
assertNotNull(outMin, "Output min cannot be NULL");
|
||||
assertNotNull(outMax, "Output max cannot be NULL");
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
outMin[i] = FLT_MAX;
|
||||
outMax[i] = -FLT_MAX;
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < mesh->vertexCount; i++) {
|
||||
meshvertex_t vert = mesh->vertices[i];
|
||||
for(int j = 0; j < 3; j++) {
|
||||
outMin[j] = mathMin(outMin[j], vert.pos[j]);
|
||||
outMax[j] = mathMax(outMax[j], vert.pos[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t meshGetVertexCount(const mesh_t *mesh) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
return meshGetVertexCountPlatform(mesh);
|
||||
}
|
||||
|
||||
errorret_t meshDispose(mesh_t *mesh) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
errorChain(meshDisposePlatform(mesh));
|
||||
memoryZero(mesh, sizeof(mesh_t));
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "display/display.h"
|
||||
#include "display/color.h"
|
||||
#include "display/mesh/meshplatform.h"
|
||||
|
||||
#ifndef meshInitPlatform
|
||||
#error "meshInitPlatform must be defined"
|
||||
#endif
|
||||
#ifndef meshDrawPlatform
|
||||
#error "meshDrawPlatform must be defined"
|
||||
#endif
|
||||
#ifndef meshGetVertexCountPlatform
|
||||
#error "meshGetVertexCountPlatform must be defined"
|
||||
#endif
|
||||
#ifndef meshDisposePlatform
|
||||
#error "meshDisposePlatform must be defined"
|
||||
#endif
|
||||
|
||||
typedef meshprimitivetypeplatform_t meshprimitivetype_t;
|
||||
typedef meshplatform_t mesh_t;
|
||||
|
||||
/**
|
||||
* Initializes a mesh.
|
||||
*
|
||||
* @param mesh The mesh to initialize.
|
||||
* @param primitiveType The OpenGL primitive type (e.g., GL_TRIANGLES).
|
||||
* @param vertexCount The number of vertices in the mesh.
|
||||
* @param vertices The vertex data for the mesh.
|
||||
* @return An error indicating success or failure.
|
||||
*/
|
||||
errorret_t meshInit(
|
||||
mesh_t *mesh,
|
||||
const meshprimitivetype_t primitiveType,
|
||||
const int32_t vertexCount,
|
||||
const meshvertex_t *vertices
|
||||
);
|
||||
|
||||
/**
|
||||
* Instructs the mesh to flush the vertices to the GPU. This is surprisingly
|
||||
* only really necessary on modern devices, as we tend to let older devices
|
||||
* read the vertices from the main memory directly.
|
||||
*
|
||||
* @param mesh Mesh to flush the vertices for.
|
||||
* @param vertexOffset Start vertex to flush.
|
||||
* @param vertexCount Count of vertices to flush, set to -1 for all.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t meshFlush(
|
||||
mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws a mesh.
|
||||
*
|
||||
* @param mesh The mesh to draw.
|
||||
* @param vertexOffset The offset in the vertex array to start drawing from.
|
||||
* @param vertexCount The number of vertices to draw. If -1, draws all vertices.
|
||||
* @return An error indicating success or failure.
|
||||
*/
|
||||
errorret_t meshDraw(
|
||||
const mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the axis-aligned bounding box of a mesh.
|
||||
*
|
||||
* @param mesh The mesh to get the bounds of.
|
||||
* @param outMin Output parameter for the minimum corner of the bounding box.
|
||||
* @param outMax Output parameter for the maximum corner of the bounding box.
|
||||
*/
|
||||
void meshGetBounds(
|
||||
const mesh_t *mesh,
|
||||
vec3 outMin,
|
||||
vec3 outMax
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the vertex count of a mesh.
|
||||
*
|
||||
* @param mesh The mesh to get the vertex count from.
|
||||
* @return The vertex count of the mesh.
|
||||
*/
|
||||
int32_t meshGetVertexCount(const mesh_t *mesh);
|
||||
|
||||
/**
|
||||
* Disposes a mesh.
|
||||
*
|
||||
* @param mesh The mesh to dispose.
|
||||
* @return An error indicating success or failure.
|
||||
*/
|
||||
errorret_t meshDispose(mesh_t *mesh);
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#ifndef MESH_ENABLE_COLOR
|
||||
#define MESH_ENABLE_COLOR 0
|
||||
#endif
|
||||
|
||||
#define MESH_VERTEX_UV_SIZE 2
|
||||
#define MESH_VERTEX_POS_SIZE 3
|
||||
|
||||
typedef struct {
|
||||
#if MESH_ENABLE_COLOR
|
||||
color_t color;
|
||||
#endif
|
||||
|
||||
float_t uv[MESH_VERTEX_UV_SIZE];
|
||||
float_t pos[MESH_VERTEX_POS_SIZE];
|
||||
} meshvertex_t;
|
||||
@@ -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
|
||||
);
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "quad.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t QUAD_MESH_SIMPLE;
|
||||
meshvertex_t QUAD_MESH_SIMPLE_VERTICES[QUAD_VERTEX_COUNT] = {
|
||||
{
|
||||
#if MESH_ENABLE_COLOR
|
||||
.color = COLOR_WHITE_4B,
|
||||
#endif
|
||||
|
||||
.uv = { 0.0f, 0.0f },
|
||||
.pos = { 0.0f, 0.0f, 0.0f }
|
||||
},
|
||||
|
||||
{
|
||||
#if MESH_ENABLE_COLOR
|
||||
.color = COLOR_WHITE_4B,
|
||||
#endif
|
||||
.uv = { 1.0f, 0.0f },
|
||||
.pos = { 1.0f, 0.0f, 0.0f }
|
||||
},
|
||||
|
||||
{
|
||||
#if MESH_ENABLE_COLOR
|
||||
.color = COLOR_WHITE_4B,
|
||||
#endif
|
||||
.uv = { 1.0f, 1.0f },
|
||||
.pos = { 1.0f, 1.0f, 0.0f }
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
#if MESH_ENABLE_COLOR
|
||||
.color = COLOR_WHITE_4B,
|
||||
#endif
|
||||
.uv = { 0.0f, 0.0f },
|
||||
.pos = { 0.0f, 0.0f, 0.0f }
|
||||
},
|
||||
|
||||
{
|
||||
#if MESH_ENABLE_COLOR
|
||||
.color = COLOR_WHITE_4B,
|
||||
#endif
|
||||
.uv = { 1.0f, 1.0f },
|
||||
.pos = { 1.0f, 1.0f, 0.0f }
|
||||
},
|
||||
|
||||
{
|
||||
#if MESH_ENABLE_COLOR
|
||||
.color = COLOR_WHITE_4B,
|
||||
#endif
|
||||
.uv = { 0.0f, 1.0f },
|
||||
.pos = { 0.0f, 1.0f, 0.0f }
|
||||
}
|
||||
};
|
||||
|
||||
errorret_t quadInit() {
|
||||
errorChain(meshInit(
|
||||
&QUAD_MESH_SIMPLE,
|
||||
QUAD_PRIMITIVE_TYPE,
|
||||
QUAD_VERTEX_COUNT,
|
||||
QUAD_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void quadBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t minX,
|
||||
const float_t minY,
|
||||
const float_t maxX,
|
||||
const float_t maxY,
|
||||
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");
|
||||
|
||||
// First triangle
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[0].color = color;
|
||||
#endif
|
||||
vertices[0].uv[0] = u0;
|
||||
vertices[0].uv[1] = v1;
|
||||
vertices[0].pos[0] = minX;
|
||||
vertices[0].pos[1] = maxY;
|
||||
vertices[0].pos[2] = z;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[1].color = color;
|
||||
#endif
|
||||
vertices[1].uv[0] = u1;
|
||||
vertices[1].uv[1] = v0;
|
||||
vertices[1].pos[0] = maxX;
|
||||
vertices[1].pos[1] = minY;
|
||||
vertices[1].pos[2] = z;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[2].color = color;
|
||||
#endif
|
||||
vertices[2].uv[0] = u0;
|
||||
vertices[2].uv[1] = v0;
|
||||
vertices[2].pos[0] = minX;
|
||||
vertices[2].pos[1] = minY;
|
||||
vertices[2].pos[2] = z;
|
||||
|
||||
// Second triangle
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[3].color = color;
|
||||
#endif
|
||||
vertices[3].uv[0] = u0;
|
||||
vertices[3].uv[1] = v1;
|
||||
vertices[3].pos[0] = minX;
|
||||
vertices[3].pos[1] = maxY;
|
||||
vertices[3].pos[2] = z;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[4].color = color;
|
||||
#endif
|
||||
vertices[4].uv[0] = u1;
|
||||
vertices[4].uv[1] = v1;
|
||||
vertices[4].pos[0] = maxX;
|
||||
vertices[4].pos[1] = maxY;
|
||||
vertices[4].pos[2] = z;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[5].color = color;
|
||||
#endif
|
||||
vertices[5].uv[0] = u1;
|
||||
vertices[5].uv[1] = v0;
|
||||
vertices[5].pos[0] = maxX;
|
||||
vertices[5].pos[1] = minY;
|
||||
vertices[5].pos[2] = z;
|
||||
}
|
||||
|
||||
void quadBuffer3D(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max,
|
||||
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");
|
||||
assertNotNull(max, "Max vector cannot be NULL");
|
||||
assertNotNull(uvMin, "UV Min vector cannot be NULL");
|
||||
assertNotNull(uvMax, "UV Max vector cannot be NULL");
|
||||
|
||||
// First triangle
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[0].color = color;
|
||||
#endif
|
||||
vertices[0].uv[0] = uvMin[0];
|
||||
vertices[0].uv[1] = uvMin[1];
|
||||
vertices[0].pos[0] = min[0];
|
||||
vertices[0].pos[1] = min[1];
|
||||
vertices[0].pos[2] = min[2];
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[1].color = color;
|
||||
#endif
|
||||
vertices[1].uv[0] = uvMax[0];
|
||||
vertices[1].uv[1] = uvMin[1];
|
||||
vertices[1].pos[0] = max[0];
|
||||
vertices[1].pos[1] = min[1];
|
||||
vertices[1].pos[2] = min[2];
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[2].color = color;
|
||||
#endif
|
||||
vertices[2].uv[0] = uvMax[0];
|
||||
vertices[2].uv[1] = uvMax[1];
|
||||
vertices[2].pos[0] = max[0];
|
||||
vertices[2].pos[1] = max[1];
|
||||
vertices[2].pos[2] = min[2];
|
||||
|
||||
// Second triangle
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[3].color = color;
|
||||
#endif
|
||||
vertices[3].uv[0] = uvMin[0];
|
||||
vertices[3].uv[1] = uvMin[1];
|
||||
vertices[3].pos[0] = min[0];
|
||||
vertices[3].pos[1] = min[1];
|
||||
vertices[3].pos[2] = min[2];
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[4].color = color;
|
||||
#endif
|
||||
vertices[4].uv[0] = uvMax[0];
|
||||
vertices[4].uv[1] = uvMax[1];
|
||||
vertices[4].pos[0] = max[0];
|
||||
vertices[4].pos[1] = max[1];
|
||||
vertices[4].pos[2] = min[2];
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[5].color = color;
|
||||
#endif
|
||||
vertices[5].uv[0] = uvMin[0];
|
||||
vertices[5].uv[1] = uvMax[1];
|
||||
vertices[5].pos[0] = min[0];
|
||||
vertices[5].pos[1] = max[1];
|
||||
vertices[5].pos[2] = min[2];
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "mesh.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#define QUAD_VERTEX_COUNT 6
|
||||
#define QUAD_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
extern mesh_t QUAD_MESH_SIMPLE;
|
||||
extern meshvertex_t QUAD_MESH_SIMPLE_VERTICES[QUAD_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the quad mesh.
|
||||
*
|
||||
* @return Error for initialization of the quad mesh.
|
||||
*/
|
||||
errorret_t quadInit();
|
||||
|
||||
/**
|
||||
* Buffers a quad into the provided vertex array.
|
||||
*
|
||||
* @param vertices The vertex array to buffer into.
|
||||
* @param minX The minimum X coordinate of the quad.
|
||||
* @param minY The minimum Y coordinate of the quad.
|
||||
* @param maxX The maximum X coordinate of the quad.
|
||||
* @param maxY The maximum Y coordinate of the quad.
|
||||
* @param color The color of the quad.
|
||||
* @param u0 The U texture coordinate for the first vertex.
|
||||
* @param v0 The V texture coordinate for the first vertex.
|
||||
* @param u1 The U texture coordinate for the second vertex.
|
||||
* @param v1 The V texture coordinate for the second vertex.
|
||||
*/
|
||||
void quadBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t minX,
|
||||
const float_t minY,
|
||||
const float_t maxX,
|
||||
const float_t maxY,
|
||||
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
|
||||
);
|
||||
|
||||
/**
|
||||
* Buffers a 3D quad into the provided vertex array.
|
||||
*
|
||||
* @param vertices The vertex array to buffer into.
|
||||
* @param min The minimum XYZ coordinates of the quad.
|
||||
* @param max The maximum XYZ coordinates of the quad.
|
||||
* @param color The color of the quad.
|
||||
* @param uvMin The minimum UV coordinates of the quad.
|
||||
* @param uvMax The maximum UV coordinates of the quad.
|
||||
*/
|
||||
void quadBuffer3D(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 min,
|
||||
const vec3 max,
|
||||
const vec2 uvMin,
|
||||
const vec2 uvMax
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "sphere.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t SPHERE_MESH_SIMPLE;
|
||||
meshvertex_t SPHERE_MESH_SIMPLE_VERTICES[SPHERE_VERTEX_COUNT];
|
||||
|
||||
errorret_t sphereInit() {
|
||||
vec3 center = { 0.0f, 0.0f, 0.0f };
|
||||
sphereBuffer(
|
||||
SPHERE_MESH_SIMPLE_VERTICES,
|
||||
center,
|
||||
0.5f,
|
||||
SPHERE_STACKS,
|
||||
SPHERE_SECTORS
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&SPHERE_MESH_SIMPLE,
|
||||
SPHERE_PRIMITIVE_TYPE,
|
||||
SPHERE_VERTEX_COUNT,
|
||||
SPHERE_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void sphereBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 center,
|
||||
const float_t radius,
|
||||
const int32_t stacks,
|
||||
const int32_t sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertNotNull(center, "Center vector cannot be NULL");
|
||||
|
||||
const float_t stackStep = (float_t)GLM_PI / (float_t)stacks;
|
||||
const float_t sectorStep = 2.0f * (float_t)GLM_PI / (float_t)sectors;
|
||||
int32_t vi = 0;
|
||||
|
||||
for(int32_t i = 0; i < stacks; i++) {
|
||||
/* Latitude angles: top of band -> bottom of band */
|
||||
const float_t phi1 = (float_t)GLM_PI_2 - (float_t)i * stackStep;
|
||||
const float_t phi2 = (float_t)GLM_PI_2 - (float_t)(i + 1) * stackStep;
|
||||
|
||||
const float_t y1 = radius * sinf(phi1);
|
||||
const float_t y2 = radius * sinf(phi2);
|
||||
const float_t xz1 = radius * cosf(phi1);
|
||||
const float_t xz2 = radius * cosf(phi2);
|
||||
|
||||
const float_t v1 = 1.0f - (float_t)i / (float_t)stacks;
|
||||
const float_t v2 = 1.0f - (float_t)(i + 1) / (float_t)stacks;
|
||||
|
||||
for(int32_t j = 0; j < sectors; j++) {
|
||||
const float_t theta1 = (float_t)j * sectorStep;
|
||||
const float_t theta2 = (float_t)(j + 1) * sectorStep;
|
||||
|
||||
const float_t x11 = xz1 * cosf(theta1);
|
||||
const float_t z11 = xz1 * sinf(theta1);
|
||||
const float_t x12 = xz1 * cosf(theta2);
|
||||
const float_t z12 = xz1 * sinf(theta2);
|
||||
|
||||
const float_t x21 = xz2 * cosf(theta1);
|
||||
const float_t z21 = xz2 * sinf(theta1);
|
||||
const float_t x22 = xz2 * cosf(theta2);
|
||||
const float_t z22 = xz2 * sinf(theta2);
|
||||
|
||||
const float_t u1 = (float_t)j / (float_t)sectors;
|
||||
const float_t u2 = (float_t)(j + 1) / (float_t)sectors;
|
||||
|
||||
/* Triangle 1: top-left, bottom-left, top-right */
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x11;
|
||||
vertices[vi].pos[1] = center[1] + y1;
|
||||
vertices[vi].pos[2] = center[2] + z11;
|
||||
vertices[vi].uv[0] = u1;
|
||||
vertices[vi].uv[1] = v1;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x21;
|
||||
vertices[vi].pos[1] = center[1] + y2;
|
||||
vertices[vi].pos[2] = center[2] + z21;
|
||||
vertices[vi].uv[0] = u1;
|
||||
vertices[vi].uv[1] = v2;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x12;
|
||||
vertices[vi].pos[1] = center[1] + y1;
|
||||
vertices[vi].pos[2] = center[2] + z12;
|
||||
vertices[vi].uv[0] = u2;
|
||||
vertices[vi].uv[1] = v1;
|
||||
vi++;
|
||||
|
||||
/* Triangle 2: top-right, bottom-left, bottom-right */
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x12;
|
||||
vertices[vi].pos[1] = center[1] + y1;
|
||||
vertices[vi].pos[2] = center[2] + z12;
|
||||
vertices[vi].uv[0] = u2;
|
||||
vertices[vi].uv[1] = v1;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x21;
|
||||
vertices[vi].pos[1] = center[1] + y2;
|
||||
vertices[vi].pos[2] = center[2] + z21;
|
||||
vertices[vi].uv[0] = u1;
|
||||
vertices[vi].uv[1] = v2;
|
||||
vi++;
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
vertices[vi].color = color;
|
||||
#endif
|
||||
vertices[vi].pos[0] = center[0] + x22;
|
||||
vertices[vi].pos[1] = center[1] + y2;
|
||||
vertices[vi].pos[2] = center[2] + z22;
|
||||
vertices[vi].uv[0] = u2;
|
||||
vertices[vi].uv[1] = v2;
|
||||
vi++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#define SPHERE_STACKS 8
|
||||
#define SPHERE_SECTORS 16
|
||||
#define SPHERE_VERTEX_COUNT (SPHERE_STACKS * SPHERE_SECTORS * 6)
|
||||
#define SPHERE_PRIMITIVE_TYPE MESH_PRIMITIVE_TYPE_TRIANGLES
|
||||
|
||||
extern mesh_t SPHERE_MESH_SIMPLE;
|
||||
extern meshvertex_t SPHERE_MESH_SIMPLE_VERTICES[SPHERE_VERTEX_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the simple unit sphere mesh centered at (0,0,0) with radius 0.5.
|
||||
*
|
||||
* @return Error for initialization of the sphere mesh.
|
||||
*/
|
||||
errorret_t sphereInit();
|
||||
|
||||
/**
|
||||
* Buffers a UV sphere into the provided vertex array.
|
||||
* Writes stacks * sectors * 6 vertices (CCW winding).
|
||||
*
|
||||
* @param vertices Vertex array to write into (must hold stacks*sectors*6).
|
||||
* @param center Center position of the sphere.
|
||||
* @param radius Radius of the sphere.
|
||||
* @param stacks Number of horizontal rings (latitude bands).
|
||||
* @param sectors Number of vertical segments (longitude slices).
|
||||
* @param color Color applied to all vertices.
|
||||
*/
|
||||
void sphereBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const vec3 center,
|
||||
const float_t radius,
|
||||
const int32_t stacks,
|
||||
const int32_t sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
);
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "triprism.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
mesh_t TRIPRISM_MESH_SIMPLE;
|
||||
meshvertex_t TRIPRISM_MESH_SIMPLE_VERTICES[TRIPRISM_VERTEX_COUNT];
|
||||
|
||||
errorret_t triPrismInit() {
|
||||
triPrismBuffer(
|
||||
TRIPRISM_MESH_SIMPLE_VERTICES,
|
||||
0.0f, 0.0f, /* p0: bottom-left */
|
||||
1.0f, 0.0f, /* p1: bottom-right */
|
||||
0.5f, 1.0f, /* p2: apex */
|
||||
0.0f, 1.0f /* minZ, maxZ */
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&TRIPRISM_MESH_SIMPLE,
|
||||
TRIPRISM_PRIMITIVE_TYPE,
|
||||
TRIPRISM_VERTEX_COUNT,
|
||||
TRIPRISM_MESH_SIMPLE_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void triPrismBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t x0, const float_t y0,
|
||||
const float_t x1, const float_t y1,
|
||||
const float_t x2, const float_t y2,
|
||||
const float_t minZ,
|
||||
const float_t maxZ
|
||||
#if MESH_ENABLE_COLOR
|
||||
, const color_t color
|
||||
#endif
|
||||
) {
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
|
||||
/* Helper macro: write one vertex then advance index. */
|
||||
int32_t vi = 0;
|
||||
#if MESH_ENABLE_COLOR
|
||||
#define PRISM_VERT(px, py, pz, u, v) \
|
||||
vertices[vi].color = color; \
|
||||
vertices[vi].pos[0] = (px); \
|
||||
vertices[vi].pos[1] = (py); \
|
||||
vertices[vi].pos[2] = (pz); \
|
||||
vertices[vi].uv[0] = (u); \
|
||||
vertices[vi].uv[1] = (v); \
|
||||
vi++;
|
||||
#else
|
||||
#define PRISM_VERT(px, py, pz, u, v) \
|
||||
vertices[vi].pos[0] = (px); \
|
||||
vertices[vi].pos[1] = (py); \
|
||||
vertices[vi].pos[2] = (pz); \
|
||||
vertices[vi].uv[0] = (u); \
|
||||
vertices[vi].uv[1] = (v); \
|
||||
vi++;
|
||||
#endif
|
||||
|
||||
/* --- Front face (z = maxZ), CCW from +Z --- */
|
||||
PRISM_VERT(x0, y0, maxZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 0.5f, 1.0f)
|
||||
|
||||
/* --- Back face (z = minZ), CCW from -Z (reverse winding) --- */
|
||||
PRISM_VERT(x2, y2, minZ, 0.5f, 1.0f)
|
||||
PRISM_VERT(x1, y1, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
|
||||
|
||||
/* --- Side face 0: edge p0->p1 --- */
|
||||
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 1.0f, 1.0f)
|
||||
|
||||
PRISM_VERT(x0, y0, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 1.0f, 1.0f)
|
||||
PRISM_VERT(x0, y0, maxZ, 0.0f, 1.0f)
|
||||
|
||||
/* --- Side face 1: edge p1->p2 --- */
|
||||
PRISM_VERT(x1, y1, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 1.0f, 1.0f)
|
||||
|
||||
PRISM_VERT(x1, y1, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 1.0f, 1.0f)
|
||||
PRISM_VERT(x1, y1, maxZ, 0.0f, 1.0f)
|
||||
|
||||
/* --- Side face 2: edge p2->p0 --- */
|
||||
PRISM_VERT(x2, y2, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, minZ, 1.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, maxZ, 1.0f, 1.0f)
|
||||
|
||||
PRISM_VERT(x2, y2, minZ, 0.0f, 0.0f)
|
||||
PRISM_VERT(x0, y0, maxZ, 1.0f, 1.0f)
|
||||
PRISM_VERT(x2, y2, maxZ, 0.0f, 1.0f)
|
||||
|
||||
#undef PRISM_VERT
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 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.
|
||||
* @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
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
screen.c
|
||||
)
|
||||
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "screen.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
screen_t SCREEN;
|
||||
|
||||
errorret_t screenInit() {
|
||||
memoryZero(&SCREEN, sizeof(screen_t));
|
||||
|
||||
SCREEN.background = COLOR_CORNFLOWER_BLUE;
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
SCREEN.mode = SCREEN_MODE_FIXED_VIEWPORT_HEIGHT;
|
||||
SCREEN.fixedHeight.height = DUSK_DISPLAY_SCREEN_HEIGHT;
|
||||
|
||||
quadBuffer(
|
||||
SCREEN.frameBufferMeshVertices,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE
|
||||
#endif
|
||||
);
|
||||
errorChain(meshInit(
|
||||
&SCREEN.frameBufferMesh,
|
||||
QUAD_PRIMITIVE_TYPE,
|
||||
QUAD_VERTEX_COUNT,
|
||||
SCREEN.frameBufferMeshVertices
|
||||
));
|
||||
#endif
|
||||
|
||||
// Init screen to backbuffer mode by default
|
||||
errorChain(screenBind());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t screenBind() {
|
||||
// Assume backbuffer is currently bound.
|
||||
switch(SCREEN.mode) {
|
||||
case SCREEN_MODE_BACKBUFFER: {
|
||||
// Screen mode backbuffer uses the full display size
|
||||
SCREEN.width = frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
SCREEN.height = frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
SCREEN.aspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
|
||||
|
||||
// No needd for a framebuffer.
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
case SCREEN_MODE_FIXED_SIZE: {
|
||||
SCREEN.width = SCREEN.fixedSize.width;
|
||||
SCREEN.height = SCREEN.fixedSize.height;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
// Is current framebuffer the correct size?
|
||||
int32_t curFbWidth, curFbHeight;
|
||||
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
|
||||
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
|
||||
if(curFbWidth == SCREEN.width && curFbHeight == SCREEN.height) {
|
||||
// Correct size, nothing to do.
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Need a new framebuffer.
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
|
||||
// Create new framebuffer
|
||||
errorChain(frameBufferInit(
|
||||
&SCREEN.framebuffer, SCREEN.width, SCREEN.height
|
||||
));
|
||||
SCREEN.framebufferReady = true;
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
break;
|
||||
}
|
||||
|
||||
case SCREEN_MODE_ASPECT_RATIO: {
|
||||
// Aspect ratio mode, requires a framebuffer.
|
||||
int32_t fbWidth, fbHeight;
|
||||
fbWidth = frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
fbHeight = frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
float_t currentAspect = frameBufferGetAspect(FRAMEBUFFER_BOUND);
|
||||
if(currentAspect == SCREEN.aspectRatio.ratio) {
|
||||
// No need to use framebuffer.
|
||||
SCREEN.width = fbWidth;
|
||||
SCREEN.height = fbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
int32_t newFbWidth, newFbHeight;
|
||||
if(currentAspect > SCREEN.aspectRatio.ratio) {
|
||||
// Wider than target aspect, limit by height
|
||||
newFbWidth = (int32_t)floorf(fbHeight * SCREEN.aspectRatio.ratio);
|
||||
newFbHeight = (int32_t)fbHeight;
|
||||
} else {
|
||||
// Taller than target aspect, limit by width
|
||||
newFbHeight = (int32_t)floorf(fbWidth / SCREEN.aspectRatio.ratio);
|
||||
newFbWidth = (int32_t)fbWidth;
|
||||
}
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
// Is current framebuffer the correct size?
|
||||
int32_t curFbWidth, curFbHeight;
|
||||
float_t curFbAspect = frameBufferGetAspect(&SCREEN.framebuffer);
|
||||
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
|
||||
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
|
||||
if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) {
|
||||
// Correct size, nothing to do.
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = curFbAspect;
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Need a new framebuffer.
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
|
||||
// Create new framebuffer
|
||||
errorChain(frameBufferInit(
|
||||
&SCREEN.framebuffer, newFbWidth, newFbHeight
|
||||
));
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
SCREEN.framebufferReady = true;
|
||||
|
||||
// Bind FB
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
break;
|
||||
}
|
||||
|
||||
case SCREEN_MODE_FIXED_HEIGHT: {
|
||||
float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
float_t fbAspect = fbWidth / fbHeight;
|
||||
|
||||
int32_t newFbWidth, newFbHeight;
|
||||
newFbHeight = SCREEN.fixedHeight.height;
|
||||
newFbWidth = (int32_t)floorf(newFbHeight * fbAspect);
|
||||
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
|
||||
if(fbWidth == newFbWidth && fbHeight == newFbHeight) {
|
||||
// No need to use framebuffer.
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
// Is current framebuffer the correct size?
|
||||
int32_t curFbWidth, curFbHeight;
|
||||
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
|
||||
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
|
||||
if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) {
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Need a new framebuffer.
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
|
||||
// Create a new framebuffer.
|
||||
errorChain(frameBufferInit(
|
||||
&SCREEN.framebuffer, newFbWidth, newFbHeight
|
||||
));
|
||||
SCREEN.framebufferReady = true;
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
break;
|
||||
}
|
||||
|
||||
case SCREEN_MODE_FIXED_WIDTH: {
|
||||
float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
float_t fbAspect = fbWidth / fbHeight;
|
||||
|
||||
int32_t newFbWidth, newFbHeight;
|
||||
newFbWidth = SCREEN.fixedWidth.width;
|
||||
newFbHeight = (int32_t)floorf(newFbWidth / fbAspect);
|
||||
|
||||
SCREEN.width = newFbWidth;
|
||||
SCREEN.height = newFbHeight;
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
|
||||
if(fbWidth == newFbWidth && fbHeight == newFbHeight) {
|
||||
// No need to use framebuffer.
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(SCREEN.framebufferReady) {
|
||||
// Is current framebuffer the correct size?
|
||||
int32_t curFbWidth, curFbHeight;
|
||||
curFbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
|
||||
curFbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
|
||||
if(curFbWidth == newFbWidth && curFbHeight == newFbHeight) {
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Need a new framebuffer.
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
|
||||
// Create a new framebuffer.
|
||||
errorChain(frameBufferInit(
|
||||
&SCREEN.framebuffer, newFbWidth, newFbHeight
|
||||
));
|
||||
SCREEN.framebufferReady = true;
|
||||
errorChain(frameBufferBind(&SCREEN.framebuffer));
|
||||
break;
|
||||
}
|
||||
|
||||
case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT: {
|
||||
SCREEN.height = SCREEN.fixedViewportHeight.height;
|
||||
float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
float_t fbAspect = fbWidth / fbHeight;
|
||||
SCREEN.width = (int32_t)floorf(SCREEN.height * fbAspect);
|
||||
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||
break;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
default: {
|
||||
assertUnreachable("Invalid screen mode.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t screenUnbind() {
|
||||
switch(SCREEN.mode) {
|
||||
// Nothing to do here.
|
||||
case SCREEN_MODE_BACKBUFFER:
|
||||
break;
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
case SCREEN_MODE_ASPECT_RATIO:
|
||||
case SCREEN_MODE_FIXED_HEIGHT:
|
||||
case SCREEN_MODE_FIXED_SIZE:
|
||||
case SCREEN_MODE_FIXED_WIDTH:
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferBind(NULL));
|
||||
}
|
||||
break;
|
||||
|
||||
case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT:
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid screen mode.");
|
||||
break;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t screenRender() {
|
||||
if(SCREEN.mode == SCREEN_MODE_BACKBUFFER) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
if(SCREEN.mode == SCREEN_MODE_FIXED_VIEWPORT_HEIGHT) {
|
||||
glViewport(0, 0, SCREEN.width, SCREEN.height);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
if(
|
||||
SCREEN.mode == SCREEN_MODE_ASPECT_RATIO ||
|
||||
SCREEN.mode == SCREEN_MODE_FIXED_HEIGHT ||
|
||||
SCREEN.mode == SCREEN_MODE_FIXED_SIZE ||
|
||||
SCREEN.mode == SCREEN_MODE_FIXED_WIDTH
|
||||
) {
|
||||
if(!SCREEN.framebufferReady) {
|
||||
// Nothing to do here.
|
||||
errorOk();
|
||||
}
|
||||
|
||||
float_t bbWidth, bbHeight;
|
||||
bbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||
bbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||
|
||||
float_t backBufferAspect = bbWidth / bbHeight;
|
||||
|
||||
// Determine framebuffer centering
|
||||
float_t fbWidth, fbHeight, fbAspect;
|
||||
float_t fbX, fbY;
|
||||
|
||||
fbWidth = frameBufferGetWidth(&SCREEN.framebuffer);
|
||||
fbHeight = frameBufferGetHeight(&SCREEN.framebuffer);
|
||||
fbAspect = fbWidth / fbHeight;
|
||||
|
||||
if(backBufferAspect > fbAspect) {
|
||||
fbHeight = bbHeight;
|
||||
fbWidth = fbHeight * fbAspect;
|
||||
fbX = (bbWidth - fbWidth) * 0.5f;
|
||||
fbY = 0.0f;
|
||||
} else {
|
||||
fbWidth = bbWidth;
|
||||
fbHeight = fbWidth / fbAspect;
|
||||
fbX = 0.0f;
|
||||
fbY = (bbHeight - fbHeight) * 0.5f;
|
||||
}
|
||||
|
||||
// Determine back buffer matricies
|
||||
float_t centerX = bbWidth * 0.5f;
|
||||
float_t centerY = bbHeight * 0.5f;
|
||||
mat4 view, proj, model;
|
||||
glm_ortho(
|
||||
0.0f, bbWidth, bbHeight, 0.0f, 0.01f, 1.0f,
|
||||
proj
|
||||
);
|
||||
glm_mat4_identity(view);
|
||||
glm_mat4_identity(model);
|
||||
|
||||
quadBuffer(
|
||||
SCREEN.frameBufferMeshVertices,
|
||||
centerX - fbWidth * 0.5f, centerY + fbHeight * 0.5f, // top-left
|
||||
centerX + fbWidth * 0.5f, centerY - fbHeight * 0.5f, // bottom-right
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE
|
||||
#endif
|
||||
);
|
||||
|
||||
frameBufferClear(
|
||||
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
|
||||
COLOR_BLACK
|
||||
);
|
||||
|
||||
shaderBind(&SHADER_UNLIT);
|
||||
shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj);
|
||||
shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view);
|
||||
shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model);
|
||||
|
||||
// errorChain(textureBind(&SCREEN.framebuffer.texture));
|
||||
errorChain(meshDraw(&SCREEN.frameBufferMesh, 0, -1));
|
||||
|
||||
errorOk();
|
||||
};
|
||||
#endif
|
||||
|
||||
assertUnreachable("Invalid screen mode.");
|
||||
errorThrow("Invalid screen mode.");
|
||||
}
|
||||
|
||||
errorret_t screenDispose() {
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
if(SCREEN.framebufferReady) {
|
||||
errorChain(frameBufferDispose(&SCREEN.framebuffer));
|
||||
SCREEN.framebufferReady = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/color.h"
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
#ifndef DUSK_DISPLAY_SCREEN_HEIGHT
|
||||
#error "DUSK_DISPLAY_SCREEN_HEIGHT must be defined"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
SCREEN_MODE_BACKBUFFER,
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
SCREEN_MODE_FIXED_SIZE,
|
||||
SCREEN_MODE_ASPECT_RATIO,// Maintains aspect at all cost
|
||||
SCREEN_MODE_FIXED_HEIGHT, // Fixed height, width expands/contracts as needed
|
||||
SCREEN_MODE_FIXED_WIDTH, // Fixed width, height expands/contracts as needed
|
||||
// Fixed viewport height. Fixed height but higher resolution.
|
||||
SCREEN_MODE_FIXED_VIEWPORT_HEIGHT,
|
||||
#endif
|
||||
} screenmode_t;
|
||||
|
||||
// typedef enum {
|
||||
// SCREEN_SCALE_MODE_FILL,
|
||||
// SCREEN_SCALE_MODE_INTEGER,
|
||||
// SCREEN_SCALE_MODE_INEGER_OVERFLOW
|
||||
// } screenscalemode_t;
|
||||
|
||||
typedef struct {
|
||||
screenmode_t mode;
|
||||
// screenscalemode_t scaleMode;
|
||||
|
||||
// Calculated dimensions of the viewport, to be used by the camera
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
float_t aspect;
|
||||
color_t background;
|
||||
|
||||
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
|
||||
framebuffer_t framebuffer;
|
||||
bool_t framebufferReady;
|
||||
// camera_t framebufferCamera;
|
||||
mesh_t frameBufferMesh;
|
||||
meshvertex_t frameBufferMeshVertices[QUAD_VERTEX_COUNT];
|
||||
#endif
|
||||
|
||||
union {
|
||||
struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
} fixedSize;
|
||||
|
||||
struct {
|
||||
float_t ratio;
|
||||
} aspectRatio;
|
||||
|
||||
struct {
|
||||
int32_t height;
|
||||
} fixedHeight;
|
||||
|
||||
struct {
|
||||
int32_t width;
|
||||
} fixedWidth;
|
||||
|
||||
struct {
|
||||
int32_t height;
|
||||
} fixedViewportHeight;
|
||||
};
|
||||
} screen_t;
|
||||
|
||||
extern screen_t SCREEN;
|
||||
|
||||
/**
|
||||
* Initializes the screen system.
|
||||
*
|
||||
* @return Error code and state, if error occurs.
|
||||
*/
|
||||
errorret_t screenInit();
|
||||
|
||||
/**
|
||||
* Binds the screen, this is done before rendering game content.
|
||||
*
|
||||
* @return Error code and state, if error occurs.
|
||||
*/
|
||||
errorret_t screenBind();
|
||||
|
||||
/**
|
||||
* Unbinds the screen, does nothing for now.
|
||||
*
|
||||
* @return Error code and state, if error occurs.
|
||||
*/
|
||||
errorret_t screenUnbind();
|
||||
|
||||
/**
|
||||
* Renders the screen to the current framebuffer.
|
||||
*
|
||||
* @return Error code and state, if error occurs.
|
||||
*/
|
||||
errorret_t screenRender();
|
||||
|
||||
/**
|
||||
* Disposes the screen system.
|
||||
*
|
||||
* @return Error code and state, if error occurs.
|
||||
*/
|
||||
errorret_t screenDispose();
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
shader.c
|
||||
shaderlist.c
|
||||
shaderunlit.c
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "shader.h"
|
||||
#include "shadermaterial.h"
|
||||
#include "assert/assert.h"
|
||||
#include "log/log.h"
|
||||
|
||||
shader_t *bound = NULL;
|
||||
|
||||
errorret_t shaderInit(shader_t *shader, const shaderdefinition_t *def) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
errorChain(shaderInitPlatform(shader, def));
|
||||
bound = NULL;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t shaderBind(shader_t *shader) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
errorChain(shaderBindPlatform(shader));
|
||||
bound = shader;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t shaderSetMatrix(
|
||||
shader_t *shader,
|
||||
const char_t *name,
|
||||
mat4 matrix
|
||||
) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
assertStrLenMin(name, 1, "Uniform name cannot be empty");
|
||||
assertNotNull(matrix, "Matrix cannot be null");
|
||||
assertTrue(bound == shader, "Shader must be bound.");
|
||||
errorChain(shaderSetMatrixPlatform(shader, name, matrix));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t shaderSetTexture(
|
||||
shader_t *shader,
|
||||
const char_t *name,
|
||||
texture_t *texture
|
||||
) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
assertStrLenMin(name, 1, "Uniform name cannot be empty");
|
||||
assertTrue(bound == shader, "Shader must be bound.");
|
||||
errorChain(shaderSetTexturePlatform(shader, name, texture));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t shaderSetColor(
|
||||
shader_t *shader,
|
||||
const char_t *name,
|
||||
color_t color
|
||||
) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
assertStrLenMin(name, 1, "Uniform name cannot be empty");
|
||||
assertTrue(bound == shader, "Shader must be bound.");
|
||||
errorChain(shaderSetColorPlatform(shader, name, color));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t shaderSetMaterial(
|
||||
shader_t *shader,
|
||||
const shadermaterial_t *material
|
||||
) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
assertNotNull(material, "Material cannot be null");
|
||||
assertTrue(bound == shader, "Shader must be bound.");
|
||||
assertNotNull(shader->definition, "Shader definition cannot be null");
|
||||
assertNotNull(shader->definition->setMaterial, "Def lacks setMaterial");
|
||||
return shader->definition->setMaterial(shader, material);
|
||||
}
|
||||
|
||||
errorret_t shaderDispose(shader_t *shader) {
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
bound = NULL;
|
||||
errorChain(shaderDisposePlatform(shader));
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/shader/shaderplatform.h"
|
||||
|
||||
#ifndef shaderInitPlatform
|
||||
#error "shaderInitPlatform must be defined to use shader.h"
|
||||
#endif
|
||||
#ifndef shaderBindPlatform
|
||||
#error "shaderBindPlatform must be defined to use shader.h"
|
||||
#endif
|
||||
#ifndef shaderSetMatrixPlatform
|
||||
#error "shaderSetMatrixPlatform must be defined to use shader.h"
|
||||
#endif
|
||||
#ifndef shaderDisposePlatform
|
||||
#error "shaderDisposePlatform must be defined to use shader.h"
|
||||
#endif
|
||||
|
||||
typedef union shadermaterial_u shadermaterial_t;
|
||||
typedef shaderplatform_t shader_t;
|
||||
typedef shaderdefinitionplatform_t shaderdefinition_t;
|
||||
|
||||
/**
|
||||
* Initializes a shader. This is platform dependant.
|
||||
*
|
||||
* @param shader Shader to initialize
|
||||
* @param def Definition of the shader to initialize with.
|
||||
* @return Error if failure, otherwise errorOk
|
||||
*/
|
||||
errorret_t shaderInit(shader_t *shader, const shaderdefinition_t *def);
|
||||
|
||||
/**
|
||||
* Binds a shader. This is platform dependant.
|
||||
*
|
||||
* @param shader Shader to bind
|
||||
* @return Error if failure, otherwise errorOk
|
||||
*/
|
||||
errorret_t shaderBind(shader_t *shader);
|
||||
|
||||
/**
|
||||
* Sets a matrix uniform in the shader. This is platform dependant.
|
||||
*
|
||||
* @param shader Shader to set the matrix in
|
||||
* @param name Name of the uniform to set
|
||||
* @param matrix Matrix to set
|
||||
* @return Error if failure, otherwise errorOk
|
||||
*/
|
||||
errorret_t shaderSetMatrix(
|
||||
shader_t *shader,
|
||||
const char_t *name,
|
||||
mat4 matrix
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets a texture uniform in the shader. This is platform dependant.
|
||||
*
|
||||
* @param shader Shader to set the texture in
|
||||
* @param name Name of the uniform to set
|
||||
* @param texture Texture to set
|
||||
* @return Error if failure, otherwise errorOk
|
||||
*/
|
||||
errorret_t shaderSetTexture(
|
||||
shader_t *shader,
|
||||
const char_t *name,
|
||||
texture_t *texture
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets a color uniform in the shader. This is platform dependant.
|
||||
*
|
||||
* @param shader Shader to set the color in
|
||||
* @param name Name of the uniform to set
|
||||
* @param color Color to set
|
||||
* @return Error if failure, otherwise errorOk
|
||||
*/
|
||||
errorret_t shaderSetColor(
|
||||
shader_t *shader,
|
||||
const char_t *name,
|
||||
color_t color
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets a material's properties in the shader. This is platform dependant.
|
||||
* the definition's upload function pointer.
|
||||
*
|
||||
* @param shader The shader to upload material properties to.
|
||||
* @param material The material data to upload.
|
||||
* @return Error if failure, otherwise errorOk.
|
||||
*/
|
||||
errorret_t shaderSetMaterial(
|
||||
shader_t *shader,
|
||||
const shadermaterial_t *material
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes of a shader. This is platform dependant.
|
||||
*
|
||||
* @param shader Shader to dispose
|
||||
* @return Error if failure, otherwise errorOk
|
||||
*/
|
||||
errorret_t shaderDispose(shader_t *shader);
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "shaderlist.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT] = {
|
||||
[SHADER_LIST_SHADER_UNLIT] = {
|
||||
.shader = &SHADER_UNLIT,
|
||||
.definition = &SHADER_UNLIT_DEFINITION
|
||||
},
|
||||
};
|
||||
|
||||
errorret_t shaderListInit() {
|
||||
mat4 view, proj, model;
|
||||
glm_lookat(
|
||||
(vec3){ 0.0f, 0.0f, 1.0f },
|
||||
(vec3){ 0.0f, 0.0f, 0.0f },
|
||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||
view
|
||||
);
|
||||
|
||||
glm_perspective(
|
||||
glm_rad(45.0f),
|
||||
SCREEN.aspect,
|
||||
0.1f,
|
||||
100.0f,
|
||||
proj
|
||||
);
|
||||
|
||||
glm_mat4_identity(model);
|
||||
|
||||
for(shaderlistshader_t i = 0; i < SHADER_LIST_SHADER_COUNT; i++) {
|
||||
if(i == SHADER_LIST_SHADER_NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assertNotNull(
|
||||
SHADER_LIST_DEFS[i].shader, "Shader cannot be null"
|
||||
);
|
||||
assertNotNull(
|
||||
SHADER_LIST_DEFS[i].definition, "Shader definition cannot be null"
|
||||
);
|
||||
|
||||
errorChain(shaderInit(
|
||||
SHADER_LIST_DEFS[i].shader, SHADER_LIST_DEFS[i].definition
|
||||
));
|
||||
errorChain(shaderBind(SHADER_LIST_DEFS[i].shader));
|
||||
errorChain(shaderSetMatrix(
|
||||
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_PROJECTION, proj
|
||||
));
|
||||
errorChain(shaderSetMatrix(
|
||||
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_VIEW, view
|
||||
));
|
||||
errorChain(shaderSetMatrix(
|
||||
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_MODEL, model
|
||||
));
|
||||
errorChain(shaderSetTexture(
|
||||
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_TEXTURE, NULL
|
||||
));
|
||||
errorChain(shaderSetColor(
|
||||
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_COLOR, COLOR_WHITE
|
||||
));
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t shaderListDispose(void) {
|
||||
for(shaderlistshader_t i = 0; i < SHADER_LIST_SHADER_COUNT; i++) {
|
||||
if(i == SHADER_LIST_SHADER_NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assertNotNull(
|
||||
SHADER_LIST_DEFS[i].shader, "Shader cannot be null"
|
||||
);
|
||||
|
||||
errorChain(shaderDispose(SHADER_LIST_DEFS[i].shader));
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/shader/shader.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
typedef enum {
|
||||
SHADER_LIST_SHADER_NULL,
|
||||
|
||||
SHADER_LIST_SHADER_UNLIT,
|
||||
|
||||
SHADER_LIST_SHADER_COUNT
|
||||
} shaderlistshader_t;
|
||||
|
||||
typedef struct {
|
||||
shader_t *shader;
|
||||
shaderdefinition_t *definition;
|
||||
} shaderlistdef_t;
|
||||
|
||||
extern shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes all default shaders and uploads the initial view, projection,
|
||||
* and model matrices to each.
|
||||
*
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t shaderListInit();
|
||||
|
||||
/**
|
||||
* Disposes all default shaders.
|
||||
*
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t shaderListDispose(void);
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/shader/shaderlist.h"
|
||||
|
||||
typedef union shadermaterial_u {
|
||||
shaderunlitmaterial_t unlit;
|
||||
} shadermaterial_t;
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "shaderunlit.h"
|
||||
#include "display/shader/shadermaterial.h"
|
||||
|
||||
shader_t SHADER_UNLIT = {
|
||||
.definition = &SHADER_UNLIT_DEFINITION
|
||||
};
|
||||
|
||||
errorret_t shaderUnlitSetMaterial(
|
||||
shader_t *shader,
|
||||
const shadermaterial_t *material
|
||||
) {
|
||||
errorChain(shaderSetTexture(
|
||||
shader,
|
||||
SHADER_UNLIT_TEXTURE,
|
||||
material->unlit.texture
|
||||
));
|
||||
|
||||
errorChain(shaderSetColor(
|
||||
shader,
|
||||
SHADER_UNLIT_COLOR,
|
||||
material->unlit.color
|
||||
));
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "shader.h"
|
||||
|
||||
#define SHADER_UNLIT_PROJECTION "u_Proj"
|
||||
#define SHADER_UNLIT_VIEW "u_View"
|
||||
#define SHADER_UNLIT_MODEL "u_Model"
|
||||
#define SHADER_UNLIT_TEXTURE "u_Texture"
|
||||
#define SHADER_UNLIT_COLOR "u_Color"
|
||||
|
||||
typedef struct {
|
||||
color_t color;
|
||||
texture_t *texture;
|
||||
} shaderunlitmaterial_t;
|
||||
|
||||
extern shaderdefinition_t SHADER_UNLIT_DEFINITION;
|
||||
extern shader_t SHADER_UNLIT;
|
||||
|
||||
/**
|
||||
* Uploads the unlit material properties to the shader.
|
||||
*
|
||||
* @param shader The shader to upload to.
|
||||
* @param material The material data to upload.
|
||||
* @return Error if failure, otherwise errorOk.
|
||||
*/
|
||||
errorret_t shaderUnlitSetMaterial(
|
||||
shader_t *shader,
|
||||
const shadermaterial_t *material
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
spritebatch.c
|
||||
)
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "spritebatch.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "display/shader/shadermaterial.h"
|
||||
|
||||
meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
|
||||
spritebatch_t SPRITEBATCH;
|
||||
|
||||
errorret_t spriteBatchInit() {
|
||||
memoryZero(&SPRITEBATCH, sizeof(spritebatch_t));
|
||||
errorChain(meshInit(
|
||||
&SPRITEBATCH.mesh,
|
||||
QUAD_PRIMITIVE_TYPE,
|
||||
SPRITEBATCH_VERTEX_COUNT,
|
||||
SPRITEBATCH_VERTICES
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t spriteBatchBuffer(
|
||||
const spritebatchsprite_t *sprites,
|
||||
const uint32_t count,
|
||||
shader_t *shader,
|
||||
const shadermaterial_t material
|
||||
) {
|
||||
assertNotNull(sprites, "Sprites cannot be null");
|
||||
assertTrue(count > 0, "Count must be greater than zero");
|
||||
assertNotNull(shader, "Shader cannot be null");
|
||||
|
||||
// Did the shader or material data change?
|
||||
if(shader != SPRITEBATCH.shader) {
|
||||
errorChain(spriteBatchFlush());
|
||||
SPRITEBATCH.shader = shader;
|
||||
SPRITEBATCH.material = material;
|
||||
} else if(memoryCompare(
|
||||
&material, &SPRITEBATCH.material, sizeof(shadermaterial_t)
|
||||
) != 0) {
|
||||
// Did the material data change?
|
||||
errorChain(spriteBatchFlush());
|
||||
SPRITEBATCH.shader = shader;
|
||||
SPRITEBATCH.material = material;
|
||||
}
|
||||
|
||||
// Buffer the vertices.
|
||||
uint32_t remaining = count;
|
||||
do {
|
||||
uint32_t spritesBeforeFlush = (
|
||||
SPRITEBATCH_SPRITES_MAX_PER_FLUSH - SPRITEBATCH.spriteCount
|
||||
);
|
||||
|
||||
if(spritesBeforeFlush == 0) {
|
||||
// Flush if we have no capacity before flushing.
|
||||
errorChain(spriteBatchFlush());
|
||||
spritesBeforeFlush = SPRITEBATCH_SPRITES_MAX_PER_FLUSH;
|
||||
}
|
||||
|
||||
// Many we buffering?
|
||||
const uint32_t batchCount = mathMin(
|
||||
remaining,
|
||||
spritesBeforeFlush
|
||||
);
|
||||
|
||||
// Destination
|
||||
meshvertex_t *v = &SPRITEBATCH_VERTICES[
|
||||
(SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush *
|
||||
SPRITEBATCH_SPRITES_MAX_PER_FLUSH)) * QUAD_VERTEX_COUNT
|
||||
];
|
||||
|
||||
// Buffer to the mesh vertices.
|
||||
spriteBatchBufferToMesh(
|
||||
sprites, batchCount, v, batchCount * QUAD_VERTEX_COUNT
|
||||
);
|
||||
SPRITEBATCH.spriteCount += batchCount;
|
||||
remaining -= batchCount;
|
||||
} while(remaining > 0);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void spriteBatchBufferToMesh(
|
||||
const spritebatchsprite_t *sprites,
|
||||
const uint32_t count,
|
||||
meshvertex_t *vertices,
|
||||
const uint32_t verticesSize
|
||||
) {
|
||||
assertNotNull(sprites, "Sprites cannot be null");
|
||||
assertTrue(count > 0, "Count must be greater than zero");
|
||||
assertNotNull(vertices, "Vertices cannot be null");
|
||||
assertTrue(
|
||||
verticesSize >= count * QUAD_VERTEX_COUNT, "Vertices array too small"
|
||||
);
|
||||
|
||||
for(uint32_t i = 0; i < count; i++ ){
|
||||
spritebatchsprite_t sprite = sprites[i];
|
||||
meshvertex_t *v = &vertices[i * QUAD_VERTEX_COUNT];
|
||||
|
||||
// Buffer the quad
|
||||
v[0].pos[0] = sprite.min[0];
|
||||
v[0].pos[1] = sprite.min[1];
|
||||
v[0].pos[2] = sprite.min[2];
|
||||
v[0].uv[0] = sprite.uvMin[0];
|
||||
v[0].uv[1] = sprite.uvMin[1];
|
||||
|
||||
v[1].pos[0] = sprite.max[0];
|
||||
v[1].pos[1] = sprite.min[1];
|
||||
v[1].pos[2] = sprite.min[2];
|
||||
v[1].uv[0] = sprite.uvMax[0];
|
||||
v[1].uv[1] = sprite.uvMin[1];
|
||||
|
||||
v[2].pos[0] = sprite.max[0];
|
||||
v[2].pos[1] = sprite.max[1];
|
||||
v[2].pos[2] = sprite.max[2];
|
||||
v[2].uv[0] = sprite.uvMax[0];
|
||||
v[2].uv[1] = sprite.uvMax[1];
|
||||
|
||||
v[3].pos[0] = sprite.min[0];
|
||||
v[3].pos[1] = sprite.min[1];
|
||||
v[3].pos[2] = sprite.min[2];
|
||||
v[3].uv[0] = sprite.uvMin[0];
|
||||
v[3].uv[1] = sprite.uvMin[1];
|
||||
|
||||
v[4].pos[0] = sprite.max[0];
|
||||
v[4].pos[1] = sprite.max[1];
|
||||
v[4].pos[2] = sprite.max[2];
|
||||
v[4].uv[0] = sprite.uvMax[0];
|
||||
v[4].uv[1] = sprite.uvMax[1];
|
||||
|
||||
v[5].pos[0] = sprite.min[0];
|
||||
v[5].pos[1] = sprite.max[1];
|
||||
v[5].pos[2] = sprite.max[2];
|
||||
v[5].uv[0] = sprite.uvMin[0];
|
||||
v[5].uv[1] = sprite.uvMax[1];
|
||||
}
|
||||
}
|
||||
|
||||
void spriteBatchClear() {
|
||||
SPRITEBATCH.spriteCount = 0;
|
||||
SPRITEBATCH.spriteFlush = 0;
|
||||
SPRITEBATCH.shader = NULL;
|
||||
memoryZero(&SPRITEBATCH.material, sizeof(shadermaterial_t));
|
||||
}
|
||||
|
||||
errorret_t spriteBatchFlush() {
|
||||
if(SPRITEBATCH.spriteCount == 0) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
size_t vertexCount = QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount;
|
||||
size_t vertexOffset = (
|
||||
SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH *
|
||||
QUAD_VERTEX_COUNT
|
||||
);
|
||||
|
||||
errorChain(shaderBind(SPRITEBATCH.shader));
|
||||
errorChain(shaderSetMaterial(SPRITEBATCH.shader, &SPRITEBATCH.material));
|
||||
errorChain(meshFlush(&SPRITEBATCH.mesh, vertexOffset, vertexCount));
|
||||
errorChain(meshDraw(&SPRITEBATCH.mesh, vertexOffset, vertexCount));
|
||||
|
||||
SPRITEBATCH.spriteFlush++;
|
||||
if(SPRITEBATCH.spriteFlush >= SPRITEBATCH_FLUSH_COUNT) {
|
||||
SPRITEBATCH.spriteFlush = 0;
|
||||
}
|
||||
SPRITEBATCH.spriteCount = 0;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t spriteBatchDispose() {
|
||||
errorChain(meshDispose(&SPRITEBATCH.mesh));
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/shader/shadermaterial.h"
|
||||
|
||||
#define SPRITEBATCH_SPRITES_MAX 512
|
||||
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
|
||||
#define SPRITEBATCH_FLUSH_COUNT 16
|
||||
#define SPRITEBATCH_SPRITES_MAX_PER_FLUSH (\
|
||||
SPRITEBATCH_SPRITES_MAX / SPRITEBATCH_FLUSH_COUNT \
|
||||
)
|
||||
|
||||
typedef struct {
|
||||
vec3 min;
|
||||
vec3 max;
|
||||
vec2 uvMin;
|
||||
vec2 uvMax;
|
||||
} spritebatchsprite_t;
|
||||
|
||||
typedef struct {
|
||||
mesh_t mesh;
|
||||
int32_t spriteCount;
|
||||
int32_t spriteFlush;
|
||||
|
||||
shader_t *shader;
|
||||
shadermaterial_t material;
|
||||
} spritebatch_t;
|
||||
|
||||
// Have to define these separately because of alignment on certain platforms.
|
||||
extern meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
|
||||
extern spritebatch_t SPRITEBATCH;
|
||||
|
||||
/**
|
||||
* Initializes the global sprite batch and its internal mesh buffer.
|
||||
*
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t spriteBatchInit();
|
||||
|
||||
/**
|
||||
* Lowest-level buffer function. Writes sprites into the internal vertex buffer.
|
||||
* Flushes automatically when the per-flush capacity is reached. Does not
|
||||
* modify material state - call spriteBatchSetState or use a high-level push
|
||||
* function before buffering.
|
||||
*
|
||||
* @param sprites Pointer to the sprite array.
|
||||
* @param count Number of sprites to buffer.
|
||||
* @param shader Shader to use when flushing.
|
||||
* @param material Material information passed to the shader when flushing.
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t spriteBatchBuffer(
|
||||
const spritebatchsprite_t *sprites,
|
||||
const uint32_t count,
|
||||
shader_t *shader,
|
||||
const shadermaterial_t material
|
||||
);
|
||||
|
||||
/**
|
||||
* Buffers an array of sprites to a given array of mesh vertices. This is the
|
||||
* internal method that is used to buffer to the internal spritebatch mesh, but
|
||||
* you can use it to achieve sprite buffering to a mesh you own.
|
||||
*
|
||||
* verticesSize is the size of the vertices array, we use this to ensure no
|
||||
* buffer overflows.
|
||||
*
|
||||
* @param sprites Pointer to the sprite array.
|
||||
* @param count Number of sprites to buffer.
|
||||
* @param vertices Pointer to the vertex array to write to.
|
||||
* @param verticesSize Size of the vertex array, in number of vertices.
|
||||
*/
|
||||
void spriteBatchBufferToMesh(
|
||||
const spritebatchsprite_t *sprites,
|
||||
const uint32_t count,
|
||||
meshvertex_t *vertices,
|
||||
const uint32_t verticesSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Resets sprite and flush counters and clears the current material state.
|
||||
* Calling spriteBatchFlush after this renders nothing.
|
||||
*/
|
||||
void spriteBatchClear();
|
||||
|
||||
/**
|
||||
* Uploads and draws all buffered sprites. If a material type has been set via
|
||||
* spriteBatchSetState or spriteBatchCheckState, the shader is bound and the
|
||||
* material is applied first. If matType is NULL the caller is responsible for
|
||||
* having the correct shader already bound. Does nothing if the buffer is empty.
|
||||
*
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t spriteBatchFlush();
|
||||
|
||||
/**
|
||||
* Disposes of the sprite batch and frees its internal mesh buffer.
|
||||
*
|
||||
* @return Error state.
|
||||
*/
|
||||
errorret_t spriteBatchDispose();
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
text.c
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/texture/tileset.h"
|
||||
|
||||
typedef struct {
|
||||
texture_t *texture;
|
||||
tileset_t *tileset;
|
||||
} font_t;
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "text.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/display/assettextureloader.h"
|
||||
#include "asset/loader/display/assettilesetloader.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
|
||||
font_t FONT_DEFAULT;
|
||||
|
||||
errorret_t textInit(void) {
|
||||
assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA };
|
||||
assetentry_t *entryTexture = assetLock(
|
||||
"ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input
|
||||
);
|
||||
assetentry_t *entryTileset = assetLock(
|
||||
"ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL
|
||||
);
|
||||
errorChain(assetRequireLoaded(entryTexture));
|
||||
errorChain(assetRequireLoaded(entryTileset));
|
||||
|
||||
FONT_DEFAULT.texture = &entryTexture->data.texture;
|
||||
FONT_DEFAULT.tileset = &entryTileset->data.tileset;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t textDispose(void) {
|
||||
FONT_DEFAULT.texture = NULL;
|
||||
FONT_DEFAULT.tileset = NULL;
|
||||
assetUnlock("ui/minogram.png");
|
||||
assetUnlock("ui/minogram.dtf");
|
||||
errorOk();
|
||||
}
|
||||
|
||||
spritebatchsprite_t textGetSprite(
|
||||
const vec2 pos, const char_t c, const font_t *font
|
||||
) {
|
||||
assertNotNull(font, "Font cannot be NULL");
|
||||
|
||||
// Change char from ASCII to a tile index.
|
||||
int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START;
|
||||
if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) {
|
||||
tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
|
||||
}
|
||||
assertTrue(
|
||||
tileIndex >= 0 && tileIndex <= font->tileset->tileCount,
|
||||
"Character is out of bounds for font tiles"
|
||||
);
|
||||
|
||||
// Create sprite.
|
||||
vec4 uv;
|
||||
tilesetTileGetUV(font->tileset, tileIndex, uv);
|
||||
|
||||
spritebatchsprite_t sprite;
|
||||
sprite.min[0] = pos[0];
|
||||
sprite.min[1] = pos[1];
|
||||
sprite.min[2] = 0.0f;
|
||||
sprite.max[0] = pos[0] + font->tileset->tileWidth;
|
||||
sprite.max[1] = pos[1] + font->tileset->tileHeight;
|
||||
sprite.max[2] = 0.0f;
|
||||
sprite.uvMin[0] = uv[0];
|
||||
sprite.uvMin[1] = uv[1];
|
||||
sprite.uvMax[0] = uv[2];
|
||||
sprite.uvMax[1] = uv[3];
|
||||
return sprite;
|
||||
}
|
||||
|
||||
errorret_t textDraw(
|
||||
const float_t x,
|
||||
const float_t y,
|
||||
const char_t *text,
|
||||
const color_t color,
|
||||
font_t *font
|
||||
) {
|
||||
assertNotNull(text, "Text cannot be NULL");
|
||||
|
||||
spritebatchsprite_t sprite;
|
||||
shadermaterial_t material = {
|
||||
.unlit = {
|
||||
.color = color,
|
||||
.texture = font->texture
|
||||
}
|
||||
};
|
||||
|
||||
float_t posX = x;
|
||||
float_t posY = y;
|
||||
|
||||
char_t c;
|
||||
int32_t i = 0;
|
||||
while((c = text[i++]) != '\0') {
|
||||
if(c == '\n') {
|
||||
posX = x;
|
||||
posY += font->tileset->tileHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == ' ') {
|
||||
posX += font->tileset->tileWidth;
|
||||
continue;
|
||||
}
|
||||
|
||||
sprite = textGetSprite((vec2){posX, posY}, c, font);
|
||||
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
|
||||
posX += font->tileset->tileWidth;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void textMeasure(
|
||||
const char_t *text,
|
||||
const font_t *font,
|
||||
int32_t *outWidth,
|
||||
int32_t *outHeight
|
||||
) {
|
||||
assertNotNull(text, "Text cannot be NULL");
|
||||
assertNotNull(outWidth, "Output width pointer cannot be NULL");
|
||||
assertNotNull(outHeight, "Output height pointer cannot be NULL");
|
||||
|
||||
int32_t width = 0;
|
||||
int32_t height = font->tileset->tileHeight;
|
||||
int32_t lineWidth = 0;
|
||||
|
||||
char_t c;
|
||||
int32_t i = 0;
|
||||
while((c = text[i++]) != '\0') {
|
||||
if(c == '\n') {
|
||||
if(lineWidth > width) width = lineWidth;
|
||||
lineWidth = 0;
|
||||
height += font->tileset->tileHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
lineWidth += font->tileset->tileWidth;
|
||||
}
|
||||
|
||||
if(lineWidth > width) width = lineWidth;
|
||||
|
||||
*outWidth = width;
|
||||
*outHeight = height;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/asset.h"
|
||||
#include "display/text/font.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
#define TEXT_CHAR_START '!'
|
||||
|
||||
extern font_t FONT_DEFAULT;
|
||||
|
||||
/**
|
||||
* Initializes the text system.
|
||||
*
|
||||
* @return Either an error or success result.
|
||||
*/
|
||||
errorret_t textInit(void);
|
||||
|
||||
/**
|
||||
* Disposes of the text system.
|
||||
*
|
||||
* @return Either an error or success result.
|
||||
*/
|
||||
errorret_t textDispose(void);
|
||||
|
||||
/**
|
||||
* Builds a sprite for a single character at the given position.
|
||||
*
|
||||
* @param pos The (x, y) position of the character in screen/world space.
|
||||
* @param c The character to build a sprite for.
|
||||
* @param font Font to use for tile lookup.
|
||||
* @return The populated sprite ready for spriteBatchBuffer.
|
||||
*/
|
||||
spritebatchsprite_t textGetSprite(
|
||||
const vec2 pos,
|
||||
const char_t c,
|
||||
const font_t *font
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws a string of text at the specified position.
|
||||
*
|
||||
* @param x The x-coordinate to draw the text at.
|
||||
* @param y The y-coordinate to draw the text at.
|
||||
* @param text The null-terminated string of text to draw.
|
||||
* @param color The color to draw the text in.
|
||||
* @param font Font to use for rendering.
|
||||
* @return Either an error or success result.
|
||||
*/
|
||||
errorret_t textDraw(
|
||||
const float_t x,
|
||||
const float_t y,
|
||||
const char_t *text,
|
||||
const color_t color,
|
||||
font_t *font
|
||||
);
|
||||
|
||||
/**
|
||||
* Measures the width and height of the given text string when rendered.
|
||||
*
|
||||
* @param text The null-terminated string of text to measure.
|
||||
* @param font Font to use for measurement.
|
||||
* @param outWidth Pointer to store the measured width in pixels.
|
||||
* @param outHeight Pointer to store the measured height in pixels.
|
||||
*/
|
||||
void textMeasure(
|
||||
const char_t *text,
|
||||
const font_t *font,
|
||||
int32_t *outWidth,
|
||||
int32_t *outHeight
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
tileset.c
|
||||
texture.c
|
||||
palette.c
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "palette.h"
|
||||
|
||||
palette_t PALETTES[PALETTE_COUNT];
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/color.h"
|
||||
|
||||
#define PALETTE_COLOR_COUNT 0xFF
|
||||
#define PALETTE_COUNT 6
|
||||
|
||||
typedef struct {
|
||||
color_t colors[PALETTE_COLOR_COUNT];
|
||||
uint8_t count;
|
||||
} palette_t;
|
||||
|
||||
extern palette_t PALETTES[PALETTE_COUNT];
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "texture.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
#include "display/display.h"
|
||||
|
||||
texture_t TEXTURE_WHITE;
|
||||
color_t TEXTURE_WHITE_PIXELS[4*4] = {
|
||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE,
|
||||
};
|
||||
|
||||
texture_t TEXTURE_TEST;
|
||||
color_t TEXTURE_TEST_PIXELS[4*4] = {
|
||||
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
||||
COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK,
|
||||
COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA,
|
||||
COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA, COLOR_BLACK,
|
||||
};
|
||||
|
||||
errorret_t textureInit(
|
||||
texture_t *texture,
|
||||
const int32_t width,
|
||||
const int32_t height,
|
||||
const textureformat_t format,
|
||||
const texturedata_t data
|
||||
) {
|
||||
assertNotNull(texture, "Texture cannot be NULL");
|
||||
assertTrue(width > 0 && height > 0, "width/height must be greater than 0");
|
||||
assertTrue(width == mathNextPowTwo(width), "Width must be a power of 2.");
|
||||
assertTrue(height == mathNextPowTwo(height), "Height must be a power of 2.");
|
||||
|
||||
if(texture->format == TEXTURE_FORMAT_RGBA) {
|
||||
assertNotNull(data.rgbaColors, "RGBA color data cannot be NULL");
|
||||
} else if(texture->format == TEXTURE_FORMAT_PALETTE) {
|
||||
assertNotNull(data.paletted.indices, "Palette indices cannot be NULL");
|
||||
assertNotNull(data.paletted.palette, "Palette colors cannot be NULL");
|
||||
assertTrue(
|
||||
data.paletted.palette->count ==
|
||||
mathNextPowTwo(data.paletted.palette->count),
|
||||
"Palette color count must be a power of 2"
|
||||
);
|
||||
}
|
||||
|
||||
memoryZero(texture, sizeof(texture_t));
|
||||
texture->width = width;
|
||||
texture->height = height;
|
||||
texture->format = format;
|
||||
|
||||
errorChain(textureInitPlatform(texture, width, height, format, data));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t textureDispose(texture_t *texture) {
|
||||
assertNotNull(texture, "Texture cannot be NULL");
|
||||
|
||||
errorChain(textureDisposePlatform(texture));
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/texture/palette.h"
|
||||
#include "display/texture/textureplatform.h"
|
||||
|
||||
#ifndef textureInitPlatform
|
||||
#error "textureInitPlatform should not be defined."
|
||||
#endif
|
||||
#ifndef textureDisposePlatform
|
||||
#error "textureDisposePlatform should not be defined."
|
||||
#endif
|
||||
|
||||
typedef textureformatplatform_t textureformat_t;
|
||||
typedef textureplatform_t texture_t;
|
||||
|
||||
typedef union texturedata_u {
|
||||
struct {
|
||||
uint8_t *indices;
|
||||
palette_t *palette;
|
||||
} paletted;
|
||||
color_t *rgbaColors;
|
||||
} texturedata_t;
|
||||
|
||||
extern texture_t TEXTURE_WHITE;
|
||||
extern color_t TEXTURE_WHITE_PIXELS[4*4];
|
||||
extern texture_t TEXTURE_TEST;
|
||||
extern color_t TEXTURE_TEST_PIXELS[4*4];
|
||||
|
||||
/**
|
||||
* Initializes a texture.
|
||||
*
|
||||
* @param texture The texture to initialize.
|
||||
* @param width The width of the texture.
|
||||
* @param height The height of the texture.
|
||||
* @param format The format of the texture (e.g., GL_RGBA, GL_ALPHA).
|
||||
* @param data The data for the texture, the format changes per format.
|
||||
*/
|
||||
errorret_t textureInit(
|
||||
texture_t *texture,
|
||||
const int32_t width,
|
||||
const int32_t height,
|
||||
const textureformat_t format,
|
||||
const texturedata_t data
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes a texture.
|
||||
*
|
||||
* @param texture The texture to dispose.
|
||||
*/
|
||||
errorret_t textureDispose(texture_t *texture);
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "tileset.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/string.h"
|
||||
|
||||
void tilesetTileGetUV(
|
||||
const tileset_t *tileset,
|
||||
const uint16_t tileIndex,
|
||||
vec4 outUV
|
||||
) {
|
||||
const uint16_t column = tileIndex % tileset->columns;
|
||||
const uint16_t row = tileIndex / tileset->columns;
|
||||
tilesetPositionGetUV(tileset, column, row, outUV);
|
||||
}
|
||||
|
||||
void tilesetPositionGetUV(
|
||||
const tileset_t *tileset,
|
||||
const uint16_t column,
|
||||
const uint16_t row,
|
||||
vec4 outUV
|
||||
) {
|
||||
assertNotNull(tileset, "Tileset cannot be NULL");
|
||||
assertTrue(column < tileset->columns, "Column index out of bounds");
|
||||
assertTrue(row < tileset->rows, "Row index out of bounds");
|
||||
|
||||
outUV[0] = ((float_t)column) * tileset->uv[0];
|
||||
outUV[1] = ((float_t)row) * tileset->uv[1];
|
||||
outUV[2] = outUV[0] + tileset->uv[0];
|
||||
outUV[3] = outUV[1] + tileset->uv[1];
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct tileset_s {
|
||||
uint16_t tileWidth;
|
||||
uint16_t tileHeight;
|
||||
uint16_t tileCount;
|
||||
uint16_t columns;
|
||||
uint16_t rows;
|
||||
vec2 uv;
|
||||
} tileset_t;
|
||||
|
||||
/**
|
||||
* Gets the UV coordinates for a tile index in the tileset.
|
||||
*
|
||||
* @param tileset The tileset to get the UV coordinates from.
|
||||
* @param tileIndex The index of the tile to get the UV coordinates for.
|
||||
* @param outUV The output UV coordinates (vec4).
|
||||
*/
|
||||
void tilesetTileGetUV(
|
||||
const tileset_t *tileset,
|
||||
const uint16_t tileIndex,
|
||||
vec4 outUV
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the UV coordinates for a tile position in the tileset.
|
||||
*
|
||||
* @param tileset The tileset to get the UV coordinates from.
|
||||
* @param column The column of the tile to get the UV coordinates for.
|
||||
* @param row The row of the tile to get the UV coordinates for.
|
||||
* @param outUV The output UV coordinates (vec4).
|
||||
*/
|
||||
void tilesetPositionGetUV(
|
||||
const tileset_t *tileset,
|
||||
const uint16_t column,
|
||||
const uint16_t row,
|
||||
vec4 outUV
|
||||
);
|
||||
Reference in New Issue
Block a user