What I am trying to do is create a bit more advanced physics for my (currently blocky) voxel engine. By now means am I an expert on computer graphics or physics... I just love to tinker around and learn a thing or two. (This was kinda the disclaimer)
Now my old physics implementation was really rudimentary, but it worked (kinda):
void World::doPhysics(std::shared_ptr<Entity> entity)
{
glm::vec3 distanceToGravityPoint =
entity->getPosition() -
((entity->getPosition() - gravityPoint) * 0.005d); // TODO multiply by time
glm::vec3 oldPos = entity->getPosition();
entity->setPosition(distanceToGravityPoint);
glm::vec3 potentiallyNewPosition = entity->getPosition();
glm::vec3 checkPos = glm::vec3(floorf(potentiallyNewPosition.x), floorf(potentiallyNewPosition.y), floorf(potentiallyNewPosition.z));
if (getBlock(checkPos.x, checkPos.y, checkPos.z))
{
glm::vec3 floorPos = checkPos + glm::vec3(0,1,0); // If we are in the ground, we need to add offset to y
glm::vec3 newPos = glm::vec3(oldPos.x, floorPos.y, oldPos.z);
glm::vec3 sameLevel = floorPos;
if(getBlock(sameLevel.x, sameLevel.y, sameLevel.z))
{
newPos = glm::vec3(checkPos.x, newPos.y, checkPos.z);
}
entity->setPosition(newPos);
entity->grounded = true;
}
}
}
What this did was simply move the entity towards the "gravityPoint" and if there was a hit in between, it would determine the last valid position (not really, was hackish and towards walls it was really choppy and unreliable, but against the floor it was okay)
Now for the real physics I wanted this to be a little more sophisticated:
1) More real world like -> Switching to Newton's Law of universal gravity
2) Possibility for multiple sources of gravity that would allow for interesting gameplay (unfinished, code only features one)
void World::newPhysics(std::shared_ptr<Entity> entity)
{
if (!entity->grounded)
{
glm::vec3 oldPos = entity->getPosition();
glm::vec3 nearestGravityPoint = this->gravityPoint; // TODO obviously
// TODO assuming every gravity point has mass of 50 here!
float gravityPointMass = 50;
float gravitationalConstant = 1;
glm::vec3 diff = oldPos - nearestGravityPoint;
glm::vec3 direction = glm::normalize(diff);
double distance = glm::length2(diff);
glm::vec3 force =
gravitationalConstant *
((entity->getMass() * gravityPointMass) / distance)
* direction;
entity->setPosition(entity->getPosition() - force);
glm::vec3 potentiallyNewPosition = entity->getPosition();
glm::vec3 checkPos = glm::vec3(floorf(potentiallyNewPosition.x), floorf(potentiallyNewPosition.y), floorf(potentiallyNewPosition.z));
std::cout << "OLD POS: " << glm::to_string(oldPos) << std::endl;
std::cout << "FORCE: " << glm::to_string(force) << std::endl;
std::cout << "potentially new position: " << glm::to_string(potentiallyNewPosition) << std::endl;
std::cout << "checkPos: " << glm::to_string(checkPos) << std::endl;
glm::vec3 diff2 = oldPos - potentiallyNewPosition;
if (getBlock(checkPos.x, checkPos.y, checkPos.z))
{
glm::vec3 floorPos = checkPos + glm::vec3(0, 1, 0); // If we are in the ground, we need to add offset to y
glm::vec3 newPos = glm::vec3(oldPos.x, floorPos.y, oldPos.z);
glm::vec3 sameLevel = floorPos;
if (getBlock(sameLevel.x, sameLevel.y, sameLevel.z))
{
newPos = glm::vec3(checkPos.x, newPos.y, checkPos.z);
}
entity->setPosition(newPos);
entity->grounded = true;
std::cout << "GROUNDED" << std::endl;
}
}
}
Now this does not work. The entity is warping through the ground, then again being teleported way up into space and falling again, slowly. Sometimes it is even grounded, but often too late, when it is already beneath the ground.
I think I do understand why this happens (Please do correct me if I am wrong/not entirely correct/forget something):
To have a common base, here is some log:
OLD POS: vec3(0.150880, 5.958805, -0.063934)
FORCE: vec3(0.071230, 2.813126, -0.030183)
potentially new position: vec3(0.079650, 3.145679, -0.033751)
checkPos: vec3(0.000000, 3.000000, -1.000000)
GROUNDED
Seconds since last frame: 0.006
OLD POS: vec3(0.123258, 4.000000, -0.052229)
FORCE: vec3(0.192267, 6.239515, -0.081471)
potentially new position: vec3(-0.069009, -2.239515, 0.029242)
checkPos: vec3(-1.000000, -3.000000, 0.000000)
GROUNDED
...... a few dozen more lines, as the entity got shot up back high into the air
OLD POS: vec3(-0.468390, 9.671287, 0.198475)
FORCE: vec3(-0.051565, 1.064711, 0.021850)
potentially new position: vec3(-0.416825, 8.606576, 0.176625)
checkPos: vec3(-1.000000, 8.000000, 0.000000)
OLD POS: vec3(-0.416825, 8.606576, 0.176625)
FORCE: vec3(-0.065112, 1.344433, 0.027591)
potentially new position: vec3(-0.351713, 7.262143, 0.149034)
checkPos: vec3(-1.000000, 7.000000, 0.000000)
OLD POS: vec3(-0.351713, 7.262143, 0.149034)
FORCE: vec3(-0.091452, 1.888298, 0.038752)
potentially new position: vec3(-0.260261, 5.373845, 0.110282)
checkPos: vec3(-1.000000, 5.000000, 0.000000)
OLD POS: vec3(-0.260261, 5.373845, 0.110282)
FORCE: vec3(-0.167014, 3.448498, 0.070770)
potentially new position: vec3(-0.093247, 1.925347, 0.039512)
checkPos: vec3(-1.000000, 1.000000, 0.000000)
GROUNDED
Sometimes the "potentiallyNewPos" is already beneath the ground (when the force was strong in this one [iteration] *pun intended*)
then it checks a block beneath the earth and just moves the entity up one block, as the algorithm assumes that there is the ground.
To circumvent this I assume that I could do a rather nasty nested loop and check all the blocks between the old position and the potentially new one and set the position once we have a hit (and break the loop).
But I think that is rather nasty, especially if the force is big it could be that I check a lot of blocks and there could be cases where there is only air between the two points, then the whole check is ridiculous.
Maybe it's just too late here, but I am out of nice fixes here and would appreciate any help I can get!