Compare commits
20 Commits
2916f53e65
...
main
Author | SHA1 | Date | |
---|---|---|---|
1411c2e96b | |||
6f42a6e195 | |||
1e57183ca1 | |||
d79e12ffaa | |||
bcb1616201 | |||
38127d9260 | |||
d48d927202 | |||
150a410f00 | |||
fbbd2176fd | |||
3e19771d8f | |||
3d4317260f | |||
6d6a0e4886 | |||
91b93b5b1e | |||
c3310a036f | |||
69acbd017c | |||
81ed15b171 | |||
d1ab8b0cc8 | |||
cbdc271a5e | |||
3c908dc1ed | |||
217f00ff4c |
@@ -10,8 +10,8 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||
|
||||
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()
|
||||
|
||||
# Prep cache
|
||||
|
257
archive/fixed.c
257
archive/fixed.c
@@ -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;
|
||||
}
|
379
archive/fixed.h
379
archive/fixed.h
@@ -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
|
||||
);
|
14
cmake/modules/Findcglm.cmake
Normal file
14
cmake/modules/Findcglm.cmake
Normal 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)
|
@@ -20,6 +20,7 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
game.c
|
||||
input.c
|
||||
time.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
@@ -31,7 +32,6 @@ add_subdirectory(entity)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(util)
|
||||
add_subdirectory(world)
|
@@ -10,3 +10,6 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
consolecmd.c
|
||||
consolevar.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
add_subdirectory(cmd)
|
@@ -6,5 +6,4 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
physics.c
|
||||
)
|
18
src/dusk/console/cmd/cmdecho.h
Normal file
18
src/dusk/console/cmd/cmdecho.h
Normal 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]);
|
||||
}
|
25
src/dusk/console/cmd/cmdget.h
Normal file
25
src/dusk/console/cmd/cmdget.h
Normal 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]);
|
||||
}
|
15
src/dusk/console/cmd/cmdquit.h
Normal file
15
src/dusk/console/cmd/cmdquit.h
Normal 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;
|
||||
}
|
27
src/dusk/console/cmd/cmdset.h
Normal file
27
src/dusk/console/cmd/cmdset.h
Normal 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]);
|
||||
}
|
@@ -9,6 +9,11 @@
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.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"
|
||||
|
||||
console_t CONSOLE;
|
||||
@@ -19,6 +24,7 @@ void consoleInit() {
|
||||
// Register the get and set command.
|
||||
CONSOLE.cmdGet = consoleRegCmd("get", cmdGet);
|
||||
CONSOLE.cmdSet = consoleRegCmd("set", cmdSet);
|
||||
consoleRegCmd("quit", cmdQuit);
|
||||
consoleRegCmd("echo", cmdEcho);
|
||||
|
||||
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
|
||||
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++) {
|
||||
consolecmdexec_t *exec = &CONSOLE.execBuffer[i];
|
||||
assertNotNull(exec->cmd, "Command execution has no command.");
|
||||
@@ -355,27 +327,3 @@ void consoleUpdate() {
|
||||
// Clear the exec buffer
|
||||
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
|
||||
// );
|
||||
// }
|
@@ -103,3 +103,4 @@ void consoleUpdate();
|
||||
void cmdGet(const consolecmdexec_t *exec);
|
||||
void cmdSet(const consolecmdexec_t *exec);
|
||||
void cmdEcho(const consolecmdexec_t *exec);
|
||||
void cmdQuit(const consolecmdexec_t *exec);
|
@@ -6,5 +6,46 @@
|
||||
*/
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,10 +6,42 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
SCENE_INITIAL,
|
||||
SCENE_OVERWORLD,
|
||||
|
||||
SCENE_COUNT
|
||||
} scene_t;
|
||||
|
||||
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);
|
@@ -6,6 +6,7 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
direction.c
|
||||
entity.c
|
||||
player.c
|
||||
npc.c
|
||||
|
53
src/dusk/entity/direction.c
Normal file
53
src/dusk/entity/direction.c
Normal 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;
|
||||
}
|
||||
}
|
41
src/dusk/entity/direction.h
Normal file
41
src/dusk/entity/direction.h
Normal 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
|
||||
);
|
@@ -10,7 +10,7 @@
|
||||
#include "util/memory.h"
|
||||
#include "world/world.h"
|
||||
#include "world/tiledata.h"
|
||||
#include "physics/physics.h"
|
||||
#include "time.h"
|
||||
|
||||
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->x = source->x;
|
||||
entity->y = source->y;
|
||||
entity->vx = source->vx;
|
||||
entity->vy = source->vy;
|
||||
entity->dir = source->dir;
|
||||
entity->id = source->id;
|
||||
|
||||
@@ -61,72 +59,73 @@ void entityUpdate(entity_t *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;
|
||||
} else if(entity->subX < 0) {
|
||||
entity->subX += entity->moveSpeed;
|
||||
}
|
||||
|
||||
float_t newX = entity->x + entity->vx;
|
||||
float_t newY = entity->y + entity->vy;
|
||||
float_t halfTileWH = TILE_WIDTH_HEIGHT / 2.0f;
|
||||
if(entity->subY > 0) {
|
||||
entity->subY -= entity->moveSpeed;
|
||||
} else if(entity->subY < 0) {
|
||||
entity->subY += entity->moveSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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"
|
||||
);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Check for collisions with other entities
|
||||
entity_t *otherEntity = ENTITIES;
|
||||
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 {
|
||||
// Skip self and null entities
|
||||
if(otherEntity == entity || otherEntity->type == ENTITY_TYPE_NULL) continue;
|
||||
if(entity->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;
|
||||
|
||||
// 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;
|
||||
return NULL;
|
||||
}
|
@@ -6,22 +6,13 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "direction.h"
|
||||
#include "player.h"
|
||||
#include "npc.h"
|
||||
|
||||
#define ENTITY_COUNT_MAX 32
|
||||
|
||||
typedef enum {
|
||||
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;
|
||||
#define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds
|
||||
#define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds.
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL = 0,
|
||||
@@ -32,10 +23,13 @@ typedef enum {
|
||||
|
||||
typedef struct _entity_t {
|
||||
uint32_t id;// Completely unique ID for this entity.
|
||||
float_t x, y;
|
||||
float_t vx, vy;
|
||||
uint32_t x, y;
|
||||
int8_t subX, subY;
|
||||
uint8_t moveSpeed;
|
||||
|
||||
entitytype_t type;
|
||||
entitydir_t dir;
|
||||
direction_t dir;
|
||||
|
||||
|
||||
union {
|
||||
npc_t npc;
|
||||
@@ -68,9 +62,38 @@ void entityLoad(entity_t *entity, const entity_t *source);
|
||||
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.
|
||||
* @return The angle corresponding to the entity direction.
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @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
|
||||
);
|
@@ -19,7 +19,6 @@ void npcLoad(entity_t *entity, const entity_t *source) {
|
||||
}
|
||||
|
||||
void npcUpdate(entity_t *entity) {
|
||||
|
||||
}
|
||||
|
||||
void npcInteract(entity_t *player, entity_t *self) {
|
||||
|
@@ -10,7 +10,6 @@
|
||||
#include "input.h"
|
||||
#include "display/render.h"
|
||||
#include "world/world.h"
|
||||
#include "physics/physics.h"
|
||||
|
||||
#include "ui/uitextbox.h"
|
||||
|
||||
@@ -41,117 +40,55 @@ void playerEntityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
|
||||
|
||||
if(UI_TEXTBOX.visible) {
|
||||
entity->vx = entity->vy = 0;
|
||||
// TODO: make this just a method somewhere.
|
||||
if(UI_TEXTBOX.visible) 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(entity->dir != DIRECTION_NORTH) {
|
||||
entityTurn(entity, DIRECTION_NORTH);
|
||||
return;
|
||||
}
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
|
||||
} else if(inputIsDown(INPUT_BIND_DOWN)) {
|
||||
if(entity->dir != DIRECTION_SOUTH) {
|
||||
entityTurn(entity, DIRECTION_SOUTH);
|
||||
return;
|
||||
}
|
||||
|
||||
if(inputIsDown(INPUT_BIND_UP)) {
|
||||
if(inputIsDown(INPUT_BIND_LEFT)) {
|
||||
entity->vx = -PLAYER_MOVE_SPEED_XY;
|
||||
entity->vy = -PLAYER_MOVE_SPEED_XY;
|
||||
|
||||
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;
|
||||
}
|
||||
} else if(inputIsDown(INPUT_BIND_DOWN)) {
|
||||
if(inputIsDown(INPUT_BIND_LEFT)) {
|
||||
entity->vx = -PLAYER_MOVE_SPEED_XY;
|
||||
entity->vy = PLAYER_MOVE_SPEED_XY;
|
||||
|
||||
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)) {
|
||||
entity->vx = -PLAYER_MOVE_SPEED;
|
||||
entity->vy = 0;
|
||||
entity->dir = ENTITY_DIR_WEST;
|
||||
if(entity->dir != DIRECTION_WEST) {
|
||||
entityTurn(entity, DIRECTION_WEST);
|
||||
return;
|
||||
}
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
|
||||
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
|
||||
entity->vx = PLAYER_MOVE_SPEED;
|
||||
entity->vy = 0;
|
||||
entity->dir = ENTITY_DIR_EAST;
|
||||
} else {
|
||||
entity->vx = 0;
|
||||
entity->vy = 0;
|
||||
if(entity->dir != DIRECTION_EAST) {
|
||||
entityTurn(entity, DIRECTION_EAST);
|
||||
return;
|
||||
}
|
||||
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Interact
|
||||
if(inputPressed(INPUT_BIND_ACTION)) {
|
||||
entity_t *other = ENTITIES;
|
||||
do {
|
||||
if(other == entity || other->type == ENTITY_TYPE_NULL) {
|
||||
other++;
|
||||
continue;
|
||||
}
|
||||
int8_t x, y;
|
||||
directionGetCoordinates(entity->dir, &x, &y);
|
||||
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
|
||||
|
||||
// Is the other entity interactable?
|
||||
if(ENTITY_CALLBACKS[other->type].interact == NULL) {
|
||||
other++;
|
||||
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);
|
||||
if(ent != NULL && ENTITY_CALLBACKS[ent->type].interact != NULL) {
|
||||
assertTrue(ent->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
ENTITY_CALLBACKS[ent->type].interact(entity, ent);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@@ -9,6 +9,9 @@
|
||||
#include "dusk.h"
|
||||
#include "item/inventory.h"
|
||||
|
||||
#define PLAYER_SPEED_WALK 1
|
||||
#define PLAYER_SPEED_RUN 2
|
||||
|
||||
typedef struct _entity_t entity_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -16,10 +19,6 @@ typedef struct {
|
||||
} playerentity_t;
|
||||
|
||||
#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;
|
||||
|
||||
|
@@ -14,26 +14,44 @@
|
||||
#include "event/event.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "console/console.h"
|
||||
#include "util/memory.h"
|
||||
#include "time.h"
|
||||
|
||||
game_t GAME;
|
||||
|
||||
void gameInit(void) {
|
||||
memoryZero(&GAME, sizeof(game_t));
|
||||
GAME.running = true;
|
||||
|
||||
timeInit();
|
||||
consoleInit();
|
||||
inputInit();
|
||||
eventInit();
|
||||
uiTextboxInit();
|
||||
overworldInit();
|
||||
|
||||
SCENE_CURRENT = SCENE_OVERWORLD;
|
||||
sceneInit();
|
||||
}
|
||||
|
||||
void gameUpdate(void) {
|
||||
overworldUpdate();
|
||||
timeUpdate();
|
||||
|
||||
// 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();
|
||||
|
||||
consoleUpdate();
|
||||
inputUpdate();
|
||||
|
||||
timeSinceLastTick -= DUSK_TIME_STEP;
|
||||
TIME.lastTick = TIME.time;
|
||||
}
|
||||
|
||||
if(inputPressed(INPUT_BIND_QUIT)) consoleExec("quit");
|
||||
consoleUpdate();
|
||||
}
|
||||
|
||||
void gameDispose(void) {
|
||||
|
||||
sceneDispose();
|
||||
}
|
@@ -8,6 +8,12 @@
|
||||
#pragma once
|
||||
#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.
|
||||
* This should be called by the parent platform at a time that it deems
|
||||
|
@@ -20,20 +20,20 @@ void inputUpdate(void) {
|
||||
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");
|
||||
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");
|
||||
return (INPUT.previous & bind) != 0;
|
||||
}
|
||||
|
||||
bool_t inputPressed(const uint8_t bind) {
|
||||
bool_t inputPressed(const inputbind_t 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);
|
||||
}
|
@@ -8,13 +8,19 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef uint8_t inputbind_t;
|
||||
typedef inputbind_t inputstate_t;
|
||||
|
||||
#define INPUT_BIND_UP (1 << 0)
|
||||
#define INPUT_BIND_DOWN (1 << 1)
|
||||
#define INPUT_BIND_LEFT (1 << 2)
|
||||
#define INPUT_BIND_RIGHT (1 << 3)
|
||||
#define INPUT_BIND_ACTION (1 << 4)
|
||||
#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 {
|
||||
uint8_t current;
|
||||
@@ -38,7 +44,7 @@ void inputUpdate(void);
|
||||
*
|
||||
* @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.
|
||||
@@ -46,7 +52,7 @@ uint8_t inputStateGet(void);
|
||||
* @param bind The input bind to check.
|
||||
* @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.
|
||||
@@ -54,7 +60,7 @@ bool_t inputIsDown(const uint8_t bind);
|
||||
* @param bind The input bind to check.
|
||||
* @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
|
||||
@@ -63,7 +69,7 @@ bool_t inputWasDown(const uint8_t bind);
|
||||
* @param bind The input bind to check.
|
||||
* @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.
|
||||
@@ -71,4 +77,4 @@ bool_t inputPressed(const uint8_t bind);
|
||||
* @param bind The input bind to check.
|
||||
* @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);
|
@@ -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;
|
||||
}
|
@@ -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
37
src/dusk/time.c
Normal 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
52
src/dusk/time.h
Normal 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
|
||||
|
@@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
memory.c
|
||||
string.c
|
||||
math.c
|
||||
)
|
19
src/dusk/util/math.c
Normal file
19
src/dusk/util/math.c
Normal 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
17
src/dusk/util/math.h
Normal 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);
|
@@ -10,6 +10,8 @@
|
||||
#include "assert/assert.h"
|
||||
#include "world/world.h"
|
||||
|
||||
void renderChunkUpdated(chunk_t *chunk);
|
||||
|
||||
chunkmap_t CHUNK_MAP;
|
||||
|
||||
void chunkMapInit() {
|
||||
@@ -261,6 +263,9 @@ void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
|
||||
entityLoad(entity, data);
|
||||
data++;
|
||||
}
|
||||
|
||||
// Allow the rendering platform to know this chunk is loaded.
|
||||
renderChunkUpdated(chunk);
|
||||
}
|
||||
|
||||
void chunkUnload(chunk_t *chunk) {
|
||||
|
@@ -7,16 +7,17 @@
|
||||
|
||||
#pragma once
|
||||
#include "tile.h"
|
||||
|
||||
#define CHUNK_MAP_WIDTH 4
|
||||
#define CHUNK_MAP_HEIGHT 3
|
||||
#define CHUNK_MAP_COUNT (CHUNK_MAP_WIDTH * CHUNK_MAP_HEIGHT)
|
||||
#include "display/render.h"
|
||||
|
||||
#define CHUNK_WIDTH 8
|
||||
#define CHUNK_HEIGHT 8
|
||||
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
|
||||
#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 {
|
||||
uint16_t x, y;
|
||||
tile_t tilesBase[CHUNK_TILE_COUNT];
|
||||
|
@@ -47,8 +47,8 @@ void overworldUpdate() {
|
||||
entity->type == ENTITY_TYPE_PLAYER,
|
||||
"First entity must be player"
|
||||
);
|
||||
OVERWORLD_CAMERA_X = (uint32_t)floorf(entity->x);
|
||||
OVERWORLD_CAMERA_Y = (uint32_t)floorf(entity->y);
|
||||
OVERWORLD_CAMERA_X = entity->x * TILE_WIDTH_HEIGHT + entity->subX;
|
||||
OVERWORLD_CAMERA_Y = entity->y * TILE_WIDTH_HEIGHT + entity->subY;
|
||||
|
||||
uint16_t x, y;
|
||||
uint16_t halfWidth, halfHeight;
|
||||
|
@@ -20,6 +20,8 @@ target_compile_definitions(${DUSK_TARGET_NAME}
|
||||
RENDER_HEIGHT=272
|
||||
RENDER_WINDOW_WIDTH_DEFAULT=480
|
||||
RENDER_WINDOW_HEIGHT_DEFAULT=272
|
||||
# DUSK_TIME_DYNAMIC=0
|
||||
DUSK_TIME_DYNAMIC=1
|
||||
)
|
||||
|
||||
# Includes
|
||||
|
@@ -12,11 +12,14 @@ target_compile_definitions(${DUSK_TARGET_NAME}
|
||||
# Libs
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(cglm REQUIRED)
|
||||
|
||||
target_link_libraries(${DUSK_TARGET_NAME}
|
||||
PUBLIC
|
||||
SDL2::SDL2
|
||||
OpenGL::GL
|
||||
GL
|
||||
cglm
|
||||
)
|
||||
|
||||
|
||||
@@ -31,6 +34,7 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
dusksdl2input.c
|
||||
main.c
|
||||
time.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
|
@@ -8,11 +8,14 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
render.c
|
||||
renderbackbuffer.c
|
||||
rendertext.c
|
||||
renderconsole.c
|
||||
renderscene.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(camera)
|
||||
add_subdirectory(framebuffer)
|
||||
add_subdirectory(mesh)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(texture)
|
||||
add_subdirectory(spritebatch)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(ui)
|
10
src/dusksdl2/display/camera/CMakeLists.txt
Normal file
10
src/dusksdl2/display/camera/CMakeLists.txt
Normal 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
|
||||
)
|
125
src/dusksdl2/display/camera/camera.c
Normal file
125
src/dusksdl2/display/camera/camera.c
Normal 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();
|
||||
}
|
39
src/dusksdl2/display/camera/camera.h
Normal file
39
src/dusksdl2/display/camera/camera.h
Normal 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);
|
10
src/dusksdl2/display/framebuffer/CMakeLists.txt
Normal file
10
src/dusksdl2/display/framebuffer/CMakeLists.txt
Normal 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
|
||||
)
|
59
src/dusksdl2/display/framebuffer/framebuffer.c
Normal file
59
src/dusksdl2/display/framebuffer/framebuffer.c
Normal 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
|
45
src/dusksdl2/display/framebuffer/framebuffer.h
Normal file
45
src/dusksdl2/display/framebuffer/framebuffer.h
Normal 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
|
@@ -6,8 +6,6 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
mesh
|
||||
mesh.c
|
||||
quad.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
# add_subdirectory(draw)
|
@@ -26,8 +26,6 @@ void meshInit(
|
||||
mesh->primitiveType = primitiveType;
|
||||
mesh->vertexCount = vertexCount;
|
||||
mesh->vertices = vertices;
|
||||
|
||||
consolePrint("Init mesh");
|
||||
}
|
||||
|
||||
void meshDraw(
|
||||
@@ -50,19 +48,19 @@ void meshDraw(
|
||||
const GLsizei stride = sizeof(meshvertex_t);
|
||||
|
||||
glColorPointer(
|
||||
4,
|
||||
MESH_VERTEX_COLOR_SIZE,
|
||||
GL_UNSIGNED_BYTE,
|
||||
stride,
|
||||
(const GLvoid*)&mesh->vertices[offset].color[0]
|
||||
);
|
||||
glTexCoordPointer(
|
||||
2,
|
||||
MESH_VERTEX_UV_SIZE,
|
||||
GL_FLOAT,
|
||||
stride,
|
||||
(const GLvoid*)&mesh->vertices[offset].uv[0]
|
||||
);
|
||||
glVertexPointer(
|
||||
3,
|
||||
MESH_VERTEX_POS_SIZE,
|
||||
GL_FLOAT,
|
||||
stride,
|
||||
(const GLvoid*)&mesh->vertices[offset].pos[0]
|
||||
|
@@ -6,10 +6,14 @@
|
||||
#pragma once
|
||||
#include "dusksdl2.h"
|
||||
|
||||
#define MESH_VERTEX_COLOR_SIZE 4
|
||||
#define MESH_VERTEX_UV_SIZE 2
|
||||
#define MESH_VERTEX_POS_SIZE 3
|
||||
|
||||
typedef struct {
|
||||
GLubyte color[4];
|
||||
GLfloat uv[2];
|
||||
GLfloat pos[3];
|
||||
GLubyte color[MESH_VERTEX_COLOR_SIZE];
|
||||
GLfloat uv[MESH_VERTEX_UV_SIZE];
|
||||
GLfloat pos[MESH_VERTEX_POS_SIZE];
|
||||
} meshvertex_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -18,6 +22,14 @@ typedef struct {
|
||||
GLenum primitiveType;
|
||||
} 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(
|
||||
mesh_t *mesh,
|
||||
const GLenum primitiveType,
|
||||
@@ -25,10 +37,22 @@ void meshInit(
|
||||
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(
|
||||
const mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes a mesh.
|
||||
*
|
||||
* @param mesh The mesh to dispose.
|
||||
*/
|
||||
void meshDispose(mesh_t *mesh);
|
62
src/dusksdl2/display/mesh/quad.c
Normal file
62
src/dusksdl2/display/mesh/quad.c
Normal 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
|
||||
};
|
||||
}
|
44
src/dusksdl2/display/mesh/quad.h
Normal file
44
src/dusksdl2/display/mesh/quad.h
Normal 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
|
||||
);
|
10
src/dusksdl2/display/overworld/CMakeLists.txt
Normal file
10
src/dusksdl2/display/overworld/CMakeLists.txt
Normal 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
|
||||
)
|
125
src/dusksdl2/display/overworld/renderoverworld.c
Normal file
125
src/dusksdl2/display/overworld/renderoverworld.c
Normal 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);
|
||||
}
|
||||
}
|
39
src/dusksdl2/display/overworld/renderoverworld.h
Normal file
39
src/dusksdl2/display/overworld/renderoverworld.h
Normal 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);
|
@@ -5,49 +5,20 @@
|
||||
* 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 "renderscene.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "render.h"
|
||||
#include "renderbackbuffer.h"
|
||||
#include "display/scene/renderscene.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/ui/renderui.h"
|
||||
|
||||
SDL_Window *RENDER_WINDOW;
|
||||
SDL_Renderer *RENDER_RENDERER;
|
||||
SDL_GLContext RENDER_GL_CONTEXT;
|
||||
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) {
|
||||
// Init SDL
|
||||
uint32_t flags = SDL_INIT_VIDEO;
|
||||
|
||||
#if INPUT_SUPPORT_GAMEPAD
|
||||
flags |= SDL_INIT_GAMECONTROLLER;
|
||||
#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);
|
||||
|
||||
// Create window with OpenGL flag.
|
||||
@@ -69,7 +40,8 @@ errorret_t renderInit(void) {
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
RENDER_WINDOW_WIDTH_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) {
|
||||
errorThrow("SDL_CreateWindow failed: %s", SDL_GetError());
|
||||
@@ -81,50 +53,25 @@ errorret_t renderInit(void) {
|
||||
errorThrow("SDL_GL_CreateContext failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
// Enable VSync (optional)
|
||||
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_CULL_FACE);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_LIGHTING);// PSP defaults this on?
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glShadeModel(GL_SMOOTH);
|
||||
meshInit(
|
||||
&TRIANGLE_MESH, GL_TRIANGLES, sizeof(quad) / sizeof(meshvertex_t), quad
|
||||
);
|
||||
glShadeModel(GL_SMOOTH); // Fixes color on PSP?
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
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[] = {
|
||||
// Row 0 (top/bottom depends on your convention):
|
||||
255, 0, 0, 255, // Red
|
||||
0, 255, 0, 255, // Green
|
||||
0, 255, 0, 255, // PAD: repeat Green
|
||||
0, 255, 0, 255, // PAD: repeat Green
|
||||
spriteBatchInit();
|
||||
renderBackBufferInit();
|
||||
renderSceneInit();
|
||||
renderUIInit();
|
||||
|
||||
// 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;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -141,42 +88,27 @@ errorret_t renderDraw(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the backbuffer
|
||||
// renderBackBufferBind();
|
||||
// Reset the state
|
||||
spriteBatchClear();
|
||||
renderBackBufferBind();
|
||||
|
||||
// Draw everything
|
||||
// renderSceneDraw();
|
||||
// renderConsoleDraw();
|
||||
renderSceneDraw();
|
||||
renderUIDraw();
|
||||
|
||||
// Unbind the backbuffer
|
||||
// renderBackBufferUnbind();
|
||||
// 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);
|
||||
// Finish rendering, now render back buffer.
|
||||
renderBackBufferUnbind();
|
||||
renderBackBufferDraw();
|
||||
textureBind(NULL);
|
||||
|
||||
// Present the renderer (swap OpenGL buffers)
|
||||
SDL_GL_SwapWindow(RENDER_WINDOW);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t renderDispose(void) {
|
||||
meshDispose(&TRIANGLE_MESH);
|
||||
textureDispose(&TEST_TEXTURE);
|
||||
|
||||
// renderTextDispose();
|
||||
// renderSceneDispose();
|
||||
// renderBackBufferDispose();
|
||||
renderUIDispose();
|
||||
renderSceneDispose();
|
||||
renderBackBufferDispose();
|
||||
spriteBatchDispose();
|
||||
|
||||
// Destroy OpenGL context
|
||||
SDL_GL_DeleteContext(RENDER_GL_CONTEXT);
|
||||
|
@@ -7,29 +7,20 @@
|
||||
|
||||
#include "renderbackbuffer.h"
|
||||
#include "render.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/camera/camera.h"
|
||||
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
SDL_Texture *RENDER_BACKBUFFER;
|
||||
#else
|
||||
|
||||
framebuffer_t RENDER_BACKBUFFER;
|
||||
#endif
|
||||
|
||||
errorret_t renderBackBufferInit(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
RENDER_BACKBUFFER = SDL_CreateTexture(
|
||||
RENDER_RENDERER,
|
||||
SDL_PIXELFORMAT_RGBA8888,
|
||||
SDL_TEXTUREACCESS_TARGET,
|
||||
frameBufferInit(
|
||||
&RENDER_BACKBUFFER,
|
||||
RENDER_WIDTH,
|
||||
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
|
||||
// No back buffer needed for window rendering
|
||||
#endif
|
||||
@@ -39,30 +30,31 @@ errorret_t renderBackBufferInit(void) {
|
||||
|
||||
void renderBackBufferBind(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
SDL_SetRenderTarget(RENDER_RENDERER, RENDER_BACKBUFFER);
|
||||
frameBufferBind(&RENDER_BACKBUFFER);
|
||||
#endif
|
||||
|
||||
// Fill background with cornflower blue.
|
||||
SDL_SetRenderDrawColor(RENDER_RENDERER, 100, 149, 237, 255);
|
||||
SDL_RenderClear(RENDER_RENDERER);
|
||||
glClearColor(0.392f, 0.584f, 0.929f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void renderBackBufferUnbind(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
SDL_SetRenderTarget(RENDER_RENDERER, NULL);
|
||||
frameBufferBind(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void renderBackBufferDraw(void) {
|
||||
#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;
|
||||
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;
|
||||
if(RENDER_WIDTH * windowHeight > RENDER_HEIGHT * windowWidth) {
|
||||
renderWidth = windowWidth;
|
||||
@@ -77,8 +69,16 @@ void renderBackBufferDraw(void) {
|
||||
}
|
||||
|
||||
// Draw the back buffer texture
|
||||
SDL_Rect destRect = { renderX, renderY, renderWidth, renderHeight };
|
||||
SDL_RenderCopy(RENDER_RENDERER, RENDER_BACKBUFFER, NULL, &destRect);
|
||||
spriteBatchClear();
|
||||
spriteBatchPush(
|
||||
&RENDER_BACKBUFFER.texture,
|
||||
renderX, renderY,
|
||||
renderX+renderWidth, renderY+renderHeight,
|
||||
0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0, 1, 1, 0
|
||||
);
|
||||
spriteBatchFlush();
|
||||
cameraScreenPop();
|
||||
#else
|
||||
// No back buffer to draw
|
||||
#endif
|
||||
@@ -86,8 +86,7 @@ void renderBackBufferDraw(void) {
|
||||
|
||||
errorret_t renderBackBufferDispose(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
SDL_DestroyTexture(RENDER_BACKBUFFER);
|
||||
RENDER_BACKBUFFER = NULL;
|
||||
frameBufferDispose(&RENDER_BACKBUFFER);
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
|
@@ -7,6 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
#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
|
||||
|
@@ -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
|
||||
}
|
@@ -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;
|
||||
}
|
13
src/dusksdl2/display/scene/CMakeLists.txt
Normal file
13
src/dusksdl2/display/scene/CMakeLists.txt
Normal 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)
|
42
src/dusksdl2/display/scene/renderscene.c
Normal file
42
src/dusksdl2/display/scene/renderscene.c
Normal 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();
|
||||
}
|
||||
}
|
@@ -6,7 +6,15 @@
|
||||
*/
|
||||
|
||||
#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.
|
13
src/dusksdl2/display/spritebatch/CMakeLists.txt
Normal file
13
src/dusksdl2/display/spritebatch/CMakeLists.txt
Normal 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)
|
75
src/dusksdl2/display/spritebatch/spritebatch.c
Normal file
75
src/dusksdl2/display/spritebatch/spritebatch.c
Normal 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);
|
||||
}
|
52
src/dusksdl2/display/spritebatch/spritebatch.h
Normal file
52
src/dusksdl2/display/spritebatch/spritebatch.h
Normal 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();
|
@@ -8,25 +8,26 @@
|
||||
#include "texture.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/math.h"
|
||||
|
||||
void textureInit(
|
||||
texture_t *texture,
|
||||
const int32_t width,
|
||||
const int32_t height,
|
||||
const GLenum format,
|
||||
const uint8_t *data
|
||||
) {
|
||||
assertNotNull(texture, "Texture cannot be NULL");
|
||||
assertTrue(width > 0 && height > 0, "Width and height must be greater than 0");
|
||||
assertNotNull(data, "Data cannot be NULL");
|
||||
|
||||
#if PSP
|
||||
assertTrue(
|
||||
width % 4 == 0,
|
||||
"Width must be multiples of 4 for PSP"
|
||||
width == mathNextPowTwo(width),
|
||||
"Width must be powers of 2 for PSP"
|
||||
);
|
||||
assertTrue(
|
||||
height % 2 == 0,
|
||||
"Height must be multiples of 2 for PSP"
|
||||
height == mathNextPowTwo(height),
|
||||
"Height must be powers of 2 for PSP"
|
||||
);
|
||||
#endif
|
||||
|
||||
@@ -37,8 +38,8 @@ void textureInit(
|
||||
glGenTextures(1, &texture->id);
|
||||
glBindTexture(GL_TEXTURE_2D, texture->id);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, data
|
||||
GL_TEXTURE_2D, 0, format, width, height, 0,
|
||||
format, GL_UNSIGNED_BYTE, data
|
||||
);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
@@ -46,14 +47,26 @@ void textureInit(
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 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) {
|
||||
if(texture == NULL) {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
// glBindTexture(GL_TEXTURE_2D, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -20,12 +20,14 @@ typedef struct {
|
||||
* @param texture The texture to initialize.
|
||||
* @param width The width of the texture.
|
||||
* @param height The height of the texture.
|
||||
* @param format The format of the texture (e.g., GL_RGBA, GL_ALPHA).
|
||||
* @param data The pixel data for the texture.
|
||||
*/
|
||||
void textureInit(
|
||||
texture_t *texture,
|
||||
const int32_t width,
|
||||
const int32_t height,
|
||||
const GLenum format,
|
||||
const uint8_t *data
|
||||
);
|
||||
|
||||
|
14
src/dusksdl2/display/ui/CMakeLists.txt
Normal file
14
src/dusksdl2/display/ui/CMakeLists.txt
Normal 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
|
||||
)
|
@@ -7,12 +7,10 @@
|
||||
|
||||
#include "renderconsole.h"
|
||||
#include "console/console.h"
|
||||
#include "rendertext.h"
|
||||
#include "display/ui/rendertext.h"
|
||||
|
||||
void renderConsoleDraw(void) {
|
||||
// if(!CONSOLE.visible) return;
|
||||
|
||||
// renderTextDraw(0, 0, "Dusk Console");
|
||||
if(!CONSOLE.visible) return;
|
||||
|
||||
int32_t i = 0;
|
||||
char_t *line;
|
||||
@@ -22,7 +20,10 @@ void renderConsoleDraw(void) {
|
||||
i++;
|
||||
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++;
|
||||
} while(i < CONSOLE_HISTORY_MAX);
|
||||
}
|
46
src/dusksdl2/display/ui/renderfps.c
Normal file
46
src/dusksdl2/display/ui/renderfps.c
Normal 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);
|
||||
}
|
13
src/dusksdl2/display/ui/renderfps.h
Normal file
13
src/dusksdl2/display/ui/renderfps.h
Normal 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);
|
159
src/dusksdl2/display/ui/rendertext.c
Normal file
159
src/dusksdl2/display/ui/rendertext.c
Normal 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);
|
||||
}
|
@@ -8,8 +8,9 @@
|
||||
#pragma once
|
||||
#include "dusksdl2.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.
|
||||
@@ -22,11 +23,17 @@ void renderTextInit(void);
|
||||
* @param x The x-coordinate to draw the character at.
|
||||
* @param y The y-coordinate to draw the character at.
|
||||
* @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(
|
||||
const float_t x,
|
||||
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 y The y-coordinate to draw the text at.
|
||||
* @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(
|
||||
const float_t x,
|
||||
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
|
||||
);
|
||||
|
||||
/**
|
70
src/dusksdl2/display/ui/rendertextbox.c
Normal file
70
src/dusksdl2/display/ui/rendertextbox.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
10
src/dusksdl2/display/ui/rendertextbox.h
Normal file
10
src/dusksdl2/display/ui/rendertextbox.h
Normal 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);
|
63
src/dusksdl2/display/ui/renderui.c
Normal file
63
src/dusksdl2/display/ui/renderui.c
Normal 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();
|
||||
}
|
||||
}
|
32
src/dusksdl2/display/ui/renderui.h
Normal file
32
src/dusksdl2/display/ui/renderui.h
Normal 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);
|
@@ -13,3 +13,4 @@
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include <cglm/cglm.h>
|
@@ -7,8 +7,8 @@
|
||||
|
||||
#include "dusksdl2input.h"
|
||||
|
||||
uint8_t inputStateGet() {
|
||||
uint8_t state = 0;
|
||||
inputstate_t inputStateGet() {
|
||||
inputstate_t state = 0;
|
||||
|
||||
#if INPUT_SUPPORT_GAMEPAD
|
||||
// Get gamepad state.
|
||||
|
@@ -20,7 +20,7 @@
|
||||
#if INPUT_SUPPORT_GAMEPAD
|
||||
typedef struct {
|
||||
const SDL_GameControllerButton button;
|
||||
const uint8_t bind;
|
||||
const inputbind_t bind;
|
||||
} inputsdlbuttonmap_t;
|
||||
|
||||
static const inputsdlbuttonmap_t INPUT_SDL_BUTTON_MAP[] = {
|
||||
@@ -30,6 +30,7 @@
|
||||
{ SDL_CONTROLLER_BUTTON_DPAD_RIGHT, INPUT_BIND_RIGHT },
|
||||
{ SDL_CONTROLLER_BUTTON_A, INPUT_BIND_ACTION },
|
||||
{ SDL_CONTROLLER_BUTTON_B, INPUT_BIND_CANCEL },
|
||||
{ SDL_CONTROLLER_BUTTON_BACK, INPUT_BIND_CONSOLE },
|
||||
{ 0, 0 }
|
||||
};
|
||||
#endif
|
||||
@@ -37,7 +38,7 @@
|
||||
#if INPUT_SUPPORT_KEYBOARD
|
||||
typedef struct {
|
||||
SDL_Scancode code;
|
||||
uint8_t bind;
|
||||
inputbind_t bind;
|
||||
} inputsdlkbmap_t;
|
||||
|
||||
static const inputsdlkbmap_t INPUT_SDL_KEYBOARD_MAP[] = {
|
||||
@@ -45,8 +46,18 @@
|
||||
{ SDL_SCANCODE_S, INPUT_BIND_DOWN },
|
||||
{ SDL_SCANCODE_A, INPUT_BIND_LEFT },
|
||||
{ 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_E, INPUT_BIND_ACTION },
|
||||
{ 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 }
|
||||
};
|
||||
#endif
|
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "display/render.h"
|
||||
#include "game.h"
|
||||
#include "console/console.h"
|
||||
#include "input.h"
|
||||
|
||||
#define mainError(ret) \
|
||||
@@ -25,6 +24,8 @@ int main(int argc, char *argv[]) {
|
||||
while(RENDER_RUNNING) {
|
||||
gameUpdate();
|
||||
mainError(renderDraw());
|
||||
|
||||
if(!GAME.running) break;
|
||||
}
|
||||
|
||||
gameDispose();
|
||||
|
19
src/dusksdl2/time.c
Normal file
19
src/dusksdl2/time.c
Normal 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;
|
||||
}
|
@@ -4,4 +4,3 @@ CHUNK_HEIGHT = 8
|
||||
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT
|
||||
CHUNK_ENTITY_COUNT_MAX = 8
|
||||
TILE_WIDTH_HEIGHT = 16
|
||||
TILE_WIDTH_HEIGHT = 16
|
@@ -27,7 +27,7 @@ def parseEntity(obj, chunkData):
|
||||
|
||||
obj['localX'] = obj['x'] - (chunkData['topLeftTileX'] * TILE_WIDTH_HEIGHT)
|
||||
obj['localY'] = obj['y'] - (chunkData['topLeftTileY'] * TILE_WIDTH_HEIGHT)
|
||||
obj['dir'] = 'ENTITY_DIR_SOUTH'
|
||||
obj['dir'] = 'DIRECTION_SOUTH'
|
||||
obj['type'] = entType
|
||||
|
||||
def getProperty(propName):
|
||||
|
@@ -1,2 +0,0 @@
|
||||
def floatToFixed248(value):
|
||||
return value;
|
@@ -2,11 +2,10 @@ import sys, os
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import math
|
||||
from helper import floatToFixed248
|
||||
from inputParser import parseInputFile
|
||||
from mapParser import parseMap
|
||||
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
|
||||
parser = argparse.ArgumentParser(description="Generate chunk header files")
|
||||
@@ -83,8 +82,8 @@ for chunkY in range(mapData['mapHeightInRealChunks']):
|
||||
f.write(" {\n")
|
||||
f.write(f" .id = {entity['id']},\n")
|
||||
f.write(f" .type = {entity['type']},\n")
|
||||
f.write(f" .x = {floatToFixed248(entity['x'])},\n")
|
||||
f.write(f" .y = {floatToFixed248(entity['y'])},\n")
|
||||
f.write(f" .x = {round(entity['x'] / TILE_WIDTH_HEIGHT)},\n")
|
||||
f.write(f" .y = {round(entity['y'] / TILE_WIDTH_HEIGHT)},\n")
|
||||
|
||||
if 'dir' in entity:
|
||||
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")
|
||||
|
||||
# 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_Y ((float_t){floatToFixed248(mapData['playerSpawnY'])})\n")
|
||||
f.write(f"#define WORLD_PLAYER_SPAWN_X ({round(mapData['playerSpawnX'] / TILE_WIDTH_HEIGHT)})\n")
|
||||
f.write(f"#define WORLD_PLAYER_SPAWN_Y ({round(mapData['playerSpawnY'] / TILE_WIDTH_HEIGHT)})\n")
|
||||
|
||||
f.write("\n")
|
||||
|
||||
|
Reference in New Issue
Block a user