mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-16 10:04:28 +02:00
Merge remote-tracking branch 'refs/remotes/XProger/master'
This commit is contained in:
@@ -250,9 +250,18 @@ struct Animation {
|
|||||||
|
|
||||||
Box getBoundingBox(const vec3 &pos, int dir) {
|
Box getBoundingBox(const vec3 &pos, int dir) {
|
||||||
if (!model)
|
if (!model)
|
||||||
return Box(pos, pos);
|
return Box(pos, pos);
|
||||||
vec3 min = frameA->box.min().lerp(frameB->box.min(), delta);
|
|
||||||
vec3 max = frameA->box.max().lerp(frameB->box.max(), delta);
|
vec3 nextMin = frameB->box.min();
|
||||||
|
vec3 nextMax = frameB->box.max();
|
||||||
|
|
||||||
|
if (isPrepareToNext) {
|
||||||
|
nextMin += offset;
|
||||||
|
nextMax += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 min = frameA->box.min().lerp(nextMin, delta);
|
||||||
|
vec3 max = frameA->box.max().lerp(nextMax, delta);
|
||||||
Box box(min, max);
|
Box box(min, max);
|
||||||
box.rotate90(dir);
|
box.rotate90(dir);
|
||||||
box.min += pos;
|
box.min += pos;
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
struct Collision {
|
struct Collision {
|
||||||
enum Side { NONE, LEFT, RIGHT, FRONT, TOP, BOTTOM } side;
|
enum Side { NONE, LEFT, RIGHT, FRONT, BACK, TOP, BOTTOM } side;
|
||||||
|
|
||||||
struct Info {
|
struct Info {
|
||||||
int room, roomAbove, roomBelow, floor, ceiling;
|
int room, roomAbove, roomBelow, floor, ceiling;
|
||||||
@@ -67,7 +67,7 @@ struct Collision {
|
|||||||
} else
|
} else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
pos += vec3(d.x, 0.0f, d.y);
|
pos += vec3(d.x, -velocity.y, d.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool checkHeight(TR::Level *level, int roomIndex, const vec3 &pos, const vec2 &offset, int height, int maxAscent, int maxDescent, Side side) {
|
inline bool checkHeight(TR::Level *level, int roomIndex, const vec3 &pos, const vec2 &offset, int height, int maxAscent, int maxDescent, Side side) {
|
||||||
|
@@ -543,19 +543,19 @@ struct Controller {
|
|||||||
if (Core::pass != Core::passCompose || !TR::castShadow(entity.type))
|
if (Core::pass != Core::passCompose || !TR::castShadow(entity.type))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Box box = animation.getBoundingBox(pos, 0);
|
||||||
|
vec3 center = box.center();
|
||||||
|
|
||||||
TR::Level::FloorInfo info;
|
TR::Level::FloorInfo info;
|
||||||
level->getFloorInfo(entity.room, entity.x, entity.y, entity.z, info);
|
level->getFloorInfo(entity.room, int(center.x), int(center.y), int(center.z), info);
|
||||||
|
|
||||||
Box box = animation.getBoundingBox(vec3(0, 0, 0), 0);
|
const vec3 fpos = vec3(pos.x, info.floor - 16.0f, pos.z);
|
||||||
|
const vec3 size = box.size();
|
||||||
const vec3 fpos = vec3(float(entity.x), info.floor - 16.0f, float(entity.z));
|
|
||||||
const vec3 offset = box.center();
|
|
||||||
const vec3 size = box.size();
|
|
||||||
|
|
||||||
mat4 m = Core::mViewProj;
|
mat4 m = Core::mViewProj;
|
||||||
m.translate(fpos);
|
m.translate(fpos);
|
||||||
m.rotateY(angle.y);
|
m.rotateY(angle.y);
|
||||||
m.translate(vec3(offset.x, 0.0f, offset.z));
|
m.translate(vec3(center.x - pos.x, 0.0f, center.z - pos.z));
|
||||||
m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f));
|
m.scale(vec3(size.x, 0.0f, size.z) * (1.0f / 1024.0f));
|
||||||
|
|
||||||
Basis b;
|
Basis b;
|
||||||
@@ -564,7 +564,7 @@ struct Controller {
|
|||||||
game->setShader(Core::pass, Shader::FLASH, false, false);
|
game->setShader(Core::pass, Shader::FLASH, false, false);
|
||||||
Core::active.shader->setParam(uViewProj, m);
|
Core::active.shader->setParam(uViewProj, m);
|
||||||
Core::active.shader->setParam(uBasis, b);
|
Core::active.shader->setParam(uBasis, b);
|
||||||
float alpha = lerp(0.7f, 0.90f, clamp((fpos.y - pos.y) / 1024.0f, 0.0f, 1.0f) );
|
float alpha = lerp(0.7f, 0.90f, clamp((fpos.y - box.max.y) / 1024.0f, 0.0f, 1.0f) );
|
||||||
Core::active.shader->setParam(uMaterial, vec4(vec3(0.5f * (1.0f - alpha)), alpha));
|
Core::active.shader->setParam(uMaterial, vec4(vec3(0.5f * (1.0f - alpha)), alpha));
|
||||||
Core::active.shader->setParam(uAmbient, vec3(0.0f));
|
Core::active.shader->setParam(uAmbient, vec3(0.0f));
|
||||||
|
|
||||||
|
69
src/debug.h
69
src/debug.h
@@ -603,69 +603,14 @@ namespace Debug {
|
|||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *TR1_TYPE_NAMES[] = { TR1_TYPES(DECL_STR) };
|
||||||
|
|
||||||
const char *getEntityName(const TR::Level &level, const TR::Entity &entity) {
|
const char *getEntityName(const TR::Level &level, const TR::Entity &entity) {
|
||||||
switch (entity.type) {
|
if (entity.type == TR::Entity::NONE)
|
||||||
case_name(TR::Entity, LARA );
|
return "NONE";
|
||||||
case_name(TR::Entity, ENEMY_TWIN );
|
if (entity.type < 0 || entity.type >= COUNT(TR1_TYPE_NAMES))
|
||||||
case_name(TR::Entity, ENEMY_WOLF );
|
return "UNKNOWN";
|
||||||
case_name(TR::Entity, ENEMY_BEAR );
|
return TR1_TYPE_NAMES[entity.type];
|
||||||
case_name(TR::Entity, ENEMY_BAT );
|
|
||||||
case_name(TR::Entity, ENEMY_CROCODILE_LAND );
|
|
||||||
case_name(TR::Entity, ENEMY_CROCODILE_WATER);
|
|
||||||
case_name(TR::Entity, ENEMY_LION_MALE );
|
|
||||||
case_name(TR::Entity, ENEMY_LION_FEMALE );
|
|
||||||
case_name(TR::Entity, ENEMY_PUMA );
|
|
||||||
case_name(TR::Entity, ENEMY_GORILLA );
|
|
||||||
case_name(TR::Entity, ENEMY_RAT_LAND );
|
|
||||||
case_name(TR::Entity, ENEMY_RAT_WATER );
|
|
||||||
case_name(TR::Entity, ENEMY_REX );
|
|
||||||
case_name(TR::Entity, ENEMY_RAPTOR );
|
|
||||||
case_name(TR::Entity, ENEMY_MUTANT_1 );
|
|
||||||
case_name(TR::Entity, ENEMY_CENTAUR );
|
|
||||||
case_name(TR::Entity, ENEMY_MUMMY );
|
|
||||||
case_name(TR::Entity, ENEMY_LARSON );
|
|
||||||
case_name(TR::Entity, TRAP_FLOOR );
|
|
||||||
case_name(TR::Entity, TRAP_BLADE );
|
|
||||||
case_name(TR::Entity, TRAP_SPIKES );
|
|
||||||
case_name(TR::Entity, TRAP_BOULDER );
|
|
||||||
case_name(TR::Entity, TRAP_DART );
|
|
||||||
case_name(TR::Entity, TRAP_DARTGUN );
|
|
||||||
case_name(TR::Entity, BLOCK_1 );
|
|
||||||
case_name(TR::Entity, BLOCK_2 );
|
|
||||||
case_name(TR::Entity, SWITCH );
|
|
||||||
case_name(TR::Entity, SWITCH_WATER );
|
|
||||||
case_name(TR::Entity, DOOR_1 );
|
|
||||||
case_name(TR::Entity, DOOR_2 );
|
|
||||||
case_name(TR::Entity, DOOR_3 );
|
|
||||||
case_name(TR::Entity, DOOR_4 );
|
|
||||||
case_name(TR::Entity, DOOR_BIG_1 );
|
|
||||||
case_name(TR::Entity, DOOR_BIG_2 );
|
|
||||||
case_name(TR::Entity, DOOR_5 );
|
|
||||||
case_name(TR::Entity, DOOR_6 );
|
|
||||||
case_name(TR::Entity, TRAP_DOOR_1 );
|
|
||||||
case_name(TR::Entity, TRAP_DOOR_2 );
|
|
||||||
case_name(TR::Entity, BRIDGE_0 );
|
|
||||||
case_name(TR::Entity, BRIDGE_1 );
|
|
||||||
case_name(TR::Entity, BRIDGE_2 );
|
|
||||||
case_name(TR::Entity, GEARS_1 );
|
|
||||||
case_name(TR::Entity, GEARS_2 );
|
|
||||||
case_name(TR::Entity, GEARS_3 );
|
|
||||||
case_name(TR::Entity, PUZZLE_1 );
|
|
||||||
case_name(TR::Entity, PUZZLE_2 );
|
|
||||||
case_name(TR::Entity, PUZZLE_3 );
|
|
||||||
case_name(TR::Entity, PUZZLE_4 );
|
|
||||||
case_name(TR::Entity, HOLE_PUZZLE );
|
|
||||||
case_name(TR::Entity, HOLE_PUZZLE_SET );
|
|
||||||
case_name(TR::Entity, PICKUP );
|
|
||||||
case_name(TR::Entity, KEY_1 );
|
|
||||||
case_name(TR::Entity, KEY_2 );
|
|
||||||
case_name(TR::Entity, KEY_3 );
|
|
||||||
case_name(TR::Entity, KEY_4 );
|
|
||||||
case_name(TR::Entity, HOLE_KEY );
|
|
||||||
case_name(TR::Entity, VIEW_TARGET );
|
|
||||||
case_name(TR::Entity, WATERFALL );
|
|
||||||
}
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) {
|
void info(const TR::Level &level, const TR::Entity &entity, Animation &anim) {
|
||||||
|
360
src/format.h
360
src/format.h
@@ -8,6 +8,199 @@
|
|||||||
#define MAX_TRIGGER_COMMANDS 32
|
#define MAX_TRIGGER_COMMANDS 32
|
||||||
#define MAX_MESHES 512
|
#define MAX_MESHES 512
|
||||||
|
|
||||||
|
#define TR1_TYPES(E) \
|
||||||
|
E( LARA ) \
|
||||||
|
E( LARA_PISTOLS ) \
|
||||||
|
E( LARA_SHOTGUN ) \
|
||||||
|
E( LARA_MAGNUMS ) \
|
||||||
|
E( LARA_UZIS ) \
|
||||||
|
E( LARA_SPEC ) \
|
||||||
|
E( ENEMY_TWIN ) \
|
||||||
|
E( ENEMY_WOLF ) \
|
||||||
|
E( ENEMY_BEAR ) \
|
||||||
|
E( ENEMY_BAT ) \
|
||||||
|
E( ENEMY_CROCODILE_LAND ) \
|
||||||
|
E( ENEMY_CROCODILE_WATER ) \
|
||||||
|
E( ENEMY_LION_MALE ) \
|
||||||
|
E( ENEMY_LION_FEMALE ) \
|
||||||
|
E( ENEMY_PUMA ) \
|
||||||
|
E( ENEMY_GORILLA ) \
|
||||||
|
E( ENEMY_RAT_LAND ) \
|
||||||
|
E( ENEMY_RAT_WATER ) \
|
||||||
|
E( ENEMY_REX ) \
|
||||||
|
E( ENEMY_RAPTOR ) \
|
||||||
|
E( ENEMY_MUTANT_1 ) \
|
||||||
|
E( ENEMY_MUTANT_2 ) \
|
||||||
|
E( ENEMY_MUTANT_3 ) \
|
||||||
|
E( ENEMY_CENTAUR ) \
|
||||||
|
E( ENEMY_MUMMY ) \
|
||||||
|
E( UNUSED_1 ) \
|
||||||
|
E( UNUSED_2 ) \
|
||||||
|
E( ENEMY_LARSON ) \
|
||||||
|
E( ENEMY_PIERRE ) \
|
||||||
|
E( ENEMY_SKATEBOARD ) \
|
||||||
|
E( ENEMY_SKATEBOY ) \
|
||||||
|
E( ENEMY_COWBOY ) \
|
||||||
|
E( ENEMY_MR_T ) \
|
||||||
|
E( ENEMY_NATLA ) \
|
||||||
|
E( ENEMY_GIANT_MUTANT ) \
|
||||||
|
E( TRAP_FLOOR ) \
|
||||||
|
E( TRAP_BLADE ) \
|
||||||
|
E( TRAP_SPIKES ) \
|
||||||
|
E( TRAP_BOULDER ) \
|
||||||
|
E( TRAP_DART ) \
|
||||||
|
E( TRAP_DARTGUN ) \
|
||||||
|
E( DOOR_LIFT ) \
|
||||||
|
E( TRAP_SLAM ) \
|
||||||
|
E( FALLING_SWORD ) \
|
||||||
|
E( HAMMER_HANDLE ) \
|
||||||
|
E( HAMMER_BLOCK ) \
|
||||||
|
E( LIGHTNING_BALL ) \
|
||||||
|
E( BARRICADE ) \
|
||||||
|
E( BLOCK_1 ) \
|
||||||
|
E( BLOCK_2 ) \
|
||||||
|
E( BLOCK_3 ) \
|
||||||
|
E( BLOCK_4 ) \
|
||||||
|
E( MOVING_BLOCK ) \
|
||||||
|
E( FALLING_CEILING_1 ) \
|
||||||
|
E( FALLING_CEILING_2 ) \
|
||||||
|
E( SWITCH ) \
|
||||||
|
E( SWITCH_WATER ) \
|
||||||
|
E( DOOR_1 ) \
|
||||||
|
E( DOOR_2 ) \
|
||||||
|
E( DOOR_3 ) \
|
||||||
|
E( DOOR_4 ) \
|
||||||
|
E( DOOR_BIG_1 ) \
|
||||||
|
E( DOOR_BIG_2 ) \
|
||||||
|
E( DOOR_5 ) \
|
||||||
|
E( DOOR_6 ) \
|
||||||
|
E( TRAP_DOOR_1 ) \
|
||||||
|
E( TRAP_DOOR_2 ) \
|
||||||
|
E( UNUSED_3 ) \
|
||||||
|
E( BRIDGE_0 ) \
|
||||||
|
E( BRIDGE_1 ) \
|
||||||
|
E( BRIDGE_2 ) \
|
||||||
|
E( INV_GAME ) \
|
||||||
|
E( INV_COMPASS ) \
|
||||||
|
E( INV_HOME ) \
|
||||||
|
E( GEARS_1 ) \
|
||||||
|
E( GEARS_2 ) \
|
||||||
|
E( GEARS_3 ) \
|
||||||
|
E( CUT_1 ) \
|
||||||
|
E( CUT_2 ) \
|
||||||
|
E( CUT_3 ) \
|
||||||
|
E( CUT_4 ) \
|
||||||
|
E( INV_GAME_CLOSED ) \
|
||||||
|
E( INV_MAP ) \
|
||||||
|
E( CRYSTAL ) \
|
||||||
|
E( WEAPON_PISTOLS ) \
|
||||||
|
E( WEAPON_SHOTGUN ) \
|
||||||
|
E( WEAPON_MAGNUMS ) \
|
||||||
|
E( WEAPON_UZIS ) \
|
||||||
|
E( AMMO_PISTOLS ) \
|
||||||
|
E( AMMO_SHOTGUN ) \
|
||||||
|
E( AMMO_MAGNUMS ) \
|
||||||
|
E( AMMO_UZIS ) \
|
||||||
|
E( AMMO_EXPLOSIVE ) \
|
||||||
|
E( MEDIKIT_SMALL ) \
|
||||||
|
E( MEDIKIT_BIG ) \
|
||||||
|
E( INV_DETAIL ) \
|
||||||
|
E( INV_SOUND ) \
|
||||||
|
E( INV_CONTROLS ) \
|
||||||
|
E( INV_FLASHLIGHT ) \
|
||||||
|
E( INV_PISTOLS ) \
|
||||||
|
E( INV_SHOTGUN ) \
|
||||||
|
E( INV_MAGNUMS ) \
|
||||||
|
E( INV_UZIS ) \
|
||||||
|
E( INV_AMMO_POSTOLS ) \
|
||||||
|
E( INV_AMMO_SHOTGUN ) \
|
||||||
|
E( INV_AMMO_MAGNUMS ) \
|
||||||
|
E( INV_AMMO_UZIS ) \
|
||||||
|
E( INV_AMMO_EXPLOSIVE ) \
|
||||||
|
E( INV_MEDIKIT_SMALL ) \
|
||||||
|
E( INV_MEDIKIT_BIG ) \
|
||||||
|
E( PUZZLE_1 ) \
|
||||||
|
E( PUZZLE_2 ) \
|
||||||
|
E( PUZZLE_3 ) \
|
||||||
|
E( PUZZLE_4 ) \
|
||||||
|
E( INV_PUZZLE_1 ) \
|
||||||
|
E( INV_PUZZLE_2 ) \
|
||||||
|
E( INV_PUZZLE_3 ) \
|
||||||
|
E( INV_PUZZLE_4 ) \
|
||||||
|
E( PUZZLE_HOLE_1 ) \
|
||||||
|
E( PUZZLE_HOLE_2 ) \
|
||||||
|
E( PUZZLE_HOLE_3 ) \
|
||||||
|
E( PUZZLE_HOLE_4 ) \
|
||||||
|
E( PUZZLE_DONE_1 ) \
|
||||||
|
E( PUZZLE_DONE_2 ) \
|
||||||
|
E( PUZZLE_DONE_3 ) \
|
||||||
|
E( PUZZLE_DONE_4 ) \
|
||||||
|
E( LEADBAR ) \
|
||||||
|
E( INV_LEADBAR ) \
|
||||||
|
E( MIDAS_TOUCH ) \
|
||||||
|
E( KEY_1 ) \
|
||||||
|
E( KEY_2 ) \
|
||||||
|
E( KEY_3 ) \
|
||||||
|
E( KEY_4 ) \
|
||||||
|
E( INV_KEY_1 ) \
|
||||||
|
E( INV_KEY_2 ) \
|
||||||
|
E( INV_KEY_3 ) \
|
||||||
|
E( INV_KEY_4 ) \
|
||||||
|
E( KEY_HOLE_1 ) \
|
||||||
|
E( KEY_HOLE_2 ) \
|
||||||
|
E( KEY_HOLE_3 ) \
|
||||||
|
E( KEY_HOLE_4 ) \
|
||||||
|
E( UNUSED_4 ) \
|
||||||
|
E( UNUSED_5 ) \
|
||||||
|
E( SCION_1 ) \
|
||||||
|
E( SCION_2 ) \
|
||||||
|
E( SCION_3 ) \
|
||||||
|
E( SCION_TARGET ) \
|
||||||
|
E( SCION_HOLDER ) \
|
||||||
|
E( UNUSED_6 ) \
|
||||||
|
E( UNUSED_7 ) \
|
||||||
|
E( INV_SCION ) \
|
||||||
|
E( EXPLOSION ) \
|
||||||
|
E( UNUSED_8 ) \
|
||||||
|
E( WATER_SPLASH ) \
|
||||||
|
E( UNUSED_9 ) \
|
||||||
|
E( BUBBLE ) \
|
||||||
|
E( UNUSED_10 ) \
|
||||||
|
E( UNUSED_11 ) \
|
||||||
|
E( BLOOD ) \
|
||||||
|
E( UNUSED_12 ) \
|
||||||
|
E( SMOKE ) \
|
||||||
|
E( STATUE ) \
|
||||||
|
E( SHACK ) \
|
||||||
|
E( MUTANT_EGG_SMALL ) \
|
||||||
|
E( RICOCHET ) \
|
||||||
|
E( SPARKLES ) \
|
||||||
|
E( MUZZLE_FLASH ) \
|
||||||
|
E( UNUSED_13 ) \
|
||||||
|
E( UNUSED_14 ) \
|
||||||
|
E( VIEW_TARGET ) \
|
||||||
|
E( WATERFALL ) \
|
||||||
|
E( UNUSED_15 ) \
|
||||||
|
E( MUTANT_BULLET ) \
|
||||||
|
E( MUTANT_GRENADE ) \
|
||||||
|
E( UNUSED_16 ) \
|
||||||
|
E( UNUSED_17 ) \
|
||||||
|
E( LAVA_PARTICLE ) \
|
||||||
|
E( LAVA_EMITTER ) \
|
||||||
|
E( FLAME ) \
|
||||||
|
E( FLAME_EMITTER ) \
|
||||||
|
E( LAVA_FLOW ) \
|
||||||
|
E( MUTANT_EGG_BIG ) \
|
||||||
|
E( BOAT ) \
|
||||||
|
E( EARTHQUAKE ) \
|
||||||
|
E( UNUSED_18 ) \
|
||||||
|
E( UNUSED_19 ) \
|
||||||
|
E( UNUSED_20 ) \
|
||||||
|
E( UNUSED_21 ) \
|
||||||
|
E( UNUSED_22 ) \
|
||||||
|
E( BRAID ) \
|
||||||
|
E( GLYPH )
|
||||||
|
|
||||||
namespace TR {
|
namespace TR {
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -139,6 +332,42 @@ namespace TR {
|
|||||||
CUTSCENE , // play cutscene
|
CUTSCENE , // play cutscene
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace Limits {
|
||||||
|
|
||||||
|
struct Limit {
|
||||||
|
float dy, dz, ay;
|
||||||
|
::Box box;
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit SWITCH = {
|
||||||
|
0, 376, 30, {{-200, 0, 312}, {200, 0, 512}}
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit SWITCH_UNDERWATER = {
|
||||||
|
0, 100, 80, {{-1024, -1024, -1024}, {1024, 1024, 512}}
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit PICKUP = {
|
||||||
|
0, -100, 180, {{-256, -100, -256}, {256, 100, 100}}
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit PICKUP_UNDERWATER = {
|
||||||
|
-200, -350, 45, {{-512, -512, -512}, {512, 512, 512}}
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit KEY_HOLE = {
|
||||||
|
0, 362, 30, {{-200, 0, 312}, {200, 0, 512}}
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit PUZZLE_HOLE = {
|
||||||
|
0, 327, 30, {{-200, 0, 312}, {200, 0, 512}}
|
||||||
|
};
|
||||||
|
|
||||||
|
Limit BLOCK = {
|
||||||
|
0, -612, 30, {{-300, 0, -692}, {300, 0, -512}}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
struct fixed {
|
struct fixed {
|
||||||
@@ -372,131 +601,12 @@ namespace TR {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
enum Type : int16 {
|
|
||||||
NONE = -1,
|
|
||||||
LARA = 0,
|
|
||||||
LARA_PISTOLS = 1,
|
|
||||||
LARA_SHOTGUN = 2,
|
|
||||||
LARA_MAGNUMS = 3,
|
|
||||||
LARA_UZIS = 4,
|
|
||||||
LARA_SPEC = 5,
|
|
||||||
ENEMY_TWIN = 6,
|
|
||||||
ENEMY_WOLF = 7,
|
|
||||||
ENEMY_BEAR = 8,
|
|
||||||
ENEMY_BAT = 9,
|
|
||||||
ENEMY_CROCODILE_LAND = 10,
|
|
||||||
ENEMY_CROCODILE_WATER = 11,
|
|
||||||
ENEMY_LION_MALE = 12,
|
|
||||||
ENEMY_LION_FEMALE = 13,
|
|
||||||
ENEMY_PUMA = 14,
|
|
||||||
ENEMY_GORILLA = 15,
|
|
||||||
ENEMY_RAT_LAND = 16,
|
|
||||||
ENEMY_RAT_WATER = 17,
|
|
||||||
ENEMY_REX = 18,
|
|
||||||
ENEMY_RAPTOR = 19,
|
|
||||||
ENEMY_MUTANT_1 = 20,
|
|
||||||
ENEMY_MUTANT_2 = 21,
|
|
||||||
ENEMY_MUTANT_3 = 22,
|
|
||||||
ENEMY_CENTAUR = 23,
|
|
||||||
ENEMY_MUMMY = 24,
|
|
||||||
ENEMY_LARSON = 27,
|
|
||||||
ENEMY_PIERRE = 28,
|
|
||||||
ENEMY_SKATEBOARD = 29,
|
|
||||||
ENEMY_SKATEBOY = 30,
|
|
||||||
ENEMY_COWBOY = 31,
|
|
||||||
ENEMY_MR_T = 32,
|
|
||||||
ENEMY_NATLA = 33,
|
|
||||||
ENEMY_GIANT_MUTANT = 34,
|
|
||||||
TRAP_FLOOR = 35,
|
|
||||||
TRAP_BLADE = 36,
|
|
||||||
TRAP_SPIKES = 37,
|
|
||||||
TRAP_BOULDER = 38,
|
|
||||||
TRAP_DART = 39,
|
|
||||||
TRAP_DARTGUN = 40,
|
|
||||||
|
|
||||||
BLOCK_1 = 48,
|
typedef int16 Type;
|
||||||
BLOCK_2 = 49,
|
|
||||||
BLOCK_3 = 50,
|
|
||||||
BLOCK_4 = 51,
|
|
||||||
MOVING_BLOCK = 52,
|
|
||||||
FALLING_CEILING = 53,
|
|
||||||
FALLING_SWORD = 54,
|
|
||||||
SWITCH = 55,
|
|
||||||
SWITCH_WATER = 56,
|
|
||||||
DOOR_1 = 57,
|
|
||||||
DOOR_2 = 58,
|
|
||||||
DOOR_3 = 59,
|
|
||||||
DOOR_4 = 60,
|
|
||||||
DOOR_BIG_1 = 61,
|
|
||||||
DOOR_BIG_2 = 62,
|
|
||||||
DOOR_5 = 63,
|
|
||||||
DOOR_6 = 64,
|
|
||||||
TRAP_DOOR_1 = 65,
|
|
||||||
TRAP_DOOR_2 = 66,
|
|
||||||
|
|
||||||
BRIDGE_0 = 68,
|
enum { NONE = -1, TR1_TYPES(DECL_ENUM) };
|
||||||
BRIDGE_1 = 69,
|
|
||||||
BRIDGE_2 = 70,
|
|
||||||
|
|
||||||
GEARS_1 = 74,
|
int16 type;
|
||||||
GEARS_2 = 75,
|
|
||||||
GEARS_3 = 76,
|
|
||||||
|
|
||||||
CUT_1 = 77,
|
|
||||||
CUT_2 = 78,
|
|
||||||
CUT_3 = 79,
|
|
||||||
CUT_4 = 79,
|
|
||||||
|
|
||||||
CRYSTAL = 83, // sprite
|
|
||||||
WEAPON_PISTOLS = 84, // sprite
|
|
||||||
WEAPON_SHOTGUN = 85, // sprite
|
|
||||||
WEAPON_MAGNUMS = 86, // sprite
|
|
||||||
WEAPON_UZIS = 87, // sprite
|
|
||||||
AMMO_SHOTGUN = 89, // sprite
|
|
||||||
AMMO_MAGNUMS = 90, // sprite
|
|
||||||
AMMO_UZIS = 91, // sprite
|
|
||||||
|
|
||||||
MEDIKIT_SMALL = 93, // sprite
|
|
||||||
MEDIKIT_BIG = 94, // sprite
|
|
||||||
|
|
||||||
PUZZLE_1 = 110, // sprite
|
|
||||||
PUZZLE_2 = 111, // sprite
|
|
||||||
PUZZLE_3 = 112, // sprite
|
|
||||||
PUZZLE_4 = 113, // sprite
|
|
||||||
|
|
||||||
HOLE_PUZZLE = 118,
|
|
||||||
HOLE_PUZZLE_SET = 122,
|
|
||||||
|
|
||||||
PICKUP = 126, // sprite
|
|
||||||
|
|
||||||
KEY_1 = 129, // sprite
|
|
||||||
KEY_2 = 130, // sprite
|
|
||||||
KEY_3 = 131, // sprite
|
|
||||||
KEY_4 = 132, // sprite
|
|
||||||
|
|
||||||
HOLE_KEY = 137,
|
|
||||||
|
|
||||||
ARTIFACT = 143, // sprite
|
|
||||||
|
|
||||||
WATER_SPLASH = 153, // sprite
|
|
||||||
|
|
||||||
BUBBLE = 155, // sprite
|
|
||||||
|
|
||||||
BLOOD = 158, // sprite
|
|
||||||
|
|
||||||
SMOKE = 160, // sprite
|
|
||||||
|
|
||||||
SPARK = 164, // sprite
|
|
||||||
|
|
||||||
MUZZLE_FLASH = 166,
|
|
||||||
|
|
||||||
VIEW_TARGET = 169, // invisible
|
|
||||||
WATERFALL = 170, // invisible (water splash generator)
|
|
||||||
|
|
||||||
BRAID = 189, // Lara's ponytail
|
|
||||||
GLYPH = 190, // sprite
|
|
||||||
|
|
||||||
} type;
|
|
||||||
int16 room;
|
int16 room;
|
||||||
int32 x, y, z;
|
int32 x, y, z;
|
||||||
angle rotation;
|
angle rotation;
|
||||||
@@ -518,7 +628,7 @@ namespace TR {
|
|||||||
return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) ||
|
return (type >= WEAPON_PISTOLS && type <= AMMO_UZIS) ||
|
||||||
(type >= PUZZLE_1 && type <= PUZZLE_4) ||
|
(type >= PUZZLE_1 && type <= PUZZLE_4) ||
|
||||||
(type >= KEY_1 && type <= KEY_4) ||
|
(type >= KEY_1 && type <= KEY_4) ||
|
||||||
(type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == ARTIFACT || type == PICKUP);
|
(type == MEDIKIT_SMALL || type == MEDIKIT_BIG || type == SCION_1); // TODO: recheck all items
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isBlock() {
|
bool isBlock() {
|
||||||
@@ -1094,7 +1204,7 @@ namespace TR {
|
|||||||
for (int i = 0; i < modelsCount; i++)
|
for (int i = 0; i < modelsCount; i++)
|
||||||
switch (models[i].type) {
|
switch (models[i].type) {
|
||||||
case Entity::MUZZLE_FLASH : extra.muzzleFlash = i; break;
|
case Entity::MUZZLE_FLASH : extra.muzzleFlash = i; break;
|
||||||
case Entity::HOLE_PUZZLE_SET : extra.puzzleSet = i; break;
|
case Entity::PUZZLE_DONE_1 : extra.puzzleSet = i; break;
|
||||||
case Entity::LARA_PISTOLS : extra.weapons[0] = i; break;
|
case Entity::LARA_PISTOLS : extra.weapons[0] = i; break;
|
||||||
case Entity::LARA_SHOTGUN : extra.weapons[1] = i; break;
|
case Entity::LARA_SHOTGUN : extra.weapons[1] = i; break;
|
||||||
case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break;
|
case Entity::LARA_MAGNUMS : extra.weapons[2] = i; break;
|
||||||
@@ -1788,7 +1898,7 @@ namespace TR {
|
|||||||
}; // struct Level
|
}; // struct Level
|
||||||
|
|
||||||
bool castShadow(Entity::Type type) {
|
bool castShadow(Entity::Type type) {
|
||||||
return (type >= Entity::ENEMY_TWIN && type <= Entity::ENEMY_LARSON) || type == Entity::LARA || (type >= Entity::CUT_1 && type <= Entity::CUT_3);
|
return (type >= Entity::ENEMY_TWIN && type <= Entity::ENEMY_GIANT_MUTANT) || type == Entity::LARA || (type >= Entity::CUT_1 && type <= Entity::CUT_4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
214
src/lara.h
214
src/lara.h
@@ -36,7 +36,7 @@
|
|||||||
#define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec
|
#define LARA_WET_TIMER (LARA_WET_SPECULAR / 16.0f) // 4 sec
|
||||||
|
|
||||||
#define PICKUP_FRAME_GROUND 40
|
#define PICKUP_FRAME_GROUND 40
|
||||||
#define PICKUP_FRAME_UNDERWATER 16
|
#define PICKUP_FRAME_UNDERWATER 18
|
||||||
#define PUZZLE_FRAME 80
|
#define PUZZLE_FRAME 80
|
||||||
|
|
||||||
#define MAX_TRIGGER_ACTIONS 64
|
#define MAX_TRIGGER_ACTIONS 64
|
||||||
@@ -53,6 +53,8 @@ struct Lara : Character {
|
|||||||
ANIM_STAND_LEFT = 2,
|
ANIM_STAND_LEFT = 2,
|
||||||
ANIM_STAND_RIGHT = 3,
|
ANIM_STAND_RIGHT = 3,
|
||||||
|
|
||||||
|
ANIM_RUN_START = 6,
|
||||||
|
|
||||||
ANIM_STAND = 11,
|
ANIM_STAND = 11,
|
||||||
|
|
||||||
ANIM_CLIMB_JUMP = 26,
|
ANIM_CLIMB_JUMP = 26,
|
||||||
@@ -466,8 +468,10 @@ struct Lara : Character {
|
|||||||
if (room == TR::NO_ROOM)
|
if (room == TR::NO_ROOM)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (level->rooms[room].flags.water)
|
if (level->rooms[room].flags.water) {
|
||||||
stand = STAND_UNDERWATER;
|
stand = STAND_UNDERWATER;
|
||||||
|
animation.setAnim(ANIM_UNDERWATER);
|
||||||
|
}
|
||||||
|
|
||||||
velocity = vec3(0.0f);
|
velocity = vec3(0.0f);
|
||||||
|
|
||||||
@@ -765,7 +769,7 @@ struct Lara : Character {
|
|||||||
Sprite::add(game, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_ANIMATED);
|
Sprite::add(game, TR::Entity::BLOOD, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_ANIMATED);
|
||||||
} else {
|
} else {
|
||||||
hit -= d * 64.0f;
|
hit -= d * 64.0f;
|
||||||
Sprite::add(game, TR::Entity::SPARK, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_RANDOM);
|
Sprite::add(game, TR::Entity::RICOCHET, room, (int)hit.x, (int)hit.y, (int)hit.z, Sprite::FRAME_RANDOM);
|
||||||
|
|
||||||
float dist = (hit - p).length();
|
float dist = (hit - p).length();
|
||||||
if (dist < nearDist) {
|
if (dist < nearDist) {
|
||||||
@@ -1059,12 +1063,15 @@ struct Lara : Character {
|
|||||||
Character *enemy = (Character*)e.controller;
|
Character *enemy = (Character*)e.controller;
|
||||||
if (enemy->health <= 0) continue;
|
if (enemy->health <= 0) continue;
|
||||||
|
|
||||||
vec3 p = enemy->getBoundingBox().center();
|
Box box = enemy->getBoundingBox();
|
||||||
|
vec3 p = box.center();
|
||||||
|
p.y = box.min.y + (box.max.y - box.min.y) / 3.0f;
|
||||||
|
|
||||||
vec3 v = p - pos;
|
vec3 v = p - pos;
|
||||||
if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees
|
if (dir.dot(v.normal()) <= 0.5f) continue; // target is out of sight -60..+60 degrees
|
||||||
|
|
||||||
float d = v.length();
|
float d = v.length();
|
||||||
if (d < dist && checkOcclusion(pos - vec3(0, 512, 0), p, d) ) {
|
if (d < dist && checkOcclusion(pos - vec3(0, 650, 0), p, d) ) {
|
||||||
index = i;
|
index = i;
|
||||||
dist = d;
|
dist = d;
|
||||||
}
|
}
|
||||||
@@ -1104,7 +1111,7 @@ struct Lara : Character {
|
|||||||
virtual void cmdJump(const vec3 &vel) {
|
virtual void cmdJump(const vec3 &vel) {
|
||||||
vec3 v = vel;
|
vec3 v = vel;
|
||||||
if (state == STATE_HANG_UP)
|
if (state == STATE_HANG_UP)
|
||||||
v.y = (3.0f - sqrtf(-2.0f * GRAVITY / 30.0f * (collision.info[Collision::FRONT].floor - pos.y + 800.0f)));
|
v.y = (3.0f - sqrtf(-2.0f * GRAVITY / 30.0f * (collision.info[Collision::FRONT].floor - pos.y + 800.0f - 128.0f)));
|
||||||
Character::cmdJump(v);
|
Character::cmdJump(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1162,40 +1169,44 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool doPickUp() {
|
bool doPickUp() {
|
||||||
|
if ((state != STATE_STOP && state != STATE_TREAD) || !animation.canSetState(STATE_PICK_UP))
|
||||||
|
return false;
|
||||||
|
|
||||||
int room = getRoomIndex();
|
int room = getRoomIndex();
|
||||||
TR::Entity &e = getEntity();
|
TR::Limits::Limit limit = state == STATE_STOP ? TR::Limits::PICKUP : TR::Limits::PICKUP_UNDERWATER;
|
||||||
|
|
||||||
for (int i = 0; i < level->entitiesCount; i++) {
|
for (int i = 0; i < level->entitiesCount; i++) {
|
||||||
TR::Entity &item = level->entities[i];
|
TR::Entity &item = level->entities[i];
|
||||||
if (item.room == room && !item.flags.invisible) {
|
if (item.room == room && !item.flags.invisible) {
|
||||||
if (abs(item.x - e.x) > 256 || abs(item.z - e.z) > 256)
|
if (!item.isItem())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Controller *controller = (Controller*)item.controller;
|
||||||
|
|
||||||
|
if (stand == STAND_UNDERWATER)
|
||||||
|
controller->angle.x = -25 * DEG2RAD;
|
||||||
|
controller->angle.y = angle.y;
|
||||||
|
|
||||||
|
if (!checkInteraction(controller, limit, (input & ACTION) != 0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (item.isItem()) {
|
alignByItem(controller, limit, true, false);
|
||||||
lastPickUp = i;
|
|
||||||
angle.x = 0.0f;
|
if (stand == STAND_UNDERWATER)
|
||||||
pos = ((Controller*)item.controller)->pos;
|
angle.x = -25 * DEG2RAD;
|
||||||
if (stand == STAND_UNDERWATER) { // TODO: lerp to pos/angle
|
|
||||||
pos -= getDir() * 256.0f;
|
lastPickUp = i;
|
||||||
pos.y -= 256;
|
return true;
|
||||||
}
|
|
||||||
updateEntity();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkAngle(TR::angle rotation) {
|
|
||||||
return fabsf(shortAngle(rotation, getEntity().rotation)) < PI * 0.25f;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool useItem(TR::Entity::Type item, TR::Entity::Type slot) {
|
bool useItem(TR::Entity::Type item, TR::Entity::Type slot) {
|
||||||
if (item == TR::Entity::NONE) {
|
if (item == TR::Entity::NONE) {
|
||||||
switch (slot) {
|
switch (slot) {
|
||||||
case TR::Entity::HOLE_KEY : item = TR::Entity::KEY_1; break; // TODO: 1-4
|
case TR::Entity::KEY_HOLE_1 : item = TR::Entity::KEY_1; break; // TODO: 1-4
|
||||||
case TR::Entity::HOLE_PUZZLE : item = TR::Entity::PUZZLE_1; break;
|
case TR::Entity::PUZZLE_HOLE_1 : item = TR::Entity::PUZZLE_1; break;
|
||||||
default : return false;
|
default : return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1207,6 +1218,33 @@ struct Lara : Character {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void alignByItem(Controller *item, const TR::Limits::Limit &limit, bool dx, bool ay) {
|
||||||
|
if (ay)
|
||||||
|
angle = item->angle;
|
||||||
|
else
|
||||||
|
angle.x = angle.z = 0.0f;
|
||||||
|
|
||||||
|
mat4 m = item->getMatrix();
|
||||||
|
|
||||||
|
float fx = 0.0f;
|
||||||
|
if (!dx)
|
||||||
|
fx = (m.transpose() * vec4(pos - item->pos, 0.0f)).x;
|
||||||
|
|
||||||
|
pos = item->pos + (m * vec4(fx, limit.dy, limit.dz, 0.0f)).xyz;
|
||||||
|
velocity = vec3(0.0f);
|
||||||
|
speed = 0.0f;
|
||||||
|
updateEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkInteraction(Controller *controller, const TR::Limits::Limit &limit, bool action) {
|
||||||
|
if ((state != STATE_STOP && state != STATE_TREAD && state != STATE_PUSH_PULL_READY) || !action || !emptyHands())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
vec3 delta = (controller->getMatrix().transpose() * vec4(pos - controller->pos, 0.0f)).xyz; // inverse transform
|
||||||
|
|
||||||
|
return limit.box.contains(delta) && fabsf(shortAngle(angle.y, controller->angle.y)) <= limit.ay * DEG2RAD;
|
||||||
|
}
|
||||||
|
|
||||||
void checkTrigger() {
|
void checkTrigger() {
|
||||||
if (actionCommand) return;
|
if (actionCommand) return;
|
||||||
|
|
||||||
@@ -1226,6 +1264,8 @@ struct Lara : Character {
|
|||||||
|
|
||||||
if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated
|
if (info.trigInfo.once == 1 && isActive) return; // once trigger is already activated
|
||||||
|
|
||||||
|
TR::Limits::Limit *limit = NULL;
|
||||||
|
|
||||||
int actionState = state;
|
int actionState = state;
|
||||||
switch (info.trigger) {
|
switch (info.trigger) {
|
||||||
case TR::Level::Trigger::ACTIVATE :
|
case TR::Level::Trigger::ACTIVATE :
|
||||||
@@ -1236,26 +1276,28 @@ struct Lara : Character {
|
|||||||
break;
|
break;
|
||||||
case TR::Level::Trigger::SWITCH :
|
case TR::Level::Trigger::SWITCH :
|
||||||
actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN;
|
actionState = (isActive && stand == STAND_GROUND) ? STATE_SWITCH_UP : STATE_SWITCH_DOWN;
|
||||||
if (!isPressed(ACTION) || state == actionState || !emptyHands())
|
if (!animation.canSetState(actionState))
|
||||||
return;
|
|
||||||
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
|
|
||||||
return;
|
return;
|
||||||
|
limit = state == STATE_STOP ? &TR::Limits::SWITCH : &TR::Limits::SWITCH_UNDERWATER;
|
||||||
|
{
|
||||||
|
Trigger *controller = (Trigger*)level->entities[info.trigCmd[0].args].controller;
|
||||||
|
if (!controller->inState() || !checkInteraction(controller, *limit, isPressed(ACTION)))
|
||||||
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TR::Level::Trigger::KEY :
|
case TR::Level::Trigger::KEY :
|
||||||
if (level->entities[info.trigCmd[0].args].flags.active)
|
if (level->entities[info.trigCmd[0].args].flags.active)
|
||||||
return;
|
return;
|
||||||
actionState = level->entities[info.trigCmd[0].args].type == TR::Entity::HOLE_KEY ? STATE_USE_KEY : STATE_USE_PUZZLE;
|
actionState = level->entities[info.trigCmd[0].args].type == TR::Entity::KEY_HOLE_1 ? STATE_USE_KEY : STATE_USE_PUZZLE;
|
||||||
if (isActive || !isPressed(ACTION) || state == actionState || !emptyHands()) // TODO: STATE_USE_PUZZLE
|
if (!animation.canSetState(actionState))
|
||||||
return;
|
return;
|
||||||
if (!checkAngle(level->entities[info.trigCmd[0].args].rotation))
|
limit = actionState == STATE_USE_KEY ? &TR::Limits::KEY_HOLE : &TR::Limits::PUZZLE_HOLE;
|
||||||
return;
|
if (!checkInteraction((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, isPressed(ACTION)))
|
||||||
if (animation.canSetState(actionState)) {
|
return;
|
||||||
if (!useItem(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) {
|
if (!useItem(TR::Entity::NONE, level->entities[info.trigCmd[0].args].type)) {
|
||||||
playSound(TR::SND_NO, pos, Sound::PAN);
|
playSound(TR::SND_NO, pos, Sound::PAN);
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
lastPickUp = info.trigCmd[0].args; // TODO: it's not pickup, it's key/puzzle hole
|
lastPickUp = info.trigCmd[0].args; // TODO: it's not pickup, it's key/puzzle hole
|
||||||
break;
|
break;
|
||||||
case TR::Level::Trigger::PICKUP :
|
case TR::Level::Trigger::PICKUP :
|
||||||
@@ -1270,17 +1312,11 @@ struct Lara : Character {
|
|||||||
// try to activate Lara state
|
// try to activate Lara state
|
||||||
if (!animation.setState(actionState)) return;
|
if (!animation.setState(actionState)) return;
|
||||||
|
|
||||||
if (info.trigger == TR::Level::Trigger::SWITCH || info.trigger == TR::Level::Trigger::KEY) {
|
if (info.trigger == TR::Level::Trigger::KEY)
|
||||||
if (info.trigger == TR::Level::Trigger::KEY)
|
level->entities[info.trigCmd[0].args].flags.active = true;
|
||||||
level->entities[info.trigCmd[0].args].flags.active = true;
|
|
||||||
|
|
||||||
TR::Entity &p = level->entities[info.trigCmd[0].args];
|
if (limit)
|
||||||
angle.y = p.rotation;
|
alignByItem((Controller*)level->entities[info.trigCmd[0].args].controller, *limit, stand != STAND_GROUND || info.trigger != TR::Level::Trigger::SWITCH, true);
|
||||||
angle.x = 0;
|
|
||||||
pos = ((Controller*)p.controller)->pos + vec3(sinf(angle.y), 0, cosf(angle.y)) * (stand == STAND_GROUND ? 384.0f : 128.0f);
|
|
||||||
velocity = vec3(0.0f);
|
|
||||||
updateEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
// build trigger activation chain
|
// build trigger activation chain
|
||||||
ActionCommand *actionItem = &actionList[1];
|
ActionCommand *actionItem = &actionList[1];
|
||||||
@@ -1403,10 +1439,11 @@ struct Lara : Character {
|
|||||||
if (c.side != Collision::FRONT)
|
if (c.side != Collision::FRONT)
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
int floor = c.info[Collision::FRONT].floor;
|
int floor = c.info[Collision::FRONT].floor;
|
||||||
|
int ceiling = c.info[Collision::FRONT].ceiling;
|
||||||
int hands = int(bounds.min.y);
|
int hands = int(bounds.min.y);
|
||||||
|
|
||||||
if (abs(floor - hands) < 128) {
|
if (abs(floor - hands) < 64 && floor != ceiling) {
|
||||||
alignToWall(-LARA_RADIUS);
|
alignToWall(-LARA_RADIUS);
|
||||||
pos.y = float(floor + LARA_HANG_OFFSET);
|
pos.y = float(floor + LARA_HANG_OFFSET);
|
||||||
stand = STAND_HANG;
|
stand = STAND_HANG;
|
||||||
@@ -1454,21 +1491,23 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Block* getBlock() {
|
Block* getBlock() {
|
||||||
int y = int(pos.y);
|
|
||||||
|
|
||||||
for (int i = 0; i < level->entitiesCount; i++) {
|
for (int i = 0; i < level->entitiesCount; i++) {
|
||||||
TR::Entity &e = level->entities[i];
|
TR::Entity &e = level->entities[i];
|
||||||
|
if (!e.isBlock())
|
||||||
|
continue;
|
||||||
|
|
||||||
int q = entityQuadrant(e);
|
Block *block = (Block*)e.controller;
|
||||||
int dx = abs(int(pos.x) - e.x);
|
float oldAngle = block->angle.y;
|
||||||
int dz = abs(int(pos.z) - e.z);
|
block->angle.y = angleQuadrant(angle.y) * (PI * 0.5f);
|
||||||
|
|
||||||
if (q > -1 && e.isBlock() && dx < 1024 && dz < 1024 && e.y == y && alignToWall(-LARA_RADIUS, q, 64 + int(LARA_RADIUS), 512 - int(LARA_RADIUS))) {
|
if (!checkInteraction(block, TR::Limits::BLOCK, (input & ACTION) != 0)) {
|
||||||
Block *block = (Block*)e.controller;
|
block->angle.y = oldAngle;
|
||||||
block->angle.y = angle.y;
|
continue;
|
||||||
block->updateEntity();
|
|
||||||
return block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alignByItem(block, TR::Limits::BLOCK, false, true);
|
||||||
|
|
||||||
|
return block;
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -1476,15 +1515,19 @@ struct Lara : Character {
|
|||||||
virtual int getStateGround() {
|
virtual int getStateGround() {
|
||||||
angle.x = 0.0f;
|
angle.x = 0.0f;
|
||||||
|
|
||||||
if ((input & ACTION) && emptyHands() && doPickUp())
|
if ((state == STATE_STOP || state == STATE_TREAD) && (input & ACTION) && emptyHands() && doPickUp())
|
||||||
return STATE_PICK_UP;
|
return STATE_PICK_UP;
|
||||||
|
|
||||||
if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands() && collision.side == Collision::FRONT) { // TODO: get rid of animation.index
|
if ((input & (FORTH | ACTION)) == (FORTH | ACTION) && (animation.index == ANIM_STAND || animation.index == ANIM_STAND_NORMAL) && emptyHands() && collision.side == Collision::FRONT) { // TODO: get rid of animation.index
|
||||||
int floor = collision.info[Collision::FRONT].floor;
|
int floor = collision.info[Collision::FRONT].floor;
|
||||||
|
int ceiling = collision.info[Collision::FRONT].ceiling;
|
||||||
|
|
||||||
int h = (int)pos.y - floor;
|
int h = (int)pos.y - floor;
|
||||||
|
|
||||||
int aIndex = animation.index;
|
int aIndex = animation.index;
|
||||||
if (h <= 2 * 256 + 128) {
|
if (floor == ceiling || h < 256)
|
||||||
|
;// do nothing
|
||||||
|
else if (h <= 2 * 256 + 128) {
|
||||||
aIndex = ANIM_CLIMB_2;
|
aIndex = ANIM_CLIMB_2;
|
||||||
pos.y = floor + 512.0f;
|
pos.y = floor + 512.0f;
|
||||||
} else if (h <= 3 * 256 + 128) {
|
} else if (h <= 3 * 256 + 128) {
|
||||||
@@ -1499,20 +1542,31 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( (input & (FORTH | BACK)) == (FORTH | BACK) && (state == STATE_STOP || state == STATE_RUN) )
|
if ( (input & (FORTH | BACK)) == (FORTH | BACK) && (animation.index == ANIM_STAND_NORMAL || state == STATE_RUN) )
|
||||||
return animation.setAnim(ANIM_STAND_ROLL_BEGIN);
|
return animation.setAnim(ANIM_STAND_ROLL_BEGIN);
|
||||||
|
|
||||||
// ready to jump
|
// ready to jump
|
||||||
if (state == STATE_COMPRESS) {
|
if (state == STATE_COMPRESS) {
|
||||||
|
int res;
|
||||||
|
float ext = angle.y;
|
||||||
switch (input & (RIGHT | LEFT | FORTH | BACK)) {
|
switch (input & (RIGHT | LEFT | FORTH | BACK)) {
|
||||||
case RIGHT : return STATE_RIGHT_JUMP;
|
case RIGHT : res = STATE_RIGHT_JUMP; ext += PI * 0.5f; break;
|
||||||
case LEFT : return STATE_LEFT_JUMP;
|
case LEFT : res = STATE_LEFT_JUMP; ext -= PI * 0.5f; break;
|
||||||
case FORTH | LEFT :
|
case FORTH | LEFT :
|
||||||
case FORTH | RIGHT :
|
case FORTH | RIGHT :
|
||||||
case FORTH : return STATE_FORWARD_JUMP;
|
case FORTH : res = STATE_FORWARD_JUMP; break;
|
||||||
case BACK : return STATE_BACK_JUMP;
|
case BACK : res = STATE_BACK_JUMP; ext += PI; break;
|
||||||
default : return STATE_UP_JUMP;
|
default : res = STATE_UP_JUMP; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res != STATE_UP_JUMP) {
|
||||||
|
vec3 p = pos;
|
||||||
|
collision = Collision(level, getRoomIndex(), p, vec3(0.0f), vec3(0.0f), LARA_RADIUS * 2.5f, ext, 0, LARA_HEIGHT, 256 + 128, 0xFFFFFF);
|
||||||
|
if (collision.side == Collision::FRONT)
|
||||||
|
res = STATE_UP_JUMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// jump button is pressed
|
// jump button is pressed
|
||||||
@@ -1527,7 +1581,7 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// walk button is pressed
|
// walk button is pressed
|
||||||
if (input & WALK) {
|
if ((input & WALK) && animation.index != ANIM_RUN_START) {
|
||||||
if (input & FORTH) return STATE_WALK;
|
if (input & FORTH) return STATE_WALK;
|
||||||
if (input & BACK) return STATE_BACK;
|
if (input & BACK) return STATE_BACK;
|
||||||
if (input & LEFT) return STATE_STEP_LEFT;
|
if (input & LEFT) return STATE_STEP_LEFT;
|
||||||
@@ -1539,10 +1593,8 @@ struct Lara : Character {
|
|||||||
if (state == STATE_PUSH_PULL_READY && (input & (FORTH | BACK))) {
|
if (state == STATE_PUSH_PULL_READY && (input & (FORTH | BACK))) {
|
||||||
int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK;
|
int pushState = (input & FORTH) ? STATE_PUSH_BLOCK : STATE_PULL_BLOCK;
|
||||||
Block *block = getBlock();
|
Block *block = getBlock();
|
||||||
if (block && animation.canSetState(pushState) && block->doMove((input & FORTH) != 0)) {
|
if (block && animation.canSetState(pushState) && block->doMove((input & FORTH) != 0))
|
||||||
alignToWall(-LARA_RADIUS);
|
|
||||||
return pushState;
|
return pushState;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == STATE_PUSH_PULL_READY || getBlock())
|
if (state == STATE_PUSH_PULL_READY || getBlock())
|
||||||
@@ -1962,7 +2014,7 @@ struct Lara : Character {
|
|||||||
Character *enemy = (Character*)e.controller;
|
Character *enemy = (Character*)e.controller;
|
||||||
if (enemy->health <= 0) continue;
|
if (enemy->health <= 0) continue;
|
||||||
|
|
||||||
vec3 dir = pos - enemy->pos;
|
vec3 dir = pos - vec3(0.0f, 128.0f, 0.0f) - enemy->pos;
|
||||||
vec3 p = dir.rotateY(-enemy->angle.y);
|
vec3 p = dir.rotateY(-enemy->angle.y);
|
||||||
|
|
||||||
Box enemyBox = enemy->getBoundingBoxLocal();
|
Box enemyBox = enemy->getBoundingBoxLocal();
|
||||||
@@ -2096,6 +2148,16 @@ struct Lara : Character {
|
|||||||
|
|
||||||
if (collision.side == Collision::FRONT) {
|
if (collision.side == Collision::FRONT) {
|
||||||
int floor = collision.info[Collision::FRONT].floor;
|
int floor = collision.info[Collision::FRONT].floor;
|
||||||
|
/*
|
||||||
|
switch (angleQuadrant(angleExt - angle.y)) {
|
||||||
|
case 0 : collision.side = Collision::FRONT; LOG("FRONT\n"); break;
|
||||||
|
case 1 : collision.side = Collision::RIGHT; LOG("RIGHT\n"); break;
|
||||||
|
case 2 : collision.side = Collision::BACK; LOG("BACK\n"); break;
|
||||||
|
case 3 : collision.side = Collision::LEFT; LOG("LEFT\n"); break;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (velocity.dot(getDir()) <= EPS)
|
||||||
|
collision.side = Collision::NONE;
|
||||||
|
|
||||||
// hit the wall
|
// hit the wall
|
||||||
switch (stand) {
|
switch (stand) {
|
||||||
@@ -2113,7 +2175,9 @@ struct Lara : Character {
|
|||||||
break;
|
break;
|
||||||
case STAND_GROUND :
|
case STAND_GROUND :
|
||||||
case STAND_HANG :
|
case STAND_HANG :
|
||||||
if (opos.y - floor > (256 * 3 - 128) && state == STATE_RUN)
|
if (stand == STAND_GROUND && state != STATE_ROLL_1 && state != STATE_ROLL_2 && (pos - opos).length2() < 16)
|
||||||
|
animation.setAnim(ANIM_STAND_NORMAL);
|
||||||
|
else if (opos.y - floor > (256 * 3 - 128) && state == STATE_RUN)
|
||||||
animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT);
|
animation.setAnim(isLeftFoot ? ANIM_SMASH_RUN_LEFT : ANIM_SMASH_RUN_RIGHT);
|
||||||
else if (stand == STAND_HANG)
|
else if (stand == STAND_HANG)
|
||||||
animation.setAnim(ANIM_HANG, -21);
|
animation.setAnim(ANIM_HANG, -21);
|
||||||
|
19
src/level.h
19
src/level.h
@@ -205,14 +205,22 @@ struct Level : IGame {
|
|||||||
case TR::Entity::MOVING_BLOCK :
|
case TR::Entity::MOVING_BLOCK :
|
||||||
entity.controller = new MovingBlock(this, i);
|
entity.controller = new MovingBlock(this, i);
|
||||||
break;
|
break;
|
||||||
case TR::Entity::FALLING_CEILING :
|
case 1592 :
|
||||||
|
case TR::Entity::FALLING_CEILING_1 :
|
||||||
|
case TR::Entity::FALLING_CEILING_2 :
|
||||||
case TR::Entity::FALLING_SWORD :
|
case TR::Entity::FALLING_SWORD :
|
||||||
entity.controller = new Trigger(this, i, true);
|
entity.controller = new Trigger(this, i, true);
|
||||||
break;
|
break;
|
||||||
case TR::Entity::SWITCH :
|
case TR::Entity::SWITCH :
|
||||||
case TR::Entity::SWITCH_WATER :
|
case TR::Entity::SWITCH_WATER :
|
||||||
case TR::Entity::HOLE_PUZZLE :
|
case TR::Entity::PUZZLE_HOLE_1 :
|
||||||
case TR::Entity::HOLE_KEY :
|
case TR::Entity::PUZZLE_HOLE_2 :
|
||||||
|
case TR::Entity::PUZZLE_HOLE_3 :
|
||||||
|
case TR::Entity::PUZZLE_HOLE_4 :
|
||||||
|
case TR::Entity::KEY_HOLE_1 :
|
||||||
|
case TR::Entity::KEY_HOLE_2 :
|
||||||
|
case TR::Entity::KEY_HOLE_3 :
|
||||||
|
case TR::Entity::KEY_HOLE_4 :
|
||||||
entity.controller = new Trigger(this, i, false);
|
entity.controller = new Trigger(this, i, false);
|
||||||
break;
|
break;
|
||||||
case TR::Entity::WATERFALL :
|
case TR::Entity::WATERFALL :
|
||||||
@@ -492,8 +500,9 @@ struct Level : IGame {
|
|||||||
|
|
||||||
|
|
||||||
TR::Room &room = level.rooms[entity.room];
|
TR::Room &room = level.rooms[entity.room];
|
||||||
if (!room.flags.rendered || entity.flags.invisible || entity.flags.rendered)
|
if (entity.type != TR::Entity::LARA) // TODO: remove this hack (collect conjugate room entities)
|
||||||
return;
|
if (!room.flags.rendered || entity.flags.invisible || entity.flags.rendered)
|
||||||
|
return;
|
||||||
|
|
||||||
int16 lum = entity.intensity == -1 ? room.ambient : entity.intensity;
|
int16 lum = entity.intensity == -1 ? room.ambient : entity.intensity;
|
||||||
|
|
||||||
|
15
src/shader.h
15
src/shader.h
@@ -34,16 +34,13 @@
|
|||||||
E( uRoomSize ) \
|
E( uRoomSize ) \
|
||||||
E( uPosScale )
|
E( uPosScale )
|
||||||
|
|
||||||
#define ENUM(v) v,
|
enum AttribType { SHADER_ATTRIBS(DECL_ENUM) aMAX };
|
||||||
#define STR(v) #v,
|
enum SamplerType { SHADER_SAMPLERS(DECL_ENUM) sMAX };
|
||||||
|
enum UniformType { SHADER_UNIFORMS(DECL_ENUM) uMAX };
|
||||||
|
|
||||||
enum AttribType { SHADER_ATTRIBS(ENUM) aMAX };
|
const char *AttribName[aMAX] = { SHADER_ATTRIBS(DECL_STR) };
|
||||||
enum SamplerType { SHADER_SAMPLERS(ENUM) sMAX };
|
const char *SamplerName[sMAX] = { SHADER_SAMPLERS(DECL_STR) };
|
||||||
enum UniformType { SHADER_UNIFORMS(ENUM) uMAX };
|
const char *UniformName[uMAX] = { SHADER_UNIFORMS(DECL_STR) };
|
||||||
|
|
||||||
const char *AttribName[aMAX] = { SHADER_ATTRIBS(STR) };
|
|
||||||
const char *SamplerName[sMAX] = { SHADER_SAMPLERS(STR) };
|
|
||||||
const char *UniformName[uMAX] = { SHADER_UNIFORMS(STR) };
|
|
||||||
|
|
||||||
#undef SHADER_ATTRIBS
|
#undef SHADER_ATTRIBS
|
||||||
#undef SHADER_SAMPLERS
|
#undef SHADER_SAMPLERS
|
||||||
|
@@ -52,7 +52,7 @@ struct Trigger : Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inState() && entity.type != TR::Entity::HOLE_KEY && entity.type != TR::Entity::HOLE_PUZZLE)
|
if (!inState() && entity.type != TR::Entity::KEY_HOLE_1 && entity.type != TR::Entity::PUZZLE_HOLE_1)
|
||||||
animation.setState(state != baseState ? baseState : (entity.type == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1)));
|
animation.setState(state != baseState ? baseState : (entity.type == TR::Entity::TRAP_BLADE ? 2 : (baseState ^ 1)));
|
||||||
|
|
||||||
updateAnimation(true);
|
updateAnimation(true);
|
||||||
@@ -80,7 +80,7 @@ struct Dart : Controller {
|
|||||||
TR::Entity &e = getEntity();
|
TR::Entity &e = getEntity();
|
||||||
|
|
||||||
vec3 p = pos - dir * 64.0f; // wall offset = 64
|
vec3 p = pos - dir * 64.0f; // wall offset = 64
|
||||||
Sprite::add(game, TR::Entity::SPARK, e.room, (int)p.x, (int)p.y, (int)p.z, Sprite::FRAME_RANDOM);
|
Sprite::add(game, TR::Entity::RICOCHET, e.room, (int)p.x, (int)p.y, (int)p.z, Sprite::FRAME_RANDOM);
|
||||||
|
|
||||||
level->entityRemove(entity);
|
level->entityRemove(entity);
|
||||||
delete this;
|
delete this;
|
||||||
|
10
src/utils.h
10
src/utils.h
@@ -29,6 +29,8 @@
|
|||||||
#define LOG(...) __android_log_print(ANDROID_LOG_INFO,"OpenLara",__VA_ARGS__)
|
#define LOG(...) __android_log_print(ANDROID_LOG_INFO,"OpenLara",__VA_ARGS__)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define DECL_ENUM(v) v,
|
||||||
|
#define DECL_STR(v) #v,
|
||||||
|
|
||||||
#define EPS FLT_EPSILON
|
#define EPS FLT_EPSILON
|
||||||
#define INF INFINITY
|
#define INF INFINITY
|
||||||
@@ -140,6 +142,8 @@ struct vec2 {
|
|||||||
inline bool operator != (const vec2 &v) const { return !(*this == v); }
|
inline bool operator != (const vec2 &v) const { return !(*this == v); }
|
||||||
inline bool operator == (float s) const { return x == s && y == s; }
|
inline bool operator == (float s) const { return x == s && y == s; }
|
||||||
inline bool operator != (float s) const { return !(*this == s); }
|
inline bool operator != (float s) const { return !(*this == s); }
|
||||||
|
inline bool operator < (const vec2 &v) const { return x < v.x && y < v.y; }
|
||||||
|
inline bool operator > (const vec2 &v) const { return x > v.x && y > v.y; }
|
||||||
inline vec2 operator - () const { return vec2(-x, -y); }
|
inline vec2 operator - () const { return vec2(-x, -y); }
|
||||||
|
|
||||||
vec2& operator += (const vec2 &v) { x += v.x; y += v.y; return *this; }
|
vec2& operator += (const vec2 &v) { x += v.x; y += v.y; return *this; }
|
||||||
@@ -165,6 +169,7 @@ struct vec2 {
|
|||||||
|
|
||||||
float length2() const { return dot(*this); }
|
float length2() const { return dot(*this); }
|
||||||
float length() const { return sqrtf(length2()); }
|
float length() const { return sqrtf(length2()); }
|
||||||
|
vec2 abs() const { return vec2(fabsf(x), fabsf(y)); }
|
||||||
vec2 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); }
|
vec2 normal() const { float s = length(); return s == 0.0 ? (*this) : (*this)*(1.0f/s); }
|
||||||
float angle() const { return atan2f(y, x); }
|
float angle() const { return atan2f(y, x); }
|
||||||
vec2& rotate(const vec2 &cs) { *this = vec2(x*cs.x - y*cs.y, x*cs.y + y*cs.x); return *this; }
|
vec2& rotate(const vec2 &cs) { *this = vec2(x*cs.x - y*cs.y, x*cs.y + y*cs.x); return *this; }
|
||||||
@@ -189,6 +194,8 @@ struct vec3 {
|
|||||||
inline bool operator != (const vec3 &v) const { return !(*this == v); }
|
inline bool operator != (const vec3 &v) const { return !(*this == v); }
|
||||||
inline bool operator == (float s) const { return x == s && y == s && z == s; }
|
inline bool operator == (float s) const { return x == s && y == s && z == s; }
|
||||||
inline bool operator != (float s) const { return !(*this == s); }
|
inline bool operator != (float s) const { return !(*this == s); }
|
||||||
|
inline bool operator < (const vec3 &v) const { return x < v.x && y < v.y && z < v.z; }
|
||||||
|
inline bool operator > (const vec3 &v) const { return x > v.x && y > v.y && z > v.z; }
|
||||||
inline vec3 operator - () const { return vec3(-x, -y, -z); }
|
inline vec3 operator - () const { return vec3(-x, -y, -z); }
|
||||||
|
|
||||||
vec3& operator += (const vec3 &v) { x += v.x; y += v.y; z += v.z; return *this; }
|
vec3& operator += (const vec3 &v) { x += v.x; y += v.y; z += v.z; return *this; }
|
||||||
@@ -214,6 +221,7 @@ struct vec3 {
|
|||||||
|
|
||||||
float length2() const { return dot(*this); }
|
float length2() const { return dot(*this); }
|
||||||
float length() const { return sqrtf(length2()); }
|
float length() const { return sqrtf(length2()); }
|
||||||
|
vec3 abs() const { return vec3(fabsf(x), fabsf(y), fabsf(z)); }
|
||||||
vec3 normal() const { float s = length(); return s == 0.0f ? (*this) : (*this)*(1.0f/s); }
|
vec3 normal() const { float s = length(); return s == 0.0f ? (*this) : (*this)*(1.0f/s); }
|
||||||
vec3 axisXZ() const { return (fabsf(x) > fabsf(z)) ? vec3(float(sign(x)), 0, 0) : vec3(0, 0, float(sign(z))); }
|
vec3 axisXZ() const { return (fabsf(x) > fabsf(z)) ? vec3(float(sign(x)), 0, 0) : vec3(0, 0, float(sign(z))); }
|
||||||
|
|
||||||
@@ -814,7 +822,7 @@ struct Box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool contains(const vec3 &v) const {
|
bool contains(const vec3 &v) const {
|
||||||
return v.x >= min.x && v.x <= max.x && v.y >= min.y && v.y <= max.x && v.z >= min.z && v.z <= max.z;
|
return v.x >= min.x && v.x <= max.x && v.y >= min.y && v.y <= max.y && v.z >= min.z && v.z <= max.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 pushOut2D(const vec3 &v) const {
|
vec3 pushOut2D(const vec3 &v) const {
|
||||||
|
Reference in New Issue
Block a user