950 lines
29 KiB
C
950 lines
29 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_RACING_EVENT_CP_TAKEN 0x0001
|
|
#define LCR_RACING_EVENT_FINISHED 0x0002
|
|
#define LCR_RACING_EVENT_CRASH_SMALL 0x0004
|
|
#define LCR_RACING_EVENT_CRASH_BIG 0x0008
|
|
|
|
#define LCR_PHYSICS_UNIT 2048 ///< length of map square for physics engine
|
|
|
|
#define TPE_RESHAPE_TENSION_LIMIT 10
|
|
#define TPE_RESHAPE_ITERATIONS 5
|
|
|
|
#include "map.h"
|
|
#include "tinyphysicsengine.h"
|
|
|
|
// TODO: move some of this to constants?
|
|
|
|
#define LCR_GRAVITY (LCR_PHYSICS_UNIT / 160)
|
|
#define LCR_CAR_FORWARD_FRICTION (TPE_F / 180)
|
|
#define LCR_CAR_AIR_FRICTION ((LCR_GAME_UNIT * 3) / 4)
|
|
#define LCR_CAR_STAND_FRICTION_MULTIPLIER 16
|
|
#define LCR_CAR_STEER_FRICTION (TPE_F)
|
|
#define LCR_CAR_ELASTICITY (TPE_F / 50)
|
|
#define LCR_CAR_ACCELERATION (LCR_PHYSICS_UNIT / 90)
|
|
#define LCR_CAR_STEER_SPEED (LCR_GAME_UNIT / 32)
|
|
#define LCR_CAR_STEER_MAX ((7 * LCR_GAME_UNIT) / 24)
|
|
|
|
// TODO
|
|
#define LCR_CAR_FORWARD_FRICTION_ICE (TPE_F / 200)
|
|
#define LCR_CAR_STEER_FRICTION_ICE (TPE_F / 20)
|
|
#define LCR_CAR_ACCELERATION_ICE (LCR_PHYSICS_UNIT / 100)
|
|
#define LCR_CAR_FORWARD_FRICTION_DIRT (TPE_F / 7)
|
|
#define LCR_CAR_STEER_FRICTION_DIRT (TPE_F / 2)
|
|
#define LCR_CAR_STEER_FRICTION_GRASS (4 * (TPE_F / 5))
|
|
#define LCR_CAR_ACCELERATION_GRASS (LCR_PHYSICS_UNIT / 20)
|
|
|
|
#define LCR_CAR_JOINTS 5
|
|
#define LCR_CAR_CONNECTIONS 10
|
|
|
|
struct
|
|
{
|
|
TPE_World physicsWorld;
|
|
TPE_Body carBody;
|
|
TPE_Joint carJoints[LCR_CAR_JOINTS];
|
|
TPE_Connection carConnections[LCR_CAR_CONNECTIONS];
|
|
uint32_t tick;
|
|
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 in game units.
|
|
TPE_Vec3 carRotations[2]; ///* Current and previous rotation in game units.
|
|
|
|
|
|
TPE_Vec3 carOKPositions[LCR_CAR_JOINTS];
|
|
uint8_t carNotOKCount;
|
|
|
|
|
|
LCR_GameUnit wheelRotation;
|
|
LCR_GameUnit wheelSteer;
|
|
|
|
|
|
LCR_GameUnit carSpeed;
|
|
|
|
} 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_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block)
|
|
{
|
|
TPE_Vec3 v, vBest;
|
|
TPE_Unit d, dBest = TPE_INFINITY;
|
|
|
|
uint8_t bx, by, bz;
|
|
LCR_mapBlockGetCoords(block,&bx,&by,&bz);
|
|
|
|
TPE_Vec3 blockOffset = TPE_vec3(
|
|
(((int) bx) - LCR_MAP_SIZE_BLOCKS / 2) * LCR_PHYSICS_UNIT,
|
|
(((int) by) - LCR_MAP_SIZE_BLOCKS / 2) * (LCR_PHYSICS_UNIT / 2),
|
|
(((int) bz) - LCR_MAP_SIZE_BLOCKS / 2) * LCR_PHYSICS_UNIT);
|
|
|
|
point = TPE_vec3Minus(point,blockOffset); // shift to origin
|
|
|
|
uint8_t transform =
|
|
LCR_mapBlockOppositeTransform(LCR_mapBlockGetTransform(block));
|
|
|
|
LCR_TRANSFORM_COORDS(transform,point.x,point.y,point.z,LCR_PHYSICS_UNIT,
|
|
(LCR_PHYSICS_UNIT / 2))
|
|
|
|
point = TPE_vec3Minus(point,
|
|
TPE_vec3(LCR_PHYSICS_UNIT / 2,LCR_PHYSICS_UNIT / 4,LCR_PHYSICS_UNIT / 2));
|
|
|
|
switch (block[0])
|
|
{
|
|
case LCR_BLOCK_FULL:
|
|
case LCR_BLOCK_BOTTOM:
|
|
case LCR_BLOCK_LEFT:
|
|
case LCR_BLOCK_BOTTOM_LEFT:
|
|
case LCR_BLOCK_BOTTOM_LEFT_FRONT:
|
|
case LCR_BLOCK_FULL_ACCEL:
|
|
case LCR_BLOCK_FULL_FAN:
|
|
{
|
|
TPE_Vec3
|
|
offset = TPE_vec3(0,0,0),
|
|
size = TPE_vec3(LCR_PHYSICS_UNIT / 2,LCR_PHYSICS_UNIT / 4,
|
|
LCR_PHYSICS_UNIT / 2);
|
|
|
|
if (block[0] == LCR_BLOCK_BOTTOM ||
|
|
block[0] == LCR_BLOCK_BOTTOM_LEFT ||
|
|
block[0] == LCR_BLOCK_BOTTOM_LEFT_FRONT)
|
|
{
|
|
offset.y -= LCR_PHYSICS_UNIT / 8;
|
|
size.y = LCR_PHYSICS_UNIT / 8;
|
|
}
|
|
|
|
if (block[0] == LCR_BLOCK_LEFT ||
|
|
block[0] == LCR_BLOCK_BOTTOM_LEFT ||
|
|
block[0] == LCR_BLOCK_BOTTOM_LEFT_FRONT)
|
|
{
|
|
offset.x -= LCR_PHYSICS_UNIT / 4;
|
|
size.x = LCR_PHYSICS_UNIT / 4;
|
|
}
|
|
|
|
if (block[0] == LCR_BLOCK_BOTTOM_LEFT_FRONT)
|
|
{
|
|
offset.z -= LCR_PHYSICS_UNIT / 4;
|
|
size.z = LCR_PHYSICS_UNIT / 4;
|
|
}
|
|
|
|
point = TPE_envAABox(point,offset,size);
|
|
break;
|
|
}
|
|
|
|
#define _CHECK_NEXT(check)\
|
|
v = check;\
|
|
d = TPE_dist(point,v);\
|
|
if (d < dBest) {\
|
|
vBest = v;\
|
|
dBest = d;}\
|
|
if (dBest == 0) {\
|
|
point = vBest;\
|
|
break;}
|
|
|
|
case LCR_BLOCK_RAMP_CURVED_WALL:
|
|
_CHECK_NEXT(TPE_envAABox(point,TPE_vec3(5 * LCR_PHYSICS_UNIT / 12,0,0),
|
|
TPE_vec3(LCR_PHYSICS_UNIT / 12,LCR_PHYSICS_UNIT / 4,LCR_PHYSICS_UNIT
|
|
/ 2)));
|
|
// fall through
|
|
|
|
case LCR_BLOCK_RAMP_CURVED_PLAT:
|
|
_CHECK_NEXT(TPE_envAABox(point,TPE_vec3(0,0,5 * LCR_PHYSICS_UNIT / 12),
|
|
TPE_vec3(LCR_PHYSICS_UNIT / 2,LCR_PHYSICS_UNIT / 4,LCR_PHYSICS_UNIT / 12
|
|
)));
|
|
// fall through
|
|
|
|
case LCR_BLOCK_RAMP_CURVED:
|
|
{
|
|
TPE_Unit sides[6];
|
|
TPE_Unit rampShift = block[0] != LCR_BLOCK_RAMP_CURVED ?
|
|
LCR_PHYSICS_UNIT / 6 : 0;
|
|
|
|
sides[0] = LCR_PHYSICS_UNIT / 8 - rampShift;
|
|
sides[1] = -1 * LCR_PHYSICS_UNIT / 4;
|
|
sides[2] = LCR_PHYSICS_UNIT / 2 - rampShift;
|
|
sides[3] = -1 * LCR_PHYSICS_UNIT / 4;
|
|
sides[4] = LCR_PHYSICS_UNIT / 2 - rampShift;
|
|
sides[5] = LCR_PHYSICS_UNIT / 4;
|
|
|
|
_CHECK_NEXT(TPE_envAATriPrism(point,
|
|
TPE_vec3(0,0,0),sides,LCR_PHYSICS_UNIT,2))
|
|
|
|
sides[0] = -1 * LCR_PHYSICS_UNIT / 2;
|
|
sides[1] = -1 * LCR_PHYSICS_UNIT / 4;
|
|
sides[2] = LCR_PHYSICS_UNIT / 2 - rampShift;
|
|
sides[3] = -1 * LCR_PHYSICS_UNIT / 4;
|
|
sides[4] = LCR_PHYSICS_UNIT / 2 - rampShift;
|
|
sides[5] = 0;
|
|
|
|
_CHECK_NEXT(TPE_envAATriPrism(point,TPE_vec3(0,0,0),sides,LCR_PHYSICS_UNIT
|
|
,2));
|
|
|
|
point = vBest;
|
|
|
|
break;
|
|
}
|
|
|
|
#undef _CHECK_NEXT
|
|
|
|
case LCR_BLOCK_RAMP:
|
|
case LCR_BLOCK_RAMP_34:
|
|
case LCR_BLOCK_RAMP_12:
|
|
case LCR_BLOCK_RAMP_14:
|
|
case LCR_BLOCK_RAMP_STEEP:
|
|
{
|
|
uint8_t front, top;
|
|
LCR_rampGetDimensions(block[0],&top,&front);
|
|
front = 6 - front;
|
|
|
|
TPE_Unit sides[6];
|
|
sides[0] =
|
|
-1 * LCR_PHYSICS_UNIT / 2 + (LCR_PHYSICS_UNIT / 6) * ((int) front);
|
|
|
|
sides[1] = -1 * LCR_PHYSICS_UNIT / 4;
|
|
|
|
sides[2] = LCR_PHYSICS_UNIT / 2;
|
|
sides[3] = -1 * LCR_PHYSICS_UNIT / 4;
|
|
|
|
sides[4] = LCR_PHYSICS_UNIT / 2;
|
|
sides[5] = -1 * LCR_PHYSICS_UNIT / 4 +
|
|
((int) top) * (LCR_PHYSICS_UNIT / 8);
|
|
|
|
point = TPE_envAATriPrism(point,TPE_vec3(0,0,0),sides,LCR_PHYSICS_UNIT,2);
|
|
break;
|
|
}
|
|
|
|
case LCR_BLOCK_HILL:
|
|
{
|
|
point =
|
|
(point.y > -1 * LCR_PHYSICS_UNIT / 4 && point.z < LCR_PHYSICS_UNIT / 4) ?
|
|
TPE_envCylinder(point,
|
|
TPE_vec3(0,-1 * LCR_PHYSICS_UNIT / 2,LCR_PHYSICS_UNIT / 4),
|
|
TPE_vec3(LCR_PHYSICS_UNIT / 2,0,0),
|
|
LCR_PHYSICS_UNIT / 2 + LCR_PHYSICS_UNIT / 4) :
|
|
TPE_envAABox(point,TPE_vec3(0,0,0),
|
|
TPE_vec3(LCR_PHYSICS_UNIT / 2,LCR_PHYSICS_UNIT / 4,
|
|
LCR_PHYSICS_UNIT / 2));
|
|
|
|
if (point.y < -1 * LCR_PHYSICS_UNIT / 4) // for some reason happens somet.
|
|
point.y = -1 * LCR_PHYSICS_UNIT / 4;
|
|
|
|
break;
|
|
}
|
|
|
|
case LCR_BLOCK_BUMP:
|
|
point = TPE_envCone(point,
|
|
TPE_vec3(0,-1 * LCR_PHYSICS_UNIT / 4 ,0),
|
|
TPE_vec3(0,LCR_PHYSICS_UNIT / 6,0),(5 * LCR_PHYSICS_UNIT) / 12);
|
|
break;
|
|
|
|
case LCR_BLOCK_CORNER:
|
|
case LCR_BLOCK_CORNER_12:
|
|
{
|
|
TPE_Unit sides[6];
|
|
sides[0] = -1 * LCR_PHYSICS_UNIT / 2;
|
|
sides[1] = LCR_PHYSICS_UNIT / 2;
|
|
sides[2] = -1 * LCR_PHYSICS_UNIT / 2;
|
|
sides[3] = -1 * LCR_PHYSICS_UNIT / 2;
|
|
sides[4] = block[0] == LCR_BLOCK_CORNER ? LCR_PHYSICS_UNIT / 2 : 0;
|
|
sides[5] = LCR_PHYSICS_UNIT / 2;
|
|
|
|
point = TPE_envAATriPrism(point,TPE_vec3(0,0,0),sides,
|
|
LCR_PHYSICS_UNIT / 2,1);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
point = TPE_vec3(0,0,LCR_MAP_SIZE_BLOCKS * LCR_PHYSICS_UNIT);
|
|
break;
|
|
}
|
|
|
|
point = TPE_vec3Plus(point,
|
|
TPE_vec3(LCR_PHYSICS_UNIT / 2,LCR_PHYSICS_UNIT / 4,LCR_PHYSICS_UNIT / 2));
|
|
|
|
transform = LCR_mapBlockOppositeTransform(transform);
|
|
|
|
LCR_TRANSFORM_COORDS(transform,point.x,point.y,point.z,LCR_PHYSICS_UNIT,
|
|
(LCR_PHYSICS_UNIT / 2))
|
|
|
|
point = TPE_vec3Plus(point,blockOffset); // shift back
|
|
|
|
return point;
|
|
}
|
|
|
|
TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist)
|
|
{
|
|
// start with the map outside walls:
|
|
TPE_ENV_START(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)),point)
|
|
|
|
// without this check we might try to get block outside the map
|
|
if (_pBest.x == point.x && _pBest.y == point.y && _pBest.z == point.z)
|
|
return _pBest;
|
|
|
|
if (maxDist <= LCR_PHYSICS_UNIT / 4) // considering half of square height
|
|
{
|
|
/* Here we only check the 8 closest blocks => relatively fast. */
|
|
|
|
TPE_Vec3 pointShifted = TPE_vec3Plus(point,TPE_vec3(
|
|
(LCR_MAP_SIZE_BLOCKS / 2) * LCR_PHYSICS_UNIT,
|
|
(LCR_MAP_SIZE_BLOCKS / 4) * LCR_PHYSICS_UNIT,
|
|
(LCR_MAP_SIZE_BLOCKS / 2) * LCR_PHYSICS_UNIT));
|
|
|
|
uint8_t coords[6]; // x_low, x_high, y_low, y_high, z_low, z_high
|
|
|
|
coords[0] = (pointShifted.x / LCR_PHYSICS_UNIT);
|
|
coords[1] = (pointShifted.x % LCR_PHYSICS_UNIT < LCR_PHYSICS_UNIT / 2);
|
|
|
|
coords[2] = (pointShifted.y / (LCR_PHYSICS_UNIT / 2));
|
|
coords[3] =
|
|
(pointShifted.y % (LCR_PHYSICS_UNIT / 2) < LCR_PHYSICS_UNIT / 4);
|
|
|
|
coords[4] = (pointShifted.z / LCR_PHYSICS_UNIT);
|
|
coords[5] = (pointShifted.z % LCR_PHYSICS_UNIT < LCR_PHYSICS_UNIT / 2);
|
|
|
|
for (int i = 0; i < 6; i += 2)
|
|
if (coords[i + 1])
|
|
{
|
|
coords[i + 1] = coords[i];
|
|
coords[i] = coords[i] > 0 ? coords[i] - 1 : 0;
|
|
}
|
|
else
|
|
coords[i + 1] = coords[i] < 63 ? coords[i] + 1 : 63;
|
|
|
|
int start = 0, end = LCR_currentMap.blockCount - 1;
|
|
|
|
for (uint8_t i = 0; i < 8; ++i)
|
|
{
|
|
/* Black magic: here we make it so that we the lowest coord numbers
|
|
(0,0,0), then the highest (1,1,1), then second lowest (1,0,0), then
|
|
second highest (0,1,1) etc. This way we are narrowing the range (start,
|
|
end) for the binary search. */
|
|
|
|
int blockNum = LCR_mapGetBlockAtFast(
|
|
coords[0] + ((i ^ (i >> 1)) & 0x01),
|
|
coords[2] + ((i ^ (i >> 2)) & 0x01),
|
|
coords[4] + (i & 0x01),start,end);
|
|
|
|
if (blockNum >= 0) // is there a block at the coords?
|
|
{
|
|
TPE_ENV_NEXT(_LCR_racingBlockEnvFunc(point, // check it
|
|
LCR_currentMap.blocks + blockNum * LCR_BLOCK_SIZE),point)
|
|
|
|
// narrow the search range:
|
|
if (i % 2 == 0 && blockNum > start)
|
|
start = blockNum;
|
|
|
|
if (i % 2 && blockNum < end)
|
|
end = blockNum;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LCR_LOG1("collision checking all blocks (shouldn't happen often!)");
|
|
|
|
const uint8_t *block = LCR_currentMap.blocks;
|
|
|
|
/* Full check of all map blocks, slow, shouldn't happen often! */
|
|
for (int j = 0; j < LCR_currentMap.blockCount; ++j)
|
|
{
|
|
TPE_ENV_NEXT(_LCR_racingBlockEnvFunc(point,block),point)
|
|
block += LCR_BLOCK_SIZE;
|
|
}
|
|
}
|
|
|
|
TPE_ENV_END
|
|
}
|
|
|
|
LCR_GameUnit LCR_racingGetCarSpeed(void)
|
|
{
|
|
return LCR_racing.carSpeed;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
LCR_GameUnit _LCR_racingSmoothRot(LCR_GameUnit angleNew, LCR_GameUnit angleOld)
|
|
{
|
|
/* We'll smooth small rotations by averaging the last two angles; bigger
|
|
rotations won't be smoothed -- firstly this removes lag for fast rotations
|
|
and also deals with the issue of averaging e.g. 1 and 359 degrees. */
|
|
|
|
LCR_GameUnit diff = angleNew - angleOld;
|
|
|
|
if (diff > LCR_GAME_UNIT / 8 || diff < -1 * LCR_GAME_UNIT / 8)
|
|
return angleNew;
|
|
|
|
return angleOld + diff / 2;
|
|
}
|
|
|
|
/**
|
|
Initializes new run.
|
|
*/
|
|
void LCR_racingRestart(void)
|
|
{
|
|
LCR_LOG0("restarting race");
|
|
|
|
LCR_racing.tick = 0;
|
|
|
|
TPE_bodyActivate(&(LCR_racing.carBody));
|
|
LCR_racing.wheelCollisions = 0;
|
|
|
|
LCR_racing.wheelRotation = 0;
|
|
LCR_racing.wheelSteer = 0;
|
|
LCR_racing.carSpeed = 0;
|
|
|
|
LCR_racing.carPositions[0] = TPE_vec3(0,0,0);
|
|
LCR_racing.carPositions[1] = LCR_racing.carPositions[0];
|
|
|
|
LCR_racing.carRotations[0] = TPE_vec3(0,0,0);
|
|
LCR_racing.carRotations[1] = LCR_racing.carRotations[0];
|
|
|
|
TPE_bodyMoveTo(&(LCR_racing.carBody),
|
|
TPE_vec3(
|
|
(((TPE_Unit) LCR_currentMap.startPos[0]) - LCR_MAP_SIZE_BLOCKS / 2)
|
|
* LCR_PHYSICS_UNIT + LCR_PHYSICS_UNIT / 2,
|
|
(((TPE_Unit) LCR_currentMap.startPos[1]) - LCR_MAP_SIZE_BLOCKS / 2)
|
|
* LCR_PHYSICS_UNIT / 2 + LCR_PHYSICS_UNIT / 4,
|
|
(((TPE_Unit) LCR_currentMap.startPos[2]) - LCR_MAP_SIZE_BLOCKS / 2)
|
|
* LCR_PHYSICS_UNIT + LCR_PHYSICS_UNIT / 2));
|
|
|
|
// TODO: allow also flipping the car upside down on start?
|
|
if (LCR_currentMap.startPos[3])
|
|
TPE_bodyRotateByAxis(&(LCR_racing.carBody),
|
|
TPE_vec3(0,
|
|
LCR_currentMap.startPos[3] == LCR_BLOCK_TRANSFORM_ROT_90 ? 3 * TPE_F / 4 :
|
|
(LCR_currentMap.startPos[3] == LCR_BLOCK_TRANSFORM_ROT_180 ? TPE_F / 2 :
|
|
(TPE_F / 4)),0));
|
|
|
|
for (int i = 0; i < LCR_CAR_JOINTS; ++i)
|
|
LCR_racing.carOKPositions[i] = TPE_vec3(0,0,0);
|
|
|
|
LCR_racing.carNotOKCount = 0;
|
|
|
|
|
|
// TODO
|
|
}
|
|
|
|
/**
|
|
Initializes the racing module, only call once.
|
|
*/
|
|
void LCR_racingInit(void)
|
|
{
|
|
LCR_LOG0("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;
|
|
|
|
/* We disable bounding sphere checks because that would lead to calling env.
|
|
function with large min. distance which would lead to slow iteration over
|
|
all map blocks. */
|
|
LCR_racing.carBody.flags |= TPE_BODY_FLAG_NO_BSPHERE;
|
|
}
|
|
|
|
/**
|
|
Gets current car transformation intended for rendering, i.e. potentially with
|
|
smoothing and interpolation applied to the underlying internal state in the
|
|
physics engine.
|
|
*/
|
|
void LCR_racingGetCarTransform(LCR_GameUnit position[3],
|
|
LCR_GameUnit rotation[3], LCR_GameUnit interpolationParam)
|
|
{
|
|
LCR_LOG2("getting car transform");
|
|
|
|
TPE_Vec3 v;
|
|
|
|
#if LCR_SETTING_SMOOTH_ANIMATIONS
|
|
v = TPE_vec3Plus(LCR_racing.carPositions[1], // LERP previous and current pos
|
|
_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;
|
|
|
|
rotation[0] = _LCR_racingSmoothRot(LCR_racing.carRotations[0].x,
|
|
LCR_racing.carRotations[1].x);
|
|
rotation[1] = _LCR_racingSmoothRot(LCR_racing.carRotations[0].y,
|
|
LCR_racing.carRotations[1].y);
|
|
rotation[2] = _LCR_racingSmoothRot(LCR_racing.carRotations[0].z,
|
|
LCR_racing.carRotations[1].z);
|
|
#else
|
|
position[0] = LCR_racing.carPositions[0].x;
|
|
position[1] = LCR_racing.carPositions[0].y;
|
|
position[2] = LCR_racing.carPositions[0].z;
|
|
|
|
rotation[0] = LCR_racing.carRotations[0].x;
|
|
rotation[1] = LCR_racing.carRotations[0].y;
|
|
rotation[2] = LCR_racing.carRotations[0].z;
|
|
#endif
|
|
}
|
|
|
|
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,
|
|
uint8_t material)
|
|
{
|
|
TPE_Unit acc =
|
|
material == LCR_BLOCK_MATERIAL_ICE ?
|
|
LCR_CAR_ACCELERATION_ICE :
|
|
(material == LCR_BLOCK_MATERIAL_GRASS ?
|
|
LCR_CAR_ACCELERATION_GRASS :
|
|
LCR_CAR_ACCELERATION);
|
|
|
|
acc -=
|
|
(acc * LCR_racing.carSpeed) / LCR_CAR_AIR_FRICTION;
|
|
|
|
if (acc < 0)
|
|
acc = 0;
|
|
|
|
|
|
|
|
LCR_racing.carBody.joints[wheel].velocity[0] +=
|
|
(dir.x * acc) / TPE_F;
|
|
LCR_racing.carBody.joints[wheel].velocity[1] +=
|
|
(dir.y * acc) / TPE_F;
|
|
LCR_racing.carBody.joints[wheel].velocity[2] +=
|
|
(dir.z * acc) / TPE_F;
|
|
}
|
|
|
|
int _LCR_racingCarShapeOK(void)
|
|
{
|
|
int r = 1;
|
|
|
|
for (int i = 0; i < LCR_racing.carBody.jointCount; ++i)
|
|
r &= TPE_connectionTension(TPE_dist(
|
|
LCR_racing.carBody.joints[
|
|
LCR_racing.carBody.connections[i].joint1].position,
|
|
LCR_racing.carBody.joints[
|
|
LCR_racing.carBody.connections[i].joint2].position),
|
|
LCR_racing.carBody.connections[i].length) < TPE_F / 16; // TODO: const
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
Updates the racing physics world, call every LCR_RACING_TICK_MS milliseconds.
|
|
Returns a set of events (logically ORed) that occured during this step.
|
|
*/
|
|
uint32_t LCR_racingStep(unsigned int input)
|
|
{
|
|
LCR_LOG2("racing step start");
|
|
|
|
uint32_t result = 0;
|
|
TPE_Vec3 carForw, carRight, carUp;
|
|
uint8_t groundMat = LCR_BLOCK_MATERIAL_CONCRETE; // material under wheels
|
|
int groundBlockIndex = -1;
|
|
|
|
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 ((LCR_racing.wheelCollisions & 0x0f) != 0x0f) // EXPERIMENTAL: don't apply gravity with all wheels on ground
|
|
TPE_bodyApplyGravity(&(LCR_racing.carBody),LCR_GRAVITY);
|
|
|
|
if (LCR_racing.wheelCollisions) // at least one wheel on ground?
|
|
{
|
|
TPE_Unit upDot = TPE_vec3Dot(carUp,TPE_vec3(0,TPE_F,0));
|
|
|
|
if (upDot > TPE_F / 8 || upDot < -1 * TPE_F / 8) // TODO: consts
|
|
{
|
|
uint8_t
|
|
gx = (LCR_racing.carPositions[0].x + (LCR_MAP_SIZE_BLOCKS / 2) *
|
|
LCR_GAME_UNIT) / LCR_GAME_UNIT,
|
|
gy = (LCR_racing.carPositions[0].y + (LCR_MAP_SIZE_BLOCKS / 2) *
|
|
(LCR_GAME_UNIT / 2)) / (LCR_GAME_UNIT / 2),
|
|
gz = (LCR_racing.carPositions[0].z + (LCR_MAP_SIZE_BLOCKS / 2) *
|
|
LCR_GAME_UNIT) / LCR_GAME_UNIT;
|
|
|
|
TPE_Unit yMod = (LCR_racing.carPositions[0].y + LCR_MAP_SIZE_BLOCKS *
|
|
LCR_GAME_UNIT / 2) % (LCR_GAME_UNIT / 2);
|
|
|
|
if (upDot > 0 && yMod < LCR_GAME_UNIT / 2) // TODO: const
|
|
groundBlockIndex = LCR_mapGetBlockAt(gx,gy - 1,gz);
|
|
else if (upDot < 0 && yMod > LCR_GAME_UNIT / 2)
|
|
groundBlockIndex = LCR_mapGetBlockAt(gx,gy + 1,gz);
|
|
|
|
if (groundBlockIndex == -1)
|
|
groundBlockIndex = LCR_mapGetBlockAt(gx,gy,gz);
|
|
}
|
|
|
|
if (groundBlockIndex != -1)
|
|
groundMat = LCR_mapBlockGetMaterial(
|
|
LCR_currentMap.blocks + groundBlockIndex * LCR_BLOCK_SIZE);
|
|
}
|
|
|
|
LCR_racing.carBody.friction =
|
|
groundMat == LCR_BLOCK_MATERIAL_ICE ?
|
|
LCR_CAR_FORWARD_FRICTION_ICE :
|
|
(groundMat == LCR_BLOCK_MATERIAL_DIRT ?
|
|
LCR_CAR_FORWARD_FRICTION_DIRT :
|
|
LCR_CAR_FORWARD_FRICTION);
|
|
|
|
|
|
// TODO: also apply (probably twice as much) when accelerating in opposite direction than vecolcity vec.
|
|
if (!(input & (LCR_RACING_INPUT_FORW | LCR_RACING_INPUT_BACK)))
|
|
LCR_racing.carBody.friction *= LCR_CAR_STAND_FRICTION_MULTIPLIER;
|
|
|
|
|
|
|
|
|
|
if (input)
|
|
{
|
|
unsigned char steering = 0;
|
|
|
|
// TODO: magic constants
|
|
|
|
if (input & (LCR_RACING_INPUT_FORW | LCR_RACING_INPUT_BACK))
|
|
{
|
|
LCR_GameUnit rotateBy =
|
|
(LCR_racing.wheelCollisions & 0x0f) ? // on ground slow down wheel rot.
|
|
(LCR_racingGetCarSpeed() / 16) : LCR_GAME_UNIT / 32;
|
|
|
|
if (!(input & LCR_RACING_INPUT_BACK))
|
|
rotateBy *= -1;
|
|
|
|
LCR_racing.wheelRotation =
|
|
(LCR_racing.wheelRotation + rotateBy) % 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_STEER_SPEED,
|
|
LCR_CAR_STEER_MAX);
|
|
}
|
|
else if (input & LCR_RACING_INPUT_LEFT)
|
|
{
|
|
steering = 1;
|
|
|
|
LCR_racing.wheelSteer = TPE_max(
|
|
LCR_racing.wheelSteer - LCR_CAR_STEER_SPEED,
|
|
-1 * LCR_CAR_STEER_MAX);
|
|
}
|
|
|
|
if ((LCR_racing.wheelCollisions & 0x0c)) // back wheel on ground?
|
|
{
|
|
if (input & LCR_RACING_INPUT_FORW)
|
|
{
|
|
_LCR_racingWheelAccelerate(0,carForw,groundMat);
|
|
_LCR_racingWheelAccelerate(1,carForw,groundMat);
|
|
}
|
|
else if (input & LCR_RACING_INPUT_BACK)
|
|
{
|
|
_LCR_racingWheelAccelerate(0,TPE_vec3TimesPlain(carForw,-1),groundMat);
|
|
_LCR_racingWheelAccelerate(1,TPE_vec3TimesPlain(carForw,-1),groundMat);
|
|
}
|
|
}
|
|
|
|
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) *
|
|
(groundMat == LCR_BLOCK_MATERIAL_CONCRETE ?
|
|
LCR_CAR_STEER_FRICTION :
|
|
(groundMat == LCR_BLOCK_MATERIAL_DIRT ?
|
|
LCR_CAR_STEER_FRICTION_DIRT :
|
|
(
|
|
groundMat == LCR_BLOCK_MATERIAL_GRASS ?
|
|
LCR_CAR_STEER_FRICTION_GRASS :
|
|
LCR_CAR_STEER_FRICTION_ICE)))
|
|
) / 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;
|
|
|
|
LCR_racing.wheelCollisions <<= 4;
|
|
|
|
LCR_LOG2("gonna step physics engine");
|
|
TPE_worldStep(&(LCR_racing.physicsWorld));
|
|
LCR_LOG2("stepping physics engine done");
|
|
|
|
LCR_racing.carSpeed = (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;
|
|
|
|
TPE_Vec3 tmpVec = LCR_racing.carPositions[0];
|
|
|
|
TPE_Vec3 wheelAverage = _LCR_TPE_vec3DividePlain(
|
|
TPE_vec3Plus(
|
|
TPE_vec3Plus(
|
|
LCR_racing.carBody.joints[0].position,
|
|
LCR_racing.carBody.joints[1].position),
|
|
TPE_vec3Plus(
|
|
LCR_racing.carBody.joints[2].position,
|
|
LCR_racing.carBody.joints[3].position)),4);
|
|
|
|
LCR_racing.carPositions[0] = _LCR_TPE_vec3DividePlain(
|
|
TPE_vec3TimesPlain(wheelAverage,LCR_GAME_UNIT),LCR_PHYSICS_UNIT);
|
|
|
|
LCR_racing.carPositions[0] = // smooth the position
|
|
TPE_vec3KeepWithinBox(LCR_racing.carPositions[1],LCR_racing.carPositions[0],
|
|
TPE_vec3(
|
|
LCR_PHYSICS_UNIT / 64, // TODO: constant
|
|
LCR_PHYSICS_UNIT / 64,
|
|
LCR_PHYSICS_UNIT / 64));
|
|
|
|
LCR_racing.carPositions[1] = tmpVec;
|
|
|
|
tmpVec = _LCR_TPE_vec3DividePlain(TPE_vec3TimesPlain(TPE_bodyGetRotation(
|
|
&(LCR_racing.carBody),0,2,1),LCR_GAME_UNIT),TPE_F);
|
|
|
|
LCR_racing.carRotations[1] = LCR_racing.carRotations[0];
|
|
LCR_racing.carRotations[0] = tmpVec;
|
|
|
|
TPE_Unit angle = TPE_vec3Dot(carUp,TPE_vec3Normalized(TPE_vec3Minus(
|
|
LCR_racing.carBody.joints[4].position,
|
|
LCR_racing.carBody.joints[0].position)));
|
|
|
|
if (angle < TPE_F / 4) // TODO: magic constant
|
|
{
|
|
LCR_LOG2("roof squeezed, applying anti force")
|
|
|
|
tmpVec = TPE_vec3Times(carUp,LCR_PHYSICS_UNIT / 16); // TODO: 16 magic con.
|
|
|
|
if (angle <= 0)
|
|
{
|
|
LCR_LOG1("roof flipped over, fixing")
|
|
LCR_racing.carBody.joints[4].position = wheelAverage;
|
|
angle = 0;
|
|
}
|
|
|
|
angle = TPE_F - 4 * angle; // 4 comes from above TPE_F / 4
|
|
|
|
tmpVec = TPE_vec3Times(tmpVec,angle);
|
|
|
|
// accelerate roof and wheels away from each other
|
|
for (int i = 0; i < LCR_CAR_JOINTS; ++i)
|
|
{
|
|
LCR_racing.carBody.joints[i].velocity[0] += (i == 4 ? 1 : -1) * tmpVec.x;
|
|
LCR_racing.carBody.joints[i].velocity[1] += (i == 4 ? 1 : -1) * tmpVec.y;
|
|
LCR_racing.carBody.joints[i].velocity[2] += (i == 4 ? 1 : -1) * tmpVec.z;
|
|
}
|
|
}
|
|
|
|
// now make a special test for a "pinch" front collision
|
|
|
|
TPE_Vec3 frontWheelMiddle = TPE_vec3Plus(
|
|
LCR_racing.carBody.joints[0].position,
|
|
LCR_racing.carBody.joints[1].position);
|
|
|
|
frontWheelMiddle.x /= 2;
|
|
frontWheelMiddle.y /= 2;
|
|
frontWheelMiddle.z /= 2;
|
|
|
|
frontWheelMiddle = TPE_vec3Minus(frontWheelMiddle,
|
|
_LCR_racingEnvironmentFunction(frontWheelMiddle,LCR_PHYSICS_UNIT / 4));
|
|
|
|
uint8_t frontCollision = frontWheelMiddle.x == 0 && frontWheelMiddle.y == 0 &&
|
|
frontWheelMiddle.z == 0;
|
|
|
|
if (frontCollision)
|
|
{
|
|
// stop the car immediately
|
|
LCR_LOG1("car front pierced");
|
|
LCR_racing.carNotOKCount += (LCR_racing.carNotOKCount < 15 ? 32 : 0); // TODO: consts
|
|
}
|
|
|
|
if ((LCR_racing.carBody.flags & TPE_BODY_FLAG_UNRESOLVED) || frontCollision ||
|
|
!_LCR_racingCarShapeOK())
|
|
{
|
|
// car not OK
|
|
|
|
if (LCR_racing.carNotOKCount > 5) // TODO: constant
|
|
{
|
|
LCR_LOG1("car not OK (short), fixing");
|
|
|
|
for (int i = 0; i < LCR_CAR_JOINTS; ++i)
|
|
{
|
|
if (LCR_racing.carNotOKCount < 50) // TODO: const
|
|
{
|
|
// for a while try to smoothly iterate towards previous OK position
|
|
LCR_racing.carBody.joints[i].position =
|
|
TPE_vec3Plus(LCR_racing.carBody.joints[i].position,
|
|
LCR_racing.carOKPositions[i]);
|
|
|
|
LCR_racing.carBody.joints[i].position.x /= 2;
|
|
LCR_racing.carBody.joints[i].position.y /= 2;
|
|
LCR_racing.carBody.joints[i].position.z /= 2;
|
|
|
|
for (int j = 0; j < 3; ++j) // lower speed a bit
|
|
LCR_racing.carBody.joints[i].velocity[j] =
|
|
(7 * ((int) LCR_racing.carBody.joints[i].velocity[j])) / 8;
|
|
}
|
|
else // hard set the pos (iteration may be infinite due to sim.)
|
|
{
|
|
LCR_LOG1("car not OK (long), teleporting");
|
|
LCR_racing.carBody.joints[i].position = LCR_racing.carOKPositions[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
LCR_racing.carNotOKCount += LCR_racing.carNotOKCount < 255 ? 1 : 0;
|
|
}
|
|
else
|
|
{
|
|
// car OK
|
|
LCR_racing.carNotOKCount = 0;
|
|
|
|
for (int i = 0; i < LCR_CAR_JOINTS; ++i)
|
|
LCR_racing.carOKPositions[i] = LCR_racing.carBody.joints[i].position;
|
|
}
|
|
|
|
LCR_racing.tick++;
|
|
|
|
LCR_LOG2("racing step end");
|
|
|
|
return result;
|
|
}
|
|
|
|
void LCR_physicsDebugDraw(LCR_GameUnit camPos[3], LCR_GameUnit camRot[2],
|
|
LCR_GameUnit camFov)
|
|
{
|
|
LCR_LOG2("drawing physics debug overlay");
|
|
|
|
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,LCR_PHYSICS_UNIT / 4,LCR_racing.tick * 4);
|
|
}
|
|
|
|
#endif // guard
|