nitro-engine/source/NEPhysics.c
Antonio Niño Díaz f57fa76576 library: Handle failures correctly
The previous code relied too much on assertions, even for things that
are just regular things that may happen while the application is running
(like running out of memory).

- Many assertions have been turned into permanent checks
- Some functions now return "success" or "failure".
- NE_Init3D() and NE_InitDual3D() (and others) have been reworked to
  handle failures gracefully.
2023-04-21 18:19:02 +01:00

457 lines
13 KiB
C

// SPDX-License-Identifier: MIT
//
// Copyright (c) 2008-2011, 2019, 2022 Antonio Niño Díaz
//
// This file is part of Nitro Engine
#include "NEMain.h"
/// @file NEPhysics.c
static NE_Physics **NE_PhysicsPointers;
static bool ne_physics_system_inited = false;
static int NE_MAX_PHYSICS;
NE_Physics *NE_PhysicsCreate(NE_PhysicsTypes type)
{
if (!ne_physics_system_inited)
{
NE_DebugPrint("System not initialized");
return NULL;
}
// TODO
if (type == NE_BoundingSphere)
{
NE_DebugPrint("Bounding spheres not supported");
return NULL;
}
if (type == NE_Dot)
{
NE_DebugPrint("Dots not supported");
return NULL;
}
NE_Physics *temp = calloc(1, sizeof(NE_Physics));
if (temp == NULL)
{
NE_DebugPrint("Not enough memory");
return NULL;
}
int i = 0;
while (1)
{
if (i == NE_MAX_PHYSICS)
{
free(temp);
NE_DebugPrint("No free slots");
return NULL;
}
if (NE_PhysicsPointers[i] == NULL)
{
NE_PhysicsPointers[i] = temp;
break;
}
i++;
}
temp->type = type;
temp->keptpercent = 50;
temp->enabled = true;
temp->physicsgroup = 0;
temp->oncollision = NE_ColNothing;
return temp;
}
void NE_PhysicsDelete(NE_Physics *pointer)
{
if (!ne_physics_system_inited)
return;
NE_AssertPointer(pointer, "NULL pointer");
int i = 0;
while (1)
{
if (i == NE_MAX_PHYSICS)
{
NE_DebugPrint("Object not found");
return;
}
if (NE_PhysicsPointers[i] == pointer)
{
NE_PhysicsPointers[i] = NULL;
free(pointer);
return;
}
i++;
}
}
void NE_PhysicsDeleteAll(void)
{
if (!ne_physics_system_inited)
return;
for (int i = 0; i < NE_MAX_PHYSICS; i++)
NE_PhysicsDelete(NE_PhysicsPointers[i]);
}
int NE_PhysicsSystemReset(int max_objects)
{
if (ne_physics_system_inited)
NE_PhysicsSystemEnd();
if (max_objects < 1)
NE_MAX_PHYSICS = NE_DEFAULT_PHYSICS;
else
NE_MAX_PHYSICS = max_objects;
NE_PhysicsPointers = calloc(NE_MAX_PHYSICS, sizeof(NE_PhysicsPointers));
if (NE_PhysicsPointers == NULL)
{
NE_DebugPrint("Not enough memory");
return -1;
}
ne_physics_system_inited = true;
return 0;
}
void NE_PhysicsSystemEnd(void)
{
if (!ne_physics_system_inited)
return;
NE_PhysicsDeleteAll();
free(NE_PhysicsPointers);
ne_physics_system_inited = false;
}
void NE_PhysicsSetRadiusI(NE_Physics *pointer, int radius)
{
NE_AssertPointer(pointer, "NULL pointer");
NE_Assert(pointer->type == NE_BoundingSphere, "Not a bounding shpere");
NE_Assert(radius >= 0, "Radius must be positive");
pointer->radius = radius;
}
void NE_PhysicsSetSpeedI(NE_Physics *pointer, int x, int y, int z)
{
NE_AssertPointer(pointer, "NULL pointer");
pointer->xspeed = x;
pointer->yspeed = y;
pointer->zspeed = z;
}
void NE_PhysicsSetSizeI(NE_Physics *pointer, int x, int y, int z)
{
NE_AssertPointer(pointer, "NULL pointer");
NE_Assert(pointer->type == NE_BoundingBox, "Not a bounding box");
NE_Assert(x >= 0 && y >= 0 && z >= 0, "Size must be positive!!");
pointer->xsize = x;
pointer->ysize = y;
pointer->zsize = z;
}
void NE_PhysicsSetGravityI(NE_Physics *pointer, int gravity)
{
NE_AssertPointer(pointer, "NULL pointer");
pointer->gravity = gravity;
}
void NE_PhysicsSetFrictionI(NE_Physics *pointer, int friction)
{
NE_AssertPointer(pointer, "NULL pointer");
NE_Assert(friction >= 0, "Friction must be positive");
pointer->friction = friction;
}
void NE_PhysicsSetBounceEnergy(NE_Physics *pointer, int percent)
{
NE_AssertPointer(pointer, "NULL pointer");
NE_Assert(percent >= 0, "Percentage must be positive");
pointer->keptpercent = percent;
}
void NE_PhysicsEnable(NE_Physics *pointer, bool value)
{
NE_AssertPointer(pointer, "NULL pointer");
pointer->enabled = value;
}
void NE_PhysicsSetModel(NE_Physics *physics, NE_Model *modelpointer)
{
NE_AssertPointer(physics, "NULL physics pointer");
NE_AssertPointer(modelpointer, "NULL model pointer");
physics->model = modelpointer;
}
void NE_PhysicsSetGroup(NE_Physics *physics, int group)
{
NE_AssertPointer(physics, "NULL pointer");
physics->physicsgroup = group;
}
void NE_PhysicsOnCollision(NE_Physics *physics, NE_OnCollision action)
{
NE_AssertPointer(physics, "NULL pointer");
physics->oncollision = action;
}
bool NE_PhysicsIsColliding(const NE_Physics *pointer)
{
NE_AssertPointer(pointer, "NULL pointer");
return pointer->iscolliding;
}
void NE_PhysicsUpdateAll(void)
{
if (!ne_physics_system_inited)
return;
for (int i = 0; i < NE_MAX_PHYSICS; i++)
{
if (NE_PhysicsPointers[i] != NULL)
NE_PhysicsUpdate(NE_PhysicsPointers[i]);
}
}
void NE_PhysicsUpdate(NE_Physics *pointer)
{
if (!ne_physics_system_inited)
return;
NE_AssertPointer(pointer, "NULL pointer");
NE_AssertPointer(pointer->model, "NULL model pointer");
NE_Assert(pointer->type != 0, "Object has no type");
if (pointer->enabled == false)
return;
pointer->iscolliding = false;
// We change Y speed depending on gravity.
pointer->yspeed -= pointer->gravity;
// Now, let's move the object
// Used in collision checking to simplify the code
int posx = 0, posy = 0, posz = 0;
// Position before movement
int bposx = 0, bposy = 0, bposz = 0;
NE_Model *model = pointer->model;
bposx = model->x;
bposy = model->y;
bposz = model->z;
posx = model->x = model->x + pointer->xspeed;
posy = model->y = model->y + pointer->yspeed;
posz = model->z = model->z + pointer->zspeed;
// Gravity and movement have been applied, time to check collisions...
bool xenabled = true, yenabled = true, zenabled = true;
if (bposx == posx)
xenabled = false;
if (bposy == posy)
yenabled = false;
if (bposz == posz)
zenabled = false;
for (int i = 0; i < NE_MAX_PHYSICS; i++)
{
if (NE_PhysicsPointers[i] == NULL)
continue;
// Check that we aren't checking an object with itself
if (NE_PhysicsPointers[i] == pointer)
continue;
// Check that both objects are in the same group
if (NE_PhysicsPointers[i]->physicsgroup != pointer->physicsgroup)
continue;
NE_Physics *otherpointer = NE_PhysicsPointers[i];
//Get coordinates
int otherposx = 0, otherposy = 0, otherposz = 0;
model = otherpointer->model;
otherposx = model->x;
otherposy = model->y;
otherposz = model->z;
// Both are boxes
if (pointer->type == NE_BoundingBox && otherpointer->type == NE_BoundingBox)
{
bool collision =
((abs(posx - otherposx) < (pointer->xsize + otherpointer->xsize) >> 1) &&
(abs(posy - otherposy) < (pointer->ysize + otherpointer->ysize) >> 1) &&
(abs(posz - otherposz) < (pointer->zsize + otherpointer->zsize) >> 1));
if (!collision)
continue;
pointer->iscolliding = true;
if (pointer->oncollision == NE_ColBounce)
{
// Used to reduce speed:
int temp = divf32(inttof32(pointer->keptpercent), inttof32(100));
if ((yenabled) && ((abs(bposy - otherposy) >= (pointer->ysize + otherpointer->ysize) >> 1)))
{
yenabled = false;
pointer->yspeed += pointer->gravity;
if (posy > otherposy)
(pointer->model)->y = otherposy + ((pointer->ysize + otherpointer->ysize) >> 1);
if (posy < otherposy)
(pointer->model)->y = otherposy - ((pointer->ysize + otherpointer->ysize) >> 1);
if (pointer->gravity == 0)
{
pointer->yspeed =
-mulf32(temp, pointer->yspeed);
}
else
{
if (abs(pointer->yspeed) > NE_MIN_BOUNCE_SPEED)
{
pointer->yspeed = -mulf32(temp, pointer->yspeed - pointer->gravity);
}
else
{
pointer->yspeed = 0;
}
}
}
else if ((xenabled) && ((abs(bposx - otherposx) >= (pointer->xsize + otherpointer->xsize) >> 1)))
{
xenabled = false;
if (posx > otherposx)
(pointer->model)->x = otherposx + ((pointer->xsize + otherpointer->xsize) >> 1);
if (posx < otherposx)
(pointer->model)->x = otherposx - ((pointer->xsize + otherpointer->xsize) >> 1);
pointer->xspeed = -mulf32(temp, pointer->xspeed);
}
else if ((zenabled) && ((abs(bposz - otherposz) >= (pointer->zsize + otherpointer->zsize) >> 1)))
{
zenabled = false;
if (posz > otherposz)
(pointer->model)->z = otherposz + ((pointer->zsize + otherpointer->zsize) >> 1);
if (posz < otherposz)
(pointer->model)->z = otherposz - ((pointer->zsize + otherpointer->zsize) >> 1);
pointer->zspeed = -mulf32(temp, pointer->zspeed);
}
}
else if (pointer->oncollision == NE_ColStop)
{
if ((yenabled) && ((abs(bposy - otherposy) >= (pointer->ysize + otherpointer->ysize) >> 1)))
{
yenabled = false;
if (posy > otherposy)
(pointer->model)->y = otherposy + ((pointer->ysize + otherpointer->ysize) >> 1);
if (posy < otherposy)
(pointer->model)->y = otherposy - ((pointer->ysize + otherpointer->ysize) >> 1);
}
if ((xenabled) && ((abs(bposx - otherposx) >= (pointer->xsize + otherpointer->xsize) >> 1)))
{
xenabled = false;
if (posx > otherposx)
(pointer->model)->x = otherposx + ((pointer->xsize + otherpointer->xsize) >> 1);
if (posx < otherposx)
(pointer->model)->x = otherposx - ((pointer->xsize + otherpointer->xsize) >> 1);
}
if ((zenabled) && ((abs(bposz - otherposz) >= (pointer->zsize + otherpointer->zsize) >> 1)))
{
zenabled = false;
if (posz > otherposz)
(pointer->model)->z = otherposz + ((pointer->zsize + otherpointer->zsize) >> 1);
if (posz < otherposz)
(pointer->model)->z = otherposz - ((pointer->zsize + otherpointer->zsize) >> 1);
}
pointer->xspeed = pointer->yspeed = pointer->zspeed = 0;
}
}
}
// Now, we get the module of speed in order to apply friction.
if (pointer->friction != 0)
{
pointer->xspeed <<= 10;
pointer->yspeed <<= 10;
pointer->zspeed <<= 10;
int _mod_ = mulf32(pointer->xspeed, pointer->xspeed);
_mod_ += mulf32(pointer->yspeed, pointer->yspeed);
_mod_ += mulf32(pointer->zspeed, pointer->zspeed);
_mod_ = sqrtf32(_mod_);
// Check if module is very small -> speed = 0
if (_mod_ < pointer->friction)
{
pointer->xspeed = pointer->yspeed = pointer->zspeed = 0;
}
else
{
int newmod = _mod_ - pointer->friction;
// mod -- newmod -> newspeed = speed * newmod / mod
// speed -- newspeed
int number = divf32(newmod, _mod_);
pointer->xspeed = mulf32(pointer->xspeed, number);
pointer->yspeed = mulf32(pointer->yspeed, number);
pointer->zspeed = mulf32(pointer->zspeed, number);
pointer->xspeed >>= 10;
pointer->yspeed >>= 10;
pointer->zspeed >>= 10;
}
}
}
bool NE_PhysicsCheckCollision(const NE_Physics *pointer1,
const NE_Physics *pointer2)
{
NE_AssertPointer(pointer1, "NULL pointer 1");
NE_AssertPointer(pointer2, "NULL pointer 2");
NE_Assert(pointer1 != pointer2, "Both objects are the same one");
// Get coordinates
int posx = 0, posy = 0, posz = 0;
NE_Model *model = pointer1->model;
posx = model->x;
posy = model->y;
posz = model->z;
int otherposx = 0, otherposy = 0, otherposz = 0;
model = pointer2->model;
otherposx = model->x;
otherposy = model->y;
otherposz = model->z;
// Both are boxes
//if(pointer->type == NE_BoundingBox && otherpointer->type == NE_BoundingBox)
//{
if ((abs(posx - otherposx) < (pointer1->xsize + pointer2->xsize) >> 1) &&
(abs(posy - otherposy) < (pointer1->ysize + pointer2->ysize) >> 1) &&
(abs(posz - otherposz) < (pointer1->zsize + pointer2->zsize) >> 1))
{
return true;
}
// else if (...) TODO: Spheres and dots
return false;
}