Compare commits

...

20 Commits

Author SHA1 Message Date
1411c2e96b Allow different texture formats. 2025-08-18 22:51:42 -05:00
6f42a6e195 Moved entitydir to direction_t 2025-08-18 22:28:22 -05:00
1e57183ca1 Load is now rounded compile time. 2025-08-18 21:46:12 -05:00
d79e12ffaa Switched to using tile coordinates, losing my angled movement unfortunately. 2025-08-18 21:40:01 -05:00
bcb1616201 Matrices 2025-08-18 19:18:55 -05:00
38127d9260 Ortho re-added 2025-08-18 19:15:37 -05:00
d48d927202 textbox renders. 2025-08-17 17:58:53 -05:00
150a410f00 Is it really that time again? 2025-08-17 16:39:42 -05:00
fbbd2176fd Moved render UI code 2025-08-17 16:31:08 -05:00
3e19771d8f Time dr freeman? 2025-08-17 16:18:56 -05:00
3d4317260f Time stuff 2025-08-17 16:11:22 -05:00
6d6a0e4886 Technically rendering everything. 2025-08-17 14:34:51 -05:00
91b93b5b1e add quit command 2025-08-17 12:55:08 -05:00
c3310a036f Nothing really changed 2025-08-16 14:52:52 -05:00
69acbd017c Fixed framebuffer 2025-08-15 21:45:21 -05:00
81ed15b171 Builds on PSP again. 2025-08-15 21:37:44 -05:00
d1ab8b0cc8 Framebuffer done. 2025-08-15 18:50:43 -05:00
cbdc271a5e Framebuffer first 2025-08-15 18:39:19 -05:00
3c908dc1ed Camera 2025-08-15 15:56:30 -05:00
217f00ff4c Font texture. 2025-08-12 22:32:57 -05:00
87 changed files with 2021 additions and 1666 deletions

View File

@@ -10,8 +10,8 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
if(NOT DEFINED DUSK_TARGET_SYSTEM) if(NOT DEFINED DUSK_TARGET_SYSTEM)
# set(DUSK_TARGET_SYSTEM "linux") # set(DUSK_TARGET_SYSTEM "psp")
set(DUSK_TARGET_SYSTEM "psp") set(DUSK_TARGET_SYSTEM "linux")
endif() endif()
# Prep cache # Prep cache

View File

@@ -1,257 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "fixed.h"
#include "assert/assert.h"
float_t fx248Fromi32(const int32_t b) {
return (float_t)b << FIXED248_FRACTION_BITS;
}
float_t fx248Fromu32(const uint32_t b) {
return (float_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
float_t fx248Fromf32(const float_t b) {
return (float_t)(b * (1 << FIXED248_FRACTION_BITS));
}
float_t fx248Fromu16(const uint16_t b) {
return (float_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
float_t fx248Fromu8(const uint8_t b) {
return (float_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
int32_t fx248Toi32(const float_t a) {
return a >> FIXED248_FRACTION_BITS;
}
uint32_t fx248Tou32(const float_t a) {
return (uint32_t)(a >> FIXED248_FRACTION_BITS);
}
float_t fx248Tof32(const float_t a) {
return (float_t)a / (1 << FIXED248_FRACTION_BITS);
}
uint16_t fx248Tou16(const float_t a) {
return (uint16_t)(a >> FIXED248_FRACTION_BITS);
}
uint8_t fx248Tou8(const float_t a) {
return (uint8_t)(a >> FIXED248_FRACTION_BITS);
}
float_t fx248Addfx248(const float_t a, const float_t b) {
return a + b;
}
float_t fx248Addi32(const float_t a, const int32_t b) {
return fx248Addfx248(a, fx248Fromi32(b));
}
float_t fx248Addu32(const float_t a, const uint32_t b) {
return fx248Addfx248(a, fx248Fromu32(b));
}
float_t fx248Addf32(const float_t a, const float_t b) {
return fx248Addfx248(a, fx248Fromf32(b));
}
float_t fx248Subfx248(const float_t a, const float_t b) {
return a - b;
}
float_t fx248Subi32(const float_t a, const int32_t b) {
return fx248Subfx248(a, fx248Fromi32(b));
}
float_t fx248Subu32(const float_t a, const uint32_t b) {
return fx248Subfx248(a, fx248Fromu32(b));
}
float_t fx248Subf32(const float_t a, const float_t b) {
return fx248Subfx248(a, fx248Fromf32(b));
}
float_t fx248Mulfx248(const float_t a, const float_t b) {
return (float_t)(((int64_t)a * (int64_t)b) >> FIXED248_FRACTION_BITS);
}
float_t fx248Muli32(const float_t a, const int32_t b) {
return (float_t)(((int64_t)a * (int64_t)b) >> FIXED248_FRACTION_BITS);
}
float_t fx248Mulu32(const float_t a, const uint32_t b) {
return (float_t)(
((int64_t)a * (int64_t)(int32_t)b
) >> FIXED248_FRACTION_BITS);
}
float_t fx248Mulf32(const float_t a, const float_t b) {
return (float_t)((
(int64_t)a * (int64_t)(b * (1 << FIXED248_FRACTION_BITS))
) >> FIXED248_FRACTION_BITS);
}
float_t fx248Divfx248(const float_t a, const float_t b) {
assertFalse(b == 0, "Division by zero in fx248Divfx248");
return (float_t)(((int64_t)a << FIXED248_FRACTION_BITS) / (int64_t)b);
}
float_t fx248Divi32(const float_t a, const int32_t b) {
assertFalse(b == 0, "Division by zero in fx248Divi32");
return (float_t)(((int64_t)a << FIXED248_FRACTION_BITS) / (int64_t)b);
}
float_t fx248Divu32(const float_t a, const uint32_t b) {
assertFalse(b == 0, "Division by zero in fx248Divu32");
return (float_t)(
((int64_t)a << FIXED248_FRACTION_BITS
) / (int64_t)(int32_t)b);
}
float_t fx248Divf32(const float_t a, const float_t b) {
assertFalse(b == 0, "Division by zero in fx248Divf32");
return (float_t)((
(int64_t)a << FIXED248_FRACTION_BITS
) / (int64_t)(b * (1 << FIXED248_FRACTION_BITS)));
}
float_t fx248Floor(const float_t a) {
return a & ~((1 << FIXED248_FRACTION_BITS) - 1);
}
float_t fx248Ceil(const float_t a) {
if(a & ((1 << FIXED248_FRACTION_BITS) - 1)) {
return (a & ~((1 << FIXED248_FRACTION_BITS) - 1)) + (1 << FIXED248_FRACTION_BITS);
}
return a;
}
float_t fx248Round(const float_t a) {
if(a & ((1 << (FIXED248_FRACTION_BITS - 1)) - 1)) {
return (a & ~((1 << FIXED248_FRACTION_BITS) - 1)) + (1 << FIXED248_FRACTION_BITS);
}
return a & ~((1 << FIXED248_FRACTION_BITS) - 1);
}
uint32_t fx248Flooru32(const float_t a) {
return (uint32_t)((a >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
}
uint32_t fx248Ceilu32(const float_t a) {
return (uint32_t)(((a + ((1 << FIXED248_FRACTION_BITS) - 1)) >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
}
uint32_t fx248Roundu32(const float_t a) {
return (uint32_t)(((a + (1 << (FIXED248_FRACTION_BITS - 1))) >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
}
float_t fx248Sqrt(const float_t a) {
if(a == 0) return 0;
float_t y = a > FIXED248(1, 0) ? a : FIXED248(1, 0);
float_t last = 0;
int max_iter = 16;
while(y != last && max_iter-- > 0) {
last = y;
int32_t div = (int32_t)(((int64_t)a << FIXED248_FRACTION_BITS) / y);
y = (y + div) >> 1;
}
return y;
}
float_t fx248Max(const float_t a, const float_t b) {
return (a > b) ? a : b;
}
float_t fx248Min(const float_t a, const float_t b) {
return (a < b) ? a : b;
}
float_t fx248Clamp(
const float_t a,
const float_t min,
const float_t max
) {
return (a < min) ? min : (a > max) ? max : a;
}
float_t fx248Abs(const float_t a) {
return (a < 0) ? -a : a;
}
float_t fx248Atan2(
const float_t y,
const float_t x
) {
// Handle special cases
if (x == 0) {
if (y > 0) return FX248_HALF_PI;
if (y < 0) return -FX248_HALF_PI;
return 0;
}
// Use absolute values for quadrant correction
float_t abs_y = y;
if (abs_y < 0) abs_y = -abs_y;
float_t angle;
if (abs_y < fx248Abs(x)) {
float_t z = fx248Divfx248(y, x);
float_t z2 = fx248Mulfx248(z, z);
float_t z3 = fx248Mulfx248(z2, z);
float_t z5 = fx248Mulfx248(z3, z2);
angle = fx248Subfx248(
fx248Addfx248(z, fx248Divfx248(z5, fx248Fromi32(5))),
fx248Divfx248(z3, fx248Fromi32(3))
);
if (x < 0) {
if (y < 0) {
angle -= FX248_PI;
} else {
angle += FX248_PI;
}
}
} else {
float_t z = fx248Divfx248(x, y);
float_t z2 = fx248Mulfx248(z, z);
float_t z3 = fx248Mulfx248(z2, z);
float_t z5 = fx248Mulfx248(z3, z2);
angle = fx248Subfx248(
fx248Addfx248(z, fx248Divfx248(z5, fx248Fromi32(5))),
fx248Divfx248(z3, fx248Fromi32(3))
);
if (y > 0) {
angle = FX248_HALF_PI - angle;
} else {
angle = -FX248_HALF_PI - angle;
}
}
return angle;
}

View File

@@ -1,379 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef int32_t float_t;
#define FIXED248_FRACTION_BITS 8
#define FIXED248_HIGH_MULTIPLIER (1 << FIXED248_FRACTION_BITS)
#define FIXED248_MIN INT32_MIN
#define FIXED248_MAX INT32_MAX
#define FIXED248(i, f) ((float_t)( \
((i) << FIXED248_FRACTION_BITS) + \
(((f) * FIXED248_HIGH_MULTIPLIER) / 100) \
))
#define FIXED248_ONE (FIXED248(1, 0))
#define FIXED248_ZERO (FIXED248(0, 0))
#define FX248_PI 804
#define FX248_HALF_PI 402
#define FX248_3PI_4 603
#define FX248_NEG_PI -804
/**
* Convert an int32_t value to a float_t value.
*
* @param b The int32_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromi32(const int32_t b);
/**
* Convert a uint32_t value to a float_t value.
*
* @param b The uint32_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromu32(const uint32_t b);
/**
* Convert a float_t value to a float_t value.
*
* @param b The float_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromf32(const float_t b);
/**
* Convert a uint16_t value to a float_t value.
*
* @param b The uint16_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromu16(const uint16_t b);
/**
* Convert a uint8_t value to a float_t value.
*
* @param b The uint8_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Fromu8(const uint8_t b);
/**
* Convert a float_t value to an int32_t value.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
int32_t fx248Toi32(const float_t a);
/**
* Convert a float_t value to a uint32_t value.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Tou32(const float_t a);
/**
* Convert a float_t value to a float_t value.
*
* @param a The float_t value to convert.
* @return The converted float_t value.
*/
float_t fx248Tof32(const float_t a);
/**
* Convert a float_t value to a uint16_t value.
*
* @param a The float_t value to convert.
* @return The converted uint16_t value.
*/
uint16_t fx248Tou16(const float_t a);
/**
* Convert a float_t value to an uint8_t value.
*
* @param a The float_t value to convert.
* @return The converted uint8_t value.
*/
uint8_t fx248Tou8(const float_t a);
/**
* Add a float_t value to another float_t value.
*
* @param a First float_t value.
* @param b Second float_t value to add to the first value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addfx248(const float_t a, const float_t b);
/**
* Add an int32_t value to a float_t value.
*
* @param a The float_t value to which the int32_t will be added.
* @param b The int32_t value to add to the float_t value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addi32(const float_t a, const int32_t b);
/**
* Add a uint32_t value to a float_t value.
*
* @param a The float_t value to which the uint32_t will be added.
* @param b The uint32_t value to add to the float_t value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addu32(const float_t a, const uint32_t b);
/**
* Add a float_t value to a float_t value.
*
* @param a Pointer to the float_t value (will be modified).
* @param b The float_t value to add to the float_t value.
* @return The result of the addition as a float_t value.
*/
float_t fx248Addf32(const float_t a, const float_t b);
/**
* Subtract a float_t value from another float_t value.
*
* @param a First float_t value.
* @param b The float_t value to subtract from the first value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subfx248(const float_t a, const float_t b);
/**
* Subtract an int32_t value from a float_t value.
*
* @param a The float_t value from which the int32_t will be subtracted.
* @param b The int32_t value to subtract from the float_t value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subi32(const float_t a, const int32_t b);
/**
* Subtract a uint32_t value from a float_t value.
*
* @param a The float_t value from which the uint32_t will be subtracted.
* @param b The uint32_t value to subtract from the float_t value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subu32(const float_t a, const uint32_t b);
/**
* Subtract a float_t value from a float_t value.
*
* @param a The float_t value from which the float_t will be subtracted.
* @param b The float_t value to subtract from the float_t value.
* @return The result of the subtraction as a float_t value.
*/
float_t fx248Subf32(const float_t a, const float_t b);
/**
* Multiply two float_t values.
*
* @param a First float_t value.
* @param b Second float_t value to multiply with the first value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Mulfx248(const float_t a, const float_t b);
/**
* Multiply a float_t value by an int32_t value.
*
* @param a The float_t value to multiply.
* @param b The int32_t value to multiply with the float_t value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Muli32(const float_t a, const int32_t b);
/**
* Multiply a float_t value by a uint32_t value.
*
* @param a The float_t value to multiply.
* @param b The uint32_t value to multiply with the float_t value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Mulu32(const float_t a, const uint32_t b);
/**
* Multiply a float_t value by a float_t value.
*
* @param a The float_t value to multiply.
* @param b The float_t value to multiply with the float_t value.
* @return The result of the multiplication as a float_t value.
*/
float_t fx248Mulf32(const float_t a, const float_t b);
/**
* Divide two float_t values.
*
* @param a The float_t value to be divided.
* @param b The float_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divfx248(const float_t a, const float_t b);
/**
* Divide a float_t value by an int32_t value.
*
* @param a The float_t value to be divided.
* @param b The int32_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divi32(const float_t a, const int32_t b);
/**
* Divide a float_t value by a uint32_t value.
*
* @param a The float_t value to be divided.
* @param b The uint32_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divu32(const float_t a, const uint32_t b);
/**
* Divide a float_t value by a float_t value.
*
* @param a The float_t value to be divided.
* @param b The float_t value to divide by.
* @return The result of the division as a float_t value.
*/
float_t fx248Divf32(const float_t a, const float_t b);
/**
* Convert a float_t value to an int32_t value, rounding towards zero.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
float_t fx248Floor(const float_t a);
/**
* Convert a float_t value to an int32_t value, rounding towards positive
* infinity.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
float_t fx248Ceil(const float_t a);
/**
* Convert a float_t value to an int32_t value, rounding to the nearest
* integer.
*
* @param a The float_t value to convert.
* @return The converted int32_t value.
*/
float_t fx248Round(const float_t a);
/**
* Convert a float_t value to a uint32_t value, rounding towards zero.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Flooru32(const float_t a);
/**
* Convert a float_t value to a uint32_t value, rounding towards positive
* infinity.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Ceilu32(const float_t a);
/**
* Convert a float_t value to a uint32_t value, rounding to the nearest
* integer.
*
* @param a The float_t value to convert.
* @return The converted uint32_t value.
*/
uint32_t fx248Roundu32(const float_t a);
/**
* Returns the square root of a float_t value.
*
* @param a The float_t value to calculate the square root of.
*/
float_t fx248Sqrt(const float_t a);
/**
* Returns the maximum of two float_t values.
*
* @param a First float_t value.
* @param b Second float_t value.
* @return The maximum of the two values.
*/
float_t fx248Max(const float_t a, const float_t b);
/**
* Returns the minimum of two float_t values.
*
* @param a First float_t value.
* @param b Second float_t value.
* @return The minimum of the two values.
*/
float_t fx248Min(const float_t a, const float_t b);
/**
* Clamp a float_t value between a minimum and maximum value.
*
* @param a The float_t value to clamp.
* @param min The minimum value to clamp to.
* @param max The maximum value to clamp to.
* @return The clamped float_t value.
*/
float_t fx248Clamp(
const float_t a,
const float_t min,
const float_t max
);
/**
* Returns the absolute value of a float_t value.
*
* @param a The float_t value to calculate the absolute value of.
* @return The absolute value as a float_t value.
*/
float_t fx248Abs(const float_t a);
/**
* Calculate the arctangent of a float_t value.
*
* @param y Y coordinate value.
* @param x X coordinate value.
* @return The arctangent of the value as a float_t value.
*/
float_t fx248Atan2(
const float_t y,
const float_t x
);

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(FetchContent)
FetchContent_Declare(
cglm
GIT_REPOSITORY https://github.com/recp/cglm.git
GIT_TAG v0.9.6
)
FetchContent_MakeAvailable(cglm)
set(cglm_FOUND ON)

View File

@@ -20,6 +20,7 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
game.c game.c
input.c input.c
time.c
) )
# Subdirs # Subdirs
@@ -31,7 +32,6 @@ add_subdirectory(entity)
add_subdirectory(event) add_subdirectory(event)
add_subdirectory(item) add_subdirectory(item)
add_subdirectory(locale) add_subdirectory(locale)
add_subdirectory(physics)
add_subdirectory(ui) add_subdirectory(ui)
add_subdirectory(util) add_subdirectory(util)
add_subdirectory(world) add_subdirectory(world)

View File

@@ -9,4 +9,7 @@ target_sources(${DUSK_TARGET_NAME}
console.c console.c
consolecmd.c consolecmd.c
consolevar.c consolevar.c
) )
# Subdirectories
add_subdirectory(cmd)

View File

@@ -6,5 +6,4 @@
# Sources # Sources
target_sources(${DUSK_TARGET_NAME} target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
physics.c
) )

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "console/console.h"
void cmdEcho(const consolecmdexec_t *exec) {
assertTrue(
exec->argc >= 1,
"echo command requires 1 argument."
);
consolePrint("%s", exec->argv[0]);
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "console/console.h"
void cmdGet(const consolecmdexec_t *exec) {
assertTrue(
exec->argc >= 1,
"Get command requires 1 argument."
);
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[i];
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
consolePrint("%s", var->value);
return;
}
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
}

View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "console/console.h"
#include "game.h"
void cmdQuit(const consolecmdexec_t *exec) {
consolePrint("Quitting application...");
GAME.running = false;
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "console/console.h"
void cmdSet(const consolecmdexec_t *exec) {
assertTrue(exec->argc >= 2, "set command requires 2 arguments.");
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[i];
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
consoleVarSetValue(var, exec->argv[1]);
consolePrint("%s %s", var->name, var->value);
for(i = 0; i < var->eventCount; i++) {
assertNotNull(var->events[i], "Event is NULL");
var->events[i](var);
}
return;
}
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
}

View File

@@ -9,6 +9,11 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/string.h" #include "util/string.h"
#include "console/cmd/cmdquit.h"
#include "console/cmd/cmdecho.h"
#include "console/cmd/cmdset.h"
#include "console/cmd/cmdget.h"
#include "input.h" #include "input.h"
console_t CONSOLE; console_t CONSOLE;
@@ -19,6 +24,7 @@ void consoleInit() {
// Register the get and set command. // Register the get and set command.
CONSOLE.cmdGet = consoleRegCmd("get", cmdGet); CONSOLE.cmdGet = consoleRegCmd("get", cmdGet);
CONSOLE.cmdSet = consoleRegCmd("set", cmdSet); CONSOLE.cmdSet = consoleRegCmd("set", cmdSet);
consoleRegCmd("quit", cmdQuit);
consoleRegCmd("echo", cmdEcho); consoleRegCmd("echo", cmdEcho);
consolePrint(" = Dawn Console = "); consolePrint(" = Dawn Console = ");
@@ -266,51 +272,17 @@ void consoleExec(const char_t *line) {
} }
} }
void cmdGet(const consolecmdexec_t *exec) {
assertTrue(
exec->argc >= 1,
"Get command requires 1 argument."
);
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[i];
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
consolePrint("%s", var->value);
return;
}
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
}
void cmdSet(const consolecmdexec_t *exec) {
assertTrue(exec->argc >= 2, "set command requires 2 arguments.");
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
consolevar_t *var = &CONSOLE.variables[i];
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
consoleVarSetValue(var, exec->argv[1]);
consolePrint("%s %s", var->name, var->value);
for(i = 0; i < var->eventCount; i++) {
assertNotNull(var->events[i], "Event is NULL");
var->events[i](var);
}
return;
}
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
}
void cmdEcho(const consolecmdexec_t *exec) {
assertTrue(
exec->argc >= 1,
"echo command requires 1 argument."
);
consolePrint("%s", exec->argv[0]);
}
// May move these later // May move these later
void consoleUpdate() { void consoleUpdate() {
if(inputPressed(INPUT_BIND_CONSOLE)) {
CONSOLE.visible = !CONSOLE.visible;
if(CONSOLE.visible) {
consolePrint("Console opened.");
} else {
consolePrint("Console closed.");
}
}
for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) { for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) {
consolecmdexec_t *exec = &CONSOLE.execBuffer[i]; consolecmdexec_t *exec = &CONSOLE.execBuffer[i];
assertNotNull(exec->cmd, "Command execution has no command."); assertNotNull(exec->cmd, "Command execution has no command.");
@@ -354,28 +326,4 @@ void consoleUpdate() {
// Clear the exec buffer // Clear the exec buffer
CONSOLE.execBufferCount = 0; CONSOLE.execBufferCount = 0;
} }
// void consoleDraw() {
// if(!CONSOLE.open) return;
// size_t i = 0;
// char_t *line;
// int32_t fontSize = 10;
// do {
// line = CONSOLE.line[i];
// if(line[0] == '\0') {
// i++;
// continue;
// }
// DrawText(line, 0, i*fontSize, fontSize, YELLOW);
// i++;
// } while(i < CONSOLE_HISTORY_MAX);
// DrawText(
// CONSOLE.inputBuffer, 0,
// (CONSOLE_HISTORY_MAX + 1) * fontSize,
// fontSize,
// PINK
// );
// }

View File

@@ -102,4 +102,5 @@ void consoleUpdate();
void cmdGet(const consolecmdexec_t *exec); void cmdGet(const consolecmdexec_t *exec);
void cmdSet(const consolecmdexec_t *exec); void cmdSet(const consolecmdexec_t *exec);
void cmdEcho(const consolecmdexec_t *exec); void cmdEcho(const consolecmdexec_t *exec);
void cmdQuit(const consolecmdexec_t *exec);

View File

@@ -6,5 +6,46 @@
*/ */
#include "scene.h" #include "scene.h"
#include "world/overworld.h"
scene_t SCENE_CURRENT = SCENE_INITIAL; scene_t SCENE_CURRENT;
scenecallback_t SCENE_CALLBACKS[SCENE_COUNT] = {
[SCENE_INITIAL] = {
.init = NULL,
.update = NULL
},
[SCENE_OVERWORLD] = {
.init = overworldInit,
.update = overworldUpdate,
.dispose = NULL
}
};
void sceneInit(void) {
for(uint8_t i = 0; i < SCENE_COUNT; i++) {
if(SCENE_CALLBACKS[i].init) {
SCENE_CALLBACKS[i].init();
}
}
SCENE_CURRENT = SCENE_OVERWORLD;
}
void sceneSet(const scene_t scene) {
SCENE_CURRENT = scene;
}
void sceneUpdate(void) {
if(SCENE_CALLBACKS[SCENE_CURRENT].update) {
SCENE_CALLBACKS[SCENE_CURRENT].update();
}
}
void sceneDispose(void) {
for(uint8_t i = 0; i < SCENE_COUNT; i++) {
if(SCENE_CALLBACKS[i].dispose) {
SCENE_CALLBACKS[i].dispose();
}
}
}

View File

@@ -6,10 +6,42 @@
*/ */
#pragma once #pragma once
#include "dusk.h"
typedef enum { typedef enum {
SCENE_INITIAL, SCENE_INITIAL,
SCENE_OVERWORLD, SCENE_OVERWORLD,
SCENE_COUNT
} scene_t; } scene_t;
extern scene_t SCENE_CURRENT; typedef struct {
void (*init)(void);
void (*update)(void);
void (*dispose)(void);
} scenecallback_t;
extern scene_t SCENE_CURRENT;
extern scenecallback_t SCENE_CALLBACKS[SCENE_COUNT];
/**
* Initializes the scene module.
*/
void sceneInit(void);
/**
* Sets the current scene.
*
* @param scene The scene to set.
*/
void sceneSet(const scene_t scene);
/**
* Updates the current scene.
*/
void sceneUpdate(void);
/**
* Disposes of the current scene.
*/
void sceneDispose(void);

View File

@@ -6,6 +6,7 @@
# Sources # Sources
target_sources(${DUSK_TARGET_NAME} target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
direction.c
entity.c entity.c
player.c player.c
npc.c npc.c

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "direction.h"
#include "assert/assert.h"
float_t directionToAngle(const direction_t dir) {
switch(dir) {
case DIRECTION_NORTH: return (M_PI_2);
case DIRECTION_SOUTH: return -(M_PI_2);
case DIRECTION_EAST: return 0;
case DIRECTION_WEST: return (M_PI);
default: return 0; // Should never happen
}
}
void directionGetCoordinates(
const direction_t dir,
int8_t *x, int8_t *y
) {
assertNotNull(x, "X coordinate pointer cannot be NULL");
assertNotNull(y, "Y coordinate pointer cannot be NULL");
switch(dir) {
case DIRECTION_NORTH:
*x = 0;
*y = -1;
break;
case DIRECTION_SOUTH:
*x = 0;
*y = 1;
break;
case DIRECTION_EAST:
*x = 1;
*y = 0;
break;
case DIRECTION_WEST:
*x = -1;
*y = 0;
break;
default:
assertUnreachable("Invalid direction");
break;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
DIRECTION_SOUTH = 0,
DIRECTION_EAST = 1,
DIRECTION_WEST = 2,
DIRECTION_NORTH = 3,
DIRECTION_UP = DIRECTION_NORTH,
DIRECTION_DOWN = DIRECTION_SOUTH,
DIRECTION_LEFT = DIRECTION_WEST,
DIRECTION_RIGHT = DIRECTION_EAST,
} direction_t;
/**
* Converts a direction to an angle in float_t format.
*
* @param dir The direction to convert.
* @return The angle corresponding to the direction.
*/
float_t directionToAngle(const direction_t dir);
/**
* Gets the relative coordinates for a given direction.
*
* @param dir The direction to get coordinates for.
* @param x Pointer to store the x coordinate.
* @param y Pointer to store the y coordinate.
*/
void directionGetCoordinates(
const direction_t dir,
int8_t *x, int8_t *y
);

View File

@@ -10,7 +10,7 @@
#include "util/memory.h" #include "util/memory.h"
#include "world/world.h" #include "world/world.h"
#include "world/tiledata.h" #include "world/tiledata.h"
#include "physics/physics.h" #include "time.h"
entity_t ENTITIES[ENTITY_COUNT_MAX] = {0}; entity_t ENTITIES[ENTITY_COUNT_MAX] = {0};
@@ -42,8 +42,6 @@ void entityLoad(entity_t *entity, const entity_t *source) {
entity->type = source->type; entity->type = source->type;
entity->x = source->x; entity->x = source->x;
entity->y = source->y; entity->y = source->y;
entity->vx = source->vx;
entity->vy = source->vy;
entity->dir = source->dir; entity->dir = source->dir;
entity->id = source->id; entity->id = source->id;
@@ -60,73 +58,74 @@ void entityUpdate(entity_t *entity) {
); );
ENTITY_CALLBACKS[entity->type].update(entity); ENTITY_CALLBACKS[entity->type].update(entity);
if(entity->vx == 0.0f && entity->vy == 0.0f) return; if(entity->subX > 0) {
entity->subX -= entity->moveSpeed;
float_t newX = entity->x + entity->vx; } else if(entity->subX < 0) {
float_t newY = entity->y + entity->vy; entity->subX += entity->moveSpeed;
float_t halfTileWH = TILE_WIDTH_HEIGHT / 2.0f;
// Because all hit detection is done assuming the entity is a circle, with
// its position centered, we need to precalc these;
float_t selfCircX = newX + halfTileWH;
float_t selfCircY = newY + halfTileWH;
float_t selfCircR = halfTileWH;
// Check for collisions with tiles
float_t tileStartX = floorf((newX - halfTileWH) / TILE_WIDTH_HEIGHT);
float_t tileStartY = floorf((newY - halfTileWH) / TILE_WIDTH_HEIGHT);
float_t tileEndX = ceilf((newX + halfTileWH) / TILE_WIDTH_HEIGHT);
float_t tileEndY = ceilf((newY + halfTileWH) / TILE_WIDTH_HEIGHT);
// For each tile
for(float_t y = tileStartY; y <= tileEndY; y += 1) {
for(float_t x = tileStartX; x <= tileEndX; x += 1) {
uint16_t tileX = (uint16_t)x;
uint16_t tileY = (uint16_t)y;
uint16_t chunkX = tileX / CHUNK_WIDTH;
uint16_t chunkY = tileY / CHUNK_HEIGHT;
chunk_t *chunk = chunkGetChunkAt(chunkX, chunkY);
if(chunk == NULL) continue;
uint8_t chunkTileX = tileX % CHUNK_WIDTH;
uint8_t chunkTileY = tileY % CHUNK_HEIGHT;
tile_t tile = chunk->tilesBase[chunkTileY * CHUNK_WIDTH + chunkTileX];
collisionresult_t collision = physicsCheckCircleTile(
selfCircX, selfCircY, selfCircR, x, y, tile
);
if(collision.hit && collision.depth > 0.01f) {
float_t slideX = collision.normalX * collision.depth;
float_t slideY = collision.normalY * collision.depth;
newX -= slideX;
newY -= slideY;
}
}
} }
// Check for collisions with other entities if(entity->subY > 0) {
entity_t *otherEntity = ENTITIES; entity->subY -= entity->moveSpeed;
} else if(entity->subY < 0) {
entity->subY += entity->moveSpeed;
}
}
void entityMove(entity_t *entity, const uint8_t moveSpeed) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
assertFalse(
entityIsMoving(entity),
"Entity is already moving, cannot move again"
);
int8_t x = 0, y = 0;
directionGetCoordinates(entity->dir, &x, &y);
// entity in way?
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
if(ent != NULL) return;
entity->x += x;
entity->y += y;
entity->subX = TILE_WIDTH_HEIGHT * -x;
entity->subY = TILE_WIDTH_HEIGHT * -y;
entity->moveSpeed = moveSpeed;
}
void entityTurn(entity_t *entity, const direction_t dir) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
assertTrue(
dir >= DIRECTION_SOUTH && dir <= DIRECTION_NORTH, "Invalid direction"
);
assertFalse(
entityIsMoving(entity), "Entity is already moving, cannot turn"
);
entity->dir = dir;
}
bool_t entityIsMoving(const entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
return entity->subX != 0 || entity->subY != 0;
}
entity_t * entityGetAt(
const uint32_t tileX,
const uint32_t tileY
) {
entity_t *entity = ENTITIES;
do { do {
// Skip self and null entities if(entity->type == ENTITY_TYPE_NULL) continue;
if(otherEntity == entity || otherEntity->type == ENTITY_TYPE_NULL) continue; if(entity->x == tileX && entity->y == tileY) return entity;
} while((entity++) < &ENTITIES[ENTITY_COUNT_MAX - 1]);
float_t otherCircR = halfTileWH; return NULL;
}
// We DONT use selfCircX/Y here because the other entity is ALSO a circle.
collisionresult_t collision = physicsCheckCircleCircle(
newX, newY, selfCircR,
otherEntity->x, otherEntity->y, otherCircR
);
if(!collision.hit) continue;
// Collision with entity detected. Slide out of collision.
float_t slideX = collision.normalX * collision.depth;
float_t slideY = collision.normalY * collision.depth;
newX -= slideX;
newY -= slideY;
} while(++otherEntity < ENTITIES + ENTITY_COUNT_MAX);
entity->x = newX;
entity->y = newY;
}

View File

@@ -6,22 +6,13 @@
*/ */
#pragma once #pragma once
#include "direction.h"
#include "player.h" #include "player.h"
#include "npc.h" #include "npc.h"
#define ENTITY_COUNT_MAX 32 #define ENTITY_COUNT_MAX 32
#define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds
typedef enum { #define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds.
ENTITY_DIR_SOUTH = 0,
ENTITY_DIR_EAST = 1,
ENTITY_DIR_WEST = 2,
ENTITY_DIR_NORTH = 3,
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
ENTITY_DIR_RIGHT = ENTITY_DIR_EAST,
} entitydir_t;
typedef enum { typedef enum {
ENTITY_TYPE_NULL = 0, ENTITY_TYPE_NULL = 0,
@@ -32,10 +23,13 @@ typedef enum {
typedef struct _entity_t { typedef struct _entity_t {
uint32_t id;// Completely unique ID for this entity. uint32_t id;// Completely unique ID for this entity.
float_t x, y; uint32_t x, y;
float_t vx, vy; int8_t subX, subY;
uint8_t moveSpeed;
entitytype_t type; entitytype_t type;
entitydir_t dir; direction_t dir;
union { union {
npc_t npc; npc_t npc;
@@ -68,9 +62,38 @@ void entityLoad(entity_t *entity, const entity_t *source);
void entityUpdate(entity_t *entity); void entityUpdate(entity_t *entity);
/** /**
* Converts an entity direction to an angle in float_t format. * Moves the entity by the specified x and y offsets.
* *
* @param dir The entity direction to convert. * @param entity Pointer to the entity to move.
* @return The angle corresponding to the entity direction. * @param moveSpeed The speed at which to move the entity.
*/ */
float_t entityDirToAngle(const entitydir_t dir); void entityMove(entity_t *entity, const uint8_t moveSpeed);
/**
* Turns the entity to face the specified direction.
*
* @param entity Pointer to the entity to turn.
* @param dir The direction to turn the entity to.
*/
void entityTurn(entity_t *entity, const direction_t dir);
/**
* Returns whether or not an entity is currently moving.
*
* @param entity Pointer to the entity to check.
* @return True if the entity is moving, false otherwise.
*/
bool_t entityIsMoving(const entity_t *entity);
/**
* Gets the entity at the specified tile coordinates.
*
* @param tileX The x coordinate of the tile to get the entity from.
* @param tileY The y coordinate of the tile to get the entity from.
* @return Pointer to the entity at the specified coordinates, or NULL if no
* entity exists there.
*/
entity_t *entityGetAt(
const uint32_t tileX,
const uint32_t tileY
);

View File

@@ -19,7 +19,6 @@ void npcLoad(entity_t *entity, const entity_t *source) {
} }
void npcUpdate(entity_t *entity) { void npcUpdate(entity_t *entity) {
} }
void npcInteract(entity_t *player, entity_t *self) { void npcInteract(entity_t *player, entity_t *self) {

View File

@@ -10,7 +10,6 @@
#include "input.h" #include "input.h"
#include "display/render.h" #include "display/render.h"
#include "world/world.h" #include "world/world.h"
#include "physics/physics.h"
#include "ui/uitextbox.h" #include "ui/uitextbox.h"
@@ -41,117 +40,55 @@ void playerEntityUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL"); assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER"); assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
if(UI_TEXTBOX.visible) { // TODO: make this just a method somewhere.
entity->vx = entity->vy = 0; if(UI_TEXTBOX.visible) return;
return; if(entityIsMoving(entity)) return;
}
const uint8_t moveSpeed = inputIsDown(INPUT_BIND_CANCEL) ? PLAYER_SPEED_RUN : PLAYER_SPEED_WALK;
if(inputIsDown(INPUT_BIND_UP)) { if(inputIsDown(INPUT_BIND_UP)) {
if(inputIsDown(INPUT_BIND_LEFT)) { if(entity->dir != DIRECTION_NORTH) {
entity->vx = -PLAYER_MOVE_SPEED_XY; entityTurn(entity, DIRECTION_NORTH);
entity->vy = -PLAYER_MOVE_SPEED_XY; return;
if(entity->dir != ENTITY_DIR_NORTH && entity->dir != ENTITY_DIR_WEST) {
entity->dir = ENTITY_DIR_NORTH;
}
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
entity->vx = PLAYER_MOVE_SPEED_XY;
entity->vy = -PLAYER_MOVE_SPEED_XY;
if(entity->dir != ENTITY_DIR_NORTH && entity->dir != ENTITY_DIR_EAST) {
entity->dir = ENTITY_DIR_NORTH;
}
} else {
entity->vy = -PLAYER_MOVE_SPEED;
entity->vx = 0;
entity->dir = ENTITY_DIR_NORTH;
} }
entityMove(entity, moveSpeed);
return;
} else if(inputIsDown(INPUT_BIND_DOWN)) { } else if(inputIsDown(INPUT_BIND_DOWN)) {
if(inputIsDown(INPUT_BIND_LEFT)) { if(entity->dir != DIRECTION_SOUTH) {
entity->vx = -PLAYER_MOVE_SPEED_XY; entityTurn(entity, DIRECTION_SOUTH);
entity->vy = PLAYER_MOVE_SPEED_XY; return;
if(entity->dir != ENTITY_DIR_SOUTH && entity->dir != ENTITY_DIR_WEST) {
entity->dir = ENTITY_DIR_SOUTH;
}
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
entity->vx = PLAYER_MOVE_SPEED_XY;
entity->vy = PLAYER_MOVE_SPEED_XY;
if(entity->dir != ENTITY_DIR_SOUTH && entity->dir != ENTITY_DIR_EAST) {
entity->dir = ENTITY_DIR_SOUTH;
}
} else {
entity->vy = PLAYER_MOVE_SPEED;
entity->vx = 0;
entity->dir = ENTITY_DIR_SOUTH;
} }
entityMove(entity, moveSpeed);
return;
} else if(inputIsDown(INPUT_BIND_LEFT)) { } else if(inputIsDown(INPUT_BIND_LEFT)) {
entity->vx = -PLAYER_MOVE_SPEED; if(entity->dir != DIRECTION_WEST) {
entity->vy = 0; entityTurn(entity, DIRECTION_WEST);
entity->dir = ENTITY_DIR_WEST; return;
}
entityMove(entity, moveSpeed);
return;
} else if(inputIsDown(INPUT_BIND_RIGHT)) { } else if(inputIsDown(INPUT_BIND_RIGHT)) {
entity->vx = PLAYER_MOVE_SPEED; if(entity->dir != DIRECTION_EAST) {
entity->vy = 0; entityTurn(entity, DIRECTION_EAST);
entity->dir = ENTITY_DIR_EAST; return;
} else { }
entity->vx = 0;
entity->vy = 0; entityMove(entity, moveSpeed);
return;
} }
// Interact // Interact
if(inputPressed(INPUT_BIND_ACTION)) { if(inputPressed(INPUT_BIND_ACTION)) {
entity_t *other = ENTITIES; int8_t x, y;
do { directionGetCoordinates(entity->dir, &x, &y);
if(other == entity || other->type == ENTITY_TYPE_NULL) { entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
other++;
continue;
}
// Is the other entity interactable? if(ent != NULL && ENTITY_CALLBACKS[ent->type].interact != NULL) {
if(ENTITY_CALLBACKS[other->type].interact == NULL) { assertTrue(ent->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
other++; ENTITY_CALLBACKS[ent->type].interact(entity, ent);
continue; }
}
float_t distanceX = other->x - entity->x;
float_t distanceY = other->y - entity->y;
float_t distance = sqrtf(distanceX * distanceX + distanceY * distanceY);
if(distance > PLAYER_INTERACT_RANGE) {
other++;
continue;
}
// Get angle
float_t angle = atan2f(distanceY, distanceX);
while(angle < 0) angle += M_PI;
float_t selfAngle = entityDirToAngle(entity->dir);
while(selfAngle < 0) selfAngle += M_PI;
// Check if angle is within range
float_t angleDiff = angle - selfAngle;
if(angleDiff > (M_PI_2)) angleDiff -= M_PI;
if(angleDiff < -M_PI_2) angleDiff += M_PI;
if(fabsf(angleDiff) > PLAYER_INTERACT_ANGLE) {
other++;
continue;
}
ENTITY_CALLBACKS[other->type].interact(entity, other);
entity->vx = 0;
entity->vy = 0;
other++;
} while(other != ENTITIES + ENTITY_COUNT_MAX);
}
}
float_t entityDirToAngle(const entitydir_t dir) {
switch(dir) {
case ENTITY_DIR_NORTH: return (M_PI_2);
case ENTITY_DIR_SOUTH: return -(M_PI_2);
case ENTITY_DIR_EAST: return 0;
case ENTITY_DIR_WEST: return (M_PI);
default: return 0; // Should never happen
} }
} }

View File

@@ -9,6 +9,9 @@
#include "dusk.h" #include "dusk.h"
#include "item/inventory.h" #include "item/inventory.h"
#define PLAYER_SPEED_WALK 1
#define PLAYER_SPEED_RUN 2
typedef struct _entity_t entity_t; typedef struct _entity_t entity_t;
typedef struct { typedef struct {
@@ -16,10 +19,6 @@ typedef struct {
} playerentity_t; } playerentity_t;
#define PLAYER_ENTITY_ID (UINT32_MAX-1) #define PLAYER_ENTITY_ID (UINT32_MAX-1)
#define PLAYER_MOVE_SPEED 1.0f
#define PLAYER_MOVE_SPEED_XY 0.7f
#define PLAYER_INTERACT_RANGE (TILE_WIDTH_HEIGHT + (TILE_WIDTH_HEIGHT / 3))
#define PLAYER_INTERACT_ANGLE 0.68359375f
extern inventory_t PLAYER_INVENTORY; extern inventory_t PLAYER_INVENTORY;

View File

@@ -14,26 +14,44 @@
#include "event/event.h" #include "event/event.h"
#include "ui/uitextbox.h" #include "ui/uitextbox.h"
#include "console/console.h" #include "console/console.h"
#include "util/memory.h"
#include "time.h"
game_t GAME;
void gameInit(void) { void gameInit(void) {
memoryZero(&GAME, sizeof(game_t));
GAME.running = true;
timeInit();
consoleInit(); consoleInit();
inputInit(); inputInit();
eventInit(); eventInit();
uiTextboxInit(); uiTextboxInit();
overworldInit(); sceneInit();
SCENE_CURRENT = SCENE_OVERWORLD;
} }
void gameUpdate(void) { void gameUpdate(void) {
overworldUpdate(); timeUpdate();
uiTextboxUpdate();
eventUpdate();
// Game logic is tied to 60FPS for now, saves me a lot of hassle with float
// issues
float_t timeSinceLastTick = TIME.time - TIME.lastTick;
while(timeSinceLastTick >= DUSK_TIME_STEP) {
sceneUpdate();
uiTextboxUpdate();
eventUpdate();
inputUpdate();
timeSinceLastTick -= DUSK_TIME_STEP;
TIME.lastTick = TIME.time;
}
if(inputPressed(INPUT_BIND_QUIT)) consoleExec("quit");
consoleUpdate(); consoleUpdate();
inputUpdate();
} }
void gameDispose(void) { void gameDispose(void) {
sceneDispose();
} }

View File

@@ -8,6 +8,12 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
typedef struct {
bool_t running;
} game_t;
extern game_t GAME;
/** /**
* Initializes the game, this should be called before any other game functions. * Initializes the game, this should be called before any other game functions.
* This should be called by the parent platform at a time that it deems * This should be called by the parent platform at a time that it deems

View File

@@ -20,20 +20,20 @@ void inputUpdate(void) {
INPUT.current = inputStateGet(); INPUT.current = inputStateGet();
} }
bool_t inputIsDown(const uint8_t bind) { bool_t inputIsDown(const inputbind_t bind) {
assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds"); assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds");
return (INPUT.current & bind) != 0; return (INPUT.current & bind) != 0;
} }
bool_t inputWasDown(const uint8_t bind) { bool_t inputWasDown(const inputbind_t bind) {
assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds"); assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds");
return (INPUT.previous & bind) != 0; return (INPUT.previous & bind) != 0;
} }
bool_t inputPressed(const uint8_t bind) { bool_t inputPressed(const inputbind_t bind) {
return inputIsDown(bind) && !inputWasDown(bind); return inputIsDown(bind) && !inputWasDown(bind);
} }
bool_t inputReleased(const uint8_t bind) { bool_t inputReleased(const inputbind_t bind) {
return !inputIsDown(bind) && inputWasDown(bind); return !inputIsDown(bind) && inputWasDown(bind);
} }

View File

@@ -8,13 +8,19 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
typedef uint8_t inputbind_t;
typedef inputbind_t inputstate_t;
#define INPUT_BIND_UP (1 << 0) #define INPUT_BIND_UP (1 << 0)
#define INPUT_BIND_DOWN (1 << 1) #define INPUT_BIND_DOWN (1 << 1)
#define INPUT_BIND_LEFT (1 << 2) #define INPUT_BIND_LEFT (1 << 2)
#define INPUT_BIND_RIGHT (1 << 3) #define INPUT_BIND_RIGHT (1 << 3)
#define INPUT_BIND_ACTION (1 << 4) #define INPUT_BIND_ACTION (1 << 4)
#define INPUT_BIND_CANCEL (1 << 5) #define INPUT_BIND_CANCEL (1 << 5)
#define INPUT_BIND_COUNT (INPUT_BIND_CANCEL + 1) #define INPUT_BIND_CONSOLE (1 << 6)
#define INPUT_BIND_QUIT (1 << 7)
#define INPUT_BIND_COUNT (INPUT_BIND_QUIT + 1)
typedef struct { typedef struct {
uint8_t current; uint8_t current;
@@ -38,7 +44,7 @@ void inputUpdate(void);
* *
* @return The current input state as a bitmask. * @return The current input state as a bitmask.
*/ */
uint8_t inputStateGet(void); inputstate_t inputStateGet(void);
/** /**
* Checks if a specific input bind is currently pressed. * Checks if a specific input bind is currently pressed.
@@ -46,7 +52,7 @@ uint8_t inputStateGet(void);
* @param bind The input bind to check. * @param bind The input bind to check.
* @return true if the bind is currently pressed, false otherwise. * @return true if the bind is currently pressed, false otherwise.
*/ */
bool_t inputIsDown(const uint8_t bind); bool_t inputIsDown(const inputbind_t bind);
/** /**
* Checks if a specific input bind was pressed in the last update. * Checks if a specific input bind was pressed in the last update.
@@ -54,7 +60,7 @@ bool_t inputIsDown(const uint8_t bind);
* @param bind The input bind to check. * @param bind The input bind to check.
* @return true if the bind was pressed in the last update, false otherwise. * @return true if the bind was pressed in the last update, false otherwise.
*/ */
bool_t inputWasDown(const uint8_t bind); bool_t inputWasDown(const inputbind_t bind);
/** /**
* Checks if a specific input bind was down this frame but not in the the * Checks if a specific input bind was down this frame but not in the the
@@ -63,7 +69,7 @@ bool_t inputWasDown(const uint8_t bind);
* @param bind The input bind to check. * @param bind The input bind to check.
* @return true if the bind is currently pressed, false otherwise. * @return true if the bind is currently pressed, false otherwise.
*/ */
bool_t inputPressed(const uint8_t bind); bool_t inputPressed(const inputbind_t bind);
/** /**
* Checks if a specific input bind was released this frame. * Checks if a specific input bind was released this frame.
@@ -71,4 +77,4 @@ bool_t inputPressed(const uint8_t bind);
* @param bind The input bind to check. * @param bind The input bind to check.
* @return true if the bind was released this frame, false otherwise. * @return true if the bind was released this frame, false otherwise.
*/ */
bool_t inputReleased(const uint8_t bind); bool_t inputReleased(const inputbind_t bind);

View File

@@ -1,293 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "physics.h"
#include "world/tiledata.h"
collisionresult_t physicsCheckCircleCircle(
float_t circle0x, float_t circle0y, float_t circle0r,
float_t circle1x, float_t circle1y, float_t circle1r
) {
collisionresult_t result;
// Compute vector between centers
float_t dx = circle1x - circle0x;
float_t dy = circle1y - circle0y;
// Distance squared between centers
float_t distSq = (dx * dx) + (dy * dy);
// Sum of radii
float_t rSum = circle0r + circle1r;
float_t rSumSq = rSum * rSum;
if(distSq > rSumSq) {
// No collision
result.hit = false;
return result;
}
// Collision: calculate normal and penetration depth
float_t dist = sqrtf(distSq);
// If centers are the same, pick arbitrary normal (1,0)
if(dist == 0) {
result.normalX = 1;
result.normalY = 0;
result.depth = rSum;
} else {
// Normalized direction from circle0 to circle1
result.normalX = dx / dist;
result.normalY = dy / dist;
// Penetration depth = sum of radii - distance
result.depth = rSum - dist;
}
result.hit = true;
return result;
}
collisionresult_t physicsCheckCircleAABB(
float_t circleX, float_t circleY, float_t circleR,
float_t aabbX, float_t aabbY,
float_t aabbWidth, float_t aabbHeight
) {
collisionresult_t result;
// Find the closest point on the AABB to the circle center
float_t closestX = fmaxf(
aabbX, fminf(circleX, aabbX + aabbWidth)
);
float_t closestY = fmaxf(
aabbY, fminf(circleY, aabbY + aabbHeight)
);
// Vector from circle center to closest point
float_t dx = closestX - circleX;
float_t dy = closestY - circleY;
// Distance squared from circle center to closest point
float_t distSq = (dx * dx) + (dy * dy);
// Check if distance is less than radius squared
if(distSq > (circleR * circleR)) {
result.hit = false;
return result;
}
// Collision: calculate normal and penetration depth
float_t dist = sqrtf(distSq);
if(dist <= 1) {
// Circle center is at the AABB corner
result.normalX = 1.0f;
result.normalY = 0.0f;
result.depth = circleR;
} else {
// Normalized direction from circle center to closest point
result.normalX = dx / dist;
result.normalY = dy / dist;
// Penetration depth = radius - distance
result.depth = circleR - dist;
}
result.hit = true;
return result;
}
void physicsClosestPointOnSegment(
float_t ax, float_t ay,
float_t bx, float_t by,
float_t px, float_t py,
float_t *outX, float_t *outY
) {
float_t abx = bx - ax;
float_t aby = by - ay;
float_t apx = px - ax;
float_t apy = py - ay;
float_t abLenSq = (abx * abx) + (aby * aby);
if(abLenSq == 0) {
*outX = ax;
*outY = ay;
return;
}
float_t t = apx * abx + apy * aby;
t /= abLenSq;
if(t < 0) t = 0;
if(t > 1) t = 1;
*outX = ax + (abx * t);
*outY = ay + (aby * t);
}
bool_t physicsIsPointInTriangle(
float_t px, float_t py,
float_t ax, float_t ay,
float_t bx, float_t by,
float_t cx, float_t cy
) {
float_t abx = bx - ax;
float_t aby = by - ay;
float_t bcx = cx - bx;
float_t bcy = cy - by;
float_t cax = ax - cx;
float_t cay = ay - cy;
float_t apx = px - ax;
float_t apy = py - ay;
float_t bpx = px - bx;
float_t bpy = py - by;
float_t cpx = px - cx;
float_t cpy = py - cy;
float_t cross1 = (abx * apy) - (aby * apx);
float_t cross2 = (bcx * bpy) - (bcy * bpx);
float_t cross3 = (cax * cpy) - (cay * cpx);
bool_t hasNeg = (
(cross1 < 0) ||
(cross2 < 0) ||
(cross3 < 0)
);
bool_t hasPos = (
(cross1 > 0) ||
(cross2 > 0) ||
(cross3 > 0)
);
return !(hasNeg && hasPos);
}
collisionresult_t physicsCheckCircleTriangle(
float_t circleX, float_t circleY, float_t circleR,
float_t triX0, float_t triY0,
float_t triX1, float_t triY1,
float_t triX2, float_t triY2
) {
collisionresult_t result = { .hit = false };
float_t vx[3] = { triX0, triX1, triX2 };
float_t vy[3] = { triY0, triY1, triY2 };
float_t closestX = 0;
float_t closestY = 0;
float_t minDistSq = FLT_MAX;
for(uint8_t i = 0; i < 3; ++i) {
uint8_t j = (i + 1) % 3;
float_t testX, testY;
physicsClosestPointOnSegment(
vx[i], vy[i], vx[j], vy[j],
circleX, circleY, &testX, &testY
);
float_t dx = circleX - testX;
float_t dy = circleY - testY;
float_t distSq = (dx * dx) + (dy * dy);
if(distSq < minDistSq) {
minDistSq = distSq;
closestX = testX;
closestY = testY;
result.normalX = dx;
result.normalY = dy;
}
}
float_t dist = sqrtf(minDistSq);
float_t invDist = (
(dist != 0) ?
(1.0f / dist) :
1.0f
);
result.normalX = -result.normalX * invDist;
result.normalY = -result.normalY * invDist;
if(physicsIsPointInTriangle(
circleX, circleY, vx[0], vy[0], vx[1], vy[1], vx[2], vy[2]
)) {
result.hit = true;
result.depth = circleR - dist;
return result;
}
if(dist < circleR) {
result.hit = true;
result.depth = circleR - dist;
}
return result;
}
collisionresult_t physicsCheckCircleTile(
float_t circleX, float_t circleY, float_t circleR,
float_t tileX, float_t tileY, tile_t tile
) {
collisionresult_t result;
#define tw (TILE_WIDTH_HEIGHT)
#define th (TILE_WIDTH_HEIGHT)
#define lx (tileX * tw)
#define ty (tileY * th)
#define rx (lx + tw)
#define by (ty + th)
switch(TILE_META_DATA[tile].solidType) {
case TILE_SOLID_FULL:
result = physicsCheckCircleAABB(
circleX, circleY, circleR,
lx, ty,
tw, th
);
break;
case TILE_SOLID_TRIANGLE_TOP_RIGHT:
result = physicsCheckCircleTriangle(
circleX, circleY, circleR,
rx, by,
rx, ty,
lx, ty
);
break;
case TILE_SOLID_TRIANGLE_TOP_LEFT:
result = physicsCheckCircleTriangle(
circleX, circleY, circleR,
lx, by,
lx, ty,
rx, ty
);
break;
case TILE_SOLID_TRIANGLE_BOTTOM_RIGHT:
result = physicsCheckCircleTriangle(
circleX, circleY, circleR,
rx, ty,
rx, by,
lx, by
);
break;
case TILE_SOLID_TRIANGLE_BOTTOM_LEFT:
result = physicsCheckCircleTriangle(
circleX, circleY, circleR,
lx, ty,
lx, by,
rx, by
);
break;
default:
result.hit = false;
break;
}
return result;
}

View File

@@ -1,125 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "world/tile.h"
typedef struct {
bool_t hit;
float_t normalX, normalY;
float_t depth;
} collisionresult_t;
/**
* Check for collision between two circles.
*
* @param circle0x X coordinate of the first circle's center.
* @param circle0y Y coordinate of the first circle's center.
* @param circle0r Radius of the first circle.
* @param circle1x X coordinate of the second circle's center.
* @param circle1y Y coordinate of the second circle's center.
* @param circle1r Radius of the second circle.
* @return A collisionresult_t structure containing collision information.
*/
collisionresult_t physicsCheckCircleCircle(
float_t circle0x, float_t circle0y, float_t circle0r,
float_t circle1x, float_t circle1y, float_t circle1r
);
/**
* Check for collision between a circle and an axis-aligned bounding box (AABB).
*
* @param circleX X coordinate of the circle's center.
* @param circleY Y coordinate of the circle's center.
* @param circleR Radius of the circle.
* @param aabb X coordinate of the AABB's top-left corner.
* @param aabbY Y coordinate of the AABB's top-left corner.
* @param aabbWidth Width of the AABB.
* @param aabbHeight Height of the AABB.
* @return A collisionresult_t structure containing collision information.
*/
collisionresult_t physicsCheckCircleAABB(
float_t circleX, float_t circleY, float_t circleR,
float_t aabb, float_t aabbY,
float_t aabbWidth, float_t aabbHeight
);
/**
* Calculate the closest point on a line segment to a point.
*
* @param ax X coordinate of the first endpoint of the segment.
* @param ay Y coordinate of the first endpoint of the segment.
* @param bx X coordinate of the second endpoint of the segment.
* @param by Y coordinate of the second endpoint of the segment.
* @param px X coordinate of the point.
* @param py Y coordinate of the point.
* @param outX Pointer to store the X coordinate of the closest point.
* @param outY Pointer to store the Y coordinate of the closest point.
*/
void physicsClosestPointOnSegment(
float_t ax, float_t ay,
float_t bx, float_t by,
float_t px, float_t py,
float_t *outX, float_t *outY
);
/**
* Check if a point is inside a triangle defined by three vertices.
*
* @param px X coordinate of the point.
* @param py Y coordinate of the point.
* @param x0 X coordinate of the first vertex of the triangle.
* @param y0 Y coordinate of the first vertex of the triangle.
* @param x1 X coordinate of the second vertex of the triangle.
* @param y1 Y coordinate of the second vertex of the triangle.
* @param x2 X coordinate of the third vertex of the triangle.
* @param y2 Y coordinate of the third vertex of the triangle.
* @return true if the point is inside the triangle, false otherwise.
*/
bool_t physicsIsPointInTriangle(
float_t px, float_t py,
float_t x0, float_t y0,
float_t x1, float_t y1,
float_t x2, float_t y2
);
/**
* Check for collision between a circle and a triangle.
*
* @param circleX X coordinate of the circle's center.
* @param circleY Y coordinate of the circle's center.
* @param circleR Radius of the circle.
* @param triX0 X coordinate of the first vertex of the triangle.
* @param triY0 Y coordinate of the first vertex of the triangle.
* @param triX1 X coordinate of the second vertex of the triangle.
* @param triY1 Y coordinate of the second vertex of the triangle.
* @param triX2 X coordinate of the third vertex of the triangle.
* @param triY2 Y coordinate of the third vertex of the triangle.
* @return A collisionresult_t structure containing collision information.
*/
collisionresult_t physicsCheckCircleTriangle(
float_t circleX, float_t circleY, float_t circleR,
float_t triX0, float_t triY0,
float_t triX1, float_t triY1,
float_t triX2, float_t triY2
);
/**
* Check for collision between a circle and a tile.
*
* @param circleX X coordinate of the circle's center.
* @param circleY Y coordinate of the circle's center.
* @param circleR Radius of the circle.
* @param tileX X coordinate of the tile's top-left corner.
* @param tileY Y coordinate of the tile's top-left corner.
* @param tile The tile to check against.
* @return A collisionresult_t structure containing collision information.
*/
collisionresult_t physicsCheckCircleTile(
float_t circleX, float_t circleY, float_t circleR,
float_t tileX, float_t tileY, tile_t tile
);

37
src/dusk/time.c Normal file
View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "time.h"
#include "util/memory.h"
#include "assert/assert.h"
dusktime_t TIME;
void timeInit(void) {
memoryZero(&TIME, sizeof(TIME));
// Set these to something non-zero.
TIME.lastTick = DUSK_TIME_STEP;
TIME.delta = TIME.realDelta = DUSK_TIME_STEP;
TIME.realTime = TIME.time = DUSK_TIME_STEP * 2;
}
void timeUpdate(void) {
TIME.realDelta = timeDeltaGet();
#if DUSK_TIME_DYNAMIC
TIME.delta = TIME.realDelta;
#else
TIME.delta = DUSK_TIME_PLATFORM_STEP;
#endif
assertTrue(TIME.delta >= 0.0f, "Time delta is negative");
assertTrue(TIME.realDelta >= 0.0f, "Real time delta is negative");
TIME.time += TIME.delta;
TIME.realTime += TIME.realDelta;
}

52
src/dusk/time.h Normal file
View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
float_t delta;
float_t lastTick;
float_t time;
float_t realDelta;
float_t realTime;
} dusktime_t;
extern dusktime_t TIME;
#define DUSK_TIME_STEP (1.0f / 60.0f) // Default to 60FPS
#ifndef DUSK_TIME_DYNAMIC
#define DUSK_TIME_DYNAMIC 1
#endif
#if DUSK_TIME_DYNAMIC == 0
#ifndef DUSK_TIME_PLATFORM_STEP
#define DUSK_TIME_PLATFORM_STEP DUSK_TIME_STEP
#endif
#endif
/**
* Initializes the time system.
*/
void timeInit(void);
/**
* Updates the time system
*/
void timeUpdate(void);
#if DUSK_TIME_DYNAMIC == 1
/**
* Gets the time delta since the last frame, in seconds. Tied to the
* platform.
*
* This will only get called once per gameUpdate.
*/
float_t timeDeltaGet(void);
#endif

View File

@@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
memory.c memory.c
string.c string.c
math.c
) )

19
src/dusk/util/math.c Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "math.h"
uint32_t mathNextPowTwo(uint32_t value) {
if (value == 0) return 1; // Handle zero case
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}

17
src/dusk/util/math.h Normal file
View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Finds the next power of two greater than or equal to the given value.
*
* @param value The value to find the next power of two for.
* @return The next power of two greater than or equal to the value.
*/
uint32_t mathNextPowTwo(uint32_t value);

View File

@@ -10,6 +10,8 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "world/world.h" #include "world/world.h"
void renderChunkUpdated(chunk_t *chunk);
chunkmap_t CHUNK_MAP; chunkmap_t CHUNK_MAP;
void chunkMapInit() { void chunkMapInit() {
@@ -261,6 +263,9 @@ void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
entityLoad(entity, data); entityLoad(entity, data);
data++; data++;
} }
// Allow the rendering platform to know this chunk is loaded.
renderChunkUpdated(chunk);
} }
void chunkUnload(chunk_t *chunk) { void chunkUnload(chunk_t *chunk) {

View File

@@ -7,16 +7,17 @@
#pragma once #pragma once
#include "tile.h" #include "tile.h"
#include "display/render.h"
#define CHUNK_MAP_WIDTH 4
#define CHUNK_MAP_HEIGHT 3
#define CHUNK_MAP_COUNT (CHUNK_MAP_WIDTH * CHUNK_MAP_HEIGHT)
#define CHUNK_WIDTH 8 #define CHUNK_WIDTH 8
#define CHUNK_HEIGHT 8 #define CHUNK_HEIGHT 8
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT) #define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
#define CHUNK_ENTITY_COUNT_MAX 8 #define CHUNK_ENTITY_COUNT_MAX 8
#define CHUNK_MAP_WIDTH (((RENDER_WIDTH / TILE_WIDTH_HEIGHT)/CHUNK_WIDTH)+2)
#define CHUNK_MAP_HEIGHT (((RENDER_HEIGHT / TILE_WIDTH_HEIGHT)/CHUNK_HEIGHT)+2)
#define CHUNK_MAP_COUNT (CHUNK_MAP_WIDTH * CHUNK_MAP_HEIGHT)
typedef struct { typedef struct {
uint16_t x, y; uint16_t x, y;
tile_t tilesBase[CHUNK_TILE_COUNT]; tile_t tilesBase[CHUNK_TILE_COUNT];

View File

@@ -47,8 +47,8 @@ void overworldUpdate() {
entity->type == ENTITY_TYPE_PLAYER, entity->type == ENTITY_TYPE_PLAYER,
"First entity must be player" "First entity must be player"
); );
OVERWORLD_CAMERA_X = (uint32_t)floorf(entity->x); OVERWORLD_CAMERA_X = entity->x * TILE_WIDTH_HEIGHT + entity->subX;
OVERWORLD_CAMERA_Y = (uint32_t)floorf(entity->y); OVERWORLD_CAMERA_Y = entity->y * TILE_WIDTH_HEIGHT + entity->subY;
uint16_t x, y; uint16_t x, y;
uint16_t halfWidth, halfHeight; uint16_t halfWidth, halfHeight;

View File

@@ -20,6 +20,8 @@ target_compile_definitions(${DUSK_TARGET_NAME}
RENDER_HEIGHT=272 RENDER_HEIGHT=272
RENDER_WINDOW_WIDTH_DEFAULT=480 RENDER_WINDOW_WIDTH_DEFAULT=480
RENDER_WINDOW_HEIGHT_DEFAULT=272 RENDER_WINDOW_HEIGHT_DEFAULT=272
# DUSK_TIME_DYNAMIC=0
DUSK_TIME_DYNAMIC=1
) )
# Includes # Includes

View File

@@ -12,11 +12,14 @@ target_compile_definitions(${DUSK_TARGET_NAME}
# Libs # Libs
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
find_package(cglm REQUIRED)
target_link_libraries(${DUSK_TARGET_NAME} target_link_libraries(${DUSK_TARGET_NAME}
PUBLIC PUBLIC
SDL2::SDL2 SDL2::SDL2
OpenGL::GL OpenGL::GL
GL
cglm
) )
@@ -31,6 +34,7 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
dusksdl2input.c dusksdl2input.c
main.c main.c
time.c
) )
# Subdirs # Subdirs

View File

@@ -8,11 +8,14 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
render.c render.c
renderbackbuffer.c renderbackbuffer.c
rendertext.c
renderconsole.c
renderscene.c
) )
# Subdirs # Subdirs
add_subdirectory(camera)
add_subdirectory(framebuffer)
add_subdirectory(mesh) add_subdirectory(mesh)
add_subdirectory(texture) add_subdirectory(overworld)
add_subdirectory(texture)
add_subdirectory(spritebatch)
add_subdirectory(scene)
add_subdirectory(ui)

View File

@@ -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_TARGET_NAME}
PRIVATE
camera.c
)

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "camera.h"
#include "display/render.h"
#include "world/overworld.h"
void cameraUIPush(void) {
glPushMatrix();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT);
mat4 ortho;
glm_ortho(
0.0f, (float_t)RENDER_WIDTH,
(float_t)RENDER_HEIGHT, 0.0f,
-1.0f, 1.0f,
ortho
);
glLoadMatrixf((const GLfloat*)ortho);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void cameraUIPop(void) {
glPopMatrix();
}
void cameraScreenPush(void) {
glPushMatrix();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
mat4 ortho;
#if RENDER_USE_FRAMEBUFFER
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(RENDER_WINDOW, &windowWidth, &windowHeight);
glViewport(0, 0, windowWidth, windowHeight);
glm_ortho(
0.0f, (float_t) windowWidth,
(float_t)windowHeight, 0.0f,
-1.0f, 1.0f,
ortho
);
#else
glm_ortho(
0.0f, (float_t)RENDER_WIDTH,
(float_t)RENDER_HEIGHT, 0.0f,
-1.0f, 1.0f,
ortho
);
#endif
glLoadMatrixf((const GLfloat*)ortho);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void cameraScreenPop(void) {
glPopMatrix();
}
void cameraOverworldPush(void) {
glPushMatrix();
glLoadIdentity();
#if RENDER_USE_FRAMEBUFFER
glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT);
#endif
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
const float_t fov = glm_rad(75.0f);
const float_t camOffset = 12.0f;
const float_t aspect = (float_t)RENDER_WIDTH / (float_t)RENDER_HEIGHT;
const float_t pixelPerfectOffset = (
tanf((glm_rad(180) - fov) / 2.0f) *
((float_t)RENDER_HEIGHT/ 2.0f)
);
vec3 look = {
OVERWORLD_CAMERA_X,
OVERWORLD_CAMERA_Y,
0.0f
};
vec3 eye = {
look[0],
look[1] + camOffset,
look[2] + pixelPerfectOffset
};
vec3 up = { 0.0f, 1.0f, 0.0f };
mat4 proj;
glm_perspective(fov, aspect, 0.1f, 1000.0f, proj);
// Flips rendering on the Y axis, so that it is still right-down even in 3D;
proj[1][1] = -proj[1][1];
mat4 view;
glm_lookat(eye, look, up, view);
mat4 pv;
glm_mat4_mul(proj, view, pv);
glLoadMatrixf((const GLfloat*)pv);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void cameraOverworldPop(void) {
glPopMatrix();
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusksdl2.h"
/**
* Pushes the UI camera matrix onto the stack.
*/
void cameraUIPush(void);
/**
* Pops the UI camera matrix from the stack.
*/
void cameraUIPop(void);
/**
* Pushes the screen space camera matrix onto the stack.
*/
void cameraScreenPush(void);
/**
* Pops the screen space camera matrix.
*/
void cameraScreenPop(void);
/**
* Pushes the overworld camera matrix onto the stack.
*/
void cameraOverworldPush(void);
/**
* Pops the overworld camera matrix.
*/
void cameraOverworldPop(void);

View File

@@ -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_TARGET_NAME}
PRIVATE
framebuffer.c
)

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "framebuffer.h"
#include "assert/assert.h"
#include "util/memory.h"
#if RENDER_USE_FRAMEBUFFER
void frameBufferInit(
framebuffer_t *framebuffer,
const uint32_t width,
const uint32_t height
) {
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
assertTrue(width > 0 && height > 0, "Width & height must be greater than 0");
memoryZero(framebuffer, sizeof(framebuffer_t));
textureInit(&framebuffer->texture, width, height, GL_RGBA, NULL);
// Generate the framebuffer object using EXT
glGenFramebuffersEXT(1, &framebuffer->id);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
// Attach the texture to the framebuffer
glFramebufferTexture2DEXT(
GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, framebuffer->texture.id, 0
);
// Check if the framebuffer is complete
if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) {
assertUnreachable("Framebuffer is not complete");
}
// Unbind the framebuffer
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
void frameBufferBind(const framebuffer_t *framebuffer) {
if(framebuffer == NULL) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return;
}
// Bind the framebuffer for rendering
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
}
void frameBufferDispose(framebuffer_t *framebuffer) {
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
glDeleteFramebuffersEXT(1, &framebuffer->id);
textureDispose(&framebuffer->texture);
}
#endif

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/render.h"
#include "display/texture/texture.h"
#if RENDER_USE_FRAMEBUFFER
typedef struct {
GLuint id;
texture_t texture;
} framebuffer_t;
/**
* Initializes a framebuffer using EXT methods.
*
* @param framebuffer The framebuffer to initialize.
* @param width The width of the framebuffer.
* @param height The height of the framebuffer.
* @return An error code indicating success or failure.
*/
void frameBufferInit(
framebuffer_t *framebuffer,
const uint32_t width,
const uint32_t height
);
/**
* Binds the framebuffer for rendering using EXT methods.
*
* @param framebuffer The framebuffer to bind, or NULL to unbind.
*/
void frameBufferBind(const framebuffer_t *framebuffer);
/**
* Disposes of the framebuffer using EXT methods.
*
* @param framebuffer The framebuffer to dispose of.
*/
void frameBufferDispose(framebuffer_t *framebuffer);
#endif

View File

@@ -6,8 +6,6 @@
# Sources # Sources
target_sources(${DUSK_TARGET_NAME} target_sources(${DUSK_TARGET_NAME}
PRIVATE PRIVATE
mesh mesh.c
) quad.c
)
# Subdirs
# add_subdirectory(draw)

View File

@@ -26,8 +26,6 @@ void meshInit(
mesh->primitiveType = primitiveType; mesh->primitiveType = primitiveType;
mesh->vertexCount = vertexCount; mesh->vertexCount = vertexCount;
mesh->vertices = vertices; mesh->vertices = vertices;
consolePrint("Init mesh");
} }
void meshDraw( void meshDraw(
@@ -50,19 +48,19 @@ void meshDraw(
const GLsizei stride = sizeof(meshvertex_t); const GLsizei stride = sizeof(meshvertex_t);
glColorPointer( glColorPointer(
4, MESH_VERTEX_COLOR_SIZE,
GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE,
stride, stride,
(const GLvoid*)&mesh->vertices[offset].color[0] (const GLvoid*)&mesh->vertices[offset].color[0]
); );
glTexCoordPointer( glTexCoordPointer(
2, MESH_VERTEX_UV_SIZE,
GL_FLOAT, GL_FLOAT,
stride, stride,
(const GLvoid*)&mesh->vertices[offset].uv[0] (const GLvoid*)&mesh->vertices[offset].uv[0]
); );
glVertexPointer( glVertexPointer(
3, MESH_VERTEX_POS_SIZE,
GL_FLOAT, GL_FLOAT,
stride, stride,
(const GLvoid*)&mesh->vertices[offset].pos[0] (const GLvoid*)&mesh->vertices[offset].pos[0]

View File

@@ -6,10 +6,14 @@
#pragma once #pragma once
#include "dusksdl2.h" #include "dusksdl2.h"
#define MESH_VERTEX_COLOR_SIZE 4
#define MESH_VERTEX_UV_SIZE 2
#define MESH_VERTEX_POS_SIZE 3
typedef struct { typedef struct {
GLubyte color[4]; GLubyte color[MESH_VERTEX_COLOR_SIZE];
GLfloat uv[2]; GLfloat uv[MESH_VERTEX_UV_SIZE];
GLfloat pos[3]; GLfloat pos[MESH_VERTEX_POS_SIZE];
} meshvertex_t; } meshvertex_t;
typedef struct { typedef struct {
@@ -18,6 +22,14 @@ typedef struct {
GLenum primitiveType; GLenum primitiveType;
} mesh_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.
*/
void meshInit( void meshInit(
mesh_t *mesh, mesh_t *mesh,
const GLenum primitiveType, const GLenum primitiveType,
@@ -25,10 +37,22 @@ void meshInit(
const meshvertex_t *vertices const meshvertex_t *vertices
); );
/**
* 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.
*/
void meshDraw( void meshDraw(
const mesh_t *mesh, const mesh_t *mesh,
const int32_t vertexOffset, const int32_t vertexOffset,
const int32_t vertexCount const int32_t vertexCount
); );
/**
* Disposes a mesh.
*
* @param mesh The mesh to dispose.
*/
void meshDispose(mesh_t *mesh); void meshDispose(mesh_t *mesh);

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "quad.h"
#include "assert/assert.h"
void quadBuffer(
meshvertex_t *vertices,
const float_t minX,
const float_t minY,
const float_t maxX,
const float_t maxY,
const uint8_t r,
const uint8_t g,
const uint8_t b,
const uint8_t a,
const float_t u0,
const float_t v0,
const float_t u1,
const float_t v1
) {
const float_t z = 0.0f; // Z coordinate for 2D rendering
assertNotNull(vertices, "Vertices cannot be NULL");
// First triangle
vertices[0] = (meshvertex_t) {
{ r, g, b, a }, // Color
{ u0, v0 }, // UV
{ minX, minY, z } // Position
};
vertices[1] = (meshvertex_t) {
{ r, g, b, a }, // Color
{ u1, v0 }, // UV
{ maxX, minY, z } // Position
};
vertices[2] = (meshvertex_t) {
{ r, g, b, a }, // Color
{ u1, v1 }, // UV
{ maxX, maxY, z } // Position
};
// Second triangle
vertices[3] = (meshvertex_t) {
{ r, g, b, a }, // Color
{ u0, v0 }, // UV
{ minX, minY, z } // Position
};
vertices[4] = (meshvertex_t) {
{ r, g, b, a }, // Color
{ u1, v1 }, // UV
{ maxX, maxY, z } // Position
};
vertices[5] = (meshvertex_t) {
{ r, g, b, a }, // Color
{ u0, v1 }, // UV
{ minX, maxY, z } // Position
};
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "mesh.h"
#define QUAD_VERTEX_COUNT 6
/**
* 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 r The red color component (0-255).
* @param g The green color component (0-255).
* @param b The blue color component (0-255).
* @param a The alpha color component (0-255).
* @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 uint8_t r,
const uint8_t g,
const uint8_t b,
const uint8_t a,
const float_t u0,
const float_t v0,
const float_t u1,
const float_t v1
);

View File

@@ -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_TARGET_NAME}
PRIVATE
renderoverworld.c
)

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "renderoverworld.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "display/camera/camera.h"
#include "entity/entity.h"
#include "display/spritebatch/spritebatch.h"
renderoverworld_t RENDER_OVERWORLD;
void renderOverworldInit(void) {
memoryZero(&RENDER_OVERWORLD, sizeof(RENDER_OVERWORLD));
for(uint8_t i = 0; i < CHUNK_MAP_COUNT; i++) {
renderchunk_t *chunk = &RENDER_OVERWORLD.chunks[i];
meshInit(
&chunk->meshBase,
GL_TRIANGLES,
CHUNK_TILE_COUNT * QUAD_VERTEX_COUNT,
chunk->verticesBase
);
meshInit(
&chunk->meshBaseOverlay,
GL_TRIANGLES,
CHUNK_TILE_COUNT,
chunk->verticesBaseOverlay
);
}
}
void renderOverworldDraw(void) {
cameraOverworldPush();
for(uint8_t i = 0; i < CHUNK_MAP_COUNT; i++) {
renderchunk_t *chunk = &RENDER_OVERWORLD.chunks[i];
meshDraw(&chunk->meshBase, -1, -1);
}
for(uint8_t i = 0; i < ENTITY_COUNT_MAX; i++) {
entity_t *entity = &ENTITIES[i];
if(entity->type == ENTITY_TYPE_NULL) continue;
float_t x = (entity->x * TILE_WIDTH_HEIGHT) + entity->subX;
float_t y = (entity->y * TILE_WIDTH_HEIGHT) + entity->subY;
// Draw the entity
spriteBatchPush(
NULL,
x, y,
x + TILE_WIDTH_HEIGHT, y + TILE_WIDTH_HEIGHT,
0xFF, 0x00, 0xFF, 0XFF,
0.0f, 0.0f, 1.0f, 1.0f
);
}
spriteBatchFlush();
cameraOverworldPop();
}
void renderChunkUpdated(chunk_t *chunk) {
uint8_t r, g, b;
assertNotNull(chunk, "Chunk pointer is null");
int32_t chunkIndex = chunk - CHUNK_MAP.chunks;
assertTrue(
chunkIndex >= 0 && chunkIndex < CHUNK_MAP_COUNT,
"Chunk index out of bounds"
);
for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) {
tile_t base = chunk->tilesBase[i];
tile_t overlay = chunk->tilesBaseOverlay[i];
float_t posX = (i % CHUNK_WIDTH) + (chunk->x * CHUNK_WIDTH);
float_t posY = (i / CHUNK_WIDTH) + (chunk->y * CHUNK_HEIGHT);
switch(base) {
case 0:
r = 0; g = 0; b = 0; // Black for empty
break;
case 1:
r = 34; g = 139; b = 34; // Forest Green
break;
case 2:
r = 0; g = 191; b = 255; // Deep Sky Blue
break;
case 3:
r = 139; g = 69; b = 19; // Saddle Brown
break;
case 4:
r = 255; g = 255; b = 0; // Yellow
break;
default:
r = 255; g = 20; b = 147; // Pink for unknown
break;
}
quadBuffer(
&RENDER_OVERWORLD.chunks[chunkIndex].verticesBase[i * QUAD_VERTEX_COUNT],
posX * TILE_WIDTH_HEIGHT,
posY * TILE_WIDTH_HEIGHT,
(posX + 1) * TILE_WIDTH_HEIGHT,
(posY + 1) * TILE_WIDTH_HEIGHT,
r, g, b, 255,
0, 0, 1, 1
);
}
}
void renderOverworldDispose(void) {
// Clean up overworld rendering resources here
for(uint8_t i = 0; i < CHUNK_MAP_COUNT; i++) {
renderchunk_t *chunk = &RENDER_OVERWORLD.chunks[i];
meshDispose(&chunk->meshBase);
meshDispose(&chunk->meshBaseOverlay);
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "world/chunk.h"
#include "display/mesh/quad.h"
typedef struct {
mesh_t meshBase;
meshvertex_t verticesBase[CHUNK_TILE_COUNT * QUAD_VERTEX_COUNT];
mesh_t meshBaseOverlay;
meshvertex_t verticesBaseOverlay[CHUNK_TILE_COUNT];
} renderchunk_t;
typedef struct {
renderchunk_t chunks[CHUNK_MAP_COUNT];
} renderoverworld_t;
extern renderoverworld_t RENDER_OVERWORLD;
/**
* Initializes the render overworld.
*/
void renderOverworldInit(void);
/**
* Draws the render overworld.
*/
void renderOverworldDraw(void);
/**
* Disposes of the render overworld.
*/
void renderOverworldDispose(void);

View File

@@ -5,49 +5,20 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#include "render.h"
#include "assert/assert.h"
#include "renderbackbuffer.h"
#include "rendertext.h"
#include "renderconsole.h"
#include "console/console.h"
#include "dusksdl2input.h" #include "dusksdl2input.h"
#include "renderscene.h" #include "render.h"
#include "display/texture/texture.h" #include "renderbackbuffer.h"
#include "display/mesh/mesh.h" #include "display/scene/renderscene.h"
#include "display/spritebatch/spritebatch.h"
#include "display/ui/renderui.h"
SDL_Window *RENDER_WINDOW; SDL_Window *RENDER_WINDOW;
SDL_Renderer *RENDER_RENDERER;
SDL_GLContext RENDER_GL_CONTEXT; SDL_GLContext RENDER_GL_CONTEXT;
bool_t RENDER_RUNNING; bool_t RENDER_RUNNING;
static texture_t TEST_TEXTURE;
static mesh_t TRIANGLE_MESH;
// Interleaved in native order: COORD -> COLOR -> VERTEX
static const meshvertex_t tri[3] = {
// Color then coord then vertex test
{ { 0xFF, 0, 0, 0xFF }, { 0.0f, 0.0f }, { -0.6f, -0.4f, 0.0f } }, // Red
{ { 0, 0xFF, 0, 0xFF }, { 1.0f, 0.0f }, { 0.6f, -0.4f, 0.0f } }, // Green
{ { 0, 0, 0xFF, 0xFF }, { 1.0f, 1.0f }, { 0.0f, 0.6f, 0.0f } } // Blue
};
static const meshvertex_t quad[6] = {
// First triangle
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 0.0f, 0.0f }, { -1.0f, -1.0f, 0.0f } }, // Red
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 1.0f, 0.0f }, { 1.0f, -1.0f, 0.0f } }, // Green
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 1.0f, 1.0f }, { 1.0f, 1.0f, 0.0f } }, // Blue
// Second triangle
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 0.0f, 0.0f }, { -1.0f, -1.0f, 0.0f } }, // Red
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 1.0f, 1.0f }, { 1.0f, 1.0f, 0.0f } }, // Blue
{ { 0xFF, 0xFF, 0xFF, 0xFF }, { 0.0f, 1.0f }, { -1.0f, 1.0f, 0.0f } } // Green
};
errorret_t renderInit(void) { errorret_t renderInit(void) {
// Init SDL // Init SDL
uint32_t flags = SDL_INIT_VIDEO; uint32_t flags = SDL_INIT_VIDEO;
#if INPUT_SUPPORT_GAMEPAD #if INPUT_SUPPORT_GAMEPAD
flags |= SDL_INIT_GAMECONTROLLER; flags |= SDL_INIT_GAMECONTROLLER;
#endif #endif
@@ -59,7 +30,7 @@ errorret_t renderInit(void) {
); );
} }
// Set OpenGL attributes (optional, adjust as needed) // Set OpenGL attributes (Needs to be done now or later?)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// Create window with OpenGL flag. // Create window with OpenGL flag.
@@ -69,7 +40,8 @@ errorret_t renderInit(void) {
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
RENDER_WINDOW_WIDTH_DEFAULT, RENDER_WINDOW_WIDTH_DEFAULT,
RENDER_WINDOW_HEIGHT_DEFAULT, RENDER_WINDOW_HEIGHT_DEFAULT,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_OPENGL
); );
if(!RENDER_WINDOW) { if(!RENDER_WINDOW) {
errorThrow("SDL_CreateWindow failed: %s", SDL_GetError()); errorThrow("SDL_CreateWindow failed: %s", SDL_GetError());
@@ -81,50 +53,25 @@ errorret_t renderInit(void) {
errorThrow("SDL_GL_CreateContext failed: %s", SDL_GetError()); errorThrow("SDL_GL_CreateContext failed: %s", SDL_GetError());
} }
// Enable VSync (optional)
SDL_GL_SetSwapInterval(1); SDL_GL_SetSwapInterval(1);
// No SDL_Renderer needed for OpenGL, set to NULL
RENDER_RENDERER = NULL;
// Disable GL crap we don't need
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
glDisable(GL_LIGHTING);// PSP defaults this on? glDisable(GL_LIGHTING);// PSP defaults this on?
glEnable(GL_TEXTURE_2D); glShadeModel(GL_SMOOTH); // Fixes color on PSP?
glShadeModel(GL_SMOOTH); glEnable(GL_BLEND);
meshInit( glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
&TRIANGLE_MESH, GL_TRIANGLES, sizeof(quad) / sizeof(meshvertex_t), quad glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
);
// --- Create a simple 2x2 texture --- glEnableClientState(GL_COLOR_ARRAY);// To confirm: every frame on PSP?
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
static const uint8_t texData4x2[] = { spriteBatchInit();
// Row 0 (top/bottom depends on your convention): renderBackBufferInit();
255, 0, 0, 255, // Red renderSceneInit();
0, 255, 0, 255, // Green renderUIInit();
0, 255, 0, 255, // PAD: repeat Green
0, 255, 0, 255, // PAD: repeat Green
// Row 1:
0, 0, 255, 255, // Blue
255, 255, 255, 255, // White
255, 255, 255, 255, // PAD: repeat White
255, 255, 255, 255 // PAD: repeat White
};
textureInit(&TEST_TEXTURE, 4, 2, texData4x2);
// Create back buffer.
// renderBackBufferInit();
// Init other things
// renderTextInit();
// renderSceneInit();
// Mark ready.
RENDER_RUNNING = true; RENDER_RUNNING = true;
errorOk(); errorOk();
} }
@@ -141,42 +88,27 @@ errorret_t renderDraw(void) {
} }
} }
// Bind the backbuffer // Reset the state
// renderBackBufferBind(); spriteBatchClear();
renderBackBufferBind();
// Draw everything renderSceneDraw();
// renderSceneDraw(); renderUIDraw();
// renderConsoleDraw();
// Unbind the backbuffer // Finish rendering, now render back buffer.
// renderBackBufferUnbind(); renderBackBufferUnbind();
// renderBackBufferDraw(); renderBackBufferDraw();
glClearColor(0.392f, 0.584f, 0.929f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
textureBind(&TEST_TEXTURE);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
meshDraw(&TRIANGLE_MESH, -1, -1);
textureBind(NULL); textureBind(NULL);
// Present the renderer (swap OpenGL buffers)
SDL_GL_SwapWindow(RENDER_WINDOW); SDL_GL_SwapWindow(RENDER_WINDOW);
errorOk(); errorOk();
} }
errorret_t renderDispose(void) { errorret_t renderDispose(void) {
meshDispose(&TRIANGLE_MESH); renderUIDispose();
textureDispose(&TEST_TEXTURE); renderSceneDispose();
renderBackBufferDispose();
// renderTextDispose(); spriteBatchDispose();
// renderSceneDispose();
// renderBackBufferDispose();
// Destroy OpenGL context // Destroy OpenGL context
SDL_GL_DeleteContext(RENDER_GL_CONTEXT); SDL_GL_DeleteContext(RENDER_GL_CONTEXT);

View File

@@ -7,29 +7,20 @@
#include "renderbackbuffer.h" #include "renderbackbuffer.h"
#include "render.h" #include "render.h"
#include "display/spritebatch/spritebatch.h"
#include "display/camera/camera.h"
#if RENDER_USE_FRAMEBUFFER #if RENDER_USE_FRAMEBUFFER
SDL_Texture *RENDER_BACKBUFFER; framebuffer_t RENDER_BACKBUFFER;
#else
#endif #endif
errorret_t renderBackBufferInit(void) { errorret_t renderBackBufferInit(void) {
#if RENDER_USE_FRAMEBUFFER #if RENDER_USE_FRAMEBUFFER
RENDER_BACKBUFFER = SDL_CreateTexture( frameBufferInit(
RENDER_RENDERER, &RENDER_BACKBUFFER,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET,
RENDER_WIDTH, RENDER_WIDTH,
RENDER_HEIGHT RENDER_HEIGHT
); );
if(!RENDER_BACKBUFFER) {
errorThrow("SDL_CreateTexture failed: %s", SDL_GetError());
}
// Make sure we unbind the back buffer after creation
SDL_SetRenderTarget(RENDER_RENDERER, NULL);
#else #else
// No back buffer needed for window rendering // No back buffer needed for window rendering
#endif #endif
@@ -39,30 +30,31 @@ errorret_t renderBackBufferInit(void) {
void renderBackBufferBind(void) { void renderBackBufferBind(void) {
#if RENDER_USE_FRAMEBUFFER #if RENDER_USE_FRAMEBUFFER
SDL_SetRenderTarget(RENDER_RENDERER, RENDER_BACKBUFFER); frameBufferBind(&RENDER_BACKBUFFER);
#endif #endif
// Fill background with cornflower blue. // Fill background with cornflower blue.
SDL_SetRenderDrawColor(RENDER_RENDERER, 100, 149, 237, 255); glClearColor(0.392f, 0.584f, 0.929f, 1.0f);
SDL_RenderClear(RENDER_RENDERER); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
} }
void renderBackBufferUnbind(void) { void renderBackBufferUnbind(void) {
#if RENDER_USE_FRAMEBUFFER #if RENDER_USE_FRAMEBUFFER
SDL_SetRenderTarget(RENDER_RENDERER, NULL); frameBufferBind(NULL);
#endif #endif
} }
void renderBackBufferDraw(void) { void renderBackBufferDraw(void) {
#if RENDER_USE_FRAMEBUFFER #if RENDER_USE_FRAMEBUFFER
// Clear background black
SDL_SetRenderDrawColor(RENDER_RENDERER, 0, 0, 0, 255);
SDL_RenderClear(RENDER_RENDERER);
// Create a quad that is scaled to fit but maintain original aspect ratio
int32_t windowWidth, windowHeight; int32_t windowWidth, windowHeight;
SDL_GetWindowSize(RENDER_WINDOW, &windowWidth, &windowHeight); SDL_GetWindowSize(RENDER_WINDOW, &windowWidth, &windowHeight);
// Set viewport to match window size
cameraScreenPush();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Create a quad that is scaled to fit but maintain original aspect ratio
int32_t renderWidth, renderHeight, renderX, renderY; int32_t renderWidth, renderHeight, renderX, renderY;
if(RENDER_WIDTH * windowHeight > RENDER_HEIGHT * windowWidth) { if(RENDER_WIDTH * windowHeight > RENDER_HEIGHT * windowWidth) {
renderWidth = windowWidth; renderWidth = windowWidth;
@@ -77,8 +69,16 @@ void renderBackBufferDraw(void) {
} }
// Draw the back buffer texture // Draw the back buffer texture
SDL_Rect destRect = { renderX, renderY, renderWidth, renderHeight }; spriteBatchClear();
SDL_RenderCopy(RENDER_RENDERER, RENDER_BACKBUFFER, NULL, &destRect); spriteBatchPush(
&RENDER_BACKBUFFER.texture,
renderX, renderY,
renderX+renderWidth, renderY+renderHeight,
0xFF, 0xFF, 0xFF, 0xFF,
0, 1, 1, 0
);
spriteBatchFlush();
cameraScreenPop();
#else #else
// No back buffer to draw // No back buffer to draw
#endif #endif
@@ -86,8 +86,7 @@ void renderBackBufferDraw(void) {
errorret_t renderBackBufferDispose(void) { errorret_t renderBackBufferDispose(void) {
#if RENDER_USE_FRAMEBUFFER #if RENDER_USE_FRAMEBUFFER
SDL_DestroyTexture(RENDER_BACKBUFFER); frameBufferDispose(&RENDER_BACKBUFFER);
RENDER_BACKBUFFER = NULL;
#endif #endif
errorOk(); errorOk();

View File

@@ -7,6 +7,11 @@
#pragma once #pragma once
#include "display/renderbase.h" #include "display/renderbase.h"
#include "display/framebuffer/framebuffer.h"
#if RENDER_USE_FRAMEBUFFER
extern framebuffer_t RENDER_BACKBUFFER;
#endif
/** /**
* Initializes the render back buffer. May be either a framebuffer or a texture * Initializes the render back buffer. May be either a framebuffer or a texture

View File

@@ -1,20 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "renderscene.h"
void renderSceneInit(void) {
// Initialize scene-related resources here
}
void renderSceneDraw(void) {
// Draw the current scene here
}
void renderSceneDispose(void) {
// Dispose of scene-related resources here
}

View File

@@ -1,121 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rendertext.h"
#include "render.h"
#include "assert/assert.h"
SDL_Texture* RENDER_TEXT_TEXTURE = NULL;
void renderTextInit(void) {
const int32_t cols = FONT_COLUMN_COUNT;
const int32_t rows = (FONT_TILE_COUNT + cols - 1) / cols;
const int32_t fontWidth = cols * FONT_TILE_WIDTH;
const int32_t fontHeight = rows * FONT_TILE_HEIGHT;
// RGBA8888 surface
SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(
0,
fontWidth,
fontHeight,
32,
SDL_PIXELFORMAT_RGBA32
);
assertNotNull(surface, "Failed to create surface for text rendering");
// Get the pixel format and pitch
const int32_t pitch_px = surface->pitch / 4;
uint32_t *pixels = (uint32_t *)surface->pixels;
// Buffer the pixels.
for(int tileIndex = 0; tileIndex < FONT_TILE_COUNT; ++tileIndex) {
const int32_t tileX = (tileIndex % FONT_COLUMN_COUNT) * FONT_TILE_WIDTH;
const int32_t tileY = (tileIndex / FONT_COLUMN_COUNT) * FONT_TILE_HEIGHT;
const uint8_t* tile = TILE_PIXEL_DATA[tileIndex];
for (int y = 0; y < FONT_TILE_HEIGHT; ++y) {
for (int x = 0; x < FONT_TILE_WIDTH; ++x) {
pixels[(tileY + y) * pitch_px + (tileX + x)] = (
tile[y * FONT_TILE_WIDTH + x] ? 0xFFFFFFFF : 0x00000000
);
}
}
}
// Create texture from the surface
RENDER_TEXT_TEXTURE = SDL_CreateTextureFromSurface(
RENDER_RENDERER,
surface
);
assertNotNull(RENDER_TEXT_TEXTURE, "Failed to create texture from surface");
// Cleanup the surface
SDL_FreeSurface(surface);
SDL_SetTextureBlendMode(RENDER_TEXT_TEXTURE, SDL_BLENDMODE_BLEND);
}
void renderTextDrawChar(
const float_t x,
const float_t y,
const char_t c
) {
int32_t tileIndex = (int32_t)(c) - FONT_CHAR_START;
assertTrue(
tileIndex >= 0 && tileIndex < FONT_TILE_COUNT,
"Character is out of bounds for font tiles"
);
assertNotNull(RENDER_TEXT_TEXTURE, "Texture cannot be NULL");
const int32_t tileX = (tileIndex % FONT_COLUMN_COUNT) * FONT_TILE_WIDTH;
const int32_t tileY = (tileIndex / FONT_COLUMN_COUNT) * FONT_TILE_HEIGHT;
SDL_Rect srcRect = {
.x = tileX,
.y = tileY,
.w = FONT_TILE_WIDTH,
.h = FONT_TILE_HEIGHT
};
SDL_Rect dstRect = {
.x = (int32_t)roundf(x),
.y = (int32_t)roundf(y),
.w = FONT_TILE_WIDTH,
.h = FONT_TILE_HEIGHT
};
SDL_RenderCopy(RENDER_RENDERER, RENDER_TEXT_TEXTURE, &srcRect, &dstRect);
}
void renderTextDraw(
const float_t x,
const float_t y,
const char_t *text
) {
assertNotNull(text, "Text cannot be NULL");
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_TILE_HEIGHT;
continue;
}
renderTextDrawChar(posX, posY, c);
posX += FONT_TILE_WIDTH;
}
}
void renderTextDispose(void) {
assertNotNull(RENDER_TEXT_TEXTURE, "Texture cannot be NULL");
SDL_DestroyTexture(RENDER_TEXT_TEXTURE);
RENDER_TEXT_TEXTURE = NULL;
}

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
renderscene.c
)
# Subdirs
# add_subdirectory(draw)

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "renderscene.h"
#include "display/overworld/renderoverworld.h"
renderscenecallback_t RENDER_SCENE_CALLBACKS[SCENE_COUNT] = {
[SCENE_INITIAL] = {
.init = NULL,
.draw = NULL,
.dispose = NULL
},
[SCENE_OVERWORLD] = {
.init = renderOverworldInit,
.draw = renderOverworldDraw,
.dispose = renderOverworldDispose
},
};
void renderSceneInit(void) {
for(int32_t i = 0; i < SCENE_COUNT; i++) {
if(!RENDER_SCENE_CALLBACKS[i].init) continue;
RENDER_SCENE_CALLBACKS[i].init();
}
}
void renderSceneDraw(void) {
if(!RENDER_SCENE_CALLBACKS[SCENE_CURRENT].draw) return;
RENDER_SCENE_CALLBACKS[SCENE_CURRENT].draw();
}
void renderSceneDispose(void) {
for(int32_t i = 0; i < SCENE_COUNT; i++) {
if(!RENDER_SCENE_CALLBACKS[i].dispose) continue;
RENDER_SCENE_CALLBACKS[i].dispose();
}
}

View File

@@ -6,7 +6,15 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "display/scene.h"
typedef struct {
void (*init)(void);
void (*draw)(void);
void (*dispose)(void);
} renderscenecallback_t;
extern renderscenecallback_t RENDER_SCENE_CALLBACKS[SCENE_COUNT];
/** /**
* Initializes the render scene module. * Initializes the render scene module.

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
spritebatch.c
)
# Subdirs
# add_subdirectory(draw)

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2025 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 "console/console.h"
spritebatch_t SPRITEBATCH;
void spriteBatchInit() {
SPRITEBATCH.spriteCount = 0;
SPRITEBATCH.currentTexture = NULL;
meshInit(
&SPRITEBATCH.mesh,
GL_TRIANGLES,
SPRITEBATCH_VERTEX_COUNT,
&SPRITEBATCH.vertices[0]
);
}
void spriteBatchPush(
texture_t *texture,
const float_t minX,
const float_t minY,
const float_t maxX,
const float_t maxY,
const uint8_t r,
const uint8_t g,
const uint8_t b,
const uint8_t a,
const float_t u0,
const float_t v0,
const float_t u1,
const float_t v1
) {
// Need to flush?
if(
SPRITEBATCH.currentTexture != texture ||
SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX
) {
spriteBatchFlush();
SPRITEBATCH.currentTexture = texture;
}
quadBuffer(
&SPRITEBATCH.vertices[SPRITEBATCH.spriteCount * QUAD_VERTEX_COUNT],
minX, minY, maxX, maxY,
r, g, b, a,
u0, v0, u1, v1
);
SPRITEBATCH.spriteCount++;
}
void spriteBatchClear() {
SPRITEBATCH.spriteCount = 0;
SPRITEBATCH.currentTexture = NULL;
}
void spriteBatchFlush() {
if(SPRITEBATCH.spriteCount == 0) return;
textureBind(SPRITEBATCH.currentTexture);
meshDraw(&SPRITEBATCH.mesh, 0, QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount);
spriteBatchClear();
}
void spriteBatchDispose() {
meshDispose(&SPRITEBATCH.mesh);
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2025 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"
#define SPRITEBATCH_SPRITES_MAX 1
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
typedef struct {
mesh_t mesh;
int32_t spriteCount;
texture_t *currentTexture;
meshvertex_t vertices[SPRITEBATCH_VERTEX_COUNT];
} spritebatch_t;
extern spritebatch_t SPRITEBATCH;
/**
* Initializes a sprite batch.
*
* @param spriteBatch The sprite batch to initialize.
*/
void spriteBatchInit();
void spriteBatchPush(
texture_t *texture,
const float_t minX,
const float_t minY,
const float_t maxX,
const float_t maxY,
const uint8_t r,
const uint8_t g,
const uint8_t b,
const uint8_t a,
const float_t u0,
const float_t v0,
const float_t u1,
const float_t v1
);
void spriteBatchClear();
void spriteBatchFlush();
void spriteBatchDispose();

View File

@@ -8,52 +8,65 @@
#include "texture.h" #include "texture.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/math.h"
void textureInit( void textureInit(
texture_t *texture, texture_t *texture,
const int32_t width, const int32_t width,
const int32_t height, const int32_t height,
const GLenum format,
const uint8_t *data const uint8_t *data
) { ) {
assertNotNull(texture, "Texture cannot be NULL"); assertNotNull(texture, "Texture cannot be NULL");
assertTrue(width > 0 && height > 0, "Width and height must be greater than 0"); assertTrue(width > 0 && height > 0, "Width and height must be greater than 0");
assertNotNull(data, "Data cannot be NULL");
#if PSP #if PSP
assertTrue( assertTrue(
width % 4 == 0, width == mathNextPowTwo(width),
"Width must be multiples of 4 for PSP" "Width must be powers of 2 for PSP"
); );
assertTrue( assertTrue(
height % 2 == 0, height == mathNextPowTwo(height),
"Height must be multiples of 2 for PSP" "Height must be powers of 2 for PSP"
); );
#endif #endif
memoryZero(texture, sizeof(texture_t)); memoryZero(texture, sizeof(texture_t));
texture->width = width; texture->width = width;
texture->height = height; texture->height = height;
glGenTextures(1, &texture->id); glGenTextures(1, &texture->id);
glBindTexture(GL_TEXTURE_2D, texture->id); glBindTexture(GL_TEXTURE_2D, texture->id);
glTexImage2D( glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_TEXTURE_2D, 0, format, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, data format, GL_UNSIGNED_BYTE, data
); );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
} }
void textureBind(const texture_t *texture) { void textureBind(const texture_t *texture) {
if(texture == NULL) { if(texture == NULL) {
glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D);
// glBindTexture(GL_TEXTURE_2D, 0);
return; return;
} }
assertTrue(
texture->id != 0,
"Texture ID must not be 0"
);
assertTrue(
texture->width > 0 && texture->height > 0,
"Texture width and height must be greater than 0"
);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture->id); glBindTexture(GL_TEXTURE_2D, texture->id);
} }

View File

@@ -20,12 +20,14 @@ typedef struct {
* @param texture The texture to initialize. * @param texture The texture to initialize.
* @param width The width of the texture. * @param width The width of the texture.
* @param height The height 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 pixel data for the texture. * @param data The pixel data for the texture.
*/ */
void textureInit( void textureInit(
texture_t *texture, texture_t *texture,
const int32_t width, const int32_t width,
const int32_t height, const int32_t height,
const GLenum format,
const uint8_t *data const uint8_t *data
); );

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
renderconsole.c
renderfps.c
rendertext.c
renderui.c
rendertextbox.c
)

View File

@@ -7,12 +7,10 @@
#include "renderconsole.h" #include "renderconsole.h"
#include "console/console.h" #include "console/console.h"
#include "rendertext.h" #include "display/ui/rendertext.h"
void renderConsoleDraw(void) { void renderConsoleDraw(void) {
// if(!CONSOLE.visible) return; if(!CONSOLE.visible) return;
// renderTextDraw(0, 0, "Dusk Console");
int32_t i = 0; int32_t i = 0;
char_t *line; char_t *line;
@@ -22,7 +20,10 @@ void renderConsoleDraw(void) {
i++; i++;
continue; continue;
} }
renderTextDraw(0, (CONSOLE_HISTORY_MAX - i - 1) * FONT_TILE_HEIGHT, line); renderTextDraw(
0, (CONSOLE_HISTORY_MAX - i - 1) * FONT_TILE_HEIGHT, line,
0xFF, 0xFF, 0xFF
);
i++; i++;
} while(i < CONSOLE_HISTORY_MAX); } while(i < CONSOLE_HISTORY_MAX);
} }

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "renderfps.h"
#include "display/render.h"
#include "display/ui/rendertext.h"
#include "time.h"
#include "game.h"
float_t RENDER_FPS_AVG = -1.0f;
float_t RENDER_TPS_AVG = -1.0f;
void renderFPSDraw(void) {
if(TIME.delta > 0) {
float_t fps = 1.0f / TIME.realDelta;
if(RENDER_FPS_AVG == -1.0f) {
RENDER_FPS_AVG = fps;
} else {
RENDER_FPS_AVG = (RENDER_FPS_AVG + fps) / 2.0f;
}
}
if(TIME.time != TIME.lastTick) {
float_t timeSinceLastTick = TIME.realTime - TIME.lastTick;
float_t tps = 1.0f / timeSinceLastTick;
if(RENDER_TPS_AVG == -1.0f) {
RENDER_TPS_AVG = tps;
} else {
RENDER_TPS_AVG = (RENDER_TPS_AVG + tps) / 2.0f;
}
}
char_t buffer[64];
snprintf(buffer, sizeof(buffer), "%.1f/%.1f", RENDER_FPS_AVG, RENDER_TPS_AVG);
int32_t width, height;
renderTextMeasure(buffer, &width, &height);
renderTextDraw(RENDER_WIDTH - width, 0, buffer, 0x00, 0xFF, 0x00);
}

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
/**
* Draws the FPS overlay.
*/
void renderFPSDraw(void);

View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rendertext.h"
#include "display/render.h"
#include "assert/assert.h"
#include "display/spritebatch/spritebatch.h"
#include "util/memory.h"
#include "util/math.h"
texture_t RENDER_TEXT_TEXTURE;
static mesh_t RENDER_TEXT_QUAD_MESH;
void renderTextInit(void) {
const int32_t cols = FONT_COLUMN_COUNT;
const int32_t rows = (FONT_TILE_COUNT + cols - 1) / cols;
const int32_t inputFontWidth = cols * FONT_TILE_WIDTH;
const int32_t inputFontHeight = rows * FONT_TILE_HEIGHT;
int32_t outputFontWidth = inputFontWidth;
int32_t outputFontHeight = inputFontHeight;
// Round up to nearest power of 2
#if PSP
outputFontWidth = mathNextPowTwo(inputFontWidth);
outputFontHeight = mathNextPowTwo(inputFontHeight);
#endif
uint8_t *pixels = (uint8_t *)memoryAllocate(
outputFontWidth * outputFontHeight *
sizeof(uint8_t)
);
// Buffer the pixels.
for(int tileIndex = 0; tileIndex < FONT_TILE_COUNT; ++tileIndex) {
const int32_t tileX = (tileIndex % FONT_COLUMN_COUNT) * FONT_TILE_WIDTH;
const int32_t tileY = (tileIndex / FONT_COLUMN_COUNT) * FONT_TILE_HEIGHT;
const uint8_t* tile = TILE_PIXEL_DATA[tileIndex];
for (int y = 0; y < FONT_TILE_HEIGHT; ++y) {
for (int x = 0; x < FONT_TILE_WIDTH; ++x) {
const int32_t pixel = (tileY + y) * outputFontWidth + (tileX + x);
const int32_t pixelOffset = pixel;
uint8_t value = tile[y * FONT_TILE_WIDTH + x];
pixels[pixel] = value ? 0xFF : 0x00; // Alpha channel
}
}
}
textureInit(
&RENDER_TEXT_TEXTURE,
outputFontWidth, outputFontHeight,
GL_ALPHA, pixels
);
memoryFree(pixels);
}
void renderTextDrawChar(
const float_t x,
const float_t y,
const char_t c,
const uint8_t r,
const uint8_t g,
const uint8_t b
) {
int32_t tileIndex = (int32_t)(c) - FONT_CHAR_START;
assertTrue(
tileIndex >= 0 && tileIndex < FONT_TILE_COUNT,
"Character is out of bounds for font tiles"
);
const float_t w = (float)RENDER_TEXT_TEXTURE.width;
const float_t h = (float)RENDER_TEXT_TEXTURE.height;
const int32_t tileX = (tileIndex % FONT_COLUMN_COUNT);
const int32_t tileY = (tileIndex / FONT_COLUMN_COUNT);
spriteBatchPush(
&RENDER_TEXT_TEXTURE,
x, y,
x + FONT_TILE_WIDTH, y + FONT_TILE_HEIGHT,
r, g, b, 0xFF,
(tileX * FONT_TILE_WIDTH) / w,
(tileY * FONT_TILE_HEIGHT) / h,
((tileX + 1) * FONT_TILE_WIDTH) / w,
((tileY + 1) * FONT_TILE_HEIGHT) / h
);
}
void renderTextDraw(
const float_t x,
const float_t y,
const char_t *text,
const uint8_t r,
const uint8_t g,
const uint8_t b
) {
assertNotNull(text, "Text cannot be NULL");
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_TILE_HEIGHT;
continue;
}
renderTextDrawChar(posX, posY, c, r, g, b);
posX += FONT_TILE_WIDTH;
}
}
void renderTextMeasure(
const char_t *text,
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_TILE_HEIGHT;
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_TILE_HEIGHT;
continue;
}
lineWidth += FONT_TILE_WIDTH;
}
if(lineWidth > width) {
width = lineWidth;
}
*outWidth = width;
*outHeight = height;
}
void renderTextDispose(void) {
textureDispose(&RENDER_TEXT_TEXTURE);
}

View File

@@ -8,8 +8,9 @@
#pragma once #pragma once
#include "dusksdl2.h" #include "dusksdl2.h"
#include "ui/font.h" #include "ui/font.h"
#include "display/texture/texture.h"
extern SDL_Texture* RENDER_TEXT_TEXTURE; extern texture_t RENDER_TEXT_TEXTURE;
/** /**
* Initializes the text rendering system. * Initializes the text rendering system.
@@ -22,11 +23,17 @@ void renderTextInit(void);
* @param x The x-coordinate to draw the character at. * @param x The x-coordinate to draw the character at.
* @param y The y-coordinate to draw the character at. * @param y The y-coordinate to draw the character at.
* @param c The character to draw. * @param c The character to draw.
* @param r The red component of the color (0-255).
* @param g The green component of the color (0-255).
* @param b The blue component of the color (0-255).
*/ */
void renderTextDrawChar( void renderTextDrawChar(
const float_t x, const float_t x,
const float_t y, const float_t y,
const char_t c const char_t c,
const uint8_t r,
const uint8_t g,
const uint8_t b
); );
/** /**
@@ -35,11 +42,30 @@ void renderTextDrawChar(
* @param x The x-coordinate to draw the text at. * @param x The x-coordinate to draw the text at.
* @param y The y-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 text The null-terminated string of text to draw.
* @param r The red component of the color (0-255).
* @param g The green component of the color (0-255).
* @param b The blue component of the color (0-255).
*/ */
void renderTextDraw( void renderTextDraw(
const float_t x, const float_t x,
const float_t y, const float_t y,
const char_t *text const char_t *text,
const uint8_t r,
const uint8_t g,
const uint8_t b
);
/**
* Measures the width and height of the given text string when rendered.
*
* @param text The null-terminated string of text to measure.
* @param outWidth Pointer to store the measured width in pixels.
* @param outHeight Pointer to store the measured height in pixels.
*/
void renderTextMeasure(
const char_t *text,
int32_t *outWidth,
int32_t *outHeight
); );
/** /**

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rendertextbox.h"
#include "ui/uitextbox.h"
#include "display/ui/rendertext.h"
#include "display/spritebatch/spritebatch.h"
#include "assert/assert.h"
void renderTextboxDraw(void) {
if(!UI_TEXTBOX.visible) return;
// Background
spriteBatchPush(
NULL,
0, RENDER_HEIGHT - UI_TEXTBOX_HEIGHT,
RENDER_WIDTH, RENDER_HEIGHT,
0x00, 0x00, 0x00, 0xFF,
0.0f, 0.0f, 1.0f, 1.0f
);
uint32_t x = 0;
uint32_t y = RENDER_HEIGHT - UI_TEXTBOX_HEIGHT;
if(UI_TEXTBOX.charsRevealed > 0) {
uint8_t charsRendered = 0;
// For each line
for(uint8_t i = 0; i < UI_TEXTBOX_LINES_PER_PAGE; i++) {
// Get count of chars in the line
uint8_t lineLength = UI_TEXTBOX.lineLengths[
i + (UI_TEXTBOX.page * UI_TEXTBOX_LINES_PER_PAGE)
];
if(lineLength == 0) continue;
// Determine how many chars left to render
uint8_t lineChars = UI_TEXTBOX.charsRevealed - charsRendered;
// Don't render more than in line
if(lineChars > lineLength) lineChars = lineLength;
assertTrue(lineChars > 0, "Line chars must be greater than 0");
// Update how many rendered
charsRendered += lineChars;
for(uint8_t j = 0; j < lineChars; j++) {
renderTextDrawChar(
x + UI_TEXTBOX_PADDING_X + UI_TEXTBOX_BORDER_WIDTH + (
j * FONT_TILE_WIDTH
),
y + UI_TEXTBOX_PADDING_Y + UI_TEXTBOX_BORDER_HEIGHT + (
i * FONT_TILE_HEIGHT
),
UI_TEXTBOX.text[
(i * UI_TEXTBOX_CHARS_PER_LINE) + j +
(UI_TEXTBOX.page * UI_TEXTBOX_CHARS_PER_PAGE)
],
0xFF, 0xFF, 0xFF
);
}
// Check if we're done rendering text
if(UI_TEXTBOX.charsRevealed - charsRendered == 0) break;
}
}
}

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
void renderTextboxDraw(void);

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "renderui.h"
#include "display/ui/rendertext.h"
#include "display/ui/renderconsole.h"
#include "display/ui/renderfps.h"
#include "display/ui/rendertextbox.h"
#include "display/spritebatch/spritebatch.h"
#include "display/camera/camera.h"
renderuicallback_t RENDER_UI_CALLBACKS[] = {
{
.init = renderTextInit,
.dispose = renderTextDispose
},
{
.draw = renderTextboxDraw,
},
{
.draw = renderConsoleDraw,
},
{
.draw = renderFPSDraw,
},
};
#define RENDER_UI_CALLBACKS_COUNT ( \
sizeof(RENDER_UI_CALLBACKS) / sizeof(RENDER_UI_CALLBACKS[0]) \
)
void renderUIInit(void) {
for (int32_t i = 0; i < RENDER_UI_CALLBACKS_COUNT; i++) {
if(!RENDER_UI_CALLBACKS[i].init) continue;
RENDER_UI_CALLBACKS[i].init();
}
}
void renderUIDraw(void) {
cameraUIPush();
for (int32_t i = 0; i < RENDER_UI_CALLBACKS_COUNT; i++) {
if(!RENDER_UI_CALLBACKS[i].draw) continue;
RENDER_UI_CALLBACKS[i].draw();
}
spriteBatchFlush();
cameraUIPop();
}
void renderUIDispose(void) {
for (int32_t i = 0; i < RENDER_UI_CALLBACKS_COUNT; i++) {
if(!RENDER_UI_CALLBACKS[i].dispose) continue;
RENDER_UI_CALLBACKS[i].dispose();
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusksdl2.h"
typedef struct {
void (*init)(void);
void (*draw)(void);
void (*dispose)(void);
} renderuicallback_t;
extern renderuicallback_t RENDER_UI_CALLBACKS[];
/**
* Initialize the UI rendering system.
*/
void renderUIInit(void);
/**
* Draw the UI elements.
*/
void renderUIDraw(void);
/**
* Dispose of the UI rendering system.
*/
void renderUIDispose(void);

View File

@@ -13,3 +13,4 @@
#include <GL/gl.h> #include <GL/gl.h>
#include <GL/glext.h> #include <GL/glext.h>
#include <cglm/cglm.h>

View File

@@ -7,8 +7,8 @@
#include "dusksdl2input.h" #include "dusksdl2input.h"
uint8_t inputStateGet() { inputstate_t inputStateGet() {
uint8_t state = 0; inputstate_t state = 0;
#if INPUT_SUPPORT_GAMEPAD #if INPUT_SUPPORT_GAMEPAD
// Get gamepad state. // Get gamepad state.

View File

@@ -20,7 +20,7 @@
#if INPUT_SUPPORT_GAMEPAD #if INPUT_SUPPORT_GAMEPAD
typedef struct { typedef struct {
const SDL_GameControllerButton button; const SDL_GameControllerButton button;
const uint8_t bind; const inputbind_t bind;
} inputsdlbuttonmap_t; } inputsdlbuttonmap_t;
static const inputsdlbuttonmap_t INPUT_SDL_BUTTON_MAP[] = { static const inputsdlbuttonmap_t INPUT_SDL_BUTTON_MAP[] = {
@@ -30,6 +30,7 @@
{ SDL_CONTROLLER_BUTTON_DPAD_RIGHT, INPUT_BIND_RIGHT }, { SDL_CONTROLLER_BUTTON_DPAD_RIGHT, INPUT_BIND_RIGHT },
{ SDL_CONTROLLER_BUTTON_A, INPUT_BIND_ACTION }, { SDL_CONTROLLER_BUTTON_A, INPUT_BIND_ACTION },
{ SDL_CONTROLLER_BUTTON_B, INPUT_BIND_CANCEL }, { SDL_CONTROLLER_BUTTON_B, INPUT_BIND_CANCEL },
{ SDL_CONTROLLER_BUTTON_BACK, INPUT_BIND_CONSOLE },
{ 0, 0 } { 0, 0 }
}; };
#endif #endif
@@ -37,7 +38,7 @@
#if INPUT_SUPPORT_KEYBOARD #if INPUT_SUPPORT_KEYBOARD
typedef struct { typedef struct {
SDL_Scancode code; SDL_Scancode code;
uint8_t bind; inputbind_t bind;
} inputsdlkbmap_t; } inputsdlkbmap_t;
static const inputsdlkbmap_t INPUT_SDL_KEYBOARD_MAP[] = { static const inputsdlkbmap_t INPUT_SDL_KEYBOARD_MAP[] = {
@@ -45,8 +46,18 @@
{ SDL_SCANCODE_S, INPUT_BIND_DOWN }, { SDL_SCANCODE_S, INPUT_BIND_DOWN },
{ SDL_SCANCODE_A, INPUT_BIND_LEFT }, { SDL_SCANCODE_A, INPUT_BIND_LEFT },
{ SDL_SCANCODE_D, INPUT_BIND_RIGHT }, { SDL_SCANCODE_D, INPUT_BIND_RIGHT },
{ SDL_SCANCODE_LEFT, INPUT_BIND_LEFT },
{ SDL_SCANCODE_RIGHT, INPUT_BIND_RIGHT },
{ SDL_SCANCODE_UP, INPUT_BIND_UP },
{ SDL_SCANCODE_DOWN, INPUT_BIND_DOWN },
{ SDL_SCANCODE_RETURN, INPUT_BIND_ACTION },
{ SDL_SCANCODE_SPACE, INPUT_BIND_ACTION }, { SDL_SCANCODE_SPACE, INPUT_BIND_ACTION },
{ SDL_SCANCODE_E, INPUT_BIND_ACTION },
{ SDL_SCANCODE_ESCAPE, INPUT_BIND_CANCEL }, { SDL_SCANCODE_ESCAPE, INPUT_BIND_CANCEL },
{ SDL_SCANCODE_BACKSPACE, INPUT_BIND_CANCEL },
{ SDL_SCANCODE_TAB, INPUT_BIND_CONSOLE },
{ SDL_SCANCODE_GRAVE, INPUT_BIND_CONSOLE },
{ SDL_SCANCODE_Q, INPUT_BIND_QUIT },
{ 0, 0 } { 0, 0 }
}; };
#endif #endif

View File

@@ -7,7 +7,6 @@
#include "display/render.h" #include "display/render.h"
#include "game.h" #include "game.h"
#include "console/console.h"
#include "input.h" #include "input.h"
#define mainError(ret) \ #define mainError(ret) \
@@ -25,6 +24,8 @@ int main(int argc, char *argv[]) {
while(RENDER_RUNNING) { while(RENDER_RUNNING) {
gameUpdate(); gameUpdate();
mainError(renderDraw()); mainError(renderDraw());
if(!GAME.running) break;
} }
gameDispose(); gameDispose();

19
src/dusksdl2/time.c Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "time.h"
#include "dusksdl2.h"
uint32_t TIME_LAST = 0;
float_t timeDeltaGet(void) {
// Get the current time in milliseconds
uint32_t currentTime = SDL_GetTicks();
float_t delta = (currentTime - TIME_LAST) / 1000.0f;
TIME_LAST = currentTime;
return delta;
}

View File

@@ -3,5 +3,4 @@ CHUNK_WIDTH = 8
CHUNK_HEIGHT = 8 CHUNK_HEIGHT = 8
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT
CHUNK_ENTITY_COUNT_MAX = 8 CHUNK_ENTITY_COUNT_MAX = 8
TILE_WIDTH_HEIGHT = 16
TILE_WIDTH_HEIGHT = 16 TILE_WIDTH_HEIGHT = 16

View File

@@ -27,7 +27,7 @@ def parseEntity(obj, chunkData):
obj['localX'] = obj['x'] - (chunkData['topLeftTileX'] * TILE_WIDTH_HEIGHT) obj['localX'] = obj['x'] - (chunkData['topLeftTileX'] * TILE_WIDTH_HEIGHT)
obj['localY'] = obj['y'] - (chunkData['topLeftTileY'] * TILE_WIDTH_HEIGHT) obj['localY'] = obj['y'] - (chunkData['topLeftTileY'] * TILE_WIDTH_HEIGHT)
obj['dir'] = 'ENTITY_DIR_SOUTH' obj['dir'] = 'DIRECTION_SOUTH'
obj['type'] = entType obj['type'] = entType
def getProperty(propName): def getProperty(propName):

View File

@@ -1,2 +0,0 @@
def floatToFixed248(value):
return value;

View File

@@ -2,11 +2,10 @@ import sys, os
import argparse import argparse
from datetime import datetime from datetime import datetime
import math import math
from helper import floatToFixed248
from inputParser import parseInputFile from inputParser import parseInputFile
from mapParser import parseMap from mapParser import parseMap
from chunkParser import parseChunk from chunkParser import parseChunk
from constants import CHUNK_WIDTH, CHUNK_HEIGHT from constants import CHUNK_WIDTH, CHUNK_HEIGHT, TILE_WIDTH_HEIGHT
# Check if the script is run with the correct arguments # Check if the script is run with the correct arguments
parser = argparse.ArgumentParser(description="Generate chunk header files") parser = argparse.ArgumentParser(description="Generate chunk header files")
@@ -83,8 +82,8 @@ for chunkY in range(mapData['mapHeightInRealChunks']):
f.write(" {\n") f.write(" {\n")
f.write(f" .id = {entity['id']},\n") f.write(f" .id = {entity['id']},\n")
f.write(f" .type = {entity['type']},\n") f.write(f" .type = {entity['type']},\n")
f.write(f" .x = {floatToFixed248(entity['x'])},\n") f.write(f" .x = {round(entity['x'] / TILE_WIDTH_HEIGHT)},\n")
f.write(f" .y = {floatToFixed248(entity['y'])},\n") f.write(f" .y = {round(entity['y'] / TILE_WIDTH_HEIGHT)},\n")
if 'dir' in entity: if 'dir' in entity:
f.write(f" .dir = {entity['dir']},\n") f.write(f" .dir = {entity['dir']},\n")
@@ -128,8 +127,8 @@ with open(headerPath, 'w') as f:
f.write(f"#define WORLD_HEIGHT {worldHeight}\n") f.write(f"#define WORLD_HEIGHT {worldHeight}\n")
# Write out other global variables. # Write out other global variables.
f.write(f"#define WORLD_PLAYER_SPAWN_X ((float_t){floatToFixed248(mapData['playerSpawnX'])})\n") f.write(f"#define WORLD_PLAYER_SPAWN_X ({round(mapData['playerSpawnX'] / TILE_WIDTH_HEIGHT)})\n")
f.write(f"#define WORLD_PLAYER_SPAWN_Y ((float_t){floatToFixed248(mapData['playerSpawnY'])})\n") f.write(f"#define WORLD_PLAYER_SPAWN_Y ({round(mapData['playerSpawnY'] / TILE_WIDTH_HEIGHT)})\n")
f.write("\n") f.write("\n")