physics impl part 1

efd/v1
Dominik Madarรกsz 2023-01-31 18:41:01 +01:00
parent 36a4a010c8
commit 4d1a90d5cb
41 changed files with 6474 additions and 40 deletions

BIN
art/gen/enemy1.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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)

View File

@ -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;

View File

@ -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 },

View File

@ -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;

View File

@ -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)) {
{

View File

@ -57,6 +57,9 @@ Texture2D texgen_build_sprite_fallback(asset_id id) {
case ASSET_SPLITTER: return LoadTexEco("item_splitter");
case ASSET_ASSEMBLER: return LoadTexEco("assembler");
// Mobs
case ASSET_MOB: return LoadTexEco("enemy1");
default: break;
}

View File

@ -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);

View File

@ -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)\

View File

@ -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),

View File

@ -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])))

View File

@ -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)\

View File

@ -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;

View File

@ -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);

View File

@ -117,3 +117,21 @@ uint64_t storage_spawn(void) {
}
//------------------------------------------------------------------------
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;
}

View File

@ -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;
}
}
}

View File

@ -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);
}
@ -243,16 +337,22 @@ void SystemsImport(ecs_world_t *ecs) {
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_SYSTEM(ecs, IntegratePositions, EcsOnValidate, components.Position, components.Velocity);
ECS_OBSERVER(ecs, OnHealthChangePutDelay, EcsOnAdd, components.HealthDecreased);
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);
@ -276,11 +376,20 @@ void SystemsImport(ecs_world_t *ecs) {
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);
}

View File

@ -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;
}

View File

@ -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);

View File

@ -1,2 +1,3 @@
add_subdirectory(sandbox)
add_subdirectory(minimal)
add_subdirectory(survival)

View File

@ -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)

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -0,0 +1,5 @@
#include "core/rules.h"
void rules_setup() {
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -1,6 +1,7 @@
add_subdirectory(flecs)
add_subdirectory(cwpack)
add_subdirectory(raylib-nuklear)
add_subdirectory(ferox)
add_library(vendors-bundle STATIC
sfd.c

View File

@ -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)

View File

@ -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

View File

@ -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);
}

View File

@ -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;
}

195
code/vendors/ferox/src/debug.c vendored 100644
View File

@ -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

529
code/vendors/ferox/src/dynamics.c vendored 100644
View File

@ -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;
}

View File

@ -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

474
code/vendors/ferox/src/geometry.c vendored 100644
View File

@ -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;
}

65
code/vendors/ferox/src/timer.c vendored 100644
View File

@ -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);
}

48
code/vendors/ferox/src/utils.c vendored 100644
View File

@ -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;
}

377
code/vendors/ferox/src/world.c vendored 100644
View File

@ -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);
}