Licar/racing.h

377 lines
10 KiB
C

/**
racing module: implements the racing physics and logic.
*/
#ifndef _LCR_RACING_H
#define _LCR_RACING_H
typedef int32_t LCR_GameUnit; ///< abstract game unit
#define LCR_GAME_UNIT 1024 ///< length of map square in LCR_GameUnits
#define LCR_RACING_INPUT_FORW 0x01
#define LCR_RACING_INPUT_RIGHT 0x02
#define LCR_RACING_INPUT_BACK 0x04
#define LCR_RACING_INPUT_LEFT 0x08
#define LCR_PHYSICS_UNIT 1024 ///< length of map square for physics engine
#include "map.h"
#include "tinyphysicsengine.h"
#define LCR_CAR_JOINTS 5
#define LCR_CAR_CONNECTIONS 10
#define LCR_GRAVITY (LCR_PHYSICS_UNIT / 100)
#define LCR_CAR_FORWARD_FRICTION TPE_F / 14
#define LCR_CAR_TURN_FRICTION (3 * TPE_F / 4)
#define LCR_CAR_ELASTICITY (TPE_F / 100)
#define LCR_CAR_ACCELERATION (LCR_PHYSICS_UNIT / 20)
#define LCR_CAR_TURN_SPEED (LCR_GAME_UNIT / 3)
#define LCR_CAR_TURN_MAX (LCR_GAME_UNIT / 4)
struct
{
TPE_World physicsWorld;
TPE_Body carBody;
TPE_Joint carJoints[LCR_CAR_JOINTS];
TPE_Connection carConnections[LCR_CAR_CONNECTIONS];
uint8_t wheelCollisions; /**< In individual bits records for each car wheel
whether it's currently touching the ground.
Lower bits record current collisions, higher
bits the previous state (for averaging). */
TPE_Vec3 carPositions[2]; ///* Current and previous position.
LCR_GameUnit wheelRotation;
LCR_GameUnit wheelSteer;
} LCR_racing;
TPE_Vec3 _LCR_TPE_vec3DividePlain(TPE_Vec3 v, TPE_Unit d)
{
v.x /= d;
v.y /= d;
v.z /= d;
return v;
}
TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist)
{
return TPE_envAABoxInside(point,TPE_vec3(0,0,0),TPE_vec3(
LCR_PHYSICS_UNIT * LCR_MAP_SIZE_BLOCKS,
(LCR_PHYSICS_UNIT * LCR_MAP_SIZE_BLOCKS) / 2,
LCR_PHYSICS_UNIT * LCR_MAP_SIZE_BLOCKS));
}
LCR_GameUnit LCR_racingGetCarSpeed(void)
{
return (TPE_vec3Len(TPE_vec3(
LCR_racing.carBody.joints[4].velocity[0],
LCR_racing.carBody.joints[4].velocity[1],
LCR_racing.carBody.joints[4].velocity[2])) * LCR_GAME_UNIT)
/ LCR_PHYSICS_UNIT;
}
uint8_t _LCR_racingCollisionHandler(uint16_t b1, uint16_t j1, uint16_t b2,
uint16_t j2, TPE_Vec3 p)
{
// check which wheels are touching the ground.
if (j1 < 4) // wheel joint?
LCR_racing.wheelCollisions |= 0x01 << j1;
return 1;
}
/**
Initializes new run.
*/
void LCR_racingRestart(void)
{
TPE_bodyActivate(&(LCR_racing.carBody));
LCR_racing.wheelCollisions = 0;
LCR_racing.wheelRotation = 0;
LCR_racing.wheelSteer = 0;
// TODO
}
/**
Initializes the racing module, only call once.
*/
void LCR_racingInit(void)
{
LCR_log("initializing racing engine");
// make the car body:
TPE_makeCenterRectFull(LCR_racing.carJoints,
LCR_racing.carConnections,
LCR_PHYSICS_UNIT / 2,
(LCR_PHYSICS_UNIT * 3) / 4,
LCR_PHYSICS_UNIT / 8);
LCR_racing.carJoints[4].position.y += LCR_PHYSICS_UNIT / 6;
LCR_racing.carJoints[4].sizeDivided *= 3;
LCR_racing.carJoints[4].sizeDivided /= 2;
TPE_bodyInit(&(LCR_racing.carBody),
LCR_racing.carJoints,LCR_CAR_JOINTS,
LCR_racing.carConnections,LCR_CAR_CONNECTIONS,
TPE_F);
TPE_worldInit(&(LCR_racing.physicsWorld),
&(LCR_racing.carBody),1,_LCR_racingEnvironmentFunction);
LCR_racing.physicsWorld.collisionCallback = _LCR_racingCollisionHandler;
LCR_racing.carBody.friction = LCR_CAR_FORWARD_FRICTION;
LCR_racing.carBody.elasticity = LCR_CAR_ELASTICITY;
LCR_racing.carBody.flags |= TPE_BODY_FLAG_ALWAYS_ACTIVE;
LCR_racingRestart();
}
void LCR_racingGetCarTransform(LCR_GameUnit position[3],
LCR_GameUnit rotation[3], LCR_GameUnit interpolationParam)
{
#if LCR_SETTING_SMOOTH_ANIMATIONS
TPE_Vec3 v = TPE_vec3Plus(
LCR_racing.carPositions[1],
_LCR_TPE_vec3DividePlain(
TPE_vec3TimesPlain(
TPE_vec3Minus(
LCR_racing.carPositions[0],LCR_racing.carPositions[1]),
interpolationParam),LCR_GAME_UNIT));
position[0] = v.x;
position[1] = v.y;
position[2] = v.z;
#else
TPE_Vec3 v;
position[0] = LCR_racing.carPositions[0].x;
position[1] = LCR_racing.carPositions[0].y;
position[2] = LCR_racing.carPositions[0].z;
#endif
v = TPE_bodyGetRotation(&(LCR_racing.carBody),0,2,1);
rotation[0] = (v.x * LCR_GAME_UNIT) / TPE_F;
rotation[1] = (v.y * LCR_GAME_UNIT) / TPE_F;
rotation[2] = (v.z * LCR_GAME_UNIT) / TPE_F;
// TODO: also smooth out rotation?
}
void _LCR_drawPhysicsDebugPixel(uint16_t x, uint16_t y, uint8_t color)
{
if (x > 1 && x < LCR_EFFECTIVE_RESOLUTION_X - 2 &&
y > 1 && y < LCR_EFFECTIVE_RESOLUTION_Y - 2)
{
uint16_t c = 0x8101 | (0x8f1f << (2 * color));
for (int j = -1; j <= 2; ++j)
for (int i = -1; i <= 2; ++i)
LCR_drawPixelXYUnsafe(x + i,y + j,c);
}
}
int LCR_racingCarWheelTouchesGround(int wheel)
{
return ((LCR_racing.wheelCollisions & (LCR_racing.wheelCollisions >> 4))
>> wheel) & 0x01;
}
LCR_GameUnit LCR_racingGetWheelRotation(void)
{
return LCR_racing.wheelRotation;
}
LCR_GameUnit LCR_racingGetWheelSteer(void)
{
return LCR_racing.wheelSteer;
}
void _LCR_racingWheelAccelerate(unsigned int wheel, TPE_Vec3 dir)
{
LCR_racing.carBody.joints[wheel].velocity[0] +=
(dir.x * LCR_CAR_ACCELERATION) / TPE_F;
LCR_racing.carBody.joints[wheel].velocity[1] +=
(dir.y * LCR_CAR_ACCELERATION) / TPE_F;
LCR_racing.carBody.joints[wheel].velocity[2] +=
(dir.z * LCR_CAR_ACCELERATION) / TPE_F;
}
/**
Updates the racing physics world, call every LCR_RACING_TICK_MS milliseconds.
*/
void LCR_racingStep(unsigned int input)
{
TPE_Vec3 carForw, carRight, carUp;
carForw = TPE_vec3Normalized(TPE_vec3Plus(
TPE_vec3Minus(LCR_racing.carBody.joints[0].position,
LCR_racing.carBody.joints[2].position),
TPE_vec3Minus(LCR_racing.carBody.joints[1].position,
LCR_racing.carBody.joints[3].position)));
carRight = TPE_vec3Normalized(TPE_vec3Plus(
TPE_vec3Minus(LCR_racing.carBody.joints[0].position,
LCR_racing.carBody.joints[1].position),
TPE_vec3Minus(LCR_racing.carBody.joints[2].position,
LCR_racing.carBody.joints[3].position)));
carUp = TPE_vec3Cross(carForw,carRight);
if (input)
{
unsigned char steering = 0;
// TODO: magic constants
if (input & (LCR_RACING_INPUT_FORW | LCR_RACING_INPUT_BACK))
{
// TODO: in air always rotate wheels
LCR_racing.wheelRotation = LCR_racing.wheelRotation +
(LCR_racingGetCarSpeed() / 32)
% LCR_GAME_UNIT;
if (LCR_racing.wheelRotation < 0)
LCR_racing.wheelRotation += LCR_GAME_UNIT;
}
if (input & LCR_RACING_INPUT_RIGHT)
{
steering = 2;
LCR_racing.wheelSteer = TPE_min(
LCR_racing.wheelSteer + LCR_CAR_TURN_SPEED,
LCR_CAR_TURN_MAX);
}
else if (input & LCR_RACING_INPUT_LEFT)
{
steering = 1;
LCR_racing.wheelSteer = TPE_max(
LCR_racing.wheelSteer - LCR_CAR_TURN_SPEED,
-1 * LCR_CAR_TURN_MAX);
}
if ((LCR_racing.wheelCollisions & 0x0c)) // back wheel on ground?
{
if (input & LCR_RACING_INPUT_FORW)
{
_LCR_racingWheelAccelerate(0,carForw);
_LCR_racingWheelAccelerate(1,carForw);
}
else if (input & LCR_RACING_INPUT_BACK)
{
_LCR_racingWheelAccelerate(0,TPE_vec3TimesPlain(carForw,-1));
_LCR_racingWheelAccelerate(1,TPE_vec3TimesPlain(carForw,-1));
}
}
for (int i = 0; i < 4; ++i)
if (LCR_racing.wheelCollisions & (0x01 << i)) // wheel on ground?
{
TPE_Vec3 jv = TPE_vec3( // joint velocity
LCR_racing.carBody.joints[i].velocity[0],
LCR_racing.carBody.joints[i].velocity[1],
LCR_racing.carBody.joints[i].velocity[2]);
TPE_Vec3 ja = carRight; // wheel axis of rotation
if (i >= 2 && steering)
{
// for front wheels with turning we tilt the wheel axis 45 degrees
TPE_Unit steer =
(LCR_racing.wheelSteer * TPE_F) / LCR_GAME_UNIT;
ja = TPE_vec3Normalized(
TPE_vec3Plus(TPE_vec3Times(carForw,steer),carRight));
}
/* friction is in the direction if the axis and its magnitude is
determined by the dot product (angle) of the axis and velocity */
TPE_Vec3 fric = TPE_vec3Times(ja,(TPE_vec3Dot(ja,jv) *
LCR_CAR_TURN_FRICTION) / TPE_F);
jv = TPE_vec3Minus(jv,fric); // subtract the friction
LCR_racing.carBody.joints[i].velocity[0] = jv.x;
LCR_racing.carBody.joints[i].velocity[1] = jv.y;
LCR_racing.carBody.joints[i].velocity[2] = jv.z;
}
}
if ((!(input & LCR_RACING_INPUT_LEFT)) &&
(!(input & LCR_RACING_INPUT_RIGHT)))
LCR_racing.wheelSteer /= 2;
if ((LCR_racing.wheelCollisions & 0x0f) != 0x0f) // EXPERIMENTAL: don't apply gravity with all wheels on ground
TPE_bodyApplyGravity(&(LCR_racing.carBody),LCR_GRAVITY);
LCR_racing.wheelCollisions <<= 4;
TPE_worldStep(&(LCR_racing.physicsWorld));
if (TPE_vec3Dot(carUp,TPE_vec3Minus(LCR_racing.carBody.joints[4].position,
LCR_racing.carBody.joints[0].position)) < 0)
{
/* if the car falls on its roof the center joint may flip to the other
side, here we fix it */
// LCR_log("car flipped over, fixing");
LCR_racing.carBody.joints[4].position = TPE_vec3Plus(TPE_vec3Times(carUp,
LCR_GAME_UNIT / 4),LCR_racing.carBody.joints[4].position);
}
TPE_Vec3 tmpVec = LCR_racing.carPositions[0];
LCR_racing.carPositions[0] =
_LCR_TPE_vec3DividePlain(
TPE_vec3TimesPlain(
LCR_racing.carBody.joints[4].position,LCR_GAME_UNIT),
LCR_PHYSICS_UNIT);
LCR_racing.carPositions[0] =
TPE_vec3KeepWithinBox(
LCR_racing.carPositions[1],
LCR_racing.carPositions[0],
TPE_vec3(
LCR_PHYSICS_UNIT / 64, // TODO: 64
LCR_PHYSICS_UNIT / 64,
LCR_PHYSICS_UNIT / 64
)
);
LCR_racing.carPositions[1] = tmpVec;
}
void LCR_physicsDebugDraw(LCR_GameUnit camPos[3], LCR_GameUnit camRot[2],
LCR_GameUnit camFov)
{
TPE_Vec3 cPos, cRot, cView;
cPos.x = (camPos[0] * LCR_PHYSICS_UNIT) / LCR_GAME_UNIT;
cPos.y = (camPos[1] * LCR_PHYSICS_UNIT) / LCR_GAME_UNIT;
cPos.z = (camPos[2] * LCR_PHYSICS_UNIT) / LCR_GAME_UNIT;
cRot.x = (camRot[0] * TPE_F) / LCR_GAME_UNIT;
cRot.y = (camRot[1] * TPE_F) / LCR_GAME_UNIT;
cRot.z = 0;
cView.x = LCR_EFFECTIVE_RESOLUTION_X;
cView.y = LCR_EFFECTIVE_RESOLUTION_Y;
cView.z = (camFov * TPE_F) / LCR_GAME_UNIT;
TPE_worldDebugDraw(&(LCR_racing.physicsWorld),_LCR_drawPhysicsDebugPixel,
cPos,cRot,cView,16,2 * LCR_PHYSICS_UNIT);
}
#endif // guard