Starting on shaped tile hitboxes

This commit is contained in:
2025-06-19 23:49:53 -05:00
parent 6b16c97c64
commit 1022f45565
17 changed files with 360 additions and 48 deletions

View File

@ -33,6 +33,7 @@
"overworld.tsx"
],
"project": "map project.tiled-project",
"property.type": "int",
"recentFiles": [
"overworld.tsx",
"map.tmj",

View File

@ -411,17 +411,11 @@
"id":3,
"name":"Object Layer 1",
"objects":[
{
"id":3,
"template":"templates\/NPC.tx",
"x":6551.3106060606,
"y":6829.13636363636
},
{
"id":4,
"template":"templates\/NPC.tx",
"x":6649.66666666667,
"y":6741.58333333334
"x":6650.41666666667,
"y":6753.58333333334
},
{
"gid":257,
@ -438,14 +432,14 @@
{
"id":7,
"template":"templates\/NPC.tx",
"x":6497,
"y":6942
"x":6700.25,
"y":6838.75
},
{
"id":8,
"template":"templates\/NPC.tx",
"x":6640,
"y":6817
"x":6559,
"y":6754.75
},
{
"id":9,
@ -456,8 +450,8 @@
{
"id":10,
"template":"templates\/NPC.tx",
"x":6781,
"y":6746.33333333333
"x":6697,
"y":6769.08333333333
}],
"opacity":1,
"type":"objectgroup",
@ -466,7 +460,7 @@
"y":0
}],
"nextlayerid":6,
"nextobjectid":11,
"nextobjectid":13,
"orientation":"orthogonal",
"renderorder":"right-down",
"tiledversion":"1.11.1",

View File

@ -1,6 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.1" name="overworld" tilewidth="16" tileheight="16" tilecount="256" columns="16">
<image source="tilemap.png" width="256" height="256"/>
<tile id="1">
<properties>
<property name="solid" type="int" value="4"/>
</properties>
</tile>
<tile id="3">
<properties>
<property name="solid" type="int" value="5"/>
</properties>
</tile>
<tile id="18">
<properties>
<property name="solid" type="int" value="1"/>
</properties>
</tile>
<tile id="33">
<properties>
<property name="solid" type="int" value="2"/>
</properties>
</tile>
<tile id="35">
<properties>
<property name="solid" type="int" value="3"/>
</properties>
</tile>
<tile id="54">
<objectgroup draworder="index" id="3">
<object id="3" x="5.04743" y="5.92885" width="7.93676" height="6.0751"/>

View File

@ -55,33 +55,77 @@ void entityUpdate(entity_t *entity) {
fixed248_t halfTileWH = FIXED248(TILE_WIDTH_HEIGHT / 2, 0);
fixed248_t selfCircR = halfTileWH;
// Check for collisions with the world boundaries
// Check for collisions with tiles
uint8_t tileChecks = ENTITY_TILE_COLISSION_RESOLUTION_COUNT;
do {
// loop to resolve tile collisions multiple times to handle sliding
// correctly
bool tileResolved = false;
// Check for collisions with tile
fixed248_t tileStartX = fx248Floor(newX - halfTileWH);
fixed248_t tileStartY = fx248Floor(newY - halfTileWH);
fixed248_t tileEndX = fx248Ceil(newX + halfTileWH);
fixed248_t tileEndY = fx248Ceil(newY + halfTileWH);
// Compute affected tile range
fixed248_t tileStartX = fx248Floor(fx248Divfx248(
(newX - halfTileWH), FIXED248(TILE_WIDTH_HEIGHT, 0)
));
fixed248_t tileStartY = fx248Floor(fx248Divfx248(
(newY - halfTileWH), FIXED248(TILE_WIDTH_HEIGHT, 0)
));
fixed248_t tileEndX = fx248Ceil(fx248Divfx248(
(newX + halfTileWH), FIXED248(TILE_WIDTH_HEIGHT, 0)
));
fixed248_t tileEndY = fx248Ceil(fx248Divfx248(
(newY + halfTileWH), FIXED248(TILE_WIDTH_HEIGHT, 0)
));
for(fixed248_t y = tileStartY; y <= tileEndY; y+= FIXED248_ONE) {
for(fixed248_t x = tileStartX; x <= tileEndX; x+= FIXED248_ONE) {
//
}
// For each tile
for(fixed248_t y = tileStartY; y <= tileEndY; y += FIXED248_ONE) {
for(fixed248_t x = tileStartX; x <= tileEndX; x += FIXED248_ONE) {
uint16_t tileX = fx248Tou16(x);
uint16_t tileY = fx248Tou16(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];
tilesolidtype_t solidType = (
tile < TILE_META_DATA_COUNT ? TILE_META_DATA[tile].solidType :
TILE_SOLID_NONE
);
collisionresult_t collision;
switch(solidType) {
case TILE_SOLID_FULL:
collision = physicsCheckCircleAABB(
newX, newY, selfCircR,
fx248Mulfx248(x, FIXED248(TILE_WIDTH_HEIGHT, 0)),
fx248Mulfx248(y, FIXED248(TILE_WIDTH_HEIGHT, 0)),
FIXED248(TILE_WIDTH_HEIGHT, 0),
FIXED248(TILE_WIDTH_HEIGHT, 0)
);
default:
continue;
}
// uint16_t tileStartX = fx248Tou16(fx248Floor(newX - halfTileWH));
// uint16_t tileStartY = fx248Tou16(fx248Floor(newY - halfTileWH));
// uint16_t tileEndX = fx248Tou16(fx248Ceil(newX + halfTileWH)) + 1;
// uint16_t tileEndY = fx248Tou16(fx248Ceil(newY + halfTileWH)) + 1;
// printf("Checking tiles between (%u, %u) and (%u, %u)\n",
// tileStartX, tileStartY, tileEndX, tileEndY
// );
// for(uint16_t tileY = tileStartY; tileY <= tileEndY; tileY++) {
// for(uint16_t tileX = tileStartX; tileX <= tileEndX; tileX++) {
// }
// }
if(collision.hit && collision.depth > FIXED248(0, 1)) {
fixed248_t slideX = fx248Mulfx248(
collision.normalX, collision.depth
);
fixed248_t slideY = fx248Mulfx248(
collision.normalY, collision.depth
);
newX -= slideX;
newY -= slideY;
tileResolved = true;
}
}
}
if(!tileResolved) break; // no more overlaps
} while(--tileChecks > 0);
// Check for collisions with other entities
entity_t *otherEntity = ENTITIES;
@ -98,8 +142,8 @@ void entityUpdate(entity_t *entity) {
if(!collision.hit) continue;
// Collision with entity detected. Slide out of collision.
fixed248_t slideX = fx248Mulf32(collision.normalX, fx248Tof32(collision.depth));
fixed248_t slideY = fx248Mulf32(collision.normalY, fx248Tof32(collision.depth));
fixed248_t slideX = fx248Mulfx248(collision.normalX, collision.depth);
fixed248_t slideY = fx248Mulfx248(collision.normalY, collision.depth);
newX -= slideX;
newY -= slideY;
} while(++otherEntity < ENTITIES + ENTITY_COUNT_MAX);

View File

@ -11,6 +11,7 @@
#include "util/fixed.h"
#define ENTITY_COUNT_MAX 32
#define ENTITY_TILE_COLISSION_RESOLUTION_COUNT 4
typedef enum {
ENTITY_DIR_SOUTH = 0,

View File

@ -50,3 +50,51 @@ collisionresult_t physicsCheckCircleCircle(
result.hit = true;
return result;
}
collisionresult_t physicsCheckCircleAABB(
fixed248_t circleX, fixed248_t circleY, fixed248_t circleR,
fixed248_t aabbX, fixed248_t aabbY,
fixed248_t aabbWidth, fixed248_t aabbHeight
) {
collisionresult_t result;
// Find the closest point on the AABB to the circle center
fixed248_t closestX = fx248Max(
aabbX, fx248Min(circleX, fx248Addfx248(aabbX, aabbWidth))
);
fixed248_t closestY = fx248Max(
aabbY, fx248Min(circleY, fx248Addfx248(aabbY, aabbHeight))
);
// Vector from circle center to closest point
fixed248_t dx = fx248Subfx248(closestX, circleX);
fixed248_t dy = fx248Subfx248(closestY, circleY);
// Distance squared from circle center to closest point
fixed248_t distSq = fx248Addfx248(fx248Mulfx248(dx, dx), fx248Mulfx248(dy, dy));
// Check if distance is less than radius squared
if(distSq > fx248Mulfx248(circleR, circleR)) {
result.hit = false;
return result;
}
// Collision: calculate normal and penetration depth
fixed248_t dist = fx248Sqrt(distSq);
if(dist <= FIXED248(0, 1)) {
// Circle center is at the AABB corner
result.normalX = FIXED248(1,0);
result.normalY = FIXED248(0,1);
result.depth = circleR;
} else {
// Normalized direction from circle center to closest point
result.normalX = fx248Divfx248(dx, dist);
result.normalY = fx248Divfx248(dy, dist);
// Penetration depth = radius - distance
result.depth = fx248Subfx248(circleR, dist);
}
result.hit = true;
return result;
}

View File

@ -29,3 +29,21 @@ collisionresult_t physicsCheckCircleCircle(
fixed248_t circle0x, fixed248_t circle0y, fixed248_t circle0r,
fixed248_t circle1x, fixed248_t circle1y, fixed248_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(
fixed248_t circleX, fixed248_t circleY, fixed248_t circleR,
fixed248_t aabb, fixed248_t aabbY,
fixed248_t aabbWidth, fixed248_t aabbHeight
);

View File

@ -24,24 +24,34 @@ fixed248_t fx248Fromu16(const uint16_t b) {
return (fixed248_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
fixed248_t fx248Fromu8(const uint8_t b) {
return (fixed248_t)((int32_t)b << FIXED248_FRACTION_BITS);
}
int32_t fx248Toi32(const fixed248_t a) {
return (int32_t)(a >> FIXED248_FRACTION_BITS);
return a >> FIXED248_FRACTION_BITS;
}
uint32_t fx248Tou32(const fixed248_t a) {
return (uint32_t)((a >> FIXED248_FRACTION_BITS) & 0xFFFFFFFF);
return (uint32_t)(a >> FIXED248_FRACTION_BITS);
}
float_t fx248Tof32(const fixed248_t a) {
return (float_t)(a / (float_t)(1 << FIXED248_FRACTION_BITS));
return (float_t)a / (1 << FIXED248_FRACTION_BITS);
}
uint16_t fx248Tou16(const fixed248_t a) {
return (uint16_t)((a >> FIXED248_FRACTION_BITS) & 0xFFFF);
return (uint16_t)(a >> FIXED248_FRACTION_BITS);
}
uint8_t fx248Tou8(const fixed248_t a) {
return (uint8_t)(a >> FIXED248_FRACTION_BITS);
}
fixed248_t fx248Addfx248(const fixed248_t a, const fixed248_t b) {
return a + b;
}
@ -171,3 +181,13 @@ fixed248_t fx248Sqrt(const fixed248_t a) {
}
return y;
}
fixed248_t fx248Max(const fixed248_t a, const fixed248_t b) {
return (a > b) ? a : b;
}
fixed248_t fx248Min(const fixed248_t a, const fixed248_t b) {
return (a < b) ? a : b;
}

View File

@ -52,6 +52,14 @@ fixed248_t fx248Fromf32(const float_t b);
*/
fixed248_t fx248Fromu16(const uint16_t b);
/**
* Convert a uint8_t value to a fixed248_t value.
*
* @param b The uint8_t value to convert.
* @return The converted fixed248_t value.
*/
fixed248_t fx248Fromu8(const uint8_t b);
/**
@ -86,6 +94,14 @@ float_t fx248Tof32(const fixed248_t a);
*/
uint16_t fx248Tou16(const fixed248_t a);
/**
* Convert a fixed248_t value to an uint8_t value.
*
* @param a The fixed248_t value to convert.
* @return The converted uint8_t value.
*/
uint8_t fx248Tou8(const fixed248_t a);
/**
@ -300,3 +316,23 @@ uint32_t fx248Roundu32(const fixed248_t a);
* @param a The fixed248_t value to calculate the square root of.
*/
fixed248_t fx248Sqrt(const fixed248_t a);
/**
* Returns the maximum of two fixed248_t values.
*
* @param a First fixed248_t value.
* @param b Second fixed248_t value.
* @return The maximum of the two values.
*/
fixed248_t fx248Max(const fixed248_t a, const fixed248_t b);
/**
* Returns the minimum of two fixed248_t values.
*
* @param a First fixed248_t value.
* @param b Second fixed248_t value.
* @return The minimum of the two values.
*/
fixed248_t fx248Min(const fixed248_t a, const fixed248_t b);

View File

@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE
chunk.c
overworld.c
tile.c
)

View File

@ -169,6 +169,20 @@ void chunkMapSetPosition(const uint16_t x, const uint16_t y) {
chunkMapShift(shiftX, shiftY);
}
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY) {
assertTrue(
chunkX < WORLD_WIDTH && chunkY < WORLD_HEIGHT,
"Chunk coordinates out of bounds"
);
chunk_t *chunk = CHUNK_MAP.chunks;
do {
if(chunk->x == chunkX && chunk->y == chunkY) return chunk;
chunk++;
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
return NULL;
}
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
assertNotNull(chunk, "Chunk pointer is null");

View File

@ -56,6 +56,16 @@ void chunkMapShift(const int16_t x, const int16_t y);
*/
void chunkMapSetPosition(const uint16_t x, const uint16_t y);
/**
* Gets the chunk at the specified chunk coordinates.
*
* @param chunkX The x coordinate of the chunk.
* @param chunkY The y coordinate of the chunk.
* @return A pointer to the chunk at the specified chunk coordinates, or NULL if
* no chunk exists at those coordinates.
*/
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY);
/**
* Loads a chunk at the specified coordinates.
*

8
src/dusk/world/tile.c Normal file
View File

@ -0,0 +1,8 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "tile.h"

View File

@ -11,3 +11,42 @@
#define TILE_WIDTH_HEIGHT 16
typedef uint8_t tile_t;
typedef enum {
TILE_SOLID_NONE = 0,
TILE_SOLID_FULL = 1,
TILE_SOLID_TRIANGLE_TOP_RIGHT = 2,
TILE_SOLID_TRIANGLE_TOP_LEFT = 3,
TILE_SOLID_TRIANGLE_BOTTOM_RIGHT = 4,
TILE_SOLID_TRIANGLE_BOTTOM_LEFT = 5,
} tilesolidtype_t;
typedef struct {
tilesolidtype_t solidType;
} tilemeta_t;
static const tilemeta_t TILE_META_DATA[] = {
{TILE_SOLID_NONE}, // 0
{TILE_SOLID_NONE}, // 1
{TILE_SOLID_NONE}, // 2
{TILE_SOLID_NONE}, // 3
{TILE_SOLID_NONE}, // 4
{TILE_SOLID_NONE}, // 5
{TILE_SOLID_NONE}, // 6
{TILE_SOLID_NONE}, // 7
{TILE_SOLID_NONE}, // 8
{TILE_SOLID_NONE}, // 9
{TILE_SOLID_NONE}, // 10
{TILE_SOLID_NONE}, // 11
{TILE_SOLID_NONE}, // 12
{TILE_SOLID_NONE}, // 13
{TILE_SOLID_NONE}, // 14
{TILE_SOLID_NONE}, // 15
{TILE_SOLID_NONE}, // 16
{TILE_SOLID_NONE}, // 17
{TILE_SOLID_FULL}, // 18
{TILE_SOLID_FULL}, // 19
{TILE_SOLID_FULL}, // 19
};
#define TILE_META_DATA_COUNT (sizeof(TILE_META_DATA) / sizeof(tilemeta_t))

View File

@ -422,7 +422,7 @@ with open(headerPath, 'w') as f:
for i in range(worldHeight):
f.write(" ")
for j in range(worldWidth):
if (j, i) in chunksDone:
if(j, i) in chunksDone:
f.write(f"&CHUNK_{j}_{i}, ")
else:
f.write("NULL, ")

View File

@ -0,0 +1,21 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
find_package(Python3 COMPONENTS Interpreter REQUIRED)
# Custom command to generate all header files
add_custom_target(DUSK_CHUNKS
# OUTPUT ${DUSK_GENERATED_HEADERS_DIR}/world/world.h
COMMAND
${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/mapcompile.py
--output ${DUSK_GENERATED_HEADERS_DIR}
--input ${DUSK_DATA_DIR}/map.tmj
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mapcompile.py
COMMENT "Generating chunk header files"
VERBATIM
)
# Ensure headers are generated before compiling main
add_dependencies(${DUSK_TARGET_NAME} DUSK_CHUNKS)

View File

@ -0,0 +1,32 @@
import sys, os
import argparse
from datetime import datetime
import json
import math
# Check if the script is run with the correct arguments
parser = argparse.ArgumentParser(description="Generate chunk header files")
parser.add_argument('--output', required=True, help='Dir to output headers')
parser.add_argument('--input', required=True, help='Input JSON file from tiled')
args = parser.parse_args()
# Ensure outdir exists
outputDir = args.output
os.makedirs(outputDir, exist_ok=True)
# Create world directory if it does not exist
worldDir = os.path.join(outputDir, "world")
os.makedirs(worldDir, exist_ok=True)
# Some vars used during printing
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Read the input JSON file
inputFile = args.input
if not os.path.isfile(inputFile):
print(f"Error: Input file '{inputFile}' does not exist.")
sys.exit(1)
with open(inputFile, 'r') as f:
data = json.load(f)