physics impl part 1
parent
36a4a010c8
commit
4d1a90d5cb
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -45,6 +45,6 @@ add_library(eco2d-foundation STATIC
|
|||
|
||||
target_compile_definitions(eco2d-foundation PRIVATE CLIENT)
|
||||
include_directories(src ../modules ../../art/gen)
|
||||
target_link_libraries(eco2d-foundation raylib raylib_nuklear cwpack flecs-bundle vendors-bundle)
|
||||
target_link_libraries(eco2d-foundation raylib raylib_nuklear ferox cwpack flecs-bundle vendors-bundle)
|
||||
|
||||
link_system_libs(eco2d-foundation)
|
||||
|
|
|
@ -24,6 +24,8 @@ typedef struct {
|
|||
float vehicle_brake_force;
|
||||
float veh_enter_radius;
|
||||
float blueprint_build_time;
|
||||
|
||||
// survival rules
|
||||
} game_rulesdef;
|
||||
|
||||
extern game_rulesdef game_rules;
|
||||
|
|
|
@ -163,7 +163,8 @@ static debug_item items[] = {
|
|||
.is_collapsed = false
|
||||
}
|
||||
},
|
||||
{ .kind = DITEM_BUTTON, .name = "spawn car", .on_click = ActSpawnCar },
|
||||
{ .kind = DITEM_BUTTON, .name = "spawn mobs", .on_click = ActSpawnMobs },
|
||||
{ .kind = DITEM_BUTTON, .name = "spawn car", .on_click = ActSpawnCar },
|
||||
{ .kind = DITEM_BUTTON, .name = "place ice rink", .on_click = ActPlaceIceRink },
|
||||
{ .kind = DITEM_BUTTON, .name = "erase world changes", .on_click = ActEraseWorldChanges },
|
||||
{ .kind = DITEM_BUTTON, .name = "spawn circling driver", .on_click = ActSpawnCirclingDriver },
|
||||
|
|
|
@ -54,6 +54,32 @@ ActSpawnSelItem(void) {
|
|||
entity_set_position(e, origin->x, origin->y);
|
||||
}
|
||||
|
||||
void
|
||||
ActSpawnMobs(void) {
|
||||
ecs_entity_t plr = camera_get().ent_id;
|
||||
Position const* origin = ecs_get(world_ecs(), plr, Position);
|
||||
|
||||
const uint32_t w = 12*WORLD_BLOCK_SIZE;
|
||||
const uint32_t h = 12*WORLD_BLOCK_SIZE;
|
||||
uint32_t x = (uint32_t)origin->x - w/2;
|
||||
uint32_t y = (uint32_t)origin->y - h/2;
|
||||
|
||||
for (uint32_t cy=y; cy<y+h; cy+=WORLD_BLOCK_SIZE) {
|
||||
for (uint32_t cx=x; cx<x+w; cx+=WORLD_BLOCK_SIZE) {
|
||||
if (cx < 0 || cx >= world_dim()) continue;
|
||||
if (cy < 0 || cy >= world_dim()) continue;
|
||||
|
||||
if ((cy == y || cy == (y + h-WORLD_BLOCK_SIZE)) ||
|
||||
(cx == x || cx == (x + w-WORLD_BLOCK_SIZE))) {
|
||||
ecs_entity_t e = entity_spawn_id(ASSET_MOB);
|
||||
entity_set_position(e, (float)cx, (float)cy);
|
||||
|
||||
ecs_add(world_ecs(), e, MobMelee);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ActSpawnCirclingDriver(void) {
|
||||
ecs_entity_t plr = camera_get().ent_id;
|
||||
|
|
|
@ -23,6 +23,14 @@ void ToolAssetInspector(void) {
|
|||
tooltip_show_cursor(asset_names[i]);
|
||||
}
|
||||
|
||||
if (nk_button_label(dev_ui, "spawn at me")) {
|
||||
uint64_t e = entity_spawn_id(i);
|
||||
ecs_entity_t plr = camera_get().ent_id;
|
||||
|
||||
Position const* origin = ecs_get(world_ecs(), plr, Position);
|
||||
entity_set_position(e, origin->x, origin->y);
|
||||
}
|
||||
|
||||
// draw help text
|
||||
if (nk_tree_push_id(dev_ui, NK_TREE_NODE, "description", NK_MINIMIZED, i)) {
|
||||
{
|
||||
|
|
|
@ -56,6 +56,9 @@ Texture2D texgen_build_sprite_fallback(asset_id id) {
|
|||
case ASSET_CRAFTBENCH: return LoadTexEco("craftbench");
|
||||
case ASSET_SPLITTER: return LoadTexEco("item_splitter");
|
||||
case ASSET_ASSEMBLER: return LoadTexEco("assembler");
|
||||
|
||||
// Mobs
|
||||
case ASSET_MOB: return LoadTexEco("enemy1");
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ void tooltip_register_defaults(void) {
|
|||
tooltip_register( (tooltip) { .name = "ASSET_IRON_ORE", .content = "Natural resource that can be smelted in ASSET_FURNACE." } );
|
||||
tooltip_register( (tooltip) { .name = "ASSET_IRON_INGOT", .content = "Used as a building material. It is smelted from ASSET_IRON_ORE." } );
|
||||
tooltip_register( (tooltip) { .name = "ASSET_SCREWS", .content = "Used as a building material. It is crafted from ASSET_IRON_PLATES." } );
|
||||
tooltip_register( (tooltip) { .name = "ASSET_MOB", .content = "Enemy hunting player down." } );
|
||||
tooltip_register( (tooltip) { .name = "craft", .content = "Crafting is the process of constructing tools, items, and blocks." } );
|
||||
tooltip_register( (tooltip) { .name = "smelt", .content = "Smelting is a process of applying heat to ore, to extract a base metal. It is a form of extractive metallurgy. It is used to extract many metals from their ores, including silver, iron, copper, and other base metals." } );
|
||||
}
|
||||
|
@ -89,6 +90,8 @@ tooltip *tooltip_find_desc(const char *name) {
|
|||
}
|
||||
|
||||
const char *tooltip_find_desc_contents(const char *name) {
|
||||
if (!tooltips) return 0;
|
||||
|
||||
for (zpl_isize i = 0; i < zpl_array_count(tooltips); ++i) {
|
||||
tooltip *tp = (tooltips + i);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
X(ASSET_BLUEPRINT)\
|
||||
X(ASSET_BLUEPRINT_DEMO_HOUSE)\
|
||||
X(ASSET_BLUEPRINT_END)\
|
||||
X(ASSET_MOB)\
|
||||
X(ASSET_FENCE)\
|
||||
X(ASSET_DEV)\
|
||||
X(ASSET_GROUND)\
|
||||
|
|
|
@ -31,6 +31,7 @@ static asset assets[] = {
|
|||
ASSET_TEX(ASSET_CRAFTBENCH),
|
||||
ASSET_TEX(ASSET_BLUEPRINT),
|
||||
ASSET_TEX(ASSET_BLUEPRINT_DEMO_HOUSE),
|
||||
ASSET_TEX(ASSET_MOB),
|
||||
|
||||
// NOTE(zaklaus): blocks
|
||||
ASSET_TEX(ASSET_FENCE),
|
||||
|
|
|
@ -12,6 +12,7 @@ static struct {
|
|||
{ .id = ASSET_ASSEMBLER, .proc = assembler_spawn },
|
||||
{ .id = ASSET_BLUEPRINT, .proc_udata = blueprint_spawn_udata },
|
||||
{ .id = ASSET_CREATURE, .proc = creature_spawn },
|
||||
{ .id = ASSET_MOB, .proc = mob_spawn },
|
||||
};
|
||||
|
||||
#define MAX_ENTITY_SPAWNDEFS ((sizeof(entity_spawnlist))/(sizeof(entity_spawnlist[0])))
|
||||
|
|
|
@ -36,7 +36,41 @@ typedef struct {
|
|||
} Drawable;
|
||||
|
||||
typedef Vector2D Position;
|
||||
typedef Vector2D Velocity;
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float m;
|
||||
} Velocity;
|
||||
|
||||
enum {
|
||||
PHYS_CIRCLE,
|
||||
PHYS_RECT
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t kind;
|
||||
union {
|
||||
struct {
|
||||
float r;
|
||||
} circle;
|
||||
|
||||
struct {
|
||||
float w;
|
||||
float h;
|
||||
} rect;
|
||||
};
|
||||
|
||||
float density;
|
||||
float static_friction;
|
||||
float dynamic_friction;
|
||||
|
||||
// flags
|
||||
uint8_t inf_inertia:4;
|
||||
uint8_t inf_mass:4;
|
||||
|
||||
// internals
|
||||
uintptr_t body_ptr;
|
||||
} PhysicsBody;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
|
@ -81,12 +115,20 @@ typedef struct {
|
|||
typedef struct {
|
||||
float hp;
|
||||
float max_hp;
|
||||
|
||||
//NOTE(zaklaus): Intentionally global, to allow for creative use of damage combos
|
||||
float pain_time;
|
||||
float heal_time;
|
||||
} Health;
|
||||
|
||||
typedef struct {
|
||||
float amt;
|
||||
} HealthRegen;
|
||||
|
||||
typedef struct {
|
||||
uint8_t delay;
|
||||
} HealDelay;
|
||||
|
||||
typedef struct {
|
||||
uint8_t _unused;
|
||||
} HealthDecreased;
|
||||
|
||||
typedef struct {
|
||||
uint16_t id;
|
||||
} Classify;
|
||||
|
@ -194,15 +236,34 @@ typedef struct {
|
|||
typedef struct { char _unused; } SeeksFood;
|
||||
typedef struct { char _unused; } SeeksCompanion;
|
||||
|
||||
// survival comps
|
||||
typedef struct {
|
||||
uint8_t atk_delay;
|
||||
} Mob;
|
||||
typedef struct {
|
||||
uint64_t plr;
|
||||
} MobHuntPlayer;
|
||||
|
||||
typedef struct {
|
||||
char _unused;
|
||||
} MobMelee;
|
||||
|
||||
#define _COMPS\
|
||||
X(Vector2D)\
|
||||
X(Position)\
|
||||
X(Velocity)\
|
||||
X(PhysicsBody)\
|
||||
X(Chunk)\
|
||||
X(Drawable)\
|
||||
X(Input)\
|
||||
X(ClientInfo)\
|
||||
X(Health)\
|
||||
X(HealthRegen)\
|
||||
X(HealDelay)\
|
||||
X(HealthDecreased)\
|
||||
X(Mob)\
|
||||
X(MobHuntPlayer)\
|
||||
X(MobMelee)\
|
||||
X(Classify)\
|
||||
X(Vehicle)\
|
||||
X(IsInVehicle)\
|
||||
|
|
|
@ -19,7 +19,7 @@ uint64_t entity_spawn(uint16_t class_id) {
|
|||
|
||||
if (class_id != EKIND_SERVER) {
|
||||
librg_entity_track(world_tracker(), e);
|
||||
ecs_set(world_ecs(), e, Velocity, {0});
|
||||
ecs_set(world_ecs(), e, Velocity, {.x = 0, .y = 0, .m = 500.0f});
|
||||
entity_set_position(e, (float)(rand() % world_dim()), (float)(rand() % world_dim()));
|
||||
|
||||
librg_entity_owner_set(world_tracker(), e, (int64_t)e);
|
||||
|
@ -70,6 +70,7 @@ void entity_despawn(uint64_t ent_id) {
|
|||
}
|
||||
|
||||
void entity_set_position(uint64_t ent_id, float x, float y) {
|
||||
ecs_set(world_ecs(), ent_id, Position, {x, y});
|
||||
Position *p = ecs_get_mut_ex(world_ecs(), ent_id, Position);
|
||||
p->x = x;
|
||||
p->y = y;
|
||||
|
|
|
@ -16,10 +16,21 @@ uint64_t player_spawn(char *name) {
|
|||
}
|
||||
|
||||
ecs_set_name(world_ecs(), e, name);
|
||||
ecs_set(world_ecs(), e, PhysicsBody, {
|
||||
.kind = PHYS_CIRCLE,
|
||||
.circle.r = 1.5f,
|
||||
.density = 0.5f,
|
||||
.static_friction = 0.35f,
|
||||
.dynamic_friction = 0.15f,
|
||||
.inf_inertia = 1,
|
||||
});
|
||||
ecs_set(world_ecs(), e, ClientInfo, {0});
|
||||
ecs_set(world_ecs(), e, Input, {0});
|
||||
ecs_set(world_ecs(), e, Inventory, {0});
|
||||
ecs_set(world_ecs(), e, Health, {.hp = PLAYER_MAX_HP, .max_hp = PLAYER_MAX_HP});
|
||||
ecs_set(world_ecs(), e, Health, {.hp = PLAYER_MAX_HP, .max_hp = PLAYER_MAX_HP});
|
||||
ecs_set(world_ecs(), e, HealthRegen, {.amt = 15.0f});
|
||||
Velocity *v = ecs_get_mut(world_ecs(), e, Velocity);
|
||||
*v = (Velocity) { 0, 0, 0.0f };
|
||||
|
||||
librg_entity_owner_set(world_tracker(), e, (int64_t)e);
|
||||
|
||||
|
|
|
@ -116,4 +116,22 @@ uint64_t storage_spawn(void) {
|
|||
return (uint64_t)e;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
uint64_t mob_spawn(void) {
|
||||
ecs_entity_t e = entity_spawn(EKIND_MONSTER);
|
||||
|
||||
Health *hp = ecs_get_mut_ex(world_ecs(), e, Health);
|
||||
hp->max_hp = hp->hp = 100.0f;
|
||||
|
||||
ecs_add(world_ecs(), e, Mob);
|
||||
ecs_set(world_ecs(), e, PhysicsBody, {
|
||||
.kind = PHYS_CIRCLE,
|
||||
.circle.r = 1.5f,
|
||||
.density = 0.25f,
|
||||
.static_friction = 0.35f,
|
||||
.dynamic_friction = 0.15f
|
||||
});
|
||||
|
||||
return (uint64_t)e;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
void MobDetectPlayers(ecs_iter_t *it) {
|
||||
Position *p = ecs_field(it, Position, 1);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
float closest_ent_dist = ZPL_F32_MAX;
|
||||
uint64_t closest_ent = 0;
|
||||
|
||||
ecs_iter_t pit = ecs_query_iter(world_ecs(), world_ecs_player());
|
||||
|
||||
while (ecs_query_next(&pit)) {
|
||||
Position *p2 = ecs_field(&pit, Position, 2);
|
||||
|
||||
for (int j = 0; j < pit.count; j++) {
|
||||
float dx = p2->x - p[j].x;
|
||||
float dy = p2->y - p[j].y;
|
||||
float range = (dx*dx + dy*dy);
|
||||
|
||||
if (range < closest_ent_dist)
|
||||
closest_ent = pit.entities[j];
|
||||
}
|
||||
}
|
||||
|
||||
if (!closest_ent)
|
||||
continue;
|
||||
|
||||
ecs_set(it->world, it->entities[i], MobHuntPlayer, { .plr = closest_ent });
|
||||
}
|
||||
}
|
||||
|
||||
#define MOB_MOVEMENT_SPEED 300.0f
|
||||
|
||||
void MobMovement(ecs_iter_t *it) {
|
||||
Velocity *v = ecs_field(it, Velocity, 1);
|
||||
Position *p = ecs_field(it, Position, 2);
|
||||
MobHuntPlayer *m = ecs_field(it, MobHuntPlayer, 3);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
const Position *p2 = ecs_get(it->world, m->plr, Position);
|
||||
zpl_vec2 pos1 = { .x = p[i].x, .y = p[i].y };
|
||||
zpl_vec2 pos2 = { .x = p2->x, .y = p2->y };
|
||||
zpl_vec2 dir;
|
||||
zpl_vec2_sub(&dir, pos2, pos1);
|
||||
zpl_vec2_norm(&dir, dir);
|
||||
|
||||
v[i].x += dir.x*(MOB_MOVEMENT_SPEED*safe_dt(it));
|
||||
v[i].y += dir.y*(MOB_MOVEMENT_SPEED*safe_dt(it));
|
||||
|
||||
entity_wake(it->entities[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#define MOB_MELEE_DIST 4000.0f
|
||||
#define MOB_MELEE_DMG 5.0f
|
||||
#define MOB_ATK_DELAY 10
|
||||
|
||||
void MobMeleeAtk(ecs_iter_t *it) {
|
||||
Position *p = ecs_field(it, Position, 1);
|
||||
Mob *mob = ecs_field(it, Mob, 2);
|
||||
MobHuntPlayer *m = ecs_field(it, MobHuntPlayer, 3);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
if (mob[i].atk_delay > 0) {
|
||||
TICK_VAR(mob[i].atk_delay);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Position *p2 = ecs_get(it->world, m->plr, Position);
|
||||
float dx = p2->x - p[i].x;
|
||||
float dy = p2->y - p[i].y;
|
||||
float range = (dx*dx + dy*dy);
|
||||
|
||||
if (range < MOB_MELEE_DIST) {
|
||||
Health *hp = ecs_get_mut_ex(it->world, m->plr, Health);
|
||||
hp->hp = zpl_max(hp->hp-MOB_MELEE_DMG, 0.0f);
|
||||
ecs_add(it->world, m->plr, HealthDecreased);
|
||||
mob[i].atk_delay = MOB_ATK_DELAY;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,9 @@
|
|||
#include "dev/debug_draw.h"
|
||||
#include "core/game.h"
|
||||
#include "core/rules.h"
|
||||
#include "ferox.h"
|
||||
|
||||
extern frWorld *phys_world;
|
||||
|
||||
#define PHY_BLOCK_COLLISION 1
|
||||
#define PHY_WALK_DRAG 4.23f
|
||||
|
@ -19,6 +22,7 @@
|
|||
#include "modules/system_logistics.c"
|
||||
#include "modules/system_producer.c"
|
||||
#include "modules/system_blueprint.c"
|
||||
#include "modules/system_mob.c"
|
||||
|
||||
static inline float physics_correction(float x, float vx, float bounce, float dim) {
|
||||
float r = (((zpl_max(0.0f, dim - zpl_abs(x))*zpl_sign(x)))*dim);
|
||||
|
@ -38,6 +42,7 @@ void IntegratePositions(ecs_iter_t *it) {
|
|||
if (ecs_get(it->world, it->entities[i], IsInVehicle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//if (zpl_abs(v[i].x) >= 0.0001f || zpl_abs(v[i].y) >= 0.0001f)
|
||||
{
|
||||
// NOTE(zaklaus): world bounds
|
||||
|
@ -108,7 +113,6 @@ void IntegratePositions(ecs_iter_t *it) {
|
|||
}
|
||||
}
|
||||
|
||||
#define HAZARD_BLOCK_TIME 1.0f
|
||||
#define HAZARD_BLOCK_DMG 5.0f
|
||||
|
||||
void HurtOnHazardBlock(ecs_iter_t *it) {
|
||||
|
@ -116,40 +120,50 @@ void HurtOnHazardBlock(ecs_iter_t *it) {
|
|||
Health *h = ecs_field(it, Health, 2);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
if (h->pain_time < 0.0f) {
|
||||
h->pain_time = HAZARD_BLOCK_TIME;
|
||||
world_block_lookup l = world_block_from_realpos(p[i].x, p[i].y);
|
||||
if (blocks_get_flags(l.bid) & BLOCK_FLAG_HAZARD) {
|
||||
h->hp -= HAZARD_BLOCK_DMG;
|
||||
h->hp = zpl_max(0.0f, h->hp);
|
||||
}
|
||||
}
|
||||
world_block_lookup l = world_block_from_realpos(p[i].x, p[i].y);
|
||||
if (blocks_get_flags(l.bid) & BLOCK_FLAG_HAZARD) {
|
||||
h->hp -= HAZARD_BLOCK_DMG;
|
||||
h->hp = zpl_max(0.0f, h->hp);
|
||||
ecs_add(it->world, it->entities[i], HealthDecreased);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define HP_REGEN_TIME 2.0f
|
||||
#define HP_REGEN_PAIN_COOLDOWN 5.0f
|
||||
#define HP_REGEN_RECOVERY 15.0f
|
||||
//#define HP_REGEN_PAIN_COOLDOWN 5.0f
|
||||
|
||||
void RegenerateHP(ecs_iter_t *it) {
|
||||
Health *h = ecs_field(it, Health, 1);
|
||||
Health *h = ecs_field(it, Health, 1);
|
||||
HealthRegen *r = ecs_field(it, HealthRegen, 2);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
if (h[i].pain_time < 0.0f) {
|
||||
if (h[i].heal_time < 0.0f && h[i].hp < h[i].max_hp) {
|
||||
h[i].heal_time = HP_REGEN_TIME;
|
||||
h[i].hp += HP_REGEN_RECOVERY;
|
||||
h[i].hp = zpl_min(h[i].max_hp, h[i].hp);
|
||||
entity_wake(it->entities[i]);
|
||||
} else {
|
||||
h[i].heal_time -= safe_dt(it);
|
||||
}
|
||||
} else {
|
||||
h[i].pain_time -= safe_dt(it);
|
||||
}
|
||||
// TODO delay regen on hurt
|
||||
if (h[i].hp < h[i].max_hp) {
|
||||
h[i].hp += r->amt;
|
||||
h[i].hp = zpl_min(h[i].max_hp, h[i].hp);
|
||||
entity_wake(it->entities[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnHealthChangePutDelay(ecs_iter_t *it) {
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
ecs_set(it->world, it->entities[i], HealDelay, { .delay = 10 });
|
||||
ecs_remove(it->world, it->entities[i], HealthDecreased);
|
||||
}
|
||||
}
|
||||
|
||||
void TickDownHealDelay(ecs_iter_t *it) {
|
||||
HealDelay *h = ecs_field(it, HealDelay, 1);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
--h[i].delay;
|
||||
|
||||
if (h[i].delay == 0) {
|
||||
ecs_remove(it->world, it->entities[i], HealDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetActivators(ecs_iter_t *it) {
|
||||
Input *in = ecs_field(it, Input, 1);
|
||||
|
||||
|
@ -218,6 +232,86 @@ void PlayerClosestInteractable(ecs_iter_t *it){
|
|||
}
|
||||
}
|
||||
|
||||
void PhysOnCreateBody(ecs_iter_t *it) {
|
||||
PhysicsBody *pb = ecs_field(it, PhysicsBody, 1);
|
||||
Position *p = ecs_field(it, Position, 2);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
const frMaterial mat = {
|
||||
.density = pb[i].density,
|
||||
.staticFriction = pb[i].static_friction,
|
||||
.dynamicFriction = pb[i].dynamic_friction,
|
||||
};
|
||||
|
||||
frShape *shape = 0;
|
||||
if (pb[i].kind == PHYS_CIRCLE) {
|
||||
shape = frCreateCircle(mat, pb[i].circle.r);
|
||||
} else {
|
||||
shape = frCreateRectangle(mat, pb[i].rect.w, pb[i].rect.h);
|
||||
}
|
||||
|
||||
frBodyFlags flags = 0x0;
|
||||
if (pb[i].inf_inertia) flags |= FR_FLAG_INFINITE_INERTIA;
|
||||
if (pb[i].inf_mass) flags |= FR_FLAG_INFINITE_MASS;
|
||||
frBody *body = frCreateBodyFromShape(FR_BODY_DYNAMIC, flags, frVec2PixelsToMeters((Vector2){p[i].x, p[i].y}), shape);
|
||||
frAddToWorld(phys_world, body);
|
||||
pb[i].body_ptr = (uintptr_t)body;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysOnRemoveBody(ecs_iter_t *it) {
|
||||
PhysicsBody *pb = ecs_field(it, PhysicsBody, 1);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
frBody *body = (frBody*)pb[i].body_ptr;
|
||||
frRemoveFromWorld(phys_world, body);
|
||||
frShape *shape = frGetBodyShape(body);
|
||||
frReleaseBody(body);
|
||||
frReleaseShape(shape);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysSetVelocity(ecs_iter_t *it) {
|
||||
PhysicsBody *pb = ecs_field(it, PhysicsBody, 1);
|
||||
Velocity *v = ecs_field(it, Velocity, 2);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
frBody *body = (frBody*)pb[i].body_ptr;
|
||||
frSetBodyVelocity(body, (Vector2) { v[i].x, v[i].y });
|
||||
}
|
||||
}
|
||||
|
||||
void PhysUpdatePosition(ecs_iter_t *it) {
|
||||
PhysicsBody *pb = ecs_field(it, PhysicsBody, 1);
|
||||
Position *p = ecs_field(it, Position, 2);
|
||||
Velocity *v = ecs_field(it, Velocity, 3);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
frBody *body = (frBody*)pb[i].body_ptr;
|
||||
Vector2 pos = frVec2MetersToPixels(frGetBodyPosition(body));
|
||||
p[i].x = pos.x;
|
||||
p[i].y = pos.y;
|
||||
Vector2 vel = frVec2MetersToPixels(frGetBodyVelocity(body));
|
||||
v[i].x = vel.x;
|
||||
v[i].y = vel.y;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysResetPosition(ecs_iter_t *it) {
|
||||
Position *p = ecs_field(it, Position, 1);
|
||||
|
||||
for (int i = 0; i < it->count; i++) {
|
||||
const PhysicsBody *pb = ecs_get(it->world, it->entities[i], PhysicsBody);
|
||||
if (!pb) continue;
|
||||
frBody *body = (frBody*)pb->body_ptr;
|
||||
frSetBodyPosition(body, (Vector2){p[i].x, p[i].y});
|
||||
}
|
||||
}
|
||||
|
||||
void PhysSimulateWorld(ecs_iter_t *it) {
|
||||
frSimulateWorld(phys_world, it->delta_time);
|
||||
}
|
||||
|
||||
void EnableWorldEdit(ecs_iter_t *it) {
|
||||
world_set_stage(it->world);
|
||||
}
|
||||
|
@ -242,17 +336,23 @@ void SystemsImport(ecs_world_t *ecs) {
|
|||
ECS_MODULE(ecs, Systems);
|
||||
|
||||
ecs_entity_t timer = ecs_set_interval(ecs, 0, ECO2D_TICK_RATE);
|
||||
|
||||
ECS_SYSTEM(ecs, EnableWorldEdit, EcsOnLoad);
|
||||
|
||||
ECS_SYSTEM(ecs, EnableWorldEdit, EcsOnLoad);
|
||||
ECS_SYSTEM(ecs, MovementImpulse, EcsOnLoad, components.Input, components.Velocity, components.Position, !components.IsInVehicle);
|
||||
ECS_SYSTEM(ecs, DemoNPCMoveAround, EcsOnLoad, components.Velocity, components.DemoNPC);
|
||||
|
||||
ECS_SYSTEM(ecs, ApplyWorldDragOnVelocity, EcsOnUpdate, components.Position, components.Velocity);
|
||||
ECS_SYSTEM_TICKED(ecs, HurtOnHazardBlock, EcsOnUpdate, components.Position, components.Health);
|
||||
ECS_SYSTEM_TICKED(ecs, RegenerateHP, EcsOnUpdate, components.Health);
|
||||
ECS_SYSTEM_TICKED_EX(ecs, HurtOnHazardBlock, EcsOnUpdate, 20.0f, components.Position, components.Health);
|
||||
ECS_SYSTEM_TICKED_EX(ecs, RegenerateHP, EcsOnUpdate, 40.0f, components.Health, components.HealthRegen, !components.HealDelay);
|
||||
ECS_SYSTEM_TICKED_EX(ecs, TickDownHealDelay, EcsOnUpdate, 20.0f, components.HealDelay);
|
||||
ECS_SYSTEM(ecs, VehicleHandling, EcsOnUpdate, components.Vehicle, components.Position, components.Velocity);
|
||||
|
||||
ECS_OBSERVER(ecs, OnHealthChangePutDelay, EcsOnAdd, components.HealthDecreased);
|
||||
|
||||
ECS_SYSTEM(ecs, IntegratePositions, EcsOnValidate, components.Position, components.Velocity);
|
||||
ECS_SYSTEM(ecs, PhysSetVelocity, EcsOnValidate, components.PhysicsBody, components.Velocity);
|
||||
ECS_SYSTEM(ecs, PhysSimulateWorld, EcsOnValidate);
|
||||
ECS_SYSTEM(ecs, PhysUpdatePosition, EcsOnValidate, components.PhysicsBody, components.Position, components.Velocity);
|
||||
ECS_SYSTEM(ecs, IntegratePositions, EcsOnValidate, components.Position, components.Velocity, !components.PhysicsBody);
|
||||
|
||||
ECS_SYSTEM(ecs, EnterVehicle, EcsPostUpdate, components.Input, components.Position, !components.IsInVehicle);
|
||||
ECS_SYSTEM(ecs, LeaveVehicle, EcsPostUpdate, components.Input, components.IsInVehicle, components.Velocity);
|
||||
|
@ -275,11 +375,20 @@ void SystemsImport(ecs_world_t *ecs) {
|
|||
ECS_SYSTEM_TICKED(ecs, CreatureSeekFood, EcsPostUpdate, components.Creature, components.Position, components.Velocity, components.SeeksFood, !components.SeeksCompanion);
|
||||
ECS_SYSTEM_TICKED(ecs, CreatureSeekCompanion, EcsPostUpdate, components.Creature, components.Position, components.Velocity, components.SeeksCompanion, !components.SeeksFood);
|
||||
ECS_SYSTEM(ecs, CreatureRoamAround, EcsPostUpdate, components.Velocity, components.Creature, !components.SeeksFood, !components.SeeksCompanion);
|
||||
|
||||
ECS_SYSTEM_TICKED_EX(ecs, MobDetectPlayers, EcsPostUpdate, 100.0f, components.Position, components.Mob);
|
||||
ECS_SYSTEM(ecs, MobMovement, EcsPostUpdate, components.Velocity, components.Position, components.MobHuntPlayer);
|
||||
ECS_SYSTEM_TICKED(ecs, MobMeleeAtk, EcsPostUpdate, components.Position, components.Mob, components.MobHuntPlayer, components.MobMelee);
|
||||
|
||||
ECS_SYSTEM(ecs, ResetActivators, EcsPostUpdate, components.Input);
|
||||
|
||||
ECS_SYSTEM(ecs, ClearVehicle, EcsUnSet, components.Vehicle);
|
||||
ECS_SYSTEM(ecs, ThrowItemsOut, EcsUnSet, components.ItemContainer, components.Position);
|
||||
|
||||
// Physics hooks
|
||||
ECS_OBSERVER(ecs, PhysOnCreateBody, EcsOnSet, components.PhysicsBody, components.Position);
|
||||
ECS_OBSERVER(ecs, PhysOnRemoveBody, EcsUnSet, components.PhysicsBody);
|
||||
ECS_OBSERVER(ecs, PhysResetPosition, EcsOnSet, components.Position);
|
||||
|
||||
ECS_SYSTEM(ecs, DisableWorldEdit, EcsPostUpdate);
|
||||
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
#include "core/game.h"
|
||||
#include "models/entity.h"
|
||||
#include "models/crafting.h"
|
||||
#include "ferox.h"
|
||||
|
||||
#include "packets/pkt_send_librg_update.h"
|
||||
|
||||
|
||||
#define ECO2D_STREAM_ACTIONFILTER 1
|
||||
|
||||
ZPL_TABLE(static, world_snapshot, world_snapshot_, entity_view);
|
||||
|
@ -23,6 +25,7 @@ ZPL_TABLE(static, world_component_cache, world_component_cache_, zpl_uintptr); /
|
|||
static world_data world = {0};
|
||||
static world_snapshot streamer_snapshot;
|
||||
static world_component_cache component_cache;
|
||||
frWorld *phys_world = 0;
|
||||
|
||||
entity_view *world_build_entity_view(int64_t e) {
|
||||
entity_view *cached_ev = world_snapshot_get(&streamer_snapshot, e);
|
||||
|
@ -293,6 +296,18 @@ void world_free_worldgen_data(void) {
|
|||
world.outer_data = NULL;
|
||||
}
|
||||
|
||||
static inline
|
||||
void world_init_physics(void) {
|
||||
const Rectangle bounds = {
|
||||
.x = -0.05f * frNumberPixelsToMeters(world_dim()),
|
||||
.y = -0.05f * frNumberPixelsToMeters(world_dim()),
|
||||
.width = 1.1f * frNumberPixelsToMeters(world_dim()),
|
||||
.height = 1.1f * frNumberPixelsToMeters(world_dim())
|
||||
};
|
||||
|
||||
phys_world = frCreateWorld((Vector2) { 0.0f, 0.0f }, bounds);
|
||||
}
|
||||
|
||||
int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) {
|
||||
world.is_paused = false;
|
||||
world.seed = seed;
|
||||
|
@ -309,6 +324,7 @@ int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) {
|
|||
world_init_mapping();
|
||||
world_chunk_setup_grid();
|
||||
world_free_worldgen_data();
|
||||
world_init_physics();
|
||||
|
||||
zpl_printf("[INFO] Created a new server world\n");
|
||||
|
||||
|
@ -328,6 +344,9 @@ int32_t world_destroy(void) {
|
|||
world_snapshot_destroy(&streamer_snapshot);
|
||||
world_component_cache_destroy(&component_cache);
|
||||
zpl_memset(&world, 0, sizeof(world));
|
||||
|
||||
frReleaseWorld(phys_world);
|
||||
|
||||
zpl_printf("[INFO] World was destroyed.\n");
|
||||
return WORLD_ERROR_NONE;
|
||||
}
|
||||
|
@ -428,6 +447,10 @@ ecs_world_t * world_ecs() {
|
|||
return world.ecs;
|
||||
}
|
||||
|
||||
ecs_query_t *world_ecs_player(void) {
|
||||
return world.ecs_update;
|
||||
}
|
||||
|
||||
ecs_query_t *world_ecs_clientinfo(void) {
|
||||
return world.ecs_clientinfo;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "librg.h"
|
||||
#include "pkt/packet.h"
|
||||
#include "flecs.h"
|
||||
|
||||
#include "world/blocks.h"
|
||||
|
||||
#define WORLD_ERROR_NONE +0x0000
|
||||
|
@ -62,6 +63,7 @@ int32_t world_write(pkt_header *pkt, void *udata);
|
|||
uint32_t world_buf(block_id const **ptr, uint32_t *width);
|
||||
uint32_t world_seed(void);
|
||||
ecs_world_t *world_ecs(void);
|
||||
ecs_query_t *world_ecs_player(void);
|
||||
ecs_query_t *world_ecs_clientinfo(void);
|
||||
void world_set_stage(ecs_world_t *ecs);
|
||||
librg_world *world_tracker(void);
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
add_subdirectory(sandbox)
|
||||
add_subdirectory(minimal)
|
||||
add_subdirectory(survival)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
add_executable(survival
|
||||
src/main.c
|
||||
src/platform.c
|
||||
src/worldgen.c
|
||||
src/texgen.c
|
||||
src/rules.c
|
||||
)
|
||||
|
||||
target_compile_definitions(minimal PRIVATE CLIENT)
|
||||
include_directories(src ../../foundation/src ../../../art/gen)
|
||||
target_link_libraries(survival eco2d-foundation)
|
||||
|
||||
link_system_libs(survival)
|
|
@ -0,0 +1,95 @@
|
|||
#define ZPL_IMPL
|
||||
#include "zpl.h"
|
||||
#include "platform/system.h"
|
||||
#include "core/game.h"
|
||||
#include "models/entity.h"
|
||||
#include "world/entity_view.h"
|
||||
#include "utils/options.h"
|
||||
#include "platform/signal_handling.h"
|
||||
#include "platform/profiler.h"
|
||||
|
||||
#include "flecs.h"
|
||||
#include "flecs/flecs_os_api_stdcpp.h"
|
||||
|
||||
#include "models/components.h"
|
||||
#include "systems/systems.h"
|
||||
|
||||
#include "platform/arch.h"
|
||||
|
||||
#define DEFAULT_WORLD_SEED 302097
|
||||
#define DEFAULT_CHUNK_SIZE 16 /* amount of blocks within a chunk (single axis) */
|
||||
#define DEFAULT_WORLD_SIZE 5 /* amount of chunks within a world (single axis) */
|
||||
|
||||
/*
|
||||
TODO
|
||||
- monster spawner
|
||||
- the longer we survive, the more and stronger enemies we spawn
|
||||
- player grows HP by leveling up
|
||||
- XP increases by killing mobs
|
||||
- player can pick an "ability" upon reaching level milestones
|
||||
- abilities: armor/shield, TODO ...
|
||||
- enemies damage player when close to him in ticks (damage effects, ...)
|
||||
- basic projectile pooling (flecs)
|
||||
- somewhat believable world gen, small hamlets with cols, etc
|
||||
*/
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
zpl_opts opts={0};
|
||||
zpl_opts_init(&opts, zpl_heap(), argv[0]);
|
||||
|
||||
zpl_opts_add(&opts, "?", "help", "the HELP section", ZPL_OPTS_FLAG);
|
||||
zpl_opts_add(&opts, "v", "viewer-only", "run viewer-only client", ZPL_OPTS_FLAG);
|
||||
zpl_opts_add(&opts, "d", "server-only", "run dedicated server", ZPL_OPTS_FLAG);
|
||||
zpl_opts_add(&opts, "p", "preview-map", "draw world preview", ZPL_OPTS_FLAG);
|
||||
zpl_opts_add(&opts, "s", "seed", "world seed", ZPL_OPTS_INT);
|
||||
zpl_opts_add(&opts, "r", "random-seed", "generate random world seed", ZPL_OPTS_FLAG);
|
||||
//zpl_opts_add(&opts, "cs", "chunk-size", "amount of blocks within a chunk (single axis)", ZPL_OPTS_INT);
|
||||
zpl_opts_add(&opts, "ws", "world-size", "amount of chunks within a world (single axis)", ZPL_OPTS_INT);
|
||||
zpl_opts_add(&opts, "ip", "host", "host IP address", ZPL_OPTS_STRING);
|
||||
zpl_opts_add(&opts, "port", "port", "port number", ZPL_OPTS_INT);
|
||||
|
||||
uint32_t ok = zpl_opts_compile(&opts, argc, argv);
|
||||
|
||||
if (!ok) {
|
||||
zpl_opts_print_errors(&opts);
|
||||
zpl_opts_print_help(&opts);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int8_t is_viewer_only = zpl_opts_has_arg(&opts, "viewer-only");
|
||||
int8_t is_server_only = zpl_opts_has_arg(&opts, "server-only");
|
||||
int32_t seed = (int32_t)zpl_opts_integer(&opts, "seed", DEFAULT_WORLD_SEED);
|
||||
uint16_t world_size = (uint16_t)zpl_opts_integer(&opts, "world-size", DEFAULT_WORLD_SIZE);
|
||||
uint16_t chunk_size = DEFAULT_CHUNK_SIZE; //zpl_opts_integer(&opts, "chunk-size", DEFAULT_CHUNK_SIZE);
|
||||
zpl_string host = zpl_opts_string(&opts, "host", NULL);
|
||||
uint16_t port = (uint16_t)zpl_opts_integer(&opts, "port", 0);
|
||||
|
||||
game_kind play_mode = GAMEKIND_SINGLE;
|
||||
|
||||
if (is_viewer_only) play_mode = GAMEKIND_CLIENT;
|
||||
if (is_server_only) play_mode = GAMEKIND_HEADLESS;
|
||||
|
||||
if (zpl_opts_has_arg(&opts, "random-seed")) {
|
||||
zpl_random rnd={0};
|
||||
zpl_random_init(&rnd);
|
||||
seed = zpl_random_gen_u32(&rnd);
|
||||
zpl_printf("Seed: %u\n", seed);
|
||||
}
|
||||
|
||||
if (zpl_opts_has_arg(&opts, "preview-map")) {
|
||||
generate_minimap(seed, WORLD_BLOCK_SIZE, chunk_size, world_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sighandler_register();
|
||||
game_init(host, port, play_mode, 1, seed, chunk_size, world_size, 0);
|
||||
|
||||
game_run();
|
||||
|
||||
game_shutdown();
|
||||
sighandler_unregister();
|
||||
|
||||
zpl_string_free(host);
|
||||
zpl_opts_free(&opts);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
#include "platform/platform.h"
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
#include "net/network.h"
|
||||
#include "core/game.h"
|
||||
#include "world/entity_view.h"
|
||||
#include "world/prediction.h"
|
||||
#include "core/camera.h"
|
||||
#include "math.h"
|
||||
#include "world/blocks.h"
|
||||
#include "models/assets.h"
|
||||
#include "platform/profiler.h"
|
||||
#include "dev/debug_draw.h"
|
||||
#include "dev/debug_ui.h"
|
||||
#include "utils/raylib_helpers.h"
|
||||
#include "platform/renderer.h"
|
||||
|
||||
ZPL_DIAGNOSTIC_PUSH_WARNLEVEL(0)
|
||||
#include "raylib-nuklear.h"
|
||||
ZPL_DIAGNOSTIC_POP
|
||||
|
||||
#define ARCH_IMPL
|
||||
#include "platform/arch.h"
|
||||
|
||||
|
||||
// NOTE(zaklaus): add-ins
|
||||
#include "gui/ui_skin.c"
|
||||
#include "gui/tooltip.c"
|
||||
#include "gui/notifications.c"
|
||||
|
||||
#include "renderer.c"
|
||||
|
||||
void platform_init() {
|
||||
platform_create_window("horde survival game");
|
||||
renderer_init();
|
||||
|
||||
notification_push("test1", "Hello World!");
|
||||
}
|
||||
|
||||
void platform_shutdown() {
|
||||
renderer_shutdown();
|
||||
CloseWindow();
|
||||
}
|
||||
|
||||
void platform_input() {
|
||||
float mouse_z = (GetMouseWheelMove()*0.5f);
|
||||
float mouse_modified = target_zoom < 4 ? mouse_z / (zpl_exp(4 - (target_zoom))) : mouse_z;
|
||||
|
||||
if (mouse_z != 0.0f) {
|
||||
target_zoom = zpl_clamp(target_zoom + mouse_modified, 0.1f, 11.0f);
|
||||
}
|
||||
|
||||
|
||||
// NOTE(zaklaus): keystate handling
|
||||
{
|
||||
float x=0.0f, y=0.0f;
|
||||
uint8_t use, sprint, ctrl;
|
||||
if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) x += 1.0f;
|
||||
if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) x -= 1.0f;
|
||||
if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) y += 1.0f;
|
||||
if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) y -= 1.0f;
|
||||
|
||||
use = IsKeyPressed(KEY_SPACE);
|
||||
sprint = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
|
||||
ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);
|
||||
|
||||
// NOTE(zaklaus): NEW! mouse movement
|
||||
Vector2 mouse_pos = GetMousePosition();
|
||||
mouse_pos.x /= screenWidth;
|
||||
mouse_pos.y /= screenHeight;
|
||||
mouse_pos.x -= 0.5f;
|
||||
mouse_pos.y -= 0.5f;
|
||||
mouse_pos = Vector2Normalize(mouse_pos);
|
||||
|
||||
if (game_get_kind() == GAMEKIND_SINGLE && IsMouseButtonDown(MOUSE_MIDDLE_BUTTON)) {
|
||||
x = mouse_pos.x;
|
||||
y = -mouse_pos.y;
|
||||
}
|
||||
|
||||
game_keystate_data in_data = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
.mx = mouse_pos.x,
|
||||
.my = mouse_pos.y,
|
||||
.use = use,
|
||||
.sprint = sprint,
|
||||
.ctrl = ctrl,
|
||||
};
|
||||
|
||||
platform_input_update_input_frame(in_data);
|
||||
}
|
||||
}
|
||||
|
||||
void platform_render() {
|
||||
platform_resize_window();
|
||||
|
||||
profile(PROF_ENTITY_LERP) {
|
||||
game_world_view_active_entity_map(lerp_entity_positions);
|
||||
game_world_view_active_entity_map(do_entity_fadeinout);
|
||||
}
|
||||
|
||||
// HACK run once when player is dead
|
||||
{
|
||||
static char done = 0;
|
||||
camera cam = camera_get();
|
||||
entity_view *e = game_world_view_active_get_entity(cam.ent_id);
|
||||
if (e && e->hp <= 0.0f && !done) {
|
||||
done = 1;
|
||||
notification_push("DEAD", "YOU ARE DEAD!");
|
||||
}
|
||||
}
|
||||
|
||||
assets_frame();
|
||||
|
||||
BeginDrawing();
|
||||
{
|
||||
profile (PROF_RENDER) {
|
||||
renderer_draw();
|
||||
}
|
||||
renderer_debug_draw();
|
||||
|
||||
debug_draw();
|
||||
notification_draw();
|
||||
game_draw_ui();
|
||||
}
|
||||
EndDrawing();
|
||||
|
||||
if (request_shutdown) {
|
||||
CloseWindow();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
static Camera2D render_camera;
|
||||
static float zoom_overlay_tran = 0.0f;
|
||||
#include "ferox.h"
|
||||
|
||||
#define CAM_OVERLAY_ZOOM_LEVEL 0.15f
|
||||
#define ALPHA(x) ColorAlpha(x, data->tran_time)
|
||||
|
||||
float zpl_lerp(float,float,float);
|
||||
float zpl_to_degrees(float);
|
||||
|
||||
void DrawNametag(const char* name, uint64_t key, entity_view *data, float x, float y) {
|
||||
float size = 16.f;
|
||||
float font_size = lerp(4.0f, 32.0f, 0.5f/(float)render_camera.zoom);
|
||||
float font_spacing = 1.1f;
|
||||
float title_bg_offset = 4;
|
||||
float fixed_title_offset = 8.f;
|
||||
float health = (data->hp / data->max_hp);
|
||||
const char *title = TextFormat("%s %llu", name, key);
|
||||
float title_w = MeasureTextEco(title, font_size, font_spacing);
|
||||
DrawRectangleEco(x-title_w/2.f-title_bg_offset/2.f, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(BLACK, data->tran_time));
|
||||
DrawRectangleEco(x-title_w/2.f-title_bg_offset/2.f, y-size-fixed_title_offset, title_w*health+title_bg_offset, font_size*0.2f, ColorAlpha(RED, data->tran_time));
|
||||
DrawTextEco(title, x-title_w/2.f, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(RAYWHITE, data->tran_time), font_spacing);
|
||||
}
|
||||
|
||||
void renderer_draw_entry(uint64_t key, entity_view *data, game_world_render_entry* entry) {
|
||||
float size = 16.f;
|
||||
|
||||
switch (data->kind) {
|
||||
case EKIND_CHUNK: {
|
||||
world_view *view = game_world_view_get_active();
|
||||
float size = (float)(view->chunk_size * WORLD_BLOCK_SIZE);
|
||||
float offset = 0.0;
|
||||
|
||||
float x = data->x * size + offset;
|
||||
float y = data->y * size + offset;
|
||||
|
||||
if (entry == NULL) {
|
||||
RenderTexture2D tex = GetChunkTexture(key);
|
||||
float scale = (size)/(float)(tex.texture.width);
|
||||
tex.texture.width *= (int32_t)scale;
|
||||
tex.texture.height *= (int32_t)scale;
|
||||
DrawTextureRec(tex.texture, (Rectangle){0, 0, size, -size}, (Vector2){x, y}, ColorAlpha(WHITE, data->tran_time));
|
||||
} else {
|
||||
DrawTextureRec(GetBlockImage(entry->blk_id), ASSET_SRC_RECT(), (Vector2){entry->x-(WORLD_BLOCK_SIZE/2), entry->y-(WORLD_BLOCK_SIZE/2)}, ColorAlpha(WHITE, data->tran_time));
|
||||
}
|
||||
}break;
|
||||
case EKIND_VEHICLE: {
|
||||
float x = data->x;
|
||||
float y = data->y;
|
||||
float const w = (float)(data->veh_kind == 0 ? 80 : data->veh_kind == 1 ? 120 : 135);
|
||||
float const h = 50;
|
||||
Color color = data->veh_kind == 0 ? RED : data->veh_kind == 1 ? GREEN : BLUE;
|
||||
DrawRectanglePro((Rectangle){x,y,w,h}, (Vector2){w/2.0f,h/2.0f}, zpl_to_degrees(data->heading), ColorAlpha(color, data->tran_time));
|
||||
}break;
|
||||
case EKIND_DEVICE:{
|
||||
float x = data->x - 32.f;
|
||||
float y = data->y - 32.f;
|
||||
DrawTexturePro(GetSpriteTexture2D(assets_find(data->asset)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE));
|
||||
|
||||
if (data->progress_active) {
|
||||
float w = 64.f;
|
||||
float h = 8.f;
|
||||
float p = data->progress_value;
|
||||
float x = data->x - w/2.f;
|
||||
float y = data->y - 32.f - h;
|
||||
DrawRectangleEco(x, y, w, h, ColorAlpha(BLACK, data->tran_time));
|
||||
DrawRectangleEco(x, y, w*p, h, ColorAlpha(GREEN, data->tran_time));
|
||||
}
|
||||
}break;
|
||||
case EKIND_DEMO_NPC: {
|
||||
float x = data->x;
|
||||
float y = data->y;
|
||||
DrawNametag("Demo", key, data, x, y);
|
||||
DrawCircleEco(x, y, size, ColorAlpha(BLUE, data->tran_time));
|
||||
}break;
|
||||
case EKIND_MONSTER: {
|
||||
float x = data->x;
|
||||
float y = data->y;
|
||||
//DrawCircleEco(x, y, size, ColorAlpha(PINK, data->tran_time));
|
||||
DrawTextureRec(GetSpriteTexture2D(assets_find(ASSET_MOB)), ASSET_SRC_RECT(), (Vector2){data->x-(WORLD_BLOCK_SIZE/2), data->y-(WORLD_BLOCK_SIZE/2)}, ColorAlpha(WHITE, data->tran_time));
|
||||
float health = (data->hp / data->max_hp);
|
||||
|
||||
if (health < 1.0f) {
|
||||
DrawRectangleEco(x-32, y-48, 64, 8, ColorAlpha(BLACK, data->tran_time));
|
||||
DrawRectangleEco(x-32, y-48, 64*health, 8, ColorAlpha(RED, data->tran_time));
|
||||
}
|
||||
}break;
|
||||
case EKIND_PLAYER: {
|
||||
float x = data->x;
|
||||
float y = data->y;
|
||||
float health = (data->hp / data->max_hp);
|
||||
DrawNametag("Player", key, data, x, y);
|
||||
DrawCircleEco(x, y, size, ColorAlpha(YELLOW, data->tran_time));
|
||||
|
||||
if (data->has_items && !data->inside_vehicle) {
|
||||
float ix = data->x;
|
||||
float iy = data->y;
|
||||
if (data->items[data->selected_item].quantity > 0) {
|
||||
asset_id it_kind = data->items[data->selected_item].kind;
|
||||
uint32_t qty = data->items[data->selected_item].quantity;
|
||||
DrawTexturePro(GetSpriteTexture2D(assets_find(it_kind)), ASSET_SRC_RECT(), ((Rectangle){ix, iy, 32, 32}), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE));
|
||||
}
|
||||
}
|
||||
}break;
|
||||
case EKIND_ITEM: {
|
||||
float x = data->x - 32.f;
|
||||
float y = data->y - 32.f;
|
||||
DrawTexturePro(GetSpriteTexture2D(assets_find(data->asset)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, ALPHA(WHITE));
|
||||
|
||||
if (data->quantity > 1) {
|
||||
DrawTextEco(zpl_bprintf("%d", data->quantity), x, y, 10, ALPHA(RAYWHITE), 0.0f);
|
||||
}
|
||||
|
||||
if (data->durability < 1.0f) {
|
||||
DrawRectangleEco(x, y+32, 4, 32, BlendColor(RED, GREEN, data->durability));
|
||||
DrawRectangleEco(x, y+32, 4, 32*(1.0f-data->durability), ColorAlpha(BLACK, data->tran_time));
|
||||
}
|
||||
}break;
|
||||
default:break;
|
||||
}
|
||||
}
|
||||
|
||||
void renderer_draw(void) {
|
||||
render_camera.offset = (Vector2){(float)(screenWidth >> 1), (float)(screenHeight >> 1)};
|
||||
render_camera.zoom = zpl_lerp(render_camera.zoom, target_zoom, GetFrameTime()*2.9978f);
|
||||
camera_update();
|
||||
|
||||
camera game_camera = camera_get();
|
||||
render_camera.target = (Vector2){(float)game_camera.x, (float)game_camera.y};
|
||||
|
||||
ClearBackground(GetColor(0x222034));
|
||||
BeginMode2D(render_camera);
|
||||
|
||||
game_world_view_render_world();
|
||||
|
||||
|
||||
#if 0
|
||||
if (game_get_kind() == GAMEKIND_SINGLE) {
|
||||
extern frWorld *phys_world;
|
||||
frDrawSpatialHash(frGetWorldSpatialHash(phys_world), 0.75f, GRAY);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
EndMode2D();
|
||||
}
|
||||
|
||||
float renderer_zoom_get(void) {
|
||||
return render_camera.zoom;
|
||||
}
|
||||
|
||||
void renderer_init(void) {
|
||||
render_camera.target = (Vector2){0.0f,0.0f};
|
||||
render_camera.offset = (Vector2){(float)(screenWidth >> 1), (float)(screenHeight >> 1)};
|
||||
render_camera.rotation = 0.0f;
|
||||
render_camera.zoom = 2.9f;
|
||||
|
||||
// NOTE(zaklaus): Paint the screen before we load the game
|
||||
// TODO(zaklaus): Render a cool loading screen background maybe? :wink: :wink:
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(GetColor(0x222034));
|
||||
|
||||
char const *loading_text = "zpl.eco2d is loading...";
|
||||
int text_w = MeasureText(loading_text, 120);
|
||||
DrawText(loading_text, GetScreenWidth()-text_w-15, GetScreenHeight()-135, 120, RAYWHITE);
|
||||
EndDrawing();
|
||||
|
||||
blocks_setup();
|
||||
assets_setup();
|
||||
}
|
||||
|
||||
void renderer_shutdown(void) {
|
||||
blocks_destroy();
|
||||
assets_destroy();
|
||||
}
|
||||
|
||||
void renderer_debug_draw(void) {
|
||||
BeginMode2D(render_camera);
|
||||
debug_draw_queue *que = debug_draw_samples();
|
||||
|
||||
for (size_t i = 0; i < que->num_entries; i += 1) {
|
||||
debug_draw_entry *e = &que->entries[i];
|
||||
Color color = GetColor(e->color);
|
||||
|
||||
switch (e->kind) {
|
||||
case DDRAW_LINE: {
|
||||
float x = e->a.x;
|
||||
float y = e->a.y;
|
||||
float x2 = e->b.x;
|
||||
float y2 = e->b.y;
|
||||
DrawLineV((Vector2){x, y}, (Vector2){x2, y2}, color);
|
||||
}break;
|
||||
|
||||
case DDRAW_CIRCLE:{
|
||||
float x = e->a.x;
|
||||
float y = e->a.y;
|
||||
DrawCircleLinesEco(x, y, e->radius, color);
|
||||
}break;
|
||||
|
||||
case DDRAW_RECT:{
|
||||
float x = e->bmin.x;
|
||||
float y = e->bmin.y;
|
||||
float w = e->bmax.x - e->bmin.x;
|
||||
float h = e->bmax.y - e->bmin.y;
|
||||
DrawRectangleLinesEco(x, y, w, h, color);
|
||||
}break;
|
||||
|
||||
default: {
|
||||
|
||||
}break;
|
||||
}
|
||||
}
|
||||
|
||||
EndMode2D();
|
||||
}
|
||||
|
||||
void renderer_draw_single(float x, float y, asset_id id, Color color) {
|
||||
BeginMode2D(render_camera);
|
||||
|
||||
x -= 32.0f;
|
||||
y -= 32.0f;
|
||||
|
||||
DrawTexturePro(GetSpriteTexture2D(assets_find(id)), ASSET_SRC_RECT(), ASSET_DST_RECT(x,y), (Vector2){0.5f,0.5f}, 0.0f, color);
|
||||
|
||||
EndMode2D();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#include "core/rules.h"
|
||||
|
||||
void rules_setup() {
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#include "gen/texgen.h"
|
||||
#include "world/world.h"
|
||||
#include "zpl.h"
|
||||
#include "utils/raylib_helpers.h"
|
||||
|
||||
Texture2D texgen_build_anim(asset_id id, int64_t counter) {
|
||||
(void)counter;
|
||||
switch (id) {
|
||||
default: return texgen_build_anim_fallback(id, counter); break;
|
||||
}
|
||||
}
|
||||
|
||||
Texture2D texgen_build_sprite(asset_id id) {
|
||||
switch (id) {
|
||||
default: return texgen_build_sprite_fallback(id); break;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#include "zpl.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "world/world.h"
|
||||
#include "world/blocks.h"
|
||||
#include "world/perlin.h"
|
||||
|
||||
#include "models/components.h"
|
||||
#include "models/entity.h"
|
||||
#include "models/prefabs/vehicle.h"
|
||||
#include "models/items.h"
|
||||
#include "world/blocks_info.h"
|
||||
|
||||
#include "world/worldgen_utils.h"
|
||||
|
||||
block_id worldgen_biome_find(uint32_t biome, uint32_t kind) {
|
||||
asset_id asset = ASSET_INVALID;
|
||||
switch (biome) {
|
||||
case BLOCK_BIOME_DEV: {
|
||||
switch (kind) {
|
||||
case BLOCK_KIND_GROUND: asset = ASSET_GROUND; break;
|
||||
case BLOCK_KIND_DIRT: asset = ASSET_DIRT; break;
|
||||
case BLOCK_KIND_WALL: asset = ASSET_WALL; break;
|
||||
case BLOCK_KIND_HILL_SNOW:
|
||||
case BLOCK_KIND_HILL: asset = ASSET_HILL; break;
|
||||
case BLOCK_KIND_WATER: asset = ASSET_WATER; break;
|
||||
case BLOCK_KIND_LAVA: asset = ASSET_LAVA; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks_find(asset);
|
||||
}
|
||||
|
||||
int32_t worldgen_build(world_data *wld) {
|
||||
// TODO(zaklaus): pass world as an arg instead
|
||||
world = wld;
|
||||
|
||||
// TODO: perform world gen
|
||||
// atm, we will fill the world with ground and surround it by walls
|
||||
block_id wall_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_WALL);
|
||||
block_id grnd_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_GROUND);
|
||||
block_id dirt_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_DIRT);
|
||||
block_id watr_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_WATER);
|
||||
block_id lava_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_LAVA);
|
||||
block_id tree_id = blocks_find(ASSET_TREE);
|
||||
|
||||
srand(world->seed);
|
||||
|
||||
// walls
|
||||
world_fill_rect(world->data, wall_id, 0, 0, world->dim, world->dim, NULL);
|
||||
|
||||
// ground
|
||||
world_fill_rect(world->data, wall_id, 1, 1, world->dim-2, world->dim-2, NULL);
|
||||
|
||||
int radius = 30;
|
||||
|
||||
// wide boy circle
|
||||
world_fill_circle(world->data, dirt_id, world->dim / 2, world->dim / 2, radius, NULL);
|
||||
|
||||
// narrow boy cirlce
|
||||
world_fill_circle(world->data, grnd_id, world->dim / 2, world->dim / 2, (uint32_t)(radius * 0.7f), NULL);
|
||||
|
||||
world_fill_circle(world->data, wall_id, world->dim / 2 + 9, world->dim / 2+ 9, (uint32_t)(radius * 0.2f), NULL);
|
||||
|
||||
return WORLD_ERROR_NONE;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
add_subdirectory(flecs)
|
||||
add_subdirectory(cwpack)
|
||||
add_subdirectory(raylib-nuklear)
|
||||
add_subdirectory(ferox)
|
||||
|
||||
add_library(vendors-bundle STATIC
|
||||
sfd.c
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
add_library(ferox STATIC
|
||||
src/broadphase.c
|
||||
src/collision.c
|
||||
src/debug.c
|
||||
src/dynamics.c
|
||||
src/geometry.c
|
||||
src/timer.c
|
||||
src/utils.c
|
||||
src/world.c
|
||||
)
|
||||
|
||||
target_include_directories(ferox PUBLIC include)
|
||||
target_include_directories(ferox PRIVATE src/external)
|
||||
|
||||
target_link_libraries(ferox PRIVATE raylib)
|
|
@ -0,0 +1,746 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef FEROX_H
|
||||
#define FEROX_H
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef FEROX_STANDALONE
|
||||
#include "raylib.h"
|
||||
#else
|
||||
#ifndef PI
|
||||
#define PI 3.14159265358979323846f
|
||||
#endif
|
||||
|
||||
#define DEG2RAD (PI / 180.0f)
|
||||
#define RAD2DEG (180.0f / PI)
|
||||
|
||||
/* A struct that represents a two-dimensional vector. */
|
||||
typedef struct Vector2 {
|
||||
float x;
|
||||
float y;
|
||||
} Vector2;
|
||||
|
||||
/* A struct that represents a rectangle. */
|
||||
typedef struct Rectangle {
|
||||
float x;
|
||||
float y;
|
||||
float width;
|
||||
float height;
|
||||
} Rectangle;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/* Returns `true` if `rec1` collides with `rec2`. */
|
||||
bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) {
|
||||
return ((rec1.x + rec1.width) - rec2.x) >= 0 && ((rec2.x + rec2.width) - rec1.x) >= 0
|
||||
&& ((rec1.y + rec1.height) - rec2.y) >= 0 && ((rec2.y + rec2.height) - rec1.y) >= 0;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* | Macro Definitions (Global) | */
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define FR_API_INLINE __forceinline
|
||||
#elif defined(__GNUC__)
|
||||
#if defined(__STRICT_ANSI__)
|
||||
#define FR_API_INLINE __inline__ __attribute__((always_inline))
|
||||
#else
|
||||
#define FR_API_INLINE inline __attribute__((always_inline))
|
||||
#endif
|
||||
#else
|
||||
#define FR_API_INLINE inline
|
||||
#endif
|
||||
|
||||
#define FR_STRUCT_ZERO(T) ((T) { 0 })
|
||||
|
||||
/* | Macro Definitions (Configuration) | */
|
||||
|
||||
#define FR_BROADPHASE_CELL_SIZE 3.2f
|
||||
#define FR_BROADPHASE_INVERSE_CELL_SIZE (1.0f / (FR_BROADPHASE_CELL_SIZE))
|
||||
|
||||
#define FR_DEBUG_CIRCLE_SEGMENT_COUNT 32
|
||||
|
||||
#define FR_DYNAMICS_CORRECTION_DEPTH_SCALE 0.24f
|
||||
#define FR_DYNAMICS_CORRECTION_DEPTH_THRESHOLD 0.02f
|
||||
#define FR_DYNAMICS_DYNAMIC_FRICTION_MULTIPLIER 0.85f
|
||||
|
||||
#define FR_GEOMETRY_MAX_VERTEX_COUNT 8
|
||||
|
||||
#define FR_GLOBAL_PIXELS_PER_METER 16.0f
|
||||
|
||||
#define FR_WORLD_ACCUMULATOR_LIMIT 200.0
|
||||
#define FR_WORLD_DEFAULT_GRAVITY ((Vector2) { .y = 9.8f })
|
||||
#define FR_WORLD_MAX_BODY_COUNT 288
|
||||
#define FR_WORLD_MAX_ITERATIONS 12
|
||||
|
||||
/* | Data Type Definitions | */
|
||||
|
||||
/* A struct that represents the type of a rigid body. */
|
||||
typedef enum frBodyType {
|
||||
FR_BODY_UNKNOWN = -1,
|
||||
FR_BODY_STATIC,
|
||||
FR_BODY_KINEMATIC,
|
||||
FR_BODY_DYNAMIC
|
||||
} frBodyType;
|
||||
|
||||
/* An enum that represents the property flag of a rigid body. */
|
||||
typedef enum frBodyFlag {
|
||||
FR_FLAG_NONE = 0x00,
|
||||
FR_FLAG_INFINITE_MASS = 0x01,
|
||||
FR_FLAG_INFINITE_INERTIA = 0x02
|
||||
} frBodyFlag;
|
||||
|
||||
/* A data type that represents the property flags of a rigid body. */
|
||||
typedef uint8_t frBodyFlags;
|
||||
|
||||
/* A struct that represents the type of a collision shape. */
|
||||
typedef enum frShapeType {
|
||||
FR_SHAPE_UNKNOWN,
|
||||
FR_SHAPE_CIRCLE,
|
||||
FR_SHAPE_POLYGON
|
||||
} frShapeType;
|
||||
|
||||
/* A struct that represents the material of a collision shape. */
|
||||
typedef struct frMaterial {
|
||||
float density;
|
||||
float restitution;
|
||||
float staticFriction;
|
||||
float dynamicFriction;
|
||||
} frMaterial;
|
||||
|
||||
/*
|
||||
A struct that represents the position (in meters)
|
||||
and rotation (in radians) of an object.
|
||||
*/
|
||||
typedef struct frTransform {
|
||||
Vector2 position;
|
||||
float rotation;
|
||||
struct {
|
||||
bool valid;
|
||||
float sinA;
|
||||
float cosA;
|
||||
} cache;
|
||||
} frTransform;
|
||||
|
||||
/* A struct that represents the vertices of a polygon. */
|
||||
typedef struct frVertices {
|
||||
Vector2 data[FR_GEOMETRY_MAX_VERTEX_COUNT];
|
||||
int count;
|
||||
} frVertices;
|
||||
|
||||
/* A struct that represents a collision shape. */
|
||||
typedef struct frShape frShape;
|
||||
|
||||
/* A struct that represents a rigid body. */
|
||||
typedef struct frBody frBody;
|
||||
|
||||
/* A struct that represents information for a set of two rigid bodies. */
|
||||
typedef struct frSolverCache {
|
||||
frBody *bodies[2];
|
||||
/* TODO: ... */
|
||||
} frSolverCache;
|
||||
|
||||
/* A struct that represents the details of a collision. */
|
||||
typedef struct frCollision {
|
||||
bool check; // Returns `true` if two collision shapes collide with each other.
|
||||
frSolverCache cache; // The struct that contains rigid bodies that collided with each other.
|
||||
Vector2 direction; // The direction of the collision in a unit vector form.
|
||||
Vector2 points[2]; // The points of the collision between two collision shapes.
|
||||
float depths[2]; // The penetration depths of the collision.
|
||||
int count; // The number of points for this collision.
|
||||
} frCollision;
|
||||
|
||||
/* A callback which will be executed when a collision event occurs. */
|
||||
typedef void (*frCollisionCallback)(frCollision *collision);
|
||||
|
||||
/* A struct that represents the collision event handler of a world. */
|
||||
typedef struct frCollisionHandler {
|
||||
frCollisionCallback preSolve;
|
||||
frCollisionCallback postSolve;
|
||||
} frCollisionHandler;
|
||||
|
||||
/* A struct that represents a ray. */
|
||||
typedef struct frRay {
|
||||
Vector2 origin;
|
||||
Vector2 direction;
|
||||
float maxDistance;
|
||||
bool closest;
|
||||
} frRay;
|
||||
|
||||
/* A struct that represents the details of a raycast hit. */
|
||||
typedef struct frRaycastHit {
|
||||
bool check; // Returns `true` if the ray collides with `shape` or `body`.
|
||||
union {
|
||||
frShape *shape;
|
||||
frBody *body;
|
||||
}; // The collision shape or the body that was hit by the raycast.
|
||||
Vector2 point; // The point at which the raycast hit `shape` or `body`.
|
||||
Vector2 normal; // The normal vector of the raycast hit.
|
||||
float distance; // The distance from the ray's starting point to `shape` or `body`.
|
||||
bool inside; // Returns `true` if the ray's starting point is inside `shape` or `body`.
|
||||
} frRaycastHit;
|
||||
|
||||
/* A struct that represents a spatial hash. */
|
||||
typedef struct frSpatialHash frSpatialHash;
|
||||
|
||||
/* A struct that represents the world that holds rigid bodies. */
|
||||
typedef struct frWorld frWorld;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* | Module Functions (`broadphase`) | */
|
||||
|
||||
/* Creates a new spatial hash from the given bounds and the size of each cell. */
|
||||
frSpatialHash *frCreateSpatialHash(Rectangle bounds, float cellSize);
|
||||
|
||||
/* Releases the memory allocated for `hash`. */
|
||||
void frReleaseSpatialHash(frSpatialHash *hash);
|
||||
|
||||
/* Generates a new key from `rec` and inserts the key-`value` pair to `hash`. */
|
||||
void frAddToSpatialHash(frSpatialHash *hash, Rectangle rec, int value);
|
||||
|
||||
/* Removes all key-value pairs from `hash`. */
|
||||
void frClearSpatialHash(frSpatialHash *hash);
|
||||
|
||||
/* Removes a `key`-value pair from `hash`. */
|
||||
void frRemoveFromSpatialHash(frSpatialHash *hash, int key);
|
||||
|
||||
/* Returns the values of all pairs that collides with `rec` in `hash`. */
|
||||
void frQuerySpatialHash(frSpatialHash *hash, Rectangle rec, int **result);
|
||||
|
||||
/* Returns the bounds of `hash`. */
|
||||
Rectangle frGetSpatialHashBounds(frSpatialHash *hash);
|
||||
|
||||
/* Returns the size of each cell of `hash`. */
|
||||
float frGetSpatialHashCellSize(frSpatialHash *hash);
|
||||
|
||||
/* Sets the bounds of `hash` to `bounds`. */
|
||||
void frSetSpatialHashBounds(frSpatialHash *hash, Rectangle bounds);
|
||||
|
||||
/* Sets the size of each cell of `hash` to `cellSize`. */
|
||||
void frSetSpatialHashCellSize(frSpatialHash *hash, float cellSize);
|
||||
|
||||
/* | Module Functions (`collision`) | */
|
||||
|
||||
/*
|
||||
Computes the collision between `s1` with position and rotation from `tx1` and `s2`
|
||||
with position and rotation from `tx2`.
|
||||
*/
|
||||
frCollision frComputeShapeCollision(frShape *s1, frTransform tx1, frShape *s2, frTransform tx2);
|
||||
|
||||
/* Computes the collision between `b1` and `b2`. */
|
||||
frCollision frComputeBodyCollision(frBody *b1, frBody *b2);
|
||||
|
||||
/* Casts a `ray` to the collision shape `s`. */
|
||||
frRaycastHit frComputeShapeRaycast(frShape *s, frTransform tx, frRay ray);
|
||||
|
||||
/* Casts a `ray` to the rigid body `b`. */
|
||||
frRaycastHit frComputeBodyRaycast(frBody *b, frRay ray);
|
||||
|
||||
/* | Module Functions (`debug`) | */
|
||||
|
||||
#ifndef FEROX_STANDALONE
|
||||
/* Draws an arrow that points from `p1` to `p2`. */
|
||||
void frDrawArrow(Vector2 p1, Vector2 p2, float thick, Color color);
|
||||
|
||||
/* Draws the collision shape of `b`. */
|
||||
void frDrawBody(frBody *b, Color color);
|
||||
|
||||
/* Draws the border of the collision shape of `b`. */
|
||||
void frDrawBodyLines(frBody *b, float thick, Color color);
|
||||
|
||||
/* Draws the AABB and the center of mass of `b`. */
|
||||
void frDrawBodyAABB(frBody *b, float thick, Color color);
|
||||
|
||||
/* Draws the properties of `b`. */
|
||||
void frDrawBodyProperties(frBody *b, Color color);
|
||||
|
||||
/* Draws the border of `hm`. */
|
||||
void frDrawSpatialHash(frSpatialHash *hm, float thick, Color color);
|
||||
#endif
|
||||
|
||||
/* | Module Functions (`dynamics`) | */
|
||||
|
||||
/* Creates a new rigid body from the given type, flags, and position (in meters). */
|
||||
frBody *frCreateBody(frBodyType type, frBodyFlags flags, Vector2 p);
|
||||
|
||||
/*
|
||||
Creates a new rigid body from the given type, flags, position (in meters),
|
||||
and a collision shape.
|
||||
*/
|
||||
frBody *frCreateBodyFromShape(frBodyType type, frBodyFlags flags, Vector2 p, frShape *s);
|
||||
|
||||
/* Releases the memory allocated for `b`. */
|
||||
void frReleaseBody(frBody *b);
|
||||
|
||||
/* Attaches the collision shape `s` to `b`. */
|
||||
void frAttachShapeToBody(frBody *b, frShape *s);
|
||||
|
||||
/* Detaches the collision shape `s` from `b`. */
|
||||
void frDetachShapeFromBody(frBody *b);
|
||||
|
||||
/* Returns the size of the struct `frBody`. */
|
||||
size_t frGetBodyStructSize(void);
|
||||
|
||||
/* Returns the type of `b`. */
|
||||
frBodyType frGetBodyType(frBody *b);
|
||||
|
||||
/* Returns the property flags of `b`. */
|
||||
frBodyFlags frGetBodyFlags(frBody *b);
|
||||
|
||||
/* Returns the material of `b`. */
|
||||
frMaterial frGetBodyMaterial(frBody *b);
|
||||
|
||||
/* Returns the mass of `b`. */
|
||||
float frGetBodyMass(frBody *b);
|
||||
|
||||
/* Returns the inverse mass of `b`. */
|
||||
float frGetBodyInverseMass(frBody *b);
|
||||
|
||||
/* Returns the moment of inertia for `b`. */
|
||||
float frGetBodyInertia(frBody *b);
|
||||
|
||||
/* Returns the inverse moment of inertia for `b`. */
|
||||
float frGetBodyInverseInertia(frBody *b);
|
||||
|
||||
/* Returns the velocity of `b`. */
|
||||
Vector2 frGetBodyVelocity(frBody *b);
|
||||
|
||||
/* Returns the angular velocity of `b`. */
|
||||
float frGetBodyAngularVelocity(frBody *b);
|
||||
|
||||
/* Returns the gravity scale of `b`. */
|
||||
float frGetBodyGravityScale(frBody *b);
|
||||
|
||||
/* Returns the position (in meters) and rotation (in radians) of `b`. */
|
||||
frTransform frGetBodyTransform(frBody *b);
|
||||
|
||||
/* Returns the position (in meters) of `b`. */
|
||||
Vector2 frGetBodyPosition(frBody *b);
|
||||
|
||||
/* Returns the rotation (in radians) of `b`. */
|
||||
float frGetBodyRotation(frBody *b);
|
||||
|
||||
/* Returns the collision shape of `b`. */
|
||||
frShape *frGetBodyShape(frBody *b);
|
||||
|
||||
/* Returns the AABB (Axis-Aligned Bounding Box) of `b`. */
|
||||
Rectangle frGetBodyAABB(frBody *b);
|
||||
|
||||
/* Returns the user data of `b`. */
|
||||
void *frGetBodyUserData(frBody *b);
|
||||
|
||||
/* Converts the world coordinates `p` to a position relative to `b`'s transform value. */
|
||||
Vector2 frGetLocalPoint(frBody *b, Vector2 p);
|
||||
|
||||
/* Converts the position relative to `b`'s transform value `p` to world coordinates. */
|
||||
Vector2 frGetWorldPoint(frBody *b, Vector2 p);
|
||||
|
||||
/* Sets the type of `b` to `type`. */
|
||||
void frSetBodyType(frBody *b, frBodyType type);
|
||||
|
||||
/* Sets the property flags of `b` to `flags`. */
|
||||
void frSetBodyFlags(frBody *b, frBodyFlags flags);
|
||||
|
||||
/* Sets the velocity of `b` to `v`. */
|
||||
void frSetBodyVelocity(frBody *b, Vector2 v);
|
||||
|
||||
/* Sets the angular velocity of `b` to `a`. */
|
||||
void frSetBodyAngularVelocity(frBody *b, double a);
|
||||
|
||||
/* Sets the gravity scale value of `b` to `scale`. */
|
||||
void frSetBodyGravityScale(frBody *b, float scale);
|
||||
|
||||
/* Sets the transform value of `b` to `tx`. */
|
||||
void frSetBodyTransform(frBody *b, frTransform tx);
|
||||
|
||||
/* Sets the position of `b` to `p`. */
|
||||
void frSetBodyPosition(frBody *b, Vector2 p);
|
||||
|
||||
/* Sets the rotation of `b` to `rotation`. */
|
||||
void frSetBodyRotation(frBody *b, float rotation);
|
||||
|
||||
/* Sets the user data of `b` to `data`. */
|
||||
void frSetBodyUserData(frBody *b, void *data);
|
||||
|
||||
/* Clears all forces that are applied to `b`. */
|
||||
void frClearBodyForces(frBody *b);
|
||||
|
||||
/* Applies a `gravity` vector to `b`. */
|
||||
void frApplyGravity(frBody *b, Vector2 gravity);
|
||||
|
||||
/* Applies an `impulse` to `b`. */
|
||||
void frApplyImpulse(frBody *b, Vector2 impulse);
|
||||
|
||||
/* Applies a torque `impulse` to `b`. */
|
||||
void frApplyTorqueImpulse(frBody *b, Vector2 p, Vector2 impulse);
|
||||
|
||||
/* Integrates the position of `b` with `dt`. */
|
||||
void frIntegrateForBodyPosition(frBody *b, double dt);
|
||||
|
||||
/* Integrates the velocities of `b` with `dt`. */
|
||||
void frIntegrateForBodyVelocities(frBody *b, double dt);
|
||||
|
||||
/* Resolves the collision between two rigid bodies. */
|
||||
void frResolveCollision(frCollision *collision);
|
||||
|
||||
/* Corrects the positions of two rigid bodies. */
|
||||
void frCorrectBodyPositions(frCollision *collision, float inverseDt);
|
||||
|
||||
/* | Module Functions (`geometry`) | */
|
||||
|
||||
/*
|
||||
Creates a new circle-shaped collision shape
|
||||
from the given material and radius (in meters).
|
||||
*/
|
||||
frShape *frCreateCircle(frMaterial material, float radius);
|
||||
|
||||
/*
|
||||
Creates a new rectangle-shaped collision shape
|
||||
from the given material, width and height (in meters).
|
||||
*/
|
||||
frShape *frCreateRectangle(frMaterial material, float width, float height);
|
||||
|
||||
/*
|
||||
Creates a new polygon-shaped collision shape
|
||||
from the given material and vertices (in meters).
|
||||
*/
|
||||
frShape *frCreatePolygon(frMaterial material, frVertices vertices);
|
||||
|
||||
/* Creates an empty shape. */
|
||||
frShape *frCreateShape(void);
|
||||
|
||||
/* Returns a clone of `s`. */
|
||||
frShape *frCloneShape(frShape *s);
|
||||
|
||||
/* Releases the memory allocated for `s`. */
|
||||
void frReleaseShape(frShape *s);
|
||||
|
||||
/* Returns the size of the struct `frShape`. */
|
||||
size_t frGetShapeStructSize(void);
|
||||
|
||||
/* Returns the type of `s`. */
|
||||
frShapeType frGetShapeType(frShape *s);
|
||||
|
||||
/* Returns the material of `s`. */
|
||||
frMaterial frGetShapeMaterial(frShape *s);
|
||||
|
||||
/* Returns the area of `s`. */
|
||||
float frGetShapeArea(frShape *s);
|
||||
|
||||
/* Returns the mass of `s`. */
|
||||
float frGetShapeMass(frShape *s);
|
||||
|
||||
/* Returns the moment of inertia for `s`. */
|
||||
float frGetShapeInertia(frShape *s);
|
||||
|
||||
/* Returns the AABB (Axis-Aligned Bounding Box) of `s`. */
|
||||
Rectangle frGetShapeAABB(frShape *s, frTransform tx);
|
||||
|
||||
/* Returns the radius of the `s`. */
|
||||
float frGetCircleRadius(frShape *s);
|
||||
|
||||
/* Returns the width and height of `s`. */
|
||||
Vector2 frGetRectangleDimensions(frShape *s);
|
||||
|
||||
/* Returns the `i + 1`th vertex of `s`. */
|
||||
Vector2 frGetPolygonVertex(frShape *s, int i);
|
||||
|
||||
/* Returns the `i + 1`th normal of `s`. */
|
||||
Vector2 frGetPolygonNormal(frShape *s, int i);
|
||||
|
||||
/* Returns the vertices of `s`. */
|
||||
frVertices frGetPolygonVertices(frShape *s);
|
||||
|
||||
/* Returns the normals of `s`. */
|
||||
frVertices frGetPolygonNormals(frShape *s);
|
||||
|
||||
/* Returns `true` if `s` is a rectangular collision shape. */
|
||||
bool frIsShapeRectangle(frShape *s);
|
||||
|
||||
/* Sets the radius of `s` to `radius`. */
|
||||
void frSetCircleRadius(frShape *s, float radius);
|
||||
|
||||
/* Sets the dimensions (width and height) for `s` to `v`. */
|
||||
void frSetRectangleDimensions(frShape *s, Vector2 v);
|
||||
|
||||
/* Sets the vertices of `s` to `vertices`. */
|
||||
void frSetPolygonVertices(frShape *s, frVertices vertices);
|
||||
|
||||
/* Sets the material of `s` to `material`. */
|
||||
void frSetShapeMaterial(frShape *s, frMaterial material);
|
||||
|
||||
/* Sets the type of `s` to `type`. */
|
||||
void frSetShapeType(frShape *s, frShapeType type);
|
||||
|
||||
/* Returns `true` if `s` contains the point `p`. */
|
||||
bool frShapeContainsPoint(frShape *s, frTransform tx, Vector2 p);
|
||||
|
||||
/* | Module Functions (`timer`) | */
|
||||
|
||||
/* Initializes a monotonic clock. */
|
||||
void frInitClock(void);
|
||||
|
||||
/* Returns the current time (in milliseconds) of the monotonic clock. */
|
||||
double frGetCurrentTime(void);
|
||||
|
||||
/* Returns the difference between `newTime` and `oldTime`. */
|
||||
double frGetTimeDifference(double newTime, double oldTime);
|
||||
|
||||
/* Returns the difference between the current time and `oldTime`. */
|
||||
double frGetTimeSince(double oldTime);
|
||||
|
||||
/* | Module Functions (`utils`) | */
|
||||
|
||||
/* Normalizes `angle` (in radians) to range `[center - ฯ/2, center + ฯ/2]`. */
|
||||
float frNormalizeAngle(float angle, float center);
|
||||
|
||||
/* Returns `true` if `f1` and `f2` are approximately equal. */
|
||||
bool frNumberApproxEquals(float f1, float f2);
|
||||
|
||||
/* | Module Functions (`world`) | */
|
||||
|
||||
/* Creates a new world from the given `gravity` vector and world `bounds` in meters. */
|
||||
frWorld *frCreateWorld(Vector2 gravity, Rectangle bounds);
|
||||
|
||||
/* Removes all rigid bodies from `world`, then releases the memory allocated for `world`. */
|
||||
void frReleaseWorld(frWorld *world);
|
||||
|
||||
/* Adds `b` to `world`. */
|
||||
bool frAddToWorld(frWorld *world, frBody *b);
|
||||
|
||||
/* Removes all rigid bodies from `world`. */
|
||||
void frClearWorld(frWorld *world);
|
||||
|
||||
/* Removes `b` from `world`. */
|
||||
bool frRemoveFromWorld(frWorld *world, frBody *b);
|
||||
|
||||
/* Returns the size of the struct `frWorld`. */
|
||||
size_t frGetWorldStructSize(void);
|
||||
|
||||
/* Returns the rigid body at the `index` in `world`'s array of rigid bodies. */
|
||||
frBody *frGetWorldBody(frWorld *world, int index);
|
||||
|
||||
/* Returns the collision event handler for `world`. */
|
||||
frCollisionHandler frGetWorldCollisionHandler(frWorld *world);
|
||||
|
||||
/* Returns the length of `world`'s array of rigid bodies. */
|
||||
int frGetWorldBodyCount(frWorld *world);
|
||||
|
||||
/* Returns the bounds of `world`. */
|
||||
Rectangle frGetWorldBounds(frWorld *world);
|
||||
|
||||
/* Returns the spatial hash of `world`. */
|
||||
frSpatialHash *frGetWorldSpatialHash(frWorld *world);
|
||||
|
||||
/* Returns the gravity vector of `world`. */
|
||||
Vector2 frGetWorldGravity(frWorld *world);
|
||||
|
||||
/* Returns `true` if `b` collides with the bounds of `world`. */
|
||||
bool frIsInWorldBounds(frWorld *world, frBody *b);
|
||||
|
||||
/* Sets the world bounds of `world` to `bounds`. */
|
||||
void frSetWorldBounds(frWorld *world, Rectangle bounds);
|
||||
|
||||
/* Sets the collision event handler for `world` to `handler`. */
|
||||
void frSetWorldCollisionHandler(frWorld *world, frCollisionHandler handler);
|
||||
|
||||
/* Sets the gravity vector of `world` to `gravity`. */
|
||||
void frSetWorldGravity(frWorld *world, Vector2 gravity);
|
||||
|
||||
/* Simulates the `world` for the time step `dt` (in milliseconds). */
|
||||
void frSimulateWorld(frWorld *world, double dt);
|
||||
|
||||
/* Query the `world` for rigid bodies that collides with `rec`. */
|
||||
int frQueryWorldSpatialHash(frWorld *world, Rectangle rec, frBody **bodies);
|
||||
|
||||
/* Casts a `ray` to all rigid bodies in `world`. */
|
||||
int frComputeWorldRaycast(frWorld *world, frRay ray, frRaycastHit *hits);
|
||||
|
||||
/* | Inline Functions | */
|
||||
|
||||
/* Converts `value` (in pixels) to meters. */
|
||||
FR_API_INLINE float frNumberPixelsToMeters(float value) {
|
||||
return (FR_GLOBAL_PIXELS_PER_METER > 0.0f)
|
||||
? (value / FR_GLOBAL_PIXELS_PER_METER)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
/* Converts `value` (in meters) to pixels. */
|
||||
FR_API_INLINE float frNumberMetersToPixels(float value) {
|
||||
return (FR_GLOBAL_PIXELS_PER_METER > 0.0f)
|
||||
? (value * FR_GLOBAL_PIXELS_PER_METER)
|
||||
: 0.0f;
|
||||
}
|
||||
|
||||
/* Converts the components of `rec` (in pixels) to meters. */
|
||||
FR_API_INLINE Rectangle frRecPixelsToMeters(Rectangle rec) {
|
||||
return (Rectangle) {
|
||||
.x = frNumberPixelsToMeters(rec.x),
|
||||
.y = frNumberPixelsToMeters(rec.y),
|
||||
.width = frNumberPixelsToMeters(rec.width),
|
||||
.height = frNumberPixelsToMeters(rec.height)
|
||||
};
|
||||
}
|
||||
|
||||
/* Converts the components of `rec` (in meters) to pixels. */
|
||||
FR_API_INLINE Rectangle frRecMetersToPixels(Rectangle rec) {
|
||||
return (Rectangle) {
|
||||
.x = frNumberMetersToPixels(rec.x),
|
||||
.y = frNumberMetersToPixels(rec.y),
|
||||
.width = frNumberMetersToPixels(rec.width),
|
||||
.height = frNumberMetersToPixels(rec.height)
|
||||
};
|
||||
}
|
||||
|
||||
/* Adds `v1` and `v2`. */
|
||||
FR_API_INLINE Vector2 frVec2Add(Vector2 v1, Vector2 v2) {
|
||||
return (Vector2) { v1.x + v2.x, v1.y + v2.y };
|
||||
}
|
||||
|
||||
/* Subtracts `v2` from `v1`. */
|
||||
FR_API_INLINE Vector2 frVec2Subtract(Vector2 v1, Vector2 v2) {
|
||||
return (Vector2) { v1.x - v2.x, v1.y - v2.y };
|
||||
}
|
||||
|
||||
/* Multiplies `v` by `value`. */
|
||||
FR_API_INLINE Vector2 frVec2ScalarMultiply(Vector2 v, float value) {
|
||||
return (Vector2) { v.x * value, v.y * value };
|
||||
}
|
||||
|
||||
/* Returns the cross product of `v1` and `v2`. */
|
||||
FR_API_INLINE float frVec2CrossProduct(Vector2 v1, Vector2 v2) {
|
||||
// ํ๋ฉด ๋ฒกํฐ์ ์ธ์ ์ ์ค์นผ๋ผ ๊ฐ์ด๋ค.
|
||||
return (v1.x * v2.y) - (v1.y * v2.x);
|
||||
}
|
||||
|
||||
/* Returns the dot product of `v1` and `v2`. */
|
||||
FR_API_INLINE float frVec2DotProduct(Vector2 v1, Vector2 v2) {
|
||||
return (v1.x * v2.x) + (v1.y * v2.y);
|
||||
}
|
||||
|
||||
/* Returns the squared magnitude of `v`. */
|
||||
FR_API_INLINE float frVec2MagnitudeSqr(Vector2 v) {
|
||||
return frVec2DotProduct(v, v);
|
||||
}
|
||||
|
||||
/* Returns the magnitude of `v`. */
|
||||
FR_API_INLINE float frVec2Magnitude(Vector2 v) {
|
||||
return sqrtf(frVec2MagnitudeSqr(v));
|
||||
}
|
||||
|
||||
/* Returns the negated vector of `v`. */
|
||||
FR_API_INLINE Vector2 frVec2Negate(Vector2 v) {
|
||||
return (Vector2) { -v.x, -v.y };
|
||||
}
|
||||
|
||||
/* Converts `v` to a unit vector. */
|
||||
FR_API_INLINE Vector2 frVec2Normalize(Vector2 v) {
|
||||
const float magnitude = frVec2Magnitude(v);
|
||||
|
||||
return (magnitude > 0.0f)
|
||||
? frVec2ScalarMultiply(v, 1.0f / magnitude)
|
||||
: FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* Returns the angle (in radians) between `v1` and `v2`. */
|
||||
FR_API_INLINE float frVec2Angle(Vector2 v1, Vector2 v2) {
|
||||
return atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x);
|
||||
}
|
||||
|
||||
/* Returns `true` if `v1` and `v2` are approximately equal. */
|
||||
FR_API_INLINE bool frVec2ApproxEquals(Vector2 v1, Vector2 v2) {
|
||||
return frNumberApproxEquals(v1.x, v2.x) && frNumberApproxEquals(v1.y, v2.y);
|
||||
}
|
||||
|
||||
/* Returns the left normal of `v`. */
|
||||
FR_API_INLINE Vector2 frVec2LeftNormal(Vector2 v) {
|
||||
return frVec2Normalize((Vector2) { -v.y, v.x });
|
||||
}
|
||||
|
||||
/* Returns the right normal of `v`. */
|
||||
FR_API_INLINE Vector2 frVec2RightNormal(Vector2 v) {
|
||||
return frVec2Normalize((Vector2) { v.y, -v.x });
|
||||
}
|
||||
|
||||
/* Rotates `v` around the origin. */
|
||||
FR_API_INLINE Vector2 frVec2Rotate(Vector2 v, float angle) {
|
||||
const float sinA = sinf(angle);
|
||||
const float cosA = cosf(angle);
|
||||
|
||||
return (Vector2) { (v.x * cosA - v.y * sinA), (v.x * sinA + v.y * cosA) };
|
||||
}
|
||||
|
||||
/* Rotates `v` with the properties of `tx`. */
|
||||
FR_API_INLINE Vector2 frVec2RotateTx(Vector2 v, frTransform tx) {
|
||||
Vector2 result = {
|
||||
(v.x * tx.cache.cosA - v.y * tx.cache.sinA),
|
||||
(v.x * tx.cache.sinA + v.y * tx.cache.cosA)
|
||||
};
|
||||
|
||||
if (!tx.cache.valid) result = frVec2Rotate(v, tx.rotation);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Transforms `v` with the properties of `tx`. */
|
||||
FR_API_INLINE Vector2 frVec2Transform(Vector2 v, frTransform tx) {
|
||||
return frVec2Add(tx.position, frVec2RotateTx(v, tx));
|
||||
}
|
||||
|
||||
/* Returns `true` if `v1`, `v2` and `v3` are ordered counter-clockwise. */
|
||||
FR_API_INLINE bool frVec2CCW(Vector2 v1, Vector2 v2, Vector2 v3) {
|
||||
return (v3.y - v1.y) * (v2.x - v1.x) < (v2.y - v1.y) * (v3.x - v1.x);
|
||||
}
|
||||
|
||||
/* Converts the components of `v` (in pixels) to meters. */
|
||||
FR_API_INLINE Vector2 frVec2PixelsToMeters(Vector2 v) {
|
||||
return (FR_GLOBAL_PIXELS_PER_METER > 0.0f)
|
||||
? frVec2ScalarMultiply(v, 1.0f / FR_GLOBAL_PIXELS_PER_METER)
|
||||
: FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* Converts the components of `v` (in meters) to pixels. */
|
||||
FR_API_INLINE Vector2 frVec2MetersToPixels(Vector2 v) {
|
||||
return (FR_GLOBAL_PIXELS_PER_METER > 0.0f)
|
||||
? frVec2ScalarMultiply(v, FR_GLOBAL_PIXELS_PER_METER)
|
||||
: FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,201 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
#include "stb_ds.h"
|
||||
|
||||
/* | `broadphase` ๋ชจ๋ ์๋ฃํ ์ ์... | */
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต์ ํค์ ๊ฐ์ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด. */
|
||||
typedef struct frSpatialEntry {
|
||||
int key;
|
||||
int *values;
|
||||
} frSpatialEntry;
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต์ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด. */
|
||||
typedef struct frSpatialHash {
|
||||
Rectangle bounds;
|
||||
float cellSize;
|
||||
float inverseCellSize;
|
||||
frSpatialEntry *entries;
|
||||
int *queryCache;
|
||||
} frSpatialHash;
|
||||
|
||||
/* | `broadphase` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์์ ๋ฒกํฐ `v`์ ๋์ํ๋ ํค๋ฅผ ๋ฐํํ๋ค. */
|
||||
static FR_API_INLINE int frComputeSpatialHashKey(frSpatialHash *hash, Vector2 v);
|
||||
|
||||
/* C ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ `qsort()` ํจ์ ํธ์ถ์ ์ฌ์ฉ๋๋ ๋น๊ต ํจ์์ด๋ค. */
|
||||
static int frQuickSortCallback(const void *x, const void *y);
|
||||
|
||||
/* ๊ฒฝ๊ณ ๋ฒ์๊ฐ `bounds`์ด๊ณ ๊ฐ ์
์ ํฌ๊ธฐ๊ฐ `cellSize`์ธ ๊ณต๊ฐ ํด์๋งต์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ๋ฐํํ๋ค. */
|
||||
frSpatialHash *frCreateSpatialHash(Rectangle bounds, float cellSize) {
|
||||
if (cellSize <= 0.0f) return NULL;
|
||||
|
||||
frSpatialHash *result = calloc(1, sizeof(*result));
|
||||
|
||||
result->bounds = bounds;
|
||||
result->cellSize = cellSize;
|
||||
result->inverseCellSize = 1.0f / cellSize;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ํ ๋น๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ค. */
|
||||
void frReleaseSpatialHash(frSpatialHash *hash) {
|
||||
if (hash == NULL) return;
|
||||
|
||||
for (int i = 0; i < hmlen(hash->entries); i++)
|
||||
arrfree(hash->entries[i].values);
|
||||
|
||||
hmfree(hash->entries);
|
||||
|
||||
free(hash);
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ์ง์ฌ๊ฐํ `rec`๋ก ์์ฑํ ํค์ `value`๋ฅผ ์ถ๊ฐํ๋ค. */
|
||||
void frAddToSpatialHash(frSpatialHash *hash, Rectangle rec, int value) {
|
||||
if (hash == NULL || !CheckCollisionRecs(hash->bounds, rec)) return;
|
||||
|
||||
int x0 = frComputeSpatialHashKey(hash, (Vector2) { .x = rec.x });
|
||||
int x1 = frComputeSpatialHashKey(hash, (Vector2) { .x = rec.x + rec.width });
|
||||
|
||||
int y0 = frComputeSpatialHashKey(hash, (Vector2) { .y = rec.y });
|
||||
int y1 = frComputeSpatialHashKey(hash, (Vector2) { .y = rec.y + rec.height });
|
||||
|
||||
for (int y = y0; y <= y1; y += hash->bounds.width) {
|
||||
for (int x = x0; x <= x1; x++) {
|
||||
const int key = x + y;
|
||||
|
||||
frSpatialEntry *entry = hmgetp_null(hash->entries, key);
|
||||
|
||||
if (entry != NULL) {
|
||||
arrput(entry->values, value);
|
||||
} else {
|
||||
frSpatialEntry newEntry = { .key = key };
|
||||
|
||||
arrput(newEntry.values, value);
|
||||
hmputs(hash->entries, newEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ๋ชจ๋ ํค์ ๊ฐ์ ์ ๊ฑฐํ๋ค. */
|
||||
void frClearSpatialHash(frSpatialHash *hash) {
|
||||
if (hash == NULL) return;
|
||||
|
||||
for (int i = 0; i < hmlen(hash->entries); i++)
|
||||
arrsetlen(hash->entries[i].values, 0);
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์์ ํค๊ฐ `key`์ธ ๊ฐ์ ์ ๊ฑฐํ๋ค. */
|
||||
void frRemoveFromSpatialHash(frSpatialHash *hash, int key) {
|
||||
if (hash == NULL) return;
|
||||
|
||||
frSpatialEntry entry = hmgets(hash->entries, key);
|
||||
arrfree(entry.values);
|
||||
|
||||
hmdel(hash->entries, key);
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์์ ์ง์ฌ๊ฐํ `rec`์ ๊ฒฝ๊ณ ๋ฒ์๊ฐ ๊ฒน์น๋ ๋ชจ๋ ๊ฐ์ฒด์ ์ธ๋ฑ์ค๋ฅผ ๋ฐํํ๋ค. */
|
||||
void frQuerySpatialHash(frSpatialHash *hash, Rectangle rec, int **result) {
|
||||
if (hash == NULL || result == NULL || !CheckCollisionRecs(hash->bounds, rec)) return;
|
||||
|
||||
int x0 = frComputeSpatialHashKey(hash, (Vector2) { .x = rec.x });
|
||||
int x1 = frComputeSpatialHashKey(hash, (Vector2) { .x = rec.x + rec.width });
|
||||
|
||||
int y0 = frComputeSpatialHashKey(hash, (Vector2) { .y = rec.y });
|
||||
int y1 = frComputeSpatialHashKey(hash, (Vector2) { .y = rec.y + rec.height });
|
||||
|
||||
const int yOffset = (int) hash->bounds.width;
|
||||
|
||||
for (int y = y0; y <= y1; y += yOffset) {
|
||||
for (int x = x0; x <= x1; x++) {
|
||||
const int key = x + y;
|
||||
|
||||
// ์ฃผ์ด์ง ์
๊ณผ ๋ง๋๋ ๋ชจ๋ ๊ฐ์ฒด์ ์ธ๋ฑ์ค๋ฅผ ๊ตฌํ๋ค.
|
||||
frSpatialEntry *entry = hmgetp_null(hash->entries, key);
|
||||
|
||||
if (entry != NULL) {
|
||||
for (int j = 0; j < arrlen(entry->values); j++)
|
||||
arrput(*result, entry->values[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t oldLength = arrlen(*result);
|
||||
|
||||
if (oldLength <= 1) return;
|
||||
|
||||
// ๊ฒฐ๊ณผ ๋ฐฐ์ด์ ๋จผ์ ํต-์ ๋ ฌํ๋ค.
|
||||
qsort(*result, oldLength, sizeof(**result), frQuickSortCallback);
|
||||
|
||||
size_t newLength = 0;
|
||||
|
||||
// ๊ฒฐ๊ณผ ๋ฐฐ์ด์์ ์ค๋ณต ๊ฐ์ ์ ๊ฑฐํ๋ค.
|
||||
for (int i = 0; i < oldLength; i++)
|
||||
if ((*result)[i] != (*result)[i + 1])
|
||||
(*result)[newLength++] = (*result)[i];
|
||||
|
||||
// ๊ฒฐ๊ณผ ๋ฐฐ์ด์ ๊ธธ์ด๋ฅผ ๋ค์ ์ค์ ํ๋ค.
|
||||
arrsetlen(*result, newLength);
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ๊ฒฝ๊ณ ๋ฒ์๋ฅผ ๋ฐํํ๋ค. */
|
||||
Rectangle frGetSpatialHashBounds(frSpatialHash *hash) {
|
||||
return (hash != NULL) ? hash->bounds : FR_STRUCT_ZERO(Rectangle);
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ๊ฐ ์
์ ํฌ๊ธฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetSpatialHashCellSize(frSpatialHash *hash) {
|
||||
return (hash != NULL) ? hash->cellSize : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ๊ฒฝ๊ณ ๋ฒ์๋ฅผ `bounds`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetSpatialHashBounds(frSpatialHash *hash, Rectangle bounds) {
|
||||
if (hash != NULL) hash->bounds = bounds;
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์ ๊ฐ ์
์ ํฌ๊ธฐ๋ฅผ `cellSize`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetSpatialHashCellSize(frSpatialHash *hash, float cellSize) {
|
||||
if (hash != NULL) hash->cellSize = cellSize;
|
||||
}
|
||||
|
||||
/* ๊ณต๊ฐ ํด์๋งต `hash`์์ ๋ฒกํฐ `v`์ ๋์ํ๋ ํค๋ฅผ ๋ฐํํ๋ค. */
|
||||
static FR_API_INLINE int frComputeSpatialHashKey(frSpatialHash *hash, Vector2 v) {
|
||||
if (hash == NULL) return -1;
|
||||
|
||||
const int xIndex = v.x * hash->inverseCellSize;
|
||||
const int yIndex = v.y * hash->inverseCellSize;
|
||||
|
||||
return yIndex * (int) hash->bounds.width + xIndex;
|
||||
}
|
||||
|
||||
/* C ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ `qsort()` ํจ์ ํธ์ถ์ ์ฌ์ฉ๋๋ ๋น๊ต ํจ์์ด๋ค. */
|
||||
static int frQuickSortCallback(const void *x, const void *y) {
|
||||
int nx = *(const int *) x, ny = *(const int *) y;
|
||||
|
||||
return (nx > ny) - (nx < ny);
|
||||
}
|
|
@ -0,0 +1,661 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
|
||||
/* | `collision` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ๋ํ `s1`์ AABB๊ฐ `s2`์ AABB์ ์ถฉ๋ํ๋์ง ํ์ธํ๋ค. */
|
||||
static bool frCheckCollisionAABB(frShape *s1, frTransform tx1, frShape *s2, frTransform tx2);
|
||||
|
||||
/* ๋ค๊ฐํ `s`์์ ๋ฒกํฐ `v`์์ ๋ด์ ์ด ๊ฐ์ฅ ํฐ ๊ผญ์ง์ ์ ์ธ๋ฑ์ค๋ฅผ ๋ฐํํ๋ค. */
|
||||
static int frGetPolygonFurthestIndex(frShape *s, frTransform tx, Vector2 v);
|
||||
|
||||
/* ๋ํ `s`์์ ๋ฒกํฐ `v`์ ๊ฐ์ฅ ์์ง์ ๊ฐ๊น์ด ๋ณ์ ๋ฐํํ๋ค. */
|
||||
static frVertices frGetShapeSignificantEdge(frShape *s, frTransform tx, Vector2 v);
|
||||
|
||||
/* ๋ค๊ฐํ `s1`์์ `s2`๋ก์ ์ถฉ๋ ๋ฐฉํฅ๊ณผ ๊น์ด๋ฅผ ๊ณ์ฐํ๋ค. */
|
||||
static int frGetSeparatingAxisIndex(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2,
|
||||
float *depth
|
||||
);
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ์ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionCirclesSAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
);
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ์ `s1`์์ ๋ค๊ฐํ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionCirclePolySAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
);
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ๋ค๊ฐํ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionPolysSAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
);
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ๋ํ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionSAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
);
|
||||
|
||||
/* ์ ๋ถ `e`์์ ๋ฒกํฐ `v`์์ ๋ด์ ์ด `minDot`๋ณด๋ค ์์ ๋ถ๋ถ์ ๋ชจ๋ ์๋ผ๋ธ๋ค. */
|
||||
static frVertices frClipEdgeWithAxis(frVertices e, Vector2 v, float minDot);
|
||||
|
||||
/* `o1`์์ `v1` ๋ฐฉํฅ์ผ๋ก ์งํํ๋ ๊ด์ ์ด `o2`์์ `v2` ๋ฐฉํฅ์ผ๋ก ์งํํ๋ ๊ด์ ๊ณผ ๋ง๋๋์ง ๊ณ์ฐํ๋ค. */
|
||||
static bool frComputeIntersectionRays(
|
||||
Vector2 o1, Vector2 v1,
|
||||
Vector2 o2, Vector2 v2,
|
||||
float *distance
|
||||
);
|
||||
|
||||
/* `o`์์ `v` ๋ฐฉํฅ์ผ๋ก ์งํํ๋ ๊ด์ ์ด ์ค์ฌ์ ์ด `c`์ด๊ณ ๋ฐ์ง๋ฆ์ด `r`์ธ ์๊ณผ ๋ง๋๋์ง ๊ณ์ฐํ๋ค. */
|
||||
static bool frComputeIntersectionRayCircle(
|
||||
Vector2 o, Vector2 v,
|
||||
Vector2 c, float r,
|
||||
float *distance
|
||||
);
|
||||
|
||||
/* ๋ํ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
frCollision frComputeShapeCollision(frShape *s1, frTransform tx1, frShape *s2, frTransform tx2) {
|
||||
return frCheckCollisionAABB(s1, tx1, s2, tx2)
|
||||
? frComputeCollisionSAT(s1, tx1, s2, tx2)
|
||||
: FR_STRUCT_ZERO(frCollision);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b1`์์ `b2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
frCollision frComputeBodyCollision(frBody *b1, frBody *b2) {
|
||||
if (b1 == NULL || b2 == NULL) return FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
return frComputeShapeCollision(
|
||||
frGetBodyShape(b1),
|
||||
frGetBodyTransform(b1),
|
||||
frGetBodyShape(b2),
|
||||
frGetBodyTransform(b2)
|
||||
);
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ๊ด์ ์ ํฌ์ฌํ๋ค. */
|
||||
frRaycastHit frComputeShapeRaycast(frShape *s, frTransform tx, frRay ray) {
|
||||
if (s == NULL || frGetShapeType(s) == FR_SHAPE_UNKNOWN) return FR_STRUCT_ZERO(frRaycastHit);
|
||||
|
||||
frRaycastHit result = { .shape = s, .distance = FLT_MAX };
|
||||
|
||||
ray.direction = frVec2Normalize(ray.direction);
|
||||
|
||||
float distance = FLT_MAX;
|
||||
|
||||
if (frGetShapeType(s) == FR_SHAPE_CIRCLE) {
|
||||
bool intersects = frComputeIntersectionRayCircle(
|
||||
ray.origin, ray.direction,
|
||||
tx.position, frGetCircleRadius(s),
|
||||
&distance
|
||||
);
|
||||
|
||||
result.check = (distance >= 0.0f) && (distance <= ray.maxDistance);
|
||||
result.inside = (distance < 0.0f);
|
||||
|
||||
if (result.check) {
|
||||
result.distance = distance;
|
||||
|
||||
result.point = frVec2Add(
|
||||
ray.origin,
|
||||
frVec2ScalarMultiply(ray.direction, result.distance)
|
||||
);
|
||||
|
||||
result.normal = frVec2LeftNormal(frVec2Subtract(ray.origin, result.point));
|
||||
}
|
||||
|
||||
return result;
|
||||
} else if (frGetShapeType(s) == FR_SHAPE_POLYGON) {
|
||||
int intersectionCount = 0;
|
||||
|
||||
frVertices vertices = frGetPolygonVertices(s);
|
||||
|
||||
// ๋ค๊ฐํ์ ๋ณ ์ค์ ๊ด์ ๊ณผ ๊ต์ฐจํ๋ ๋ณ์ด ์กด์ฌํ๋์ง ํ์ธํ๋ค.
|
||||
for (int j = vertices.count - 1, i = 0; i < vertices.count; j = i, i++) {
|
||||
Vector2 v1 = frVec2Transform(vertices.data[i], tx);
|
||||
Vector2 v2 = frVec2Transform(vertices.data[j], tx);
|
||||
|
||||
Vector2 diff = frVec2Subtract(v1, v2);
|
||||
|
||||
bool intersects = frComputeIntersectionRays(
|
||||
ray.origin, ray.direction,
|
||||
v2, diff,
|
||||
&distance
|
||||
);
|
||||
|
||||
if (intersects && distance <= ray.maxDistance) {
|
||||
if (result.distance > distance) {
|
||||
result.distance = distance;
|
||||
|
||||
result.point = frVec2Add(
|
||||
ray.origin,
|
||||
frVec2ScalarMultiply(ray.direction, result.distance)
|
||||
);
|
||||
|
||||
result.normal = frVec2LeftNormal(diff);
|
||||
}
|
||||
|
||||
intersectionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
result.inside = (intersectionCount & 1);
|
||||
result.check = (!result.inside) && (intersectionCount > 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๊ด์ ์ ํฌ์ฌํ๋ค. */
|
||||
frRaycastHit frComputeBodyRaycast(frBody *b, frRay ray) {
|
||||
if (b == NULL || frGetBodyShape(b) == NULL) return FR_STRUCT_ZERO(frRaycastHit);
|
||||
|
||||
frRaycastHit result = frComputeShapeRaycast(
|
||||
frGetBodyShape(b), frGetBodyTransform(b),
|
||||
ray
|
||||
);
|
||||
|
||||
result.body = b;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๋ํ `s1`์ AABB๊ฐ `s2`์ AABB์ ์ถฉ๋ํ๋์ง ํ์ธํ๋ค. */
|
||||
static bool frCheckCollisionAABB(frShape *s1, frTransform tx1, frShape *s2, frTransform tx2) {
|
||||
return CheckCollisionRecs(frGetShapeAABB(s1, tx1), frGetShapeAABB(s2, tx2));
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`์์ ๋ฒกํฐ `v`์์ ๋ด์ ์ด ๊ฐ์ฅ ํฐ ๊ผญ์ง์ ์ ์ธ๋ฑ์ค๋ฅผ ๋ฐํํ๋ค. */
|
||||
static int frGetPolygonFurthestIndex(frShape *s, frTransform tx, Vector2 v) {
|
||||
if (frGetShapeType(s) != FR_SHAPE_POLYGON) return -1;
|
||||
|
||||
frVertices vertices = frGetPolygonVertices(s);
|
||||
|
||||
float maxDot = -FLT_MAX;
|
||||
int result = 0;
|
||||
|
||||
v = frVec2Rotate(v, -tx.rotation);
|
||||
|
||||
for (int i = 0; i < vertices.count; i++) {
|
||||
float dot = frVec2DotProduct(vertices.data[i], v);
|
||||
|
||||
if (maxDot < dot) {
|
||||
maxDot = dot;
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์์ ๋ฒกํฐ `v`์ ๊ฐ์ฅ ์์ง์ ๊ฐ๊น์ด ๋ณ์ ๋ฐํํ๋ค. */
|
||||
static frVertices frGetShapeSignificantEdge(frShape *s, frTransform tx, Vector2 v) {
|
||||
if (s == NULL || frGetShapeType(s) == FR_SHAPE_UNKNOWN) return FR_STRUCT_ZERO(frVertices);
|
||||
|
||||
frVertices result = FR_STRUCT_ZERO(frVertices);
|
||||
|
||||
if (frGetShapeType(s) == FR_SHAPE_CIRCLE) {
|
||||
result.data[0] = frVec2Transform(frVec2ScalarMultiply(v, frGetCircleRadius(s)), tx);
|
||||
result.count = 1;
|
||||
|
||||
return result;
|
||||
} else if (frGetShapeType(s) == FR_SHAPE_POLYGON) {
|
||||
int furthestIndex = frGetPolygonFurthestIndex(s, tx, v);
|
||||
|
||||
frVertices vertices = frGetPolygonVertices(s);
|
||||
|
||||
int prevIndex = (furthestIndex == 0) ? vertices.count - 1 : furthestIndex - 1;
|
||||
int nextIndex = (furthestIndex == vertices.count - 1) ? 0 : furthestIndex + 1;
|
||||
|
||||
Vector2 furthestVertex = frVec2Transform(vertices.data[furthestIndex], tx);
|
||||
|
||||
Vector2 prevVertex = frVec2Transform(vertices.data[prevIndex], tx);
|
||||
Vector2 nextVertex = frVec2Transform(vertices.data[nextIndex], tx);
|
||||
|
||||
Vector2 leftV = frVec2Normalize(frVec2Subtract(furthestVertex, prevVertex));
|
||||
Vector2 rightV = frVec2Normalize(frVec2Subtract(furthestVertex, nextVertex));
|
||||
|
||||
if (frVec2DotProduct(leftV, v) < frVec2DotProduct(rightV, v)) {
|
||||
result.data[0] = prevVertex;
|
||||
result.data[1] = furthestVertex;
|
||||
} else {
|
||||
result.data[0] = furthestVertex;
|
||||
result.data[1] = nextVertex;
|
||||
}
|
||||
|
||||
result.count = 2;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s1`์์ `s2`๋ก์ ์ถฉ๋ ๋ฐฉํฅ๊ณผ ๊น์ด๋ฅผ ๊ณ์ฐํ๋ค. */
|
||||
static int frGetSeparatingAxisIndex(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2,
|
||||
float *depth
|
||||
) {
|
||||
if (frGetShapeType(s1) != FR_SHAPE_POLYGON || frGetShapeType(s2) != FR_SHAPE_POLYGON)
|
||||
return -1;
|
||||
|
||||
int result = -1;
|
||||
|
||||
frVertices normals = frGetPolygonNormals(s1);
|
||||
|
||||
float maxDistance = -FLT_MAX;
|
||||
|
||||
for (int i = 0; i < normals.count; i++) {
|
||||
Vector2 vertex = frVec2Transform(frGetPolygonVertex(s1, i), tx1);
|
||||
Vector2 normal = frVec2RotateTx(normals.data[i], tx1);
|
||||
|
||||
int furthestIndex = frGetPolygonFurthestIndex(s2, tx2, frVec2Negate(normal));
|
||||
Vector2 furthestVertex = frVec2Transform(frGetPolygonVertex(s2, furthestIndex), tx2);
|
||||
|
||||
float distance = frVec2DotProduct(normal, frVec2Subtract(furthestVertex, vertex));
|
||||
|
||||
if (maxDistance < distance) {
|
||||
maxDistance = distance;
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth != NULL) *depth = maxDistance;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ์ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionCirclesSAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
) {
|
||||
if (frGetShapeType(s1) != FR_SHAPE_CIRCLE || frGetShapeType(s2) != FR_SHAPE_CIRCLE)
|
||||
return FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
frCollision result = FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
Vector2 diff = frVec2Subtract(tx2.position, tx1.position);
|
||||
|
||||
float radiusSum = frGetCircleRadius(s1) + frGetCircleRadius(s2);
|
||||
float magnitudeSqr = frVec2MagnitudeSqr(diff);
|
||||
|
||||
if (radiusSum * radiusSum < magnitudeSqr) return result;
|
||||
|
||||
float diffMagnitude = sqrtf(magnitudeSqr);
|
||||
|
||||
result.check = true;
|
||||
|
||||
if (diffMagnitude > 0.0f) {
|
||||
result.direction = frVec2ScalarMultiply(diff, 1.0f / diffMagnitude);
|
||||
|
||||
frVertices e = frGetShapeSignificantEdge(s1, tx1, result.direction);
|
||||
|
||||
result.points[0] = result.points[1] = e.data[0];
|
||||
result.depths[0] = result.depths[1] = radiusSum - diffMagnitude;
|
||||
} else {
|
||||
result.direction = (Vector2) { .x = 1.0f };
|
||||
|
||||
frVertices e = frGetShapeSignificantEdge(s1, tx1, result.direction);
|
||||
|
||||
result.points[0] = result.points[1] = e.data[0];
|
||||
result.depths[0] = result.depths[1] = frGetCircleRadius(s1);
|
||||
}
|
||||
|
||||
result.count = 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ์ `s1`์์ ๋ค๊ฐํ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionCirclePolySAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
) {
|
||||
if (frGetShapeType(s1) == FR_SHAPE_UNKNOWN || frGetShapeType(s2) == FR_SHAPE_UNKNOWN
|
||||
|| frGetShapeType(s1) == frGetShapeType(s2))
|
||||
return FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
frCollision result = FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
frShape *circle = s1, *polygon = s2;
|
||||
frTransform circleTx = tx1, polygonTx = tx2;
|
||||
|
||||
if (frGetShapeType(s1) == FR_SHAPE_POLYGON && frGetShapeType(s2) == FR_SHAPE_CIRCLE) {
|
||||
circle = s2, polygon = s1;
|
||||
circleTx = tx2, polygonTx = tx1;
|
||||
}
|
||||
|
||||
frVertices vertices = frGetPolygonVertices(polygon);
|
||||
frVertices normals = frGetPolygonNormals(polygon);
|
||||
|
||||
// ๋ค๊ฐํ์ ๊ผญ์ง์ ๊ณผ ๋ฒ์ ๋ฒกํฐ๋ฅผ ๋ณํํ๋ ๋์ , ์์ ์ค์ฌ์ ๋ค๊ฐํ์ ๊ธฐ์ค์ผ๋ก ๋ณํํ๋ค.
|
||||
Vector2 center = frVec2Rotate(
|
||||
frVec2Subtract(circleTx.position, polygonTx.position),
|
||||
-polygonTx.rotation
|
||||
);
|
||||
|
||||
float radius = frGetCircleRadius(circle), maxDistance = -FLT_MAX;
|
||||
int normalIndex = -1;
|
||||
|
||||
// ๋ค๊ฐํ์์ ์์ ์ค์ฌ๊ณผ ๊ฑฐ๋ฆฌ๊ฐ ๊ฐ์ฅ ๊ฐ๊น์ด ๋ณ์ ์ฐพ๋๋ค.
|
||||
for (int i = 0; i < vertices.count; i++) {
|
||||
float distance = frVec2DotProduct(
|
||||
normals.data[i],
|
||||
frVec2Subtract(center, vertices.data[i])
|
||||
);
|
||||
|
||||
// ๊ผญ์ง์ ํ๋๋ผ๋ ์์ ๋ฐ๊นฅ์ชฝ์ ์๋ค๋ฉด, ๊ณ์ฐ์ ์ข
๋ฃํ๋ค.
|
||||
if (distance > radius) return result;
|
||||
|
||||
if (maxDistance < distance) {
|
||||
maxDistance = distance;
|
||||
normalIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// ๋ค๊ฐํ์ด ์์ ์ค์ฌ์ ํฌํจํ๊ณ ์๋ค๋ฉด, ๊ณ์ฐ์ ์ข
๋ฃํ๋ค.
|
||||
if (maxDistance < 0.0f) {
|
||||
result.check = true;
|
||||
|
||||
result.direction = frVec2Negate(frVec2RotateTx(normals.data[normalIndex], polygonTx));
|
||||
|
||||
if (frVec2DotProduct(frVec2Subtract(tx2.position, tx1.position), result.direction) < 0.0f)
|
||||
result.direction = frVec2Negate(result.direction);
|
||||
|
||||
result.points[0] = result.points[1] = frVec2Add(
|
||||
circleTx.position,
|
||||
frVec2ScalarMultiply(result.direction, -radius)
|
||||
);
|
||||
|
||||
result.depths[0] = result.depths[1] = radius - maxDistance;
|
||||
|
||||
result.count = 1;
|
||||
} else {
|
||||
Vector2 v1 = (normalIndex > 0)
|
||||
? vertices.data[normalIndex - 1]
|
||||
: vertices.data[vertices.count - 1];
|
||||
Vector2 v2 = vertices.data[normalIndex];
|
||||
|
||||
/*
|
||||
1. ์์ด `v1`์ ํฌํจํ๋ ์ ๋ถ๊ณผ ๋ง๋๋๊ฐ?
|
||||
2. ์์ด `v2`๋ฅผ ํฌํจํ๋ ์ ๋ถ๊ณผ ๋ง๋๋๊ฐ?
|
||||
3. ์์ด `v1`๊ณผ `v2` ์ฌ์ด์ ์ ๋ถ๊ณผ ๋ง๋๋๊ฐ?
|
||||
|
||||
=> ๋ฒกํฐ์ ๋ด์ ์ ํตํด ํ์ธํ ์ ์๋ค.
|
||||
*/
|
||||
Vector2 diff1 = frVec2Subtract(center, v1), diff2 = frVec2Subtract(center, v2);
|
||||
|
||||
float v1Dot = frVec2DotProduct(diff1, frVec2Subtract(v2, v1));
|
||||
float v2Dot = frVec2DotProduct(diff2, frVec2Subtract(v1, v2));
|
||||
|
||||
if (v1Dot <= 0.0f) {
|
||||
float magnitudeSqr = frVec2MagnitudeSqr(diff1);
|
||||
|
||||
if (magnitudeSqr > radius * radius) return result;
|
||||
|
||||
result.direction = frVec2Normalize(frVec2RotateTx(frVec2Negate(diff1), polygonTx));
|
||||
|
||||
if (frVec2DotProduct(frVec2Subtract(tx2.position, tx1.position), result.direction) < 0.0f)
|
||||
result.direction = frVec2Negate(result.direction);
|
||||
|
||||
result.points[0] = result.points[1] = frVec2Transform(v1, polygonTx);
|
||||
result.depths[0] = result.depths[1] = radius - sqrtf(magnitudeSqr);
|
||||
} else if (v2Dot <= 0.0f) {
|
||||
float magnitudeSqr = frVec2MagnitudeSqr(diff2);
|
||||
|
||||
if (magnitudeSqr > radius * radius) return result;
|
||||
|
||||
result.direction = frVec2Normalize(frVec2RotateTx(frVec2Negate(diff2), polygonTx));
|
||||
|
||||
if (frVec2DotProduct(frVec2Subtract(tx2.position, tx1.position), result.direction) < 0.0f)
|
||||
result.direction = frVec2Negate(result.direction);
|
||||
|
||||
result.points[0] = result.points[1] = frVec2Transform(v2, polygonTx);
|
||||
result.depths[0] = result.depths[1] = radius - sqrtf(magnitudeSqr);
|
||||
} else {
|
||||
Vector2 normal = normals.data[normalIndex];
|
||||
|
||||
if (frVec2DotProduct(diff1, normal) > radius) return result;
|
||||
|
||||
result.direction = frVec2Negate(frVec2RotateTx(normal, polygonTx));
|
||||
|
||||
if (frVec2DotProduct(frVec2Subtract(tx2.position, tx1.position), result.direction) < 0.0f)
|
||||
result.direction = frVec2Negate(result.direction);
|
||||
|
||||
result.points[0] = result.points[1] = frVec2Add(
|
||||
circleTx.position,
|
||||
frVec2ScalarMultiply(result.direction, -radius)
|
||||
);
|
||||
|
||||
result.depths[0] = result.depths[1] = radius - maxDistance;
|
||||
}
|
||||
|
||||
result.check = true;
|
||||
|
||||
result.count = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ๋ค๊ฐํ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionPolysSAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
) {
|
||||
if (frGetShapeType(s1) != FR_SHAPE_POLYGON || frGetShapeType(s2) != FR_SHAPE_POLYGON)
|
||||
return FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
frCollision result = FR_STRUCT_ZERO(frCollision);
|
||||
|
||||
float depth1 = FLT_MAX, depth2 = FLT_MAX;
|
||||
|
||||
int index1 = frGetSeparatingAxisIndex(s1, tx1, s2, tx2, &depth1);
|
||||
if (depth1 >= 0.0f) return result;
|
||||
|
||||
int index2 = frGetSeparatingAxisIndex(s2, tx2, s1, tx1, &depth2);
|
||||
if (depth2 >= 0.0f) return result;
|
||||
|
||||
Vector2 direction = (depth1 > depth2)
|
||||
? frVec2RotateTx(frGetPolygonNormal(s1, index1), tx1)
|
||||
: frVec2RotateTx(frGetPolygonNormal(s2, index2), tx2);
|
||||
|
||||
if (frVec2DotProduct(frVec2Subtract(tx2.position, tx1.position), direction) < 0.0f)
|
||||
direction = frVec2Negate(direction);
|
||||
|
||||
result.check = true;
|
||||
|
||||
// ํ์ ๋ณํ์ด ์ ์ฉ๋ ๋ฒกํฐ๋ ํ์ ๋ณํ์ ์ ์ฉํ๊ธฐ ์ ์ ๋ฒกํฐ์ ํฌ๊ธฐ๊ฐ ๊ฐ๋ค.
|
||||
result.direction = direction;
|
||||
|
||||
frVertices e1 = frGetShapeSignificantEdge(s1, tx1, direction);
|
||||
frVertices e2 = frGetShapeSignificantEdge(s2, tx2, frVec2Negate(direction));
|
||||
|
||||
frVertices refEdge = e1, incEdge = e2;
|
||||
frTransform refTx = tx1, incTx = tx2;
|
||||
|
||||
float dot1 = frVec2DotProduct(frVec2Subtract(e1.data[1], e1.data[0]), direction);
|
||||
float dot2 = frVec2DotProduct(frVec2Subtract(e2.data[1], e2.data[0]), direction);
|
||||
|
||||
if (fabsf(dot1) > fabsf(dot2)) {
|
||||
refEdge = e2;
|
||||
incEdge = e1;
|
||||
|
||||
refTx = tx2;
|
||||
incTx = tx1;
|
||||
}
|
||||
|
||||
Vector2 refV = frVec2Normalize(frVec2Subtract(refEdge.data[1], refEdge.data[0]));
|
||||
|
||||
float refDot1 = frVec2DotProduct(refEdge.data[0], refV);
|
||||
float refDot2 = frVec2DotProduct(refEdge.data[1], refV);
|
||||
|
||||
incEdge = frClipEdgeWithAxis(incEdge, refV, refDot1);
|
||||
incEdge = frClipEdgeWithAxis(incEdge, frVec2Negate(refV), -refDot2);
|
||||
|
||||
Vector2 refNormal = frVec2RightNormal(refV);
|
||||
|
||||
float max_depth = frVec2DotProduct(refEdge.data[0], refNormal);
|
||||
|
||||
result.points[0] = incEdge.data[0], result.points[1] = incEdge.data[1];
|
||||
|
||||
result.depths[0] = frVec2DotProduct(result.points[0], refNormal) - max_depth;
|
||||
result.depths[1] = frVec2DotProduct(result.points[1], refNormal) - max_depth;
|
||||
|
||||
result.count = 2;
|
||||
|
||||
if (result.depths[0] < 0.0f) {
|
||||
result.points[0] = result.points[1];
|
||||
result.depths[0] = result.depths[1];
|
||||
|
||||
result.count = 1;
|
||||
} else if (result.depths[1] < 0.0f) {
|
||||
result.points[1] = result.points[0];
|
||||
result.depths[1] = result.depths[0];
|
||||
|
||||
result.count = 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ต์ ํ๋ ๋ถ๋ฆฌ ์ถ ์ ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ, ๋ํ `s1`์์ `s2`๋ก์ ์ถฉ๋์ ๊ณ์ฐํ๋ค. */
|
||||
static frCollision frComputeCollisionSAT(
|
||||
frShape *s1, frTransform tx1,
|
||||
frShape *s2, frTransform tx2
|
||||
) {
|
||||
frShapeType type1 = frGetShapeType(s1);
|
||||
frShapeType type2 = frGetShapeType(s2);
|
||||
|
||||
if (type1 == FR_SHAPE_UNKNOWN || type2 == FR_SHAPE_UNKNOWN)
|
||||
return FR_STRUCT_ZERO(frCollision);
|
||||
else if (type1 == FR_SHAPE_CIRCLE && type2 == FR_SHAPE_CIRCLE)
|
||||
return frComputeCollisionCirclesSAT(s1, tx1, s2, tx2);
|
||||
else if (type1 == FR_SHAPE_CIRCLE && type2 == FR_SHAPE_POLYGON
|
||||
|| type1 == FR_SHAPE_POLYGON && type2 == FR_SHAPE_CIRCLE)
|
||||
return frComputeCollisionCirclePolySAT(s1, tx1, s2, tx2);
|
||||
else if (type1 == FR_SHAPE_POLYGON && type2 == FR_SHAPE_POLYGON)
|
||||
return frComputeCollisionPolysSAT(s1, tx1, s2, tx2);
|
||||
}
|
||||
|
||||
/* ์ ๋ถ `e`์์ ๋ฒกํฐ `v`์์ ๋ด์ ์ด `minDot`๋ณด๋ค ์์ ๋ถ๋ถ์ ๋ชจ๋ ์๋ผ๋ธ๋ค. */
|
||||
static frVertices frClipEdgeWithAxis(frVertices e, Vector2 v, float minDot) {
|
||||
frVertices result = FR_STRUCT_ZERO(frVertices);
|
||||
|
||||
float p1Dot = frVec2DotProduct(e.data[0], v) - minDot;
|
||||
float p2Dot = frVec2DotProduct(e.data[1], v) - minDot;
|
||||
|
||||
bool insideP1 = (p1Dot >= 0.0f), insideP2 = (p2Dot >= 0.0f);
|
||||
|
||||
if (insideP1 && insideP2) {
|
||||
result.data[0] = e.data[0];
|
||||
result.data[1] = e.data[1];
|
||||
|
||||
result.count = 2;
|
||||
} else {
|
||||
Vector2 edgeV = frVec2Subtract(e.data[1], e.data[0]);
|
||||
|
||||
const float distance = p1Dot / (p1Dot - p2Dot);
|
||||
|
||||
Vector2 midpoint = frVec2Add(
|
||||
e.data[0],
|
||||
frVec2ScalarMultiply(edgeV, distance)
|
||||
);
|
||||
|
||||
if (insideP1 && !insideP2) {
|
||||
result.data[0] = e.data[0];
|
||||
result.data[1] = midpoint;
|
||||
|
||||
result.count = 2;
|
||||
} else if (!insideP1 && insideP2) {
|
||||
result.data[0] = e.data[1];
|
||||
result.data[1] = midpoint;
|
||||
|
||||
result.count = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* `o1`์์ `v1` ๋ฐฉํฅ์ผ๋ก ์งํํ๋ ๊ด์ ์ด `o2`์์ `v2` ๋ฐฉํฅ์ผ๋ก ์งํํ๋ ๊ด์ ๊ณผ ๋ง๋๋์ง ๊ณ์ฐํ๋ค. */
|
||||
static bool frComputeIntersectionRays(
|
||||
Vector2 o1, Vector2 v1,
|
||||
Vector2 o2, Vector2 v2,
|
||||
float *distance
|
||||
) {
|
||||
bool result = true;
|
||||
|
||||
float cross = frVec2CrossProduct(v1, v2);
|
||||
|
||||
// ๋ ๊ด์ ์ ํํํ๊ฑฐ๋ ์ผ์ง์ ์์ ์๋ค.
|
||||
if (frNumberApproxEquals(cross, 0.0f)) {
|
||||
*distance = 0.0f;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 diff = frVec2Subtract(o2, o1);
|
||||
|
||||
float inverseCross = 1.0f / cross;
|
||||
|
||||
float t = frVec2CrossProduct(diff, v2) * inverseCross;
|
||||
float u = frVec2CrossProduct(diff, v1) * inverseCross;
|
||||
|
||||
// ๋ ๊ด์ ์ ํํํ์ง ์์ผ๋ฉด์ ์๋ก ๋ง๋์ง ์๋๋ค.
|
||||
if (t < 0.0f || u < 0.0f || u > 1.0f) result = false;
|
||||
|
||||
*distance = t;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* `o`์์ `v` ๋ฐฉํฅ์ผ๋ก ์งํํ๋ ๊ด์ ์ด ์ค์ฌ์ ์ด `c`์ด๊ณ ๋ฐ์ง๋ฆ์ด `r`์ธ ์๊ณผ ๋ง๋๋์ง ๊ณ์ฐํ๋ค. */
|
||||
static bool frComputeIntersectionRayCircle(
|
||||
Vector2 o, Vector2 v,
|
||||
Vector2 c, float r,
|
||||
float *distance
|
||||
) {
|
||||
bool result = true;
|
||||
|
||||
Vector2 diff = frVec2Subtract(c, o);
|
||||
|
||||
float dot = frVec2DotProduct(diff, v);
|
||||
|
||||
float heightSqr = frVec2MagnitudeSqr(diff) - (dot * dot);
|
||||
float baseSqr = (r * r) - heightSqr;
|
||||
|
||||
// ๊ด์ ๊ณผ ์์ ์๋ก ๋ง๋์ง ์๋๋ค.
|
||||
if (dot < 0.0f || baseSqr < 0.0f) result = false;
|
||||
|
||||
*distance = dot - sqrtf(baseSqr);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
|
||||
/* | `debug` ๋ชจ๋ ๋ณ์... | */
|
||||
|
||||
static const float ARROW_HEAD_LENGTH = 16.0f;
|
||||
|
||||
/* | `debug` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
#ifndef FEROX_STANDALONE
|
||||
/* ๊ฐ์ฒด `b`์ ๋ค๊ฐํ ๊ผญ์ง์ ๋ฐฐ์ด์ ์ธ๊ณ ๊ธฐ์ค์ ํฝ์
์ขํ ๋ฐฐ์ด๋ก ๋ณํํ๋ค. */
|
||||
static frVertices frGetWorldVerticesInPixels(frBody *b);
|
||||
|
||||
/* ๊ฒ์ ํ๋ฉด์ ์ `p1`์์ `p2`๋ก ํฅํ๋ ํ์ดํ๋ฅผ ๊ทธ๋ฆฐ๋ค. */
|
||||
void frDrawArrow(Vector2 p1, Vector2 p2, float thick, Color color) {
|
||||
if (thick <= 0.0f) return;
|
||||
|
||||
p1 = frVec2MetersToPixels(p1);
|
||||
p2 = frVec2MetersToPixels(p2);
|
||||
|
||||
Vector2 diff = frVec2Normalize(frVec2Subtract(p1, p2));
|
||||
|
||||
Vector2 normal1 = frVec2LeftNormal(diff);
|
||||
Vector2 normal2 = frVec2RightNormal(diff);
|
||||
|
||||
Vector2 h1 = frVec2Add(
|
||||
p2,
|
||||
frVec2ScalarMultiply(
|
||||
frVec2Normalize(frVec2Add(diff, normal1)),
|
||||
ARROW_HEAD_LENGTH
|
||||
)
|
||||
);
|
||||
|
||||
Vector2 h2 = frVec2Add(
|
||||
p2,
|
||||
frVec2ScalarMultiply(
|
||||
frVec2Normalize(frVec2Add(diff, normal2)),
|
||||
ARROW_HEAD_LENGTH
|
||||
)
|
||||
);
|
||||
|
||||
DrawLineEx(p1, p2, thick, color);
|
||||
DrawLineEx(p2, h1, thick, color);
|
||||
DrawLineEx(p2, h2, thick, color);
|
||||
}
|
||||
|
||||
/* ๊ฒ์ ํ๋ฉด์ ๊ฐ์ฒด `b`์ ๋ํ์ ๊ทธ๋ฆฐ๋ค. */
|
||||
void frDrawBody(frBody *b, Color color) {
|
||||
if (b == NULL) return;
|
||||
|
||||
frShape *s = frGetBodyShape(b);
|
||||
|
||||
if (frGetShapeType(s) == FR_SHAPE_CIRCLE) {
|
||||
DrawCircleV(
|
||||
frVec2MetersToPixels(frGetBodyPosition(b)),
|
||||
frNumberMetersToPixels(frGetCircleRadius(s)),
|
||||
color
|
||||
);
|
||||
} else if (frGetShapeType(s) == FR_SHAPE_POLYGON) {
|
||||
frVertices worldVertices = frGetWorldVerticesInPixels(b);
|
||||
|
||||
DrawTriangleFan(worldVertices.data, worldVertices.count, color);
|
||||
}
|
||||
}
|
||||
|
||||
/* ๊ฒ์ ํ๋ฉด์ ๊ฐ์ฒด `b`์ ๋ํ ํ
๋๋ฆฌ๋ฅผ ๊ทธ๋ฆฐ๋ค. */
|
||||
void frDrawBodyLines(frBody *b, float thick, Color color) {
|
||||
if (b == NULL || thick <= 0.0f) return;
|
||||
|
||||
frShape *s = frGetBodyShape(b);
|
||||
|
||||
if (frGetShapeType(s) == FR_SHAPE_CIRCLE) {
|
||||
Vector2 p = frGetBodyPosition(b);
|
||||
|
||||
DrawRing(
|
||||
frVec2MetersToPixels(p),
|
||||
frNumberMetersToPixels(frGetCircleRadius(s)) - thick,
|
||||
frNumberMetersToPixels(frGetCircleRadius(s)),
|
||||
0.0f,
|
||||
360.0f,
|
||||
FR_DEBUG_CIRCLE_SEGMENT_COUNT,
|
||||
color
|
||||
);
|
||||
} else if (frGetShapeType(s) == FR_SHAPE_POLYGON) {
|
||||
frVertices worldVertices = frGetWorldVerticesInPixels(b);
|
||||
|
||||
for (int j = worldVertices.count - 1, i = 0; i < worldVertices.count; j = i, i++)
|
||||
DrawLineEx(worldVertices.data[j], worldVertices.data[i], thick, color);
|
||||
}
|
||||
}
|
||||
|
||||
/* ๊ฒ์ ํ๋ฉด์ ๊ฐ์ฒด `b`์ AABB์ ์ง๋ ์ค์ฌ์ ๊ทธ๋ฆฐ๋ค. */
|
||||
void frDrawBodyAABB(frBody *b, float thick, Color color) {
|
||||
if (b == NULL || thick <= 0.0f) return;
|
||||
|
||||
Rectangle aabb = frGetBodyAABB(b);
|
||||
|
||||
DrawRectangleLinesEx(
|
||||
(Rectangle) {
|
||||
frNumberMetersToPixels(aabb.x),
|
||||
frNumberMetersToPixels(aabb.y),
|
||||
frNumberMetersToPixels(aabb.width),
|
||||
frNumberMetersToPixels(aabb.height)
|
||||
},
|
||||
thick,
|
||||
color
|
||||
);
|
||||
|
||||
DrawCircleV(frVec2MetersToPixels(frGetBodyPosition(b)), 2.0f, color);
|
||||
}
|
||||
|
||||
/* ๊ฒ์ ํ๋ฉด์ ๊ฐ์ฒด `b`์ ๋ฌผ๋ฆฌ๋ ์ ๋ณด๋ฅผ ๊ทธ๋ฆฐ๋ค. */
|
||||
void frDrawBodyProperties(frBody *b, Color color) {
|
||||
Vector2 velocity = frGetBodyVelocity(b);
|
||||
|
||||
frTransform tx = frGetBodyTransform(b);
|
||||
|
||||
DrawTextEx(
|
||||
GetFontDefault(),
|
||||
TextFormat(
|
||||
"m: %fkg, I: %fkg*mยฒ\n"
|
||||
"(x, y) [theta]: (%f, %f) [%f rad.]\n"
|
||||
"v [omega]: (%f, %f) [%f rad.]\n",
|
||||
frGetBodyMass(b), frGetBodyInertia(b),
|
||||
tx.position.x, tx.position.y, tx.rotation,
|
||||
velocity.x, velocity.y, frGetBodyAngularVelocity(b)
|
||||
),
|
||||
frVec2MetersToPixels(frVec2Add(tx.position, (Vector2) { 1.0f, 1.0f })),
|
||||
10.0f,
|
||||
1.0f,
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
/* ๊ฒ์ ํ๋ฉด์ ๊ณต๊ฐ ํด์๋งต `hm`์ ๊ทธ๋ฆฐ๋ค. */
|
||||
void frDrawSpatialHash(frSpatialHash *hm, float thick, Color color) {
|
||||
if (hm == NULL || thick <= 0.0f) return;
|
||||
|
||||
Rectangle bounds = frGetSpatialHashBounds(hm);
|
||||
|
||||
const int vCount = bounds.width * FR_BROADPHASE_INVERSE_CELL_SIZE;
|
||||
const int hCount = bounds.height * FR_BROADPHASE_INVERSE_CELL_SIZE;
|
||||
|
||||
for (int i = 0; i <= vCount; i++)
|
||||
DrawLineEx(
|
||||
frVec2MetersToPixels((Vector2) { FR_BROADPHASE_CELL_SIZE * i, 0.0f }),
|
||||
frVec2MetersToPixels((Vector2) { FR_BROADPHASE_CELL_SIZE * i, bounds.height }),
|
||||
thick,
|
||||
color
|
||||
);
|
||||
|
||||
for (int i = 0; i <= hCount; i++)
|
||||
DrawLineEx(
|
||||
frVec2MetersToPixels((Vector2) { 0.0f, FR_BROADPHASE_CELL_SIZE * i }),
|
||||
frVec2MetersToPixels((Vector2) { bounds.width, FR_BROADPHASE_CELL_SIZE * i }),
|
||||
thick,
|
||||
color
|
||||
);
|
||||
|
||||
DrawRectangleLinesEx(frRecMetersToPixels(bounds), thick, color);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๋ค๊ฐํ ๊ผญ์ง์ ๋ฐฐ์ด์ ์ธ๊ณ ๊ธฐ์ค์ ํฝ์
์ขํ ๋ฐฐ์ด๋ก ๋ณํํ๋ค. */
|
||||
static frVertices frGetWorldVerticesInPixels(frBody *b) {
|
||||
if (b == NULL) return FR_STRUCT_ZERO(frVertices);
|
||||
|
||||
frVertices vertices = frGetPolygonVertices(frGetBodyShape(b));
|
||||
|
||||
for (int i = 0; i < vertices.count; i++)
|
||||
vertices.data[i] = frVec2MetersToPixels(frGetWorldPoint(b, vertices.data[i]));
|
||||
|
||||
return vertices;
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,529 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
|
||||
/* | `dynamics` ๋ชจ๋ ์๋ฃํ ์ ์... | */
|
||||
|
||||
/* ๊ฐ์ฒด์ ๋ฌผ๋ฆฌ๋์ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด. */
|
||||
typedef struct frMotionData {
|
||||
float mass;
|
||||
float inverseMass;
|
||||
float inertia;
|
||||
float inverseInertia;
|
||||
Vector2 velocity;
|
||||
float angularVelocity;
|
||||
float gravityScale;
|
||||
Vector2 force;
|
||||
float torque;
|
||||
} frMotionData;
|
||||
|
||||
/* ๊ฐ์ฒด๋ฅผ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด. */
|
||||
typedef struct frBody {
|
||||
frBodyType type;
|
||||
frBodyFlags flags;
|
||||
frMaterial material;
|
||||
frMotionData motion;
|
||||
frTransform tx;
|
||||
frShape *shape;
|
||||
Rectangle aabb;
|
||||
void *data;
|
||||
} frBody;
|
||||
|
||||
/* | `dynamics` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ง๋์ ๋ค์ ๊ณ์ฐํ๋ค. */
|
||||
static void frResetBodyMass(frBody *b);
|
||||
|
||||
/* ์ข
๋ฅ๊ฐ `type`์ด๊ณ ์์น๊ฐ `p`์ธ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. */
|
||||
frBody *frCreateBody(frBodyType type, frBodyFlags flags, Vector2 p) {
|
||||
if (type == FR_BODY_UNKNOWN) return NULL;
|
||||
|
||||
frBody *result = calloc(1, sizeof(*result));
|
||||
|
||||
result->material = FR_STRUCT_ZERO(frMaterial);
|
||||
|
||||
frSetBodyType(result, type);
|
||||
frSetBodyFlags(result, flags);
|
||||
frSetBodyGravityScale(result, 1.0f);
|
||||
frSetBodyPosition(result, p);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ข
๋ฅ๊ฐ `type`์ด๊ณ ์์น๊ฐ `p`์ด๋ฉฐ ์ถฉ๋ ์ฒ๋ฆฌ์ฉ ๋ํ์ด `s`์ธ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. */
|
||||
frBody *frCreateBodyFromShape(frBodyType type, frBodyFlags flags, Vector2 p, frShape *s) {
|
||||
if (type == FR_BODY_UNKNOWN) return NULL;
|
||||
|
||||
frBody *result = frCreateBody(type, flags, p);
|
||||
frAttachShapeToBody(result, s);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ํ ๋น๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ค. */
|
||||
void frReleaseBody(frBody *b) {
|
||||
if (b != NULL && b->shape != NULL)
|
||||
frDetachShapeFromBody(b);
|
||||
|
||||
free(b);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b์ ์ถฉ๋ ์ฒ๋ฆฌ์ฉ ๋ํ `s`๋ฅผ ์ถ๊ฐํ๋ค. */
|
||||
void frAttachShapeToBody(frBody *b, frShape *s) {
|
||||
if (b == NULL || s == NULL) return;
|
||||
|
||||
b->shape = s;
|
||||
b->material = frGetShapeMaterial(s);
|
||||
b->aabb = frGetShapeAABB(b->shape, b->tx);
|
||||
|
||||
frResetBodyMass(b);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์์ ์ถฉ๋ ์ฒ๋ฆฌ์ฉ ๋ํ์ ์ ๊ฑฐํ๋ค. */
|
||||
void frDetachShapeFromBody(frBody *b) {
|
||||
if (b == NULL) return;
|
||||
|
||||
b->shape = NULL;
|
||||
b->material = FR_STRUCT_ZERO(frMaterial);
|
||||
b->aabb = FR_STRUCT_ZERO(Rectangle);
|
||||
|
||||
frResetBodyMass(b);
|
||||
}
|
||||
|
||||
/* ๊ตฌ์กฐ์ฒด `frBody`์ ํฌ๊ธฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
size_t frGetBodyStructSize(void) {
|
||||
return sizeof(frBody);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ข
๋ฅ๋ฅผ ๋ฐํํ๋ค. */
|
||||
frBodyType frGetBodyType(frBody *b) {
|
||||
return (b != NULL) ? b->type : FR_BODY_UNKNOWN;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๋นํธ ํ๋๊ทธ ์กฐํฉ์ ๋ฐํํ๋ค. */
|
||||
frBodyFlags frGetBodyFlags(frBody *b) {
|
||||
return (b != NULL) ? b->flags : 0;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ฌ์ง์ ๋ฐํํ๋ค. */
|
||||
frMaterial frGetBodyMaterial(frBody *b) {
|
||||
return (b != NULL) ? b->material : FR_STRUCT_ZERO(frMaterial);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ง๋์ ๋ฐํํ๋ค. */
|
||||
float frGetBodyMass(frBody *b) {
|
||||
return (b != NULL) ? b->motion.mass : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ง๋์ ์ญ์๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetBodyInverseMass(frBody *b) {
|
||||
return (b != NULL) ? b->motion.inverseMass : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๊ด์ฑ ๋ชจ๋ฉํธ๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetBodyInertia(frBody *b) {
|
||||
return (b != NULL) ? b->motion.inertia : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๊ด์ฑ ๋ชจ๋ฉํธ์ ์ญ์๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetBodyInverseInertia(frBody *b) {
|
||||
return (b != NULL) ? b->motion.inverseInertia : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์๋๋ฅผ ๋ฐํํ๋ค. */
|
||||
Vector2 frGetBodyVelocity(frBody *b) {
|
||||
return (b != NULL) ? b->motion.velocity : FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๊ฐ์๋๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetBodyAngularVelocity(frBody *b) {
|
||||
return (b != NULL) ? b->motion.angularVelocity : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ค๋ ฅ ๊ฐ์๋ฅ ์ ๋ฐํํ๋ค. */
|
||||
float frGetBodyGravityScale(frBody *b) {
|
||||
return (b != NULL) ? b->motion.gravityScale : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์์น์ ํ์ ๊ฐ๋ (๋จ์: rad.)๋ฅผ ๋ฐํํ๋ค. */
|
||||
frTransform frGetBodyTransform(frBody *b) {
|
||||
return (b != NULL) ? b->tx : FR_STRUCT_ZERO(frTransform);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์์น๋ฅผ ๋ฐํํ๋ค. */
|
||||
Vector2 frGetBodyPosition(frBody *b) {
|
||||
return (b != NULL) ? b->tx.position : FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ํ์ ๊ฐ๋ (๋จ์: rad.)๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetBodyRotation(frBody *b) {
|
||||
return (b != NULL) ? b->tx.rotation : 0.0f;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ถฉ๋ ์ฒ๋ฆฌ์ฉ ๋ํ์ ๋ฐํํ๋ค. */
|
||||
frShape *frGetBodyShape(frBody *b) {
|
||||
return (b != NULL) ? b->shape : NULL;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ AABB๋ฅผ ๋ฐํํ๋ค. */
|
||||
Rectangle frGetBodyAABB(frBody *b) {
|
||||
return (b != NULL && b->shape != NULL) ? b->aabb : FR_STRUCT_ZERO(Rectangle);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
void *frGetBodyUserData(frBody *b) {
|
||||
return (b != NULL) ? b->data : NULL;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ ๊ธฐ์ค ์ขํ `p`๋ฅผ ๊ฐ์ฒด `b`๋ฅผ ๊ธฐ์ค์ผ๋ก ํ ์ขํ๋ก ๋ณํํ๋ค. */
|
||||
Vector2 frGetLocalPoint(frBody *b, Vector2 p) {
|
||||
return frVec2Transform(p, (frTransform) { frVec2Negate(b->tx.position), -(b->tx.rotation)});
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`๋ฅผ ๊ธฐ์ค์ผ๋ก ํ ์ขํ `p`๋ฅผ ์ธ๊ณ ๊ธฐ์ค ์ขํ๋ก ๋ณํํ๋ค. */
|
||||
Vector2 frGetWorldPoint(frBody *b, Vector2 p) {
|
||||
return frVec2Transform(p, b->tx);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ข
๋ฅ๋ฅผ `type`์ผ๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyType(frBody *b, frBodyType type) {
|
||||
if (b == NULL || b->type == FR_BODY_UNKNOWN || b->type == type)
|
||||
return;
|
||||
|
||||
b->type = type;
|
||||
|
||||
frResetBodyMass(b);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๋นํธ ํ๋๊ทธ ์กฐํฉ์ `flags`์ผ๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyFlags(frBody *b, frBodyFlags flags) {
|
||||
if (b == NULL || b->flags == flags) return;
|
||||
|
||||
b->flags = flags;
|
||||
|
||||
frResetBodyMass(b);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์๋๋ฅผ `v`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyVelocity(frBody *b, Vector2 v) {
|
||||
if (b != NULL) b->motion.velocity = v;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ๊ฐ์๋๋ฅผ `a`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyAngularVelocity(frBody *b, double a) {
|
||||
if (b != NULL) b->motion.angularVelocity = a;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ค๋ ฅ ๊ฐ์๋ฅ ์ `scale`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyGravityScale(frBody *b, float scale) {
|
||||
if (b == NULL || b->type != FR_BODY_DYNAMIC) return;
|
||||
|
||||
b->motion.gravityScale = scale;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์์น์ ํ์ ๊ฐ๋๋ฅผ `tx`์ ๊ฐ์ผ๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyTransform(frBody *b, frTransform tx) {
|
||||
if (b == NULL) return;
|
||||
|
||||
b->tx.position = tx.position;
|
||||
b->tx.rotation = frNormalizeAngle(tx.rotation, PI);
|
||||
|
||||
b->tx.cache.valid = true;
|
||||
b->tx.cache.sinA = sinf(b->tx.rotation);
|
||||
b->tx.cache.cosA = cosf(b->tx.rotation);
|
||||
|
||||
b->aabb = frGetShapeAABB(b->shape, b->tx);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์์น๋ฅผ `p`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyPosition(frBody *b, Vector2 p) {
|
||||
if (b == NULL) return;
|
||||
|
||||
b->tx.position = p;
|
||||
|
||||
b->aabb = frGetShapeAABB(b->shape, b->tx);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ํ์ ๊ฐ๋ (๋จ์: rad.)๋ฅผ `rotation`์ผ๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyRotation(frBody *b, float rotation) {
|
||||
if (b == NULL) return;
|
||||
|
||||
b->tx.rotation = frNormalizeAngle(rotation, PI);
|
||||
|
||||
b->tx.cache.valid = true;
|
||||
b->tx.cache.sinA = sinf(b->tx.rotation);
|
||||
b->tx.cache.cosA = cosf(b->tx.rotation);
|
||||
|
||||
b->aabb = frGetShapeAABB(b->shape, b->tx);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ `data`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetBodyUserData(frBody *b, void *data) {
|
||||
if (b != NULL) b->data = data;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ค๋ ฅ ๊ฐ์๋ `gravity`๋ฅผ ์ ์ฉํ๋ค. */
|
||||
void frApplyGravity(frBody *b, Vector2 gravity) {
|
||||
if (b == NULL || b->motion.inverseMass <= 0.0f) return;
|
||||
|
||||
b->motion.force = frVec2Add(
|
||||
b->motion.force,
|
||||
frVec2ScalarMultiply(
|
||||
gravity,
|
||||
b->motion.gravityScale * b->motion.mass
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ถฉ๊ฒฉ๋ `impulse`๋ฅผ ์ ์ฉํ๋ค. */
|
||||
void frApplyImpulse(frBody *b, Vector2 impulse) {
|
||||
if (b == NULL || b->motion.inverseMass <= 0.0f) return;
|
||||
|
||||
b->motion.velocity = frVec2Add(
|
||||
b->motion.velocity,
|
||||
frVec2ScalarMultiply(
|
||||
impulse,
|
||||
b->motion.inverseMass
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b` ์์ ์ `p`์ ๊ฐ์ด๋๋ `impulse`๋ฅผ ์ ์ฉํ๋ค. */
|
||||
void frApplyTorqueImpulse(frBody *b, Vector2 p, Vector2 impulse) {
|
||||
if (b == NULL || b->motion.inverseInertia <= 0.0f) return;
|
||||
|
||||
b->motion.angularVelocity += b->motion.inverseInertia
|
||||
* frVec2CrossProduct(p, impulse);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์์ฉํ๋ ๋ชจ๋ ํ์ ์ ๊ฑฐํ๋ค. */
|
||||
void frClearBodyForces(frBody *b) {
|
||||
if (b == NULL) return;
|
||||
|
||||
b->motion.force = FR_STRUCT_ZERO(Vector2);
|
||||
b->motion.torque = 0.0f;
|
||||
}
|
||||
|
||||
/* ๋จ์ ์๊ฐ `dt` ์ดํ์ ๊ฐ์ฒด `b`์ ์์น๋ฅผ ๊ณ์ฐํ๋ค. */
|
||||
void frIntegrateForBodyPosition(frBody *b, double dt) {
|
||||
if (b == NULL || b->type == FR_BODY_STATIC) return;
|
||||
|
||||
frSetBodyPosition(b, frVec2Add(b->tx.position, frVec2ScalarMultiply(b->motion.velocity, dt)));
|
||||
frSetBodyRotation(b, b->tx.rotation + (b->motion.angularVelocity * dt));
|
||||
}
|
||||
|
||||
/* ๋จ์ ์๊ฐ `dt` ์ดํ์ ๊ฐ์ฒด `b`์ ์๋์ ๊ฐ์๋๋ฅผ ๊ณ์ฐํ๋ค. */
|
||||
void frIntegrateForBodyVelocities(frBody *b, double dt) {
|
||||
if (b == NULL || b->motion.inverseMass <= 0.0f) return;
|
||||
|
||||
b->motion.velocity = frVec2Add(
|
||||
b->motion.velocity,
|
||||
frVec2ScalarMultiply(
|
||||
b->motion.force,
|
||||
b->motion.inverseMass * dt
|
||||
)
|
||||
);
|
||||
|
||||
b->motion.angularVelocity += (b->motion.torque * b->motion.inverseInertia) * dt;
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b1`๊ณผ `b2` ์ฌ์ด์ ์ถฉ๋์ ํด๊ฒฐํ๋ค. */
|
||||
void frResolveCollision(frCollision *collision) {
|
||||
if (collision == NULL || !collision->check) return;
|
||||
|
||||
frBody *b1 = collision->cache.bodies[0];
|
||||
frBody *b2 = collision->cache.bodies[1];
|
||||
|
||||
if (b1 == NULL || b2 == NULL) return;
|
||||
|
||||
if (b1->motion.inverseMass + b2->motion.inverseMass <= 0.0f) {
|
||||
if (frGetBodyType(b1) == FR_BODY_STATIC) b1->motion.velocity = FR_STRUCT_ZERO(Vector2);
|
||||
if (frGetBodyType(b2) == FR_BODY_STATIC) b2->motion.velocity = FR_STRUCT_ZERO(Vector2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float epsilon = fmaxf(0.0f, fminf(b1->material.restitution, b2->material.restitution));
|
||||
|
||||
float staticMu = b1->material.staticFriction * b2->material.staticFriction;
|
||||
float dynamicMu = b1->material.dynamicFriction * b2->material.dynamicFriction;
|
||||
|
||||
// ๋์ฒด๋ก ์ด๋ ๋ง์ฐฐ ๊ณ์๋ ์ ์ง ๋ง์ฐฐ ๊ณ์๋ณด๋ค ์์ ๊ฐ์ ๊ฐ์ง๋ค.
|
||||
if (staticMu < dynamicMu) dynamicMu = FR_DYNAMICS_DYNAMIC_FRICTION_MULTIPLIER * staticMu;
|
||||
|
||||
for (int i = 0; i < collision->count; i++) {
|
||||
Vector2 r1 = frVec2Subtract(collision->points[i], frGetBodyPosition(b1));
|
||||
Vector2 r2 = frVec2Subtract(collision->points[i], frGetBodyPosition(b2));
|
||||
|
||||
Vector2 r1Normal = frVec2LeftNormal(r1);
|
||||
Vector2 r2Normal = frVec2LeftNormal(r2);
|
||||
|
||||
// `b1`์ด ์ธก์ ํ `b2`์ ์๋ (`b1`์ ๋ํ `b2`์ ์๋ ์๋)๋ฅผ ๊ณ์ฐํ๋ค.
|
||||
Vector2 relativeVelocity = frVec2Subtract(
|
||||
frVec2Add(
|
||||
b2->motion.velocity,
|
||||
frVec2ScalarMultiply(r2Normal, b2->motion.angularVelocity)
|
||||
),
|
||||
frVec2Add(
|
||||
b1->motion.velocity,
|
||||
frVec2ScalarMultiply(r1Normal, b1->motion.angularVelocity)
|
||||
)
|
||||
);
|
||||
|
||||
float relativeVelocityDot = frVec2DotProduct(relativeVelocity, collision->direction);
|
||||
|
||||
// ๋ ๊ฐ์ฒด๊ฐ ์๋ก ์ถฉ๋ํ๋ ๋ฐฉํฅ์ผ๋ก ์งํํ๊ณ ์์ง ์์ผ๋ฉด ๊ณ์ฐ์ ์ข
๋ฃํ๋ค.
|
||||
if (relativeVelocityDot > 0.0f) return;
|
||||
|
||||
float r1NormalDot = frVec2DotProduct(r1Normal, collision->direction);
|
||||
float r2NormalDot = frVec2DotProduct(r2Normal, collision->direction);
|
||||
|
||||
float inverseMassSum = (b1->motion.inverseMass + b2->motion.inverseMass)
|
||||
+ b1->motion.inverseInertia * (r1NormalDot * r1NormalDot)
|
||||
+ b2->motion.inverseInertia * (r2NormalDot * r2NormalDot);
|
||||
|
||||
float impulseParam = (-(1.0f + epsilon) * relativeVelocityDot)
|
||||
/ (collision->count * inverseMassSum);
|
||||
|
||||
Vector2 impulse = frVec2ScalarMultiply(collision->direction, impulseParam);
|
||||
|
||||
frApplyImpulse(b1, frVec2Negate(impulse));
|
||||
frApplyTorqueImpulse(b1, r1, frVec2Negate(impulse));
|
||||
|
||||
frApplyImpulse(b2, impulse);
|
||||
frApplyTorqueImpulse(b2, r2, impulse);
|
||||
|
||||
// ๋ง์ฐฐ๋ ฅ ์ ์ฉ์ ์ํด ์๋ ์๋๋ฅผ ๋ค์ ๊ณ์ฐํ๋ค.
|
||||
relativeVelocity = frVec2Subtract(
|
||||
frVec2Add(
|
||||
b2->motion.velocity,
|
||||
frVec2ScalarMultiply(r2Normal, b2->motion.angularVelocity)
|
||||
),
|
||||
frVec2Add(
|
||||
b1->motion.velocity,
|
||||
frVec2ScalarMultiply(r1Normal, b1->motion.angularVelocity)
|
||||
)
|
||||
);
|
||||
|
||||
relativeVelocityDot = frVec2DotProduct(relativeVelocity, collision->direction);
|
||||
|
||||
Vector2 tangent = frVec2Normalize(
|
||||
frVec2Subtract(
|
||||
relativeVelocity,
|
||||
frVec2ScalarMultiply(collision->direction, relativeVelocityDot)
|
||||
)
|
||||
);
|
||||
|
||||
r1NormalDot = frVec2DotProduct(r1Normal, tangent);
|
||||
r2NormalDot = frVec2DotProduct(r2Normal, tangent);
|
||||
|
||||
inverseMassSum = (b1->motion.inverseMass + b2->motion.inverseMass)
|
||||
+ b1->motion.inverseInertia * (r1NormalDot * r1NormalDot)
|
||||
+ b2->motion.inverseInertia * (r2NormalDot * r2NormalDot);
|
||||
|
||||
float frictionParam = -frVec2DotProduct(relativeVelocity, tangent)
|
||||
/ (collision->count * inverseMassSum);
|
||||
|
||||
Vector2 friction = (fabsf(frictionParam) < impulseParam * staticMu)
|
||||
? frVec2ScalarMultiply(tangent, frictionParam)
|
||||
: frVec2ScalarMultiply(tangent, -impulseParam * dynamicMu);
|
||||
|
||||
frApplyImpulse(b1, frVec2Negate(friction));
|
||||
frApplyTorqueImpulse(b1, r1, frVec2Negate(friction));
|
||||
|
||||
frApplyImpulse(b2, friction);
|
||||
frApplyTorqueImpulse(b2, r2, friction);
|
||||
}
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b1`๊ณผ `b2`์ ์์น๋ฅผ ์ ์ ํ๊ฒ ๋ณด์ ํ๋ค. */
|
||||
void frCorrectBodyPositions(frCollision *collision, float inverseDt) {
|
||||
if (collision == NULL || !collision->check) return;
|
||||
|
||||
frBody *b1 = collision->cache.bodies[0];
|
||||
frBody *b2 = collision->cache.bodies[1];
|
||||
|
||||
if (b1 == NULL || b2 == NULL) return;
|
||||
|
||||
if (b1->motion.inverseMass + b2->motion.inverseMass <= 0.0f) {
|
||||
if (frGetBodyType(b1) == FR_BODY_STATIC) b1->motion.velocity = FR_STRUCT_ZERO(Vector2);
|
||||
if (frGetBodyType(b2) == FR_BODY_STATIC) b2->motion.velocity = FR_STRUCT_ZERO(Vector2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float maxDepth = fmaxf(collision->depths[0], collision->depths[1]);
|
||||
|
||||
if (maxDepth <= FR_DYNAMICS_CORRECTION_DEPTH_THRESHOLD) return;
|
||||
|
||||
// ์ถฉ๋ ๋ฐฉํฅ์ ๋ฌด์กฐ๊ฑด `b1`์์ `b2`๋ก ํฅํ๋ค.
|
||||
Vector2 correction = frVec2ScalarMultiply(
|
||||
collision->direction,
|
||||
FR_DYNAMICS_CORRECTION_DEPTH_SCALE * (
|
||||
(inverseDt * (maxDepth - FR_DYNAMICS_CORRECTION_DEPTH_THRESHOLD))
|
||||
/ (b1->motion.inverseMass + b2->motion.inverseMass)
|
||||
)
|
||||
);
|
||||
|
||||
frSetBodyPosition(
|
||||
b1,
|
||||
frVec2Subtract(
|
||||
b1->tx.position,
|
||||
frVec2ScalarMultiply(correction, b1->motion.inverseMass)
|
||||
)
|
||||
);
|
||||
frSetBodyPosition(
|
||||
b2,
|
||||
frVec2Add(
|
||||
b2->tx.position,
|
||||
frVec2ScalarMultiply(correction, b2->motion.inverseMass)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`์ ์ง๋์ ๋ค์ ๊ณ์ฐํ๋ค. */
|
||||
static void frResetBodyMass(frBody *b) {
|
||||
if (b == NULL) return;
|
||||
|
||||
b->motion.mass = 0.0f;
|
||||
b->motion.inertia = 0.0f;
|
||||
|
||||
if (b->type == FR_BODY_STATIC) {
|
||||
b->motion.velocity = FR_STRUCT_ZERO(Vector2);
|
||||
b->motion.angularVelocity = 0.0f;
|
||||
} else if (b->type == FR_BODY_DYNAMIC) {
|
||||
if (b->shape != NULL) {
|
||||
if (!(b->flags & FR_FLAG_INFINITE_MASS))
|
||||
b->motion.mass = frGetShapeMass(b->shape);
|
||||
|
||||
if (!(b->flags & FR_FLAG_INFINITE_INERTIA))
|
||||
b->motion.inertia = frGetShapeInertia(b->shape);
|
||||
}
|
||||
}
|
||||
|
||||
if (b->motion.mass == 0.0f) b->motion.inverseMass = 0.0f;
|
||||
else b->motion.inverseMass = 1.0f / b->motion.mass;
|
||||
|
||||
if (b->motion.inertia == 0.0f) b->motion.inverseInertia = 0.0f;
|
||||
else b->motion.inverseInertia = 1.0f / b->motion.inertia;
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL)
|
||||
#define SOKOL_TIME_IMPL
|
||||
#endif
|
||||
#ifndef SOKOL_TIME_INCLUDED
|
||||
/*
|
||||
sokol_time.h -- simple cross-platform time measurement
|
||||
|
||||
Project URL: https://github.com/floooh/sokol
|
||||
|
||||
Do this:
|
||||
#define SOKOL_IMPL or
|
||||
#define SOKOL_TIME_IMPL
|
||||
before you include this file in *one* C or C++ file to create the
|
||||
implementation.
|
||||
|
||||
Optionally provide the following defines with your own implementations:
|
||||
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
|
||||
SOKOL_TIME_API_DECL - public function declaration prefix (default: extern)
|
||||
SOKOL_API_DECL - same as SOKOL_TIME_API_DECL
|
||||
SOKOL_API_IMPL - public function implementation prefix (default: -)
|
||||
|
||||
If sokol_time.h is compiled as a DLL, define the following before
|
||||
including the declaration or implementation:
|
||||
|
||||
SOKOL_DLL
|
||||
|
||||
On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport)
|
||||
or __declspec(dllimport) as needed.
|
||||
|
||||
void stm_setup();
|
||||
Call once before any other functions to initialize sokol_time
|
||||
(this calls for instance QueryPerformanceFrequency on Windows)
|
||||
|
||||
uint64_t stm_now();
|
||||
Get current point in time in unspecified 'ticks'. The value that
|
||||
is returned has no relation to the 'wall-clock' time and is
|
||||
not in a specific time unit, it is only useful to compute
|
||||
time differences.
|
||||
|
||||
uint64_t stm_diff(uint64_t new, uint64_t old);
|
||||
Computes the time difference between new and old. This will always
|
||||
return a positive, non-zero value.
|
||||
|
||||
uint64_t stm_since(uint64_t start);
|
||||
Takes the current time, and returns the elapsed time since start
|
||||
(this is a shortcut for "stm_diff(stm_now(), start)")
|
||||
|
||||
uint64_t stm_laptime(uint64_t* last_time);
|
||||
This is useful for measuring frame time and other recurring
|
||||
events. It takes the current time, returns the time difference
|
||||
to the value in last_time, and stores the current time in
|
||||
last_time for the next call. If the value in last_time is 0,
|
||||
the return value will be zero (this usually happens on the
|
||||
very first call).
|
||||
|
||||
uint64_t stm_round_to_common_refresh_rate(uint64_t duration)
|
||||
This oddly named function takes a measured frame time and
|
||||
returns the closest "nearby" common display refresh rate frame duration
|
||||
in ticks. If the input duration isn't close to any common display
|
||||
refresh rate, the input duration will be returned unchanged as a fallback.
|
||||
The main purpose of this function is to remove jitter/inaccuracies from
|
||||
measured frame times, and instead use the display refresh rate as
|
||||
frame duration.
|
||||
NOTE: for more robust frame timing, consider using the
|
||||
sokol_app.h function sapp_frame_duration()
|
||||
|
||||
Use the following functions to convert a duration in ticks into
|
||||
useful time units:
|
||||
|
||||
double stm_sec(uint64_t ticks);
|
||||
double stm_ms(uint64_t ticks);
|
||||
double stm_us(uint64_t ticks);
|
||||
double stm_ns(uint64_t ticks);
|
||||
Converts a tick value into seconds, milliseconds, microseconds
|
||||
or nanoseconds. Note that not all platforms will have nanosecond
|
||||
or even microsecond precision.
|
||||
|
||||
Uses the following time measurement functions under the hood:
|
||||
|
||||
Windows: QueryPerformanceFrequency() / QueryPerformanceCounter()
|
||||
MacOS/iOS: mach_absolute_time()
|
||||
emscripten: emscripten_get_now()
|
||||
Linux+others: clock_gettime(CLOCK_MONOTONIC)
|
||||
|
||||
zlib/libpng license
|
||||
|
||||
Copyright (c) 2018 Andre Weissflog
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the
|
||||
use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software in a
|
||||
product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
#define SOKOL_TIME_INCLUDED (1)
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL)
|
||||
#define SOKOL_TIME_API_DECL SOKOL_API_DECL
|
||||
#endif
|
||||
#ifndef SOKOL_TIME_API_DECL
|
||||
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL)
|
||||
#define SOKOL_TIME_API_DECL __declspec(dllexport)
|
||||
#elif defined(_WIN32) && defined(SOKOL_DLL)
|
||||
#define SOKOL_TIME_API_DECL __declspec(dllimport)
|
||||
#else
|
||||
#define SOKOL_TIME_API_DECL extern
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
SOKOL_TIME_API_DECL void stm_setup(void);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_now(void);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks);
|
||||
SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_us(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
#endif // SOKOL_TIME_INCLUDED
|
||||
|
||||
/*-- IMPLEMENTATION ----------------------------------------------------------*/
|
||||
#ifdef SOKOL_TIME_IMPL
|
||||
#define SOKOL_TIME_IMPL_INCLUDED (1)
|
||||
#include <string.h> /* memset */
|
||||
|
||||
#ifndef SOKOL_API_IMPL
|
||||
#define SOKOL_API_IMPL
|
||||
#endif
|
||||
#ifndef SOKOL_ASSERT
|
||||
#include <assert.h>
|
||||
#define SOKOL_ASSERT(c) assert(c)
|
||||
#endif
|
||||
#ifndef _SOKOL_PRIVATE
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define _SOKOL_PRIVATE __attribute__((unused)) static
|
||||
#else
|
||||
#define _SOKOL_PRIVATE static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
LARGE_INTEGER freq;
|
||||
LARGE_INTEGER start;
|
||||
} _stm_state_t;
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach_time.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
mach_timebase_info_data_t timebase;
|
||||
uint64_t start;
|
||||
} _stm_state_t;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten/emscripten.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
double start;
|
||||
} _stm_state_t;
|
||||
#else /* anything else, this will need more care for non-Linux platforms */
|
||||
#ifdef ESP8266
|
||||
// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined
|
||||
#define CLOCK_MONOTONIC 0
|
||||
#endif
|
||||
#include <time.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
uint64_t start;
|
||||
} _stm_state_t;
|
||||
#endif
|
||||
static _stm_state_t _stm;
|
||||
|
||||
/* prevent 64-bit overflow when computing relative timestamp
|
||||
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3
|
||||
*/
|
||||
#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))
|
||||
_SOKOL_PRIVATE int64_t _stm_int64_muldiv(int64_t value, int64_t numer, int64_t denom) {
|
||||
int64_t q = value / denom;
|
||||
int64_t r = value % denom;
|
||||
return q * numer + r * numer / denom;
|
||||
}
|
||||
#endif
|
||||
|
||||
SOKOL_API_IMPL void stm_setup(void) {
|
||||
memset(&_stm, 0, sizeof(_stm));
|
||||
_stm.initialized = 0xABCDABCD;
|
||||
#if defined(_WIN32)
|
||||
QueryPerformanceFrequency(&_stm.freq);
|
||||
QueryPerformanceCounter(&_stm.start);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
mach_timebase_info(&_stm.timebase);
|
||||
_stm.start = mach_absolute_time();
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
_stm.start = emscripten_get_now();
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
_stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_now(void) {
|
||||
SOKOL_ASSERT(_stm.initialized == 0xABCDABCD);
|
||||
uint64_t now;
|
||||
#if defined(_WIN32)
|
||||
LARGE_INTEGER qpc_t;
|
||||
QueryPerformanceCounter(&qpc_t);
|
||||
now = (uint64_t) _stm_int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
const uint64_t mach_now = mach_absolute_time() - _stm.start;
|
||||
now = (uint64_t) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
double js_now = emscripten_get_now() - _stm.start;
|
||||
now = (uint64_t) (js_now * 1000000.0);
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start;
|
||||
#endif
|
||||
return now;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) {
|
||||
if (new_ticks > old_ticks) {
|
||||
return new_ticks - old_ticks;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) {
|
||||
return stm_diff(stm_now(), start_ticks);
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) {
|
||||
SOKOL_ASSERT(last_time);
|
||||
uint64_t dt = 0;
|
||||
uint64_t now = stm_now();
|
||||
if (0 != *last_time) {
|
||||
dt = stm_diff(now, *last_time);
|
||||
}
|
||||
*last_time = now;
|
||||
return dt;
|
||||
}
|
||||
|
||||
// first number is frame duration in ns, second number is tolerance in ns,
|
||||
// the resulting min/max values must not overlap!
|
||||
static const uint64_t _stm_refresh_rates[][2] = {
|
||||
{ 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms
|
||||
{ 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms
|
||||
{ 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms
|
||||
{ 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25
|
||||
{ 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms
|
||||
{ 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms
|
||||
{ 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms
|
||||
{ 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms
|
||||
{ 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms
|
||||
{ 0, 0 }, // keep the last element always at zero
|
||||
};
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) {
|
||||
uint64_t ns;
|
||||
int i = 0;
|
||||
while (0 != (ns = _stm_refresh_rates[i][0])) {
|
||||
uint64_t tol = _stm_refresh_rates[i][1];
|
||||
if ((ticks > (ns - tol)) && (ticks < (ns + tol))) {
|
||||
return ns;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// fallthough: didn't fit into any buckets
|
||||
return ticks;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_sec(uint64_t ticks) {
|
||||
return (double)ticks / 1000000000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_ms(uint64_t ticks) {
|
||||
return (double)ticks / 1000000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_us(uint64_t ticks) {
|
||||
return (double)ticks / 1000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_ns(uint64_t ticks) {
|
||||
return (double)ticks;
|
||||
}
|
||||
#endif /* SOKOL_TIME_IMPL */
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,474 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
|
||||
/* | `geometry` ๋ชจ๋ ์๋ฃํ ์ ์... | */
|
||||
|
||||
/* ์ถฉ๋ ๊ฐ์ง์ฉ ๋ํ์ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด. */
|
||||
typedef struct frShape {
|
||||
frShapeType type;
|
||||
frMaterial material;
|
||||
bool isRect;
|
||||
float area;
|
||||
union {
|
||||
struct {
|
||||
float radius;
|
||||
} circle;
|
||||
struct {
|
||||
frVertices vertices;
|
||||
frVertices normals;
|
||||
} polygon;
|
||||
};
|
||||
} frShape;
|
||||
|
||||
/* | `geometry` ๋ชจ๋ ์์... | */
|
||||
|
||||
static const float INVERSE_TWELVE = (1.0f / 12.0f);
|
||||
|
||||
/* | `geometry` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ๋ํ `s`์ ๋์ด๋ฅผ ๊ณ์ฐํ๋ค. */
|
||||
static void frComputeArea(frShape *s);
|
||||
|
||||
/* ๋ค๊ฐํ `s`๋ฅผ ๋ณผ๋ก ๋ค๊ฐํ์ผ๋ก ๋ณํํ๋ค. */
|
||||
static void frComputeConvex(frShape *s);
|
||||
|
||||
/* ๊ผญ์ง์ ๋ฐฐ์ด `vertices`๋ก ๋ง๋ค ์ ์๋ ๊ฐ์ฅ ํฐ ๋ณผ๋ก ๋ค๊ฐํ์ ๊ผญ์ง์ ๋ฐฐ์ด์ ๋ฐํํ๋ค. */
|
||||
static frVertices frJarvisMarch(frVertices *vertices);
|
||||
|
||||
/* ๋ฐ์ง๋ฆ์ด `radius`์ธ ์์ ๋ํ๋ด๋ ๋ํ์ ์์ฑํ๋ค. */
|
||||
frShape *frCreateCircle(frMaterial material, float radius) {
|
||||
frShape *result = frCreateShape();
|
||||
|
||||
result->type = FR_SHAPE_CIRCLE;
|
||||
result->material = material;
|
||||
|
||||
frSetCircleRadius(result, radius);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๊ฐ๋ก ๊ธธ์ด๊ฐ `width`์ด๊ณ ์ธ๋ก ๊ธธ์ด๊ฐ `height`์ธ ์ง์ฌ๊ฐํ์ ๋ํ๋ด๋ ๋ํ์ ์์ฑํ๋ค. */
|
||||
frShape *frCreateRectangle(frMaterial material, float width, float height) {
|
||||
frShape *result = frCreateShape();
|
||||
|
||||
const float halfWidth = 0.5f * width, halfHeight = 0.5f * height;
|
||||
|
||||
frVertices vertices = {
|
||||
.data = {
|
||||
(Vector2) { -halfWidth, -halfHeight },
|
||||
(Vector2) { -halfWidth, halfHeight },
|
||||
(Vector2) { halfWidth, halfHeight },
|
||||
(Vector2) { halfWidth, -halfHeight }
|
||||
},
|
||||
.count = 4
|
||||
};
|
||||
|
||||
result->type = FR_SHAPE_POLYGON;
|
||||
result->material = material;
|
||||
result->isRect = true;
|
||||
|
||||
frSetPolygonVertices(result, vertices);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๊ผญ์ง์ ๋ฐฐ์ด์ด `vertices`์ธ ๋ค๊ฐํ์ ๋ํ๋ด๋ ๋ํ์ ์์ฑํ๋ค. */
|
||||
frShape *frCreatePolygon(frMaterial material, frVertices vertices) {
|
||||
frShape *result = frCreateShape();
|
||||
|
||||
if (vertices.count > FR_GEOMETRY_MAX_VERTEX_COUNT)
|
||||
vertices.count = FR_GEOMETRY_MAX_VERTEX_COUNT;
|
||||
|
||||
result->type = FR_SHAPE_POLYGON;
|
||||
result->material = material;
|
||||
|
||||
frSetPolygonVertices(result, vertices);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ํํ๊ฐ ์ ํด์ง์ง ์์ ๋ํ์ ์์ฑํ๋ค. */
|
||||
frShape *frCreateShape(void) {
|
||||
frShape *result = calloc(1, sizeof(*result));
|
||||
|
||||
result->type = FR_SHAPE_UNKNOWN;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ํํ๊ฐ ๊ฐ์ ์๋ก์ด ๋ํ์ ๋ฐํํ๋ค. */
|
||||
frShape *frCloneShape(frShape *s) {
|
||||
if (s == NULL || s->type == FR_SHAPE_UNKNOWN) return NULL;
|
||||
|
||||
frShape *result = frCreateShape();
|
||||
|
||||
result->type = s->type;
|
||||
result->material = s->material;
|
||||
result->isRect = s->isRect;
|
||||
result->area = s->area;
|
||||
|
||||
if (result->type == FR_SHAPE_CIRCLE)
|
||||
result->circle.radius = s->circle.radius;
|
||||
else if (result->type == FR_SHAPE_POLYGON) {
|
||||
result->polygon.vertices.count = s->polygon.vertices.count;
|
||||
result->polygon.normals.count = s->polygon.normals.count;
|
||||
|
||||
for (int i = 0; i < s->polygon.vertices.count; i++)
|
||||
result->polygon.vertices.data[i] = s->polygon.vertices.data[i];
|
||||
|
||||
for (int i = 0; i < s->polygon.normals.count; i++)
|
||||
result->polygon.normals.data[i] = s->polygon.normals.data[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ํ ๋น๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ค. */
|
||||
void frReleaseShape(frShape *s) {
|
||||
free(s);
|
||||
}
|
||||
|
||||
/* ๊ตฌ์กฐ์ฒด `frShape`์ ํฌ๊ธฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
size_t frGetShapeStructSize(void) {
|
||||
return sizeof(frShape);
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ์ข
๋ฅ๋ฅผ ๋ฐํํ๋ค. */
|
||||
frShapeType frGetShapeType(frShape *s) {
|
||||
return (s != NULL) ? s->type : FR_SHAPE_UNKNOWN;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ์ฌ์ง์ ๋ฐํํ๋ค. */
|
||||
frMaterial frGetShapeMaterial(frShape *s) {
|
||||
return (s != NULL) ? s->material : FR_STRUCT_ZERO(frMaterial);
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ๋์ด๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetShapeArea(frShape *s) {
|
||||
return (s != NULL && s->area >= 0.0f) ? s->area : 0.0f;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ์ง๋์ ๋ฐํํ๋ค. */
|
||||
float frGetShapeMass(frShape *s) {
|
||||
return (s != NULL) ? s->material.density * frGetShapeArea(s) : 0.0f;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ๊ด์ฑ ๋ชจ๋ฉํธ๋ฅผ ๋ฐํํ๋ค. */
|
||||
float frGetShapeInertia(frShape *s) {
|
||||
if (s == NULL || s->type == FR_SHAPE_UNKNOWN) return 0.0f;
|
||||
|
||||
float result = 0.0f;
|
||||
|
||||
if (s->type == FR_SHAPE_CIRCLE) {
|
||||
result = 0.5f * frGetShapeMass(s) * (s->circle.radius * s->circle.radius);
|
||||
} else if (s->type == FR_SHAPE_POLYGON) {
|
||||
float xInertia = 0.0f, yInertia = 0.0f;
|
||||
|
||||
// https://en.wikipedia.org/wiki/Second_moment_of_area#Any_polygon
|
||||
for (int j = s->polygon.vertices.count - 1, i = 0; i < s->polygon.vertices.count; j = i, i++) {
|
||||
Vector2 v1 = s->polygon.vertices.data[j];
|
||||
Vector2 v2 = s->polygon.vertices.data[i];
|
||||
|
||||
const float cross = frVec2CrossProduct(v1, v2);
|
||||
|
||||
xInertia += cross * ((v1.y * v1.y) + (v1.y * v2.y) + (v2.y * v2.y));
|
||||
yInertia += cross * ((v1.x * v1.x) + (v1.x * v2.x) + (v2.x * v2.x));
|
||||
}
|
||||
|
||||
result = fabsf((xInertia + yInertia) * INVERSE_TWELVE);
|
||||
}
|
||||
|
||||
return s->material.density * result;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ AABB๋ฅผ ๋ฐํํ๋ค. */
|
||||
Rectangle frGetShapeAABB(frShape *s, frTransform tx) {
|
||||
Rectangle result = FR_STRUCT_ZERO(Rectangle);
|
||||
|
||||
if (s == NULL || s->type == FR_SHAPE_UNKNOWN) return result;
|
||||
|
||||
if (s->type == FR_SHAPE_CIRCLE) {
|
||||
result.x = tx.position.x - s->circle.radius;
|
||||
result.y = tx.position.y - s->circle.radius;
|
||||
|
||||
result.width = result.height = 2.0f * s->circle.radius;
|
||||
|
||||
return result;
|
||||
} else if (s->type == FR_SHAPE_POLYGON) {
|
||||
Vector2 minVertex = { FLT_MAX, FLT_MAX };
|
||||
Vector2 maxVertex = { -FLT_MAX, -FLT_MAX };
|
||||
|
||||
// AABB์ X์ขํ์ Y์ขํ์ ์ต์๊ฐ๊ณผ ์ต๋๊ฐ์ ๊ตฌํ๋ค.
|
||||
for (int i = 0; i < s->polygon.vertices.count; i++) {
|
||||
Vector2 vertex = frVec2Transform(s->polygon.vertices.data[i], tx);
|
||||
|
||||
if (minVertex.x > vertex.x) minVertex.x = vertex.x;
|
||||
if (minVertex.y > vertex.y) minVertex.y = vertex.y;
|
||||
|
||||
if (maxVertex.x < vertex.x) maxVertex.x = vertex.x;
|
||||
if (maxVertex.y < vertex.y) maxVertex.y = vertex.y;
|
||||
}
|
||||
|
||||
float deltaX = maxVertex.x - minVertex.x;
|
||||
float deltaY = maxVertex.y - minVertex.y;
|
||||
|
||||
result.x = minVertex.x;
|
||||
result.y = minVertex.y;
|
||||
|
||||
result.width = deltaX;
|
||||
result.height = deltaY;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/* ์ `s`์ ๋ฐ์ง๋ฆ์ ๋ฐํํ๋ค. */
|
||||
float frGetCircleRadius(frShape *s) {
|
||||
return (s != NULL && s->type == FR_SHAPE_CIRCLE) ? s->circle.radius : 0.0f;
|
||||
}
|
||||
|
||||
/* ์ง์ฌ๊ฐํ `s`์ ๊ฐ๋ก ๋ฐ ์ธ๋ก ๊ธธ์ด๋ฅผ ๋ฐํํ๋ค. */
|
||||
Vector2 frGetRectangleDimensions(frShape *s) {
|
||||
if (!frIsShapeRectangle(s)) return FR_STRUCT_ZERO(Vector2);
|
||||
|
||||
return (Vector2) {
|
||||
s->polygon.vertices.data[2].x - s->polygon.vertices.data[1].x,
|
||||
s->polygon.vertices.data[1].y - s->polygon.vertices.data[0].y
|
||||
};
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`์ `i + 1`๋ฒ์งธ ๊ผญ์ง์ ์ ๋ฐํํ๋ค. */
|
||||
Vector2 frGetPolygonVertex(frShape *s, int i) {
|
||||
return (s != NULL && i >= 0 && i < s->polygon.vertices.count)
|
||||
? s->polygon.vertices.data[i]
|
||||
: FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`์ `i + 1`๋ฒ์งธ ๋ฒ์ ๋ฒกํฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
Vector2 frGetPolygonNormal(frShape *s, int i) {
|
||||
return (s != NULL && i >= 0 && i < s->polygon.normals.count)
|
||||
? s->polygon.normals.data[i]
|
||||
: FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`์ ๊ผญ์ง์ ๋ฐฐ์ด์ ๋ฐํํ๋ค. */
|
||||
frVertices frGetPolygonVertices(frShape *s) {
|
||||
return (s != NULL) ? s->polygon.vertices : FR_STRUCT_ZERO(frVertices);
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`์ ๋ฒ์ ๋ฒกํฐ ๋ฐฐ์ด์ ๋ฐํํ๋ค. */
|
||||
frVertices frGetPolygonNormals(frShape *s) {
|
||||
return (s != NULL) ? s->polygon.normals : FR_STRUCT_ZERO(frVertices);
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`๊ฐ ์ง์ฌ๊ฐํ์ธ์ง ํ์ธํ๋ค. */
|
||||
bool frIsShapeRectangle(frShape *s) {
|
||||
return (s != NULL) && (s->type == FR_SHAPE_POLYGON) && s->isRect;
|
||||
}
|
||||
|
||||
/* ์ `s`์ ๋ฐ์ง๋ฆ์ `radius`๋ก ๋ณ๊ฒฝํ๋ค. */
|
||||
void frSetCircleRadius(frShape *s, float radius) {
|
||||
if (s == NULL || s->type != FR_SHAPE_CIRCLE) return;
|
||||
|
||||
s->circle.radius = radius;
|
||||
|
||||
frComputeArea(s);
|
||||
}
|
||||
|
||||
/* ์ง์ฌ๊ฐํ `s`์ ๊ฐ๋ก ๋ฐ ์ธ๋ก ๊ธธ์ด๋ฅผ `v`์ X๊ฐ๊ณผ Y๊ฐ์ผ๋ก ์ค์ ํ๋ค. */
|
||||
void frSetRectangleDimensions(frShape *s, Vector2 v) {
|
||||
if (!frIsShapeRectangle(s) || v.x <= 0.0f || v.y <= 0.0f) return;
|
||||
|
||||
v = frVec2ScalarMultiply(v, 0.5f);
|
||||
|
||||
frVertices vertices = {
|
||||
.data = {
|
||||
(Vector2) { -v.x, -v.y },
|
||||
(Vector2) { -v.x, v.y },
|
||||
(Vector2) { v.x, v.y },
|
||||
(Vector2) { v.x, -v.y }
|
||||
},
|
||||
.count = 4
|
||||
};
|
||||
|
||||
frSetPolygonVertices(s, vertices);
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`์ ๊ผญ์ง์ ๋ฐฐ์ด์ `vertices`๋ก ๋ณ๊ฒฝํ๋ค. */
|
||||
void frSetPolygonVertices(frShape *s, frVertices vertices) {
|
||||
if (s == NULL || s->type != FR_SHAPE_POLYGON) return;
|
||||
|
||||
s->polygon.vertices.count = vertices.count;
|
||||
s->polygon.normals.count = vertices.count;
|
||||
|
||||
for (int i = 0; i < vertices.count; i++)
|
||||
s->polygon.vertices.data[i] = vertices.data[i];
|
||||
|
||||
// ์ง์ฌ๊ฐํ์ ์ด๋ฏธ ๋ณผ๋ก ๋ค๊ฐํ์ด๋ฏ๋ก ๋ณํํ์ง ์๋๋ค.
|
||||
if (!s->isRect) frComputeConvex(s);
|
||||
|
||||
frComputeArea(s);
|
||||
|
||||
for (int j = vertices.count - 1, i = 0; i < vertices.count; j = i, i++)
|
||||
s->polygon.normals.data[i] = frVec2LeftNormal(
|
||||
frVec2Subtract(
|
||||
s->polygon.vertices.data[i],
|
||||
s->polygon.vertices.data[j]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ์ฌ์ง์ `material`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetShapeMaterial(frShape *s, frMaterial material) {
|
||||
if (s != NULL) s->material = material;
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ์ข
๋ฅ๋ฅผ `type`์ผ๋ก ๋ณ๊ฒฝํ๋ค. */
|
||||
void frSetShapeType(frShape *s, frShapeType type) {
|
||||
if (s != NULL && s->type != type) s->type = type;
|
||||
}
|
||||
|
||||
/* ์ `p`๊ฐ ๋ํ `s`์ ๋ด๋ถ์ ์๋์ง ํ์ธํ๋ค. */
|
||||
bool frShapeContainsPoint(frShape *s, frTransform tx, Vector2 p) {
|
||||
if (s == NULL || s->type == FR_SHAPE_UNKNOWN) return false;
|
||||
|
||||
Rectangle aabb = frGetShapeAABB(s, tx);
|
||||
|
||||
// `p`๊ฐ `s`์ AABB ๋ด๋ถ์ ์๋์ง ๋จผ์ ํ์ธํ๋ค.
|
||||
if (p.x < aabb.x || p.x > aabb.x + aabb.width
|
||||
|| p.y < aabb.y || p.y > aabb.y + aabb.height)
|
||||
return false;
|
||||
|
||||
if (s->type == FR_SHAPE_CIRCLE) {
|
||||
float deltaX = p.x - tx.position.x;
|
||||
float deltaY = p.y - tx.position.y;
|
||||
|
||||
float radius = frGetCircleRadius(s);
|
||||
|
||||
return (deltaX * deltaX) + (deltaY * deltaY) <= radius * radius;
|
||||
} else if (s->type == FR_SHAPE_POLYGON) {
|
||||
frRay ray = { p, (Vector2) { .x = 1.0f }, FLT_MAX };
|
||||
|
||||
frRaycastHit raycast = frComputeShapeRaycast(s, tx, ray);
|
||||
|
||||
return raycast.inside;
|
||||
}
|
||||
}
|
||||
|
||||
/* ๋ํ `s`์ ๋์ด๋ฅผ ๊ณ์ฐํ๋ค. */
|
||||
static void frComputeArea(frShape *s) {
|
||||
if (s == NULL || s->type == FR_SHAPE_UNKNOWN)
|
||||
s->area = 0.0f;
|
||||
else if (s->type == FR_SHAPE_CIRCLE)
|
||||
s->area = PI * (s->circle.radius * s->circle.radius);
|
||||
else if (s->type == FR_SHAPE_POLYGON) {
|
||||
if (s->isRect) {
|
||||
frVertices *vertices = &(s->polygon.vertices);
|
||||
|
||||
float width = vertices->data[2].x - vertices->data[1].x;
|
||||
float height = vertices->data[1].y - vertices->data[0].y;
|
||||
|
||||
s->area = width * height;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float twiceAreaSum = 0.0f;
|
||||
|
||||
for (int i = 0; i < s->polygon.vertices.count - 1; i++) {
|
||||
float twiceArea = frVec2CrossProduct(
|
||||
frVec2Subtract(s->polygon.vertices.data[i], s->polygon.vertices.data[0]),
|
||||
frVec2Subtract(s->polygon.vertices.data[i + 1], s->polygon.vertices.data[0])
|
||||
);
|
||||
|
||||
twiceAreaSum += twiceArea;
|
||||
}
|
||||
|
||||
s->area = fabsf(0.5f * twiceAreaSum);
|
||||
}
|
||||
}
|
||||
|
||||
/* ๋ค๊ฐํ `s`๋ฅผ ๋ณผ๋ก ๋ค๊ฐํ์ผ๋ก ๋ณํํ๋ค. */
|
||||
static void frComputeConvex(frShape *s) {
|
||||
if (s == NULL || s->type != FR_SHAPE_POLYGON) return;
|
||||
|
||||
frVertices result = frJarvisMarch(&(s->polygon.vertices));
|
||||
|
||||
for (int i = 0; i < result.count; i++)
|
||||
s->polygon.vertices.data[i] = result.data[i];
|
||||
|
||||
s->polygon.vertices.count = result.count;
|
||||
}
|
||||
|
||||
/* ๊ผญ์ง์ ๋ฐฐ์ด `vertices`๋ก ๋ง๋ค ์ ์๋ ๊ฐ์ฅ ํฐ ๋ณผ๋ก ๋ค๊ฐํ์ ๊ผญ์ง์ ๋ฐฐ์ด์ ๋ฐํํ๋ค. */
|
||||
static frVertices frJarvisMarch(frVertices *vertices) {
|
||||
if (vertices == NULL || vertices->count == 0) return FR_STRUCT_ZERO(frVertices);
|
||||
|
||||
frVertices result = FR_STRUCT_ZERO(frVertices);
|
||||
|
||||
int leftmostIndex = 0, pivotIndex = 0, nextIndex = 0, vertexCount = 0;
|
||||
|
||||
// ์ฃผ์ด์ง ๊ผญ์ง์ ๋ฐฐ์ด์์ X์ขํ ๊ฐ์ด ๊ฐ์ฅ ์์ ๊ผญ์ง์ L์ ์ฐพ๋๋ค.
|
||||
for (int i = 1; i < vertices->count; i++)
|
||||
if (vertices->data[leftmostIndex].x > vertices->data[i].x)
|
||||
leftmostIndex = i;
|
||||
|
||||
result.data[vertexCount++] = vertices->data[leftmostIndex];
|
||||
|
||||
// ๊ธฐ์ค์ P๋ฅผ ๋ฐฉ๊ธ ์ฐพ์ ๊ผญ์ง์ L๋ก ์ค์ ํ๋ค.
|
||||
pivotIndex = leftmostIndex;
|
||||
|
||||
while (1) {
|
||||
// ๊ธฐ์ค์ P์ ๋ฐ๋ก ๋ค์์ ์ค๋ ๊ผญ์ง์ Q๋ฅผ ์ฐพ๋๋ค.
|
||||
for (int i = 0; i < vertices->count; i++) {
|
||||
if (i == pivotIndex) continue;
|
||||
|
||||
nextIndex = i;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// ๊ธฐ์ค์ P์ ๊ผญ์ง์ Q ์ฌ์ด์ ์ค๋ ๊ผญ์ง์ R์ ์ฐพ๋๋ค.
|
||||
for (int i = 0; i < vertices->count; i++) {
|
||||
if (i == pivotIndex || i == nextIndex)
|
||||
continue;
|
||||
|
||||
// ๊ธฐ์ค์ P, ๊ผญ์ง์ R๊ณผ ๊ผญ์ง์ Q๊ฐ ๋ฐ์๊ณ ๋ฐฉํฅ์ผ๋ก ์ ๋ ฌ๋์ด ์๋์ง ํ์ธํ๋ค.
|
||||
if (frVec2CCW(vertices->data[pivotIndex], vertices->data[i], vertices->data[nextIndex]))
|
||||
nextIndex = i;
|
||||
}
|
||||
|
||||
// ๊ผญ์ง์ Q๊ฐ ์์์ ์ธ ๊ผญ์ง์ L์ด ๋๋ฉด ํ์์ ์ข
๋ฃํ๋ค.
|
||||
if (nextIndex == leftmostIndex) break;
|
||||
|
||||
pivotIndex = nextIndex;
|
||||
|
||||
// ์๋ก ์ฐพ์ ๊ผญ์ง์ ์ ๋ฐฐ์ด์ ์ ์ฅํ๋ค.
|
||||
result.data[vertexCount++] = vertices->data[nextIndex];
|
||||
}
|
||||
|
||||
result.count = vertexCount;
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
|
||||
/* | `timer` ๋ชจ๋ ๋งคํฌ๋ก ์ ์... | */
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define NOGDI
|
||||
#define NOUSER
|
||||
#endif
|
||||
|
||||
#define SOKOL_TIME_IMPL
|
||||
#include "sokol_time.h"
|
||||
|
||||
/* | `timer` ๋ชจ๋ ๋ณ์... | */
|
||||
|
||||
/* ๋จ์กฐ ์๊ณ์ ์ด๊ธฐํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ ๋ณ์. */
|
||||
static bool initialized = false;
|
||||
|
||||
/* | `timer` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ๋จ์กฐ ์๊ณ๋ฅผ ์ด๊ธฐํํ๋ค. */
|
||||
void frInitClock(void) {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
stm_setup();
|
||||
}
|
||||
}
|
||||
|
||||
/* ๋จ์กฐ ์๊ณ์ ํ์ฌ ์๊ฐ (๋จ์: ms)์ ๋ฐํํ๋ค. */
|
||||
double frGetCurrentTime(void) {
|
||||
if (!initialized) frInitClock();
|
||||
|
||||
return stm_ms(stm_now());
|
||||
}
|
||||
|
||||
/* ๋จ์กฐ ์๊ณ์ ์๊ฐ `newTime`๊ณผ `oldTime`์ ์ฐจ์ด๋ฅผ ๋ฐํํ๋ค. */
|
||||
double frGetTimeDifference(double newTime, double oldTime) {
|
||||
return newTime - oldTime;
|
||||
}
|
||||
|
||||
/* ๋จ์กฐ ์๊ณ์ ํ์ฌ ์๊ฐ๊ณผ `old_time`๊ณผ์ ์ฐจ์ด๋ฅผ ๋ฐํํ๋ค. */
|
||||
double frGetTimeSince(double oldTime) {
|
||||
return frGetTimeDifference(frGetCurrentTime(), oldTime);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
|
||||
#define STB_DS_IMPLEMENTATION
|
||||
#include "stb_ds.h"
|
||||
|
||||
/* | `utils` ๋ชจ๋ ๋งคํฌ๋ก ์ ์... | */
|
||||
|
||||
#define TWO_PI (2.0f * PI)
|
||||
|
||||
/* | `utils` ๋ชจ๋ ์์... | */
|
||||
|
||||
static const float INVERSE_TWO_PI = (1.0f / TWO_PI);
|
||||
|
||||
/* | `utils` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ๊ฐ๋ `angle` (๋จ์: rad.)์ ์ ๊ทํํ์ฌ, ๊ตฌ๊ฐ `[center - ฯ, center + ฯ]`์ ํฌํจ๋๋๋ก ํ๋ค. */
|
||||
float frNormalizeAngle(float angle, float center) {
|
||||
if (angle > center - PI && angle < center + PI) return angle;
|
||||
|
||||
return angle - (TWO_PI * floorf((angle + PI - center) * INVERSE_TWO_PI));
|
||||
}
|
||||
|
||||
/* ๋ถ๋ ์์์ ๊ฐ `f1`์ด `f2`์ ๊ทผ์ ํ ๊ฐ์ธ์ง ํ์ธํ๋ค. */
|
||||
bool frNumberApproxEquals(float f1, float f2) {
|
||||
return fabsf(f1 - f2) <= fmaxf(1.0f, fmaxf(f1, f2)) * FLT_EPSILON;
|
||||
}
|
|
@ -0,0 +1,377 @@
|
|||
๏ปฟ/*
|
||||
Copyright (c) 2021-2022 jdeokkim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ferox.h"
|
||||
#include "stb_ds.h"
|
||||
|
||||
/* | `world` ๋ชจ๋ ์๋ฃํ ์ ์... | */
|
||||
|
||||
/* ๋ฌผ๋ฆฌ ๋ฒ์น์ด ์กด์ฌํ๋ ์ธ๊ณ๋ฅผ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด. */
|
||||
typedef struct frWorld {
|
||||
Vector2 gravity;
|
||||
frBody **bodies;
|
||||
frSpatialHash *hash;
|
||||
frCollision *collisions;
|
||||
frCollisionHandler handler;
|
||||
double accumulator;
|
||||
double timestamp;
|
||||
int *queries;
|
||||
} frWorld;
|
||||
|
||||
/* | `world` ๋ชจ๋ ํจ์... | */
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์
๋ฐ์ดํธ ์ด์ ์์
์ ์คํํ๋ค. */
|
||||
static void frPreUpdateWorld(frWorld *world);
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์
๋ฐ์ดํธ ์ดํ ์์
์ ์คํํ๋ค. */
|
||||
static void frPostUpdateWorld(frWorld *world);
|
||||
|
||||
/* ์ธ๊ณ `world`๋ฅผ ์๊ฐ `dt` (๋จ์: ms)๋งํผ ์
๋ฐ์ดํธํ๋ค. */
|
||||
static void frUpdateWorld(frWorld *world, double dt);
|
||||
|
||||
/* ์ค๋ ฅ ๊ฐ์๋๊ฐ `gravity`์ด๊ณ ๊ฒฝ๊ณ ๋ฒ์๊ฐ `bounds`์ธ ์ธ๊ณ๋ฅผ ์์ฑํ๋ค. */
|
||||
frWorld *frCreateWorld(Vector2 gravity, Rectangle bounds) {
|
||||
frWorld *result = calloc(1, sizeof(*result));
|
||||
|
||||
result->gravity = gravity;
|
||||
result->hash = frCreateSpatialHash(bounds, FR_BROADPHASE_CELL_SIZE);
|
||||
result->timestamp = frGetCurrentTime();
|
||||
|
||||
arrsetcap(result->bodies, FR_WORLD_MAX_BODY_COUNT);
|
||||
arrsetcap(result->collisions, FR_WORLD_MAX_BODY_COUNT * FR_WORLD_MAX_BODY_COUNT);
|
||||
arrsetcap(result->queries, FR_WORLD_MAX_BODY_COUNT);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ํ ๋น๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ๋ค. */
|
||||
void frReleaseWorld(frWorld *world) {
|
||||
if (world == NULL) return;
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++) {
|
||||
frBody *b = world->bodies[i];
|
||||
frShape *s = frGetBodyShape(b);
|
||||
|
||||
frReleaseShape(s);
|
||||
frReleaseBody(b);
|
||||
}
|
||||
|
||||
frReleaseSpatialHash(world->hash);
|
||||
|
||||
arrfree(world->bodies);
|
||||
arrfree(world->collisions);
|
||||
arrfree(world->queries);
|
||||
|
||||
free(world);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๊ฐ์ฒด `b`๋ฅผ ์ถ๊ฐํ๋ค. */
|
||||
bool frAddToWorld(frWorld *world, frBody *b) {
|
||||
if (world == NULL || b == NULL || arrlen(world->bodies) >= FR_WORLD_MAX_BODY_COUNT)
|
||||
return false;
|
||||
|
||||
arrput(world->bodies, b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ์ ๊ฑฐํ๋ค. */
|
||||
void frClearWorld(frWorld *world) {
|
||||
if (world == NULL) return;
|
||||
|
||||
frClearSpatialHash(world->hash);
|
||||
|
||||
arrsetlen(world->bodies, 0);
|
||||
arrsetlen(world->collisions, 0);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์์ ๊ฐ์ฒด `b`๋ฅผ ์ ๊ฑฐํ๋ค. */
|
||||
bool frRemoveFromWorld(frWorld *world, frBody *b) {
|
||||
if (world == NULL || b == NULL) return false;
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++) {
|
||||
if (world->bodies[i] == b) {
|
||||
arrdeln(world->bodies, i, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ๊ตฌ์กฐ์ฒด `frWorld`์ ํฌ๊ธฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
size_t frGetWorldStructSize(void) {
|
||||
return sizeof(frWorld);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์์ ์ธ๋ฑ์ค๊ฐ `index`์ธ ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ ์ฃผ์๋ฅผ ๋ฐํํ๋ค. */
|
||||
frBody *frGetWorldBody(frWorld *world, int index) {
|
||||
return (world != NULL && index >= 0 && index < arrlen(world->bodies))
|
||||
? world->bodies[index]
|
||||
: NULL;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์ถฉ๋ ํธ๋ค๋ฌ๋ฅผ ๋ฐํํ๋ค. */
|
||||
frCollisionHandler frGetWorldCollisionHandler(frWorld *world) {
|
||||
return (world != NULL) ? world->handler : FR_STRUCT_ZERO(frCollisionHandler);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๊ฐ์ฒด ๋ฐฐ์ด์ ํฌ๊ธฐ๋ฅผ ๋ฐํํ๋ค. */
|
||||
int frGetWorldBodyCount(frWorld *world) {
|
||||
return (world != NULL) ? arrlen(world->bodies) : 0;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๊ฒฝ๊ณ ๋ฒ์๋ฅผ ๋ฐํํ๋ค. */
|
||||
Rectangle frGetWorldBounds(frWorld *world) {
|
||||
return (world != NULL)
|
||||
? frGetSpatialHashBounds(world->hash)
|
||||
: FR_STRUCT_ZERO(Rectangle);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๊ณต๊ฐ ํด์๋งต์ ๋ฐํํ๋ค. */
|
||||
frSpatialHash *frGetWorldSpatialHash(frWorld *world){
|
||||
return (world != NULL) ? world->hash : NULL;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์ค๋ ฅ ๊ฐ์๋๋ฅผ ๋ฐํํ๋ค. */
|
||||
Vector2 frGetWorldGravity(frWorld *world) {
|
||||
return (world != NULL) ? world->gravity : FR_STRUCT_ZERO(Vector2);
|
||||
}
|
||||
|
||||
/* ๊ฐ์ฒด `b`๊ฐ ์ธ๊ณ `world`์ ๊ฒฝ๊ณ ๋ฒ์ ์์ ์๋์ง ํ์ธํ๋ค. */
|
||||
bool frIsInWorldBounds(frWorld *world, frBody *b) {
|
||||
if (world == NULL || b == NULL) return false;
|
||||
|
||||
return CheckCollisionRecs(frGetBodyAABB(b), frGetWorldBounds(world));
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๊ฒฝ๊ณ ๋ฒ์๋ฅผ `bounds`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetWorldBounds(frWorld *world, Rectangle bounds) {
|
||||
if (world != NULL) frSetSpatialHashBounds(world->hash, bounds);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์ถฉ๋ ํธ๋ค๋ฌ๋ฅผ `handler`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetWorldCollisionHandler(frWorld *world, frCollisionHandler handler) {
|
||||
if (world != NULL) world->handler = handler;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์ค๋ ฅ ๊ฐ์๋๋ฅผ `gravity`๋ก ์ค์ ํ๋ค. */
|
||||
void frSetWorldGravity(frWorld *world, Vector2 gravity) {
|
||||
if (world != NULL) world->gravity = gravity;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์๊ฐ์ `dt` (๋จ์: ms)๋งํผ ํ๋ฅด๊ฒ ํ๋ค. */
|
||||
void frSimulateWorld(frWorld *world, double dt) {
|
||||
if (world == NULL || dt == 0.0f) return;
|
||||
|
||||
double currentTime = frGetCurrentTime();
|
||||
double elapsedTime = frGetTimeDifference(currentTime, world->timestamp);
|
||||
|
||||
world->accumulator += elapsedTime;
|
||||
world->timestamp = currentTime;
|
||||
|
||||
if (world->accumulator > FR_WORLD_ACCUMULATOR_LIMIT)
|
||||
world->accumulator = FR_WORLD_ACCUMULATOR_LIMIT;
|
||||
|
||||
for (; world->accumulator >= dt; world->accumulator -= dt)
|
||||
frUpdateWorld(world, dt);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์์ ์ง์ฌ๊ฐํ `rec`์ ๊ฒฝ๊ณ ๋ฒ์๊ฐ ๊ฒน์น๋ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค. */
|
||||
int frQueryWorldSpatialHash(frWorld *world, Rectangle rec, frBody **bodies) {
|
||||
if (world == NULL || bodies == NULL) return 0;
|
||||
|
||||
arrsetlen(world->queries, 0);
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++)
|
||||
frAddToSpatialHash(world->hash, frGetBodyAABB(world->bodies[i]), i);
|
||||
|
||||
frQuerySpatialHash(world->hash, rec, &world->queries);
|
||||
|
||||
int result = arrlen(world->queries);
|
||||
|
||||
for (int i = 0; i < result; i++)
|
||||
bodies[i] = world->bodies[world->queries[i]];
|
||||
|
||||
frClearSpatialHash(world->hash);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ๋ชจ๋ ๊ฐ์ฒด์ ๊ด์ ์ ํฌ์ฌํ๋ค. */
|
||||
int frComputeWorldRaycast(frWorld *world, frRay ray, frRaycastHit *hits) {
|
||||
if (world == NULL || hits == NULL) return 0;
|
||||
|
||||
arrsetlen(world->queries, 0);
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++)
|
||||
frAddToSpatialHash(world->hash, frGetBodyAABB(world->bodies[i]), i);
|
||||
|
||||
Vector2 p1 = ray.origin, p2 = frVec2Add(
|
||||
ray.origin,
|
||||
frVec2ScalarMultiply(
|
||||
frVec2Normalize(ray.direction),
|
||||
ray.maxDistance
|
||||
)
|
||||
);
|
||||
|
||||
Rectangle rec = (Rectangle) {
|
||||
.x = fminf(p1.x, p2.x),
|
||||
.y = fminf(p1.y, p2.y),
|
||||
.width = fabsf(p2.x - p1.x),
|
||||
.height = fabsf(p2.y - p1.y)
|
||||
};
|
||||
|
||||
frQuerySpatialHash(world->hash, rec, &world->queries);
|
||||
|
||||
if (arrlen(world->queries) <= 0) return 0;
|
||||
|
||||
if (ray.closest) {
|
||||
float minDistance = FLT_MAX;
|
||||
|
||||
for (int i = 0; i < arrlen(world->queries); i++) {
|
||||
frBody *body = world->bodies[world->queries[i]];
|
||||
|
||||
frRaycastHit raycast = frComputeBodyRaycast(body, ray);
|
||||
|
||||
if (!raycast.check) continue;
|
||||
|
||||
if (minDistance > raycast.distance) {
|
||||
minDistance = raycast.distance;
|
||||
hits[0] = raycast;
|
||||
}
|
||||
}
|
||||
|
||||
frClearSpatialHash(world->hash);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < arrlen(world->queries); i++) {
|
||||
frBody *body = world->bodies[world->queries[i]];
|
||||
|
||||
frRaycastHit raycast = frComputeBodyRaycast(body, ray);
|
||||
|
||||
if (!raycast.check) continue;
|
||||
|
||||
hits[count++] = raycast;
|
||||
}
|
||||
|
||||
frClearSpatialHash(world->hash);
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์
๋ฐ์ดํธ ์ด์ ์์
์ ์คํํ๋ค. */
|
||||
static void frPreUpdateWorld(frWorld *world) {
|
||||
if (world == NULL || world->hash == NULL) return;
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++)
|
||||
frAddToSpatialHash(world->hash, frGetBodyAABB(world->bodies[i]), i);
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++) {
|
||||
frQuerySpatialHash(
|
||||
world->hash,
|
||||
frGetBodyAABB(world->bodies[i]),
|
||||
&world->queries
|
||||
);
|
||||
|
||||
for (int k = 0; k < arrlen(world->queries); k++) {
|
||||
int j = world->queries[k];
|
||||
|
||||
if (j <= i) continue;
|
||||
|
||||
frBody *b1 = world->bodies[i];
|
||||
frBody *b2 = world->bodies[j];
|
||||
|
||||
frCollision collision = frComputeBodyCollision(b1, b2);
|
||||
|
||||
if (collision.check) {
|
||||
collision.cache.bodies[0] = b1;
|
||||
collision.cache.bodies[1] = b2;
|
||||
|
||||
arrput(world->collisions, collision);
|
||||
}
|
||||
}
|
||||
|
||||
arrsetlen(world->queries, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`์ ์
๋ฐ์ดํธ ์ดํ ์์
์ ์คํํ๋ค. */
|
||||
static void frPostUpdateWorld(frWorld *world) {
|
||||
if (world == NULL || world->hash == NULL) return;
|
||||
|
||||
for (int i = 0; i < arrlen(world->bodies); i++)
|
||||
frClearBodyForces(world->bodies[i]);
|
||||
|
||||
arrsetlen(world->collisions, 0);
|
||||
|
||||
frClearSpatialHash(world->hash);
|
||||
}
|
||||
|
||||
/* ์ธ๊ณ `world`๋ฅผ ์๊ฐ `dt` (๋จ์: ms)๋งํผ ์
๋ฐ์ดํธํ๋ค. */
|
||||
static void frUpdateWorld(frWorld *world, double dt) {
|
||||
if (world == NULL || world->bodies == NULL || world->collisions == NULL) return;
|
||||
|
||||
frPreUpdateWorld(world);
|
||||
|
||||
// ๊ฐ์ฒด ์ฌ์ด์ ์ถฉ๋ ํด๊ฒฐ ์ง์ ์ ์ฌ์ ์ ์๋ ํจ์๋ฅผ ํธ์ถํ๋ค.
|
||||
for (int i = 0; i < arrlen(world->collisions); i++) {
|
||||
frCollisionCallback preSolveCallback = world->handler.preSolve;
|
||||
|
||||
if (preSolveCallback != NULL) preSolveCallback(&world->collisions[i]);
|
||||
}
|
||||
|
||||
// ๊ฐ์ฒด์ ์ค๋ ฅ์ ์ ์ฉํ๊ณ , ๊ฐ์ฒด์ ์๋์ ๊ฐ์๋๋ฅผ ๊ณ์ฐํ๋ค.
|
||||
for (int i = 0; i < arrlen(world->bodies); i++) {
|
||||
frApplyGravity(world->bodies[i], world->gravity);
|
||||
frIntegrateForBodyVelocities(world->bodies[i], dt);
|
||||
}
|
||||
|
||||
// ์์ฐจ์ ์ผ๋ก ์ถฉ๊ฒฉ๋์ ๋ฐ๋ณต ์ ์ฉํ์ฌ, ๋ ๊ฐ์ฒด ์ฌ์ด์ ์ถฉ๋์ ํด๊ฒฐํ๋ค.
|
||||
for (int i = 0; i < FR_WORLD_MAX_ITERATIONS; i++)
|
||||
for (int j = 0; j < arrlen(world->collisions); j++)
|
||||
frResolveCollision(&world->collisions[j]);
|
||||
|
||||
// ๊ฐ์ฒด์ ํ์ฌ ์์น๋ฅผ ๊ณ์ฐํ๋ค.
|
||||
for (int i = 0; i < arrlen(world->bodies); i++)
|
||||
frIntegrateForBodyPosition(world->bodies[i], dt);
|
||||
|
||||
float inverseDt = (dt != 0.0f) ? (1.0f / dt) : 0.0f;
|
||||
|
||||
// ๊ฐ์ฒด์ ์์น๋ฅผ ์ ์ ํ๊ฒ ๋ณด์ ํ๋ค.
|
||||
for (int i = 0; i < arrlen(world->collisions); i++)
|
||||
frCorrectBodyPositions(&world->collisions[i], inverseDt);
|
||||
|
||||
// ๊ฐ์ฒด ์ฌ์ด์ ์ถฉ๋ ํด๊ฒฐ ์งํ์ ์ฌ์ ์ ์๋ ํจ์๋ฅผ ํธ์ถํ๋ค.
|
||||
for (int i = 0; i < arrlen(world->collisions); i++) {
|
||||
frCollisionCallback postSolveCallback = world->handler.postSolve;
|
||||
|
||||
if (postSolveCallback != NULL) postSolveCallback(&world->collisions[i]);
|
||||
}
|
||||
|
||||
frPostUpdateWorld(world);
|
||||
}
|
Loadingโฆ
Reference in New Issue