mirror of
https://github.com/XProger/OpenLara.git
synced 2025-08-10 07:06:52 +02:00
#14 Basic logic for Wolf, Bear and Bat; simple path-finding; Laras hit animation (push)
This commit is contained in:
154
src/cache.h
154
src/cache.h
@@ -741,46 +741,144 @@ struct WaterCache {
|
|||||||
#undef DETAIL
|
#undef DETAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
struct ZoneCache {
|
||||||
struct LightCache {
|
|
||||||
|
|
||||||
struct Item {
|
struct Item {
|
||||||
int room;
|
uint16 zone;
|
||||||
int index;
|
uint16 count;
|
||||||
float intensity;
|
uint16 *zones;
|
||||||
} items[MAX_CACHED_LIGHTS];
|
uint16 *boxes;
|
||||||
|
Item *next;
|
||||||
|
|
||||||
void updateLightCache(const TR::Level &level, const vec3 &pos, int room) {
|
Item(uint16 zone, uint16 count, uint16 *zones, uint16 *boxes, Item *next) :
|
||||||
// update intensity
|
zone(zone), count(count), zones(zones), boxes(boxes), next(next) {}
|
||||||
for (int i = 0; i < MAX_CACHED_LIGHTS; i++) {
|
|
||||||
Item &item = items[i];
|
~Item() {
|
||||||
if (c.intensity < 0.0f) continue;
|
delete[] boxes;
|
||||||
TR::Room::Light &light = level.rooms[c.room].lights[i];
|
delete next;
|
||||||
c.intensity = max(0.0f, 1.0f - (pos - vec3(float(light.x), float(light.y), float(light.z))).length2() / ((float)light.radius * (float)light.radius));
|
}
|
||||||
|
} *items;
|
||||||
|
|
||||||
|
IGame *game;
|
||||||
|
// dummy arrays for path search
|
||||||
|
uint16 *nodes;
|
||||||
|
uint16 *parents;
|
||||||
|
uint16 *weights;
|
||||||
|
|
||||||
|
ZoneCache(IGame *game) : items(NULL), game(game) {
|
||||||
|
TR::Level *level = game->getLevel();
|
||||||
|
nodes = new uint16[level->boxesCount * 3];
|
||||||
|
parents = nodes + level->boxesCount;
|
||||||
|
weights = nodes + level->boxesCount * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ZoneCache() {
|
||||||
|
delete items;
|
||||||
|
delete[] nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item *getBoxes(uint16 zone, uint16 *zones) {
|
||||||
|
Item *item = items;
|
||||||
|
while (item) {
|
||||||
|
if (item->zone == zone && item->zones == zones)
|
||||||
|
return item;
|
||||||
|
item = item->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for new lights
|
int count = 0;
|
||||||
int index = getLightIndex(pos, room);
|
TR::Level *level = game->getLevel();
|
||||||
|
for (int i = 0; i < level->boxesCount; i++)
|
||||||
|
if (zones[i] == zone)
|
||||||
|
nodes[count++] = i;
|
||||||
|
|
||||||
if (index >= 0 && (items[0].room != room || items[0].index != index)) [
|
ASSERT(count > 0);
|
||||||
TR::Room::Light &light = level.rooms[room].lights[index];
|
uint16 *boxes = new uint16[count];
|
||||||
float intensity = max(0.0f, 1.0f - (lara->pos - vec3(float(light.x), float(light.y), float(light.z))).length2() / ((float)light.radius * (float)light.radius));
|
memcpy(boxes, nodes, sizeof(uint16) * count);
|
||||||
|
|
||||||
int i = 0;
|
return items = new Item(zone, count, zones, boxes, items);
|
||||||
while (i < MAX_CACHED_LIGHTS && lightCache[i].intensity > intensity) // get sorted place
|
}
|
||||||
i++;
|
|
||||||
if (i < MAX_CACHED_LIGHTS) { // insert light
|
uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
|
||||||
for (int j = MAX_CACHED_LIGHTS - 1; j > i; j--)
|
if (boxStart == 0xFFFF || boxEnd == 0xFFFF)
|
||||||
lightCache[j] = lightCache[j - 1];
|
return 0;
|
||||||
lightCache[i].room = room;
|
|
||||||
lightCache[i].index = index;
|
TR::Level *level = game->getLevel();
|
||||||
lightCache[i].intensity = intensity;
|
memset(parents, 0xFF, sizeof(uint16) * level->boxesCount); // fill parents by 0xFFFF
|
||||||
|
memset(weights, 0x00, sizeof(uint16) * level->boxesCount); // zeroes weights
|
||||||
|
|
||||||
|
uint16 count = 0;
|
||||||
|
nodes[count++] = boxEnd;
|
||||||
|
|
||||||
|
uint16 zone = zones[boxStart];
|
||||||
|
|
||||||
|
if (zone != zones[boxEnd])
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
TR::Box &b = level->boxes[boxStart];
|
||||||
|
|
||||||
|
int sx = (b.minX + b.maxX) >> 11; // box center / 1024
|
||||||
|
int sz = (b.minZ + b.maxZ) >> 11;
|
||||||
|
|
||||||
|
while (count) {
|
||||||
|
// get min weight
|
||||||
|
int minI = 0;
|
||||||
|
int minW = weights[nodes[minI]];
|
||||||
|
for (int i = 1; i < count; i++)
|
||||||
|
if (weights[nodes[i]] < minW) {
|
||||||
|
minI = i;
|
||||||
|
minW = weights[nodes[i]];
|
||||||
|
}
|
||||||
|
int cur = nodes[minI];
|
||||||
|
|
||||||
|
// peek min weight item from array
|
||||||
|
count--;
|
||||||
|
for (int i = minI; i < count; i++)
|
||||||
|
nodes[i] = nodes[i + 1];
|
||||||
|
|
||||||
|
// check for end of path
|
||||||
|
if (cur == boxStart) {
|
||||||
|
count = 0;
|
||||||
|
while (cur != boxEnd) {
|
||||||
|
nodes[count++] = cur;
|
||||||
|
cur = parents[cur];
|
||||||
|
}
|
||||||
|
nodes[count++] = cur;
|
||||||
|
*boxes = nodes;
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add overlap boxes
|
||||||
|
TR::Box &b = game->getLevel()->boxes[cur];
|
||||||
|
TR::Overlap *overlap = &level->overlaps[b.overlap.index];
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint16 index = overlap->boxIndex;
|
||||||
|
// unvisited yet
|
||||||
|
if (parents[index] != 0xFFFF)
|
||||||
|
continue;
|
||||||
|
// has same zone
|
||||||
|
if (zones[index] != zone)
|
||||||
|
continue;
|
||||||
|
// check for height difference
|
||||||
|
int d = level->boxes[index].floor - b.floor;
|
||||||
|
if (d > ascend || d < descend)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int dx = sx - ((b.minX + b.maxX) >> 11);
|
||||||
|
int dz = sz - ((b.minZ + b.maxZ) >> 11);
|
||||||
|
int w = abs(dx) + abs(dz);
|
||||||
|
|
||||||
|
ASSERT(count < level->boxesCount);
|
||||||
|
nodes[count++] = index;
|
||||||
|
parents[index] = cur;
|
||||||
|
weights[index] = weights[cur] + w;
|
||||||
|
|
||||||
|
} while (!(overlap++)->end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*/
|
|
||||||
|
|
||||||
#undef UNDERWATER_COLOR
|
#undef UNDERWATER_COLOR
|
||||||
|
|
||||||
|
@@ -46,11 +46,18 @@ struct Character : Controller {
|
|||||||
updateZone();
|
updateZone();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateZone() {
|
bool updateZone() {
|
||||||
TR::Level *level = game->getLevel();
|
|
||||||
int dx, dz;
|
int dx, dz;
|
||||||
box = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz).boxIndex;
|
TR::Room::Sector &s = level->getSector(getRoomIndex(), int(pos.x), int(pos.z), dx, dz);
|
||||||
zone = flying ? level->zones[0].fly[box] : level->zones[0].ground1[box];
|
if (s.boxIndex == 0xFFFF)
|
||||||
|
return false;
|
||||||
|
box = s.boxIndex;
|
||||||
|
zone = getZones()[box];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16* getZones() {
|
||||||
|
return flying ? level->zones[0].fly : level->zones[0].ground1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void rotateY(float delta) {
|
void rotateY(float delta) {
|
||||||
@@ -130,6 +137,11 @@ struct Character : Controller {
|
|||||||
animation.setState(getStateDefault());
|
animation.setState(getStateDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void updateTilt(float value, float tiltSpeed, float tiltMax) {
|
||||||
|
value = clamp(value, -tiltMax, +tiltMax);
|
||||||
|
decrease(value - angle.z, angle.z, tiltSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void updateTilt(bool active, float tiltSpeed, float tiltMax) {
|
virtual void updateTilt(bool active, float tiltSpeed, float tiltMax) {
|
||||||
// calculate turning tilt
|
// calculate turning tilt
|
||||||
if (active && (input & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (input & LEFT)) || (tilt > 0.0f && (input & RIGHT)))) {
|
if (active && (input & (LEFT | RIGHT)) && (tilt == 0.0f || (tilt < 0.0f && (input & LEFT)) || (tilt > 0.0f && (input & RIGHT)))) {
|
||||||
@@ -154,11 +166,16 @@ struct Character : Controller {
|
|||||||
stand = getStand();
|
stand = getStand();
|
||||||
updateState();
|
updateState();
|
||||||
Controller::update();
|
Controller::update();
|
||||||
updateVelocity();
|
|
||||||
updatePosition();
|
if (getEntity().flags.active) {
|
||||||
if (p != pos) {
|
updateVelocity();
|
||||||
updateLights();
|
updatePosition();
|
||||||
updateZone();
|
if (p != pos) {
|
||||||
|
if (updateZone())
|
||||||
|
updateLights();
|
||||||
|
else
|
||||||
|
pos = p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
#include "collision.h"
|
#include "collision.h"
|
||||||
|
|
||||||
#define GRAVITY (6.0f * 30.0f)
|
#define GRAVITY (6.0f * 30.0f)
|
||||||
#define NO_OVERLAP 0x7FFFFFFF
|
|
||||||
#define SPRITE_FPS 10.0f
|
#define SPRITE_FPS 10.0f
|
||||||
|
|
||||||
#define MAX_LAYERS 4
|
#define MAX_LAYERS 4
|
||||||
@@ -17,9 +16,11 @@ struct Controller;
|
|||||||
|
|
||||||
struct IGame {
|
struct IGame {
|
||||||
virtual ~IGame() {}
|
virtual ~IGame() {}
|
||||||
virtual TR::Level* getLevel() { return NULL; }
|
virtual TR::Level* getLevel() { return NULL; }
|
||||||
virtual MeshBuilder* getMesh() { return NULL; }
|
virtual MeshBuilder* getMesh() { return NULL; }
|
||||||
virtual Controller* getCamera() { return NULL; }
|
virtual Controller* getCamera() { return NULL; }
|
||||||
|
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) { return 0; }
|
||||||
|
virtual uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) { return 0; }
|
||||||
virtual void setClipParams(float clipSign, float clipHeight) {}
|
virtual void setClipParams(float clipSign, float clipHeight) {}
|
||||||
virtual void setWaterParams(float height) {}
|
virtual void setWaterParams(float height) {}
|
||||||
virtual void updateParams() {}
|
virtual void updateParams() {}
|
||||||
@@ -178,38 +179,6 @@ struct Controller {
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getOverlap(int fromX, int fromY, int fromZ, int toX, int toZ) const {
|
|
||||||
int dx, dz;
|
|
||||||
TR::Room::Sector &s = level->getSector(getEntity().room, fromX, fromZ, dx, dz);
|
|
||||||
|
|
||||||
if (s.boxIndex == 0xFFFF)
|
|
||||||
return NO_OVERLAP;
|
|
||||||
|
|
||||||
TR::Box &b = level->boxes[s.boxIndex];
|
|
||||||
if (b.contains(toX, toZ))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int floor = NO_OVERLAP;
|
|
||||||
int delta = NO_OVERLAP;
|
|
||||||
|
|
||||||
TR::Overlap *o = &level->overlaps[b.overlap.index];
|
|
||||||
do {
|
|
||||||
TR::Box &ob = level->boxes[o->boxIndex];
|
|
||||||
if (ob.contains(toX, toZ)) { // get min delta
|
|
||||||
int d = abs(b.floor - ob.floor);
|
|
||||||
if (d < delta) {
|
|
||||||
floor = ob.floor;
|
|
||||||
delta = d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (!(o++)->end);
|
|
||||||
|
|
||||||
if (floor == NO_OVERLAP)
|
|
||||||
return NO_OVERLAP;
|
|
||||||
|
|
||||||
return b.floor - floor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Sound::Sample* playSound(int id, const vec3 &pos, int flags) const {
|
Sound::Sample* playSound(int id, const vec3 &pos, int flags) const {
|
||||||
if (level->version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET)
|
if (level->version == TR::Level::VER_TR1_PSX && id == TR::SND_SECRET)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -276,12 +245,50 @@ struct Controller {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Box getBoundingBox() {
|
Box getBoundingBox() {
|
||||||
return getBoundingBoxLocal() * getMatrix();
|
return getBoundingBoxLocal() * getMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Box getBoundingBoxLocal() {
|
Box getBoundingBoxLocal(bool oriented = false) {
|
||||||
return animation.getBoundingBox(vec3(0, 0, 0), 0);
|
return animation.getBoundingBox(vec3(0, 0, 0), oriented ? getEntity().rotation.value / 0x4000 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getSpheres(Sphere *spheres) {
|
||||||
|
TR::Model *m = getModel();
|
||||||
|
Basis basis(getMatrix());
|
||||||
|
|
||||||
|
for (int i = 0; i < m->mCount; i++) {
|
||||||
|
TR::Mesh &aMesh = level->meshes[level->meshOffsets[m->mStart + i]];
|
||||||
|
vec3 center = animation.getJoints(basis, i, true) * aMesh.center;
|
||||||
|
spheres[i] = Sphere(center, aMesh.radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool collide(Controller *controller) {
|
||||||
|
TR::Model *a = getModel();
|
||||||
|
TR::Model *b = getModel();
|
||||||
|
if (!a || !b)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!getBoundingBox().intersect(controller->getBoundingBox()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ASSERT(a->mCount <= 34);
|
||||||
|
ASSERT(b->mCount <= 34);
|
||||||
|
|
||||||
|
Sphere aSpheres[34];
|
||||||
|
Sphere bSpheres[34];
|
||||||
|
|
||||||
|
getSpheres(aSpheres);
|
||||||
|
controller->getSpheres(bSpheres);
|
||||||
|
|
||||||
|
for (int i = 0; i < a->mCount; i++)
|
||||||
|
for (int j = 0; j < b->mCount; j++)
|
||||||
|
if (aSpheres[i].intersect(bSpheres[j]))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham
|
vec3 trace(int fromRoom, const vec3 &from, const vec3 &to, int &room, bool isCamera) { // TODO: use Bresenham
|
||||||
|
124
src/debug.h
124
src/debug.h
@@ -288,8 +288,8 @@ namespace Debug {
|
|||||||
for (int x = 0; x < r.xSectors; x++) {
|
for (int x = 0; x < r.xSectors; x++) {
|
||||||
TR::Room::Sector &s = r.sectors[x * r.zSectors + z];
|
TR::Room::Sector &s = r.sectors[x * r.zSectors + z];
|
||||||
if (s.boxIndex != 0xFFFF) {
|
if (s.boxIndex != 0xFFFF) {
|
||||||
bool blockable = level.boxes[s.boxIndex].overlap.value & 0x8000;
|
bool blockable = level.boxes[s.boxIndex].overlap.blockable;
|
||||||
bool block = level.boxes[s.boxIndex].overlap.value & 0x4000;
|
bool block = level.boxes[s.boxIndex].overlap.block;
|
||||||
int floor = level.boxes[s.boxIndex].floor;
|
int floor = level.boxes[s.boxIndex].floor;
|
||||||
|
|
||||||
if (blockable || block) {
|
if (blockable || block) {
|
||||||
@@ -304,10 +304,22 @@ namespace Debug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void debugOverlaps(const TR::Level &level, int boxIndex) {
|
void debugOverlaps(const TR::Level &level, int boxIndex) {
|
||||||
glColor4f(1.0f, 1.0f, 0.0f, 0.25f);
|
char str[64];
|
||||||
|
|
||||||
|
TR::Box &b = level.boxes[boxIndex];
|
||||||
|
sprintf(str, "%d", boxIndex);
|
||||||
|
Draw::text(vec3((b.maxX + b.minX) * 0.5, b.floor, (b.maxZ + b.minZ) * 0.5), vec4(0, 1, 0, 1), str);
|
||||||
|
glColor4f(0.0f, 1.0f, 0.0f, 0.25f);
|
||||||
|
Core::setBlending(bmAlpha);
|
||||||
|
debugBox(b);
|
||||||
|
|
||||||
TR::Overlap *o = &level.overlaps[level.boxes[boxIndex].overlap.index];
|
TR::Overlap *o = &level.overlaps[level.boxes[boxIndex].overlap.index];
|
||||||
do {
|
do {
|
||||||
TR::Box &b = level.boxes[o->boxIndex];
|
TR::Box &b = level.boxes[o->boxIndex];
|
||||||
|
sprintf(str, "%d", o->boxIndex);
|
||||||
|
Draw::text(vec3((b.maxX + b.minX) * 0.5, b.floor, (b.maxZ + b.minZ) * 0.5), vec4(0, 0, 1, 1), str);
|
||||||
|
glColor4f(0.0f, 0.0f, 1.0f, 0.25f);
|
||||||
|
Core::setBlending(bmAlpha);
|
||||||
debugBox(b);
|
debugBox(b);
|
||||||
} while (!(o++)->end);
|
} while (!(o++)->end);
|
||||||
}
|
}
|
||||||
@@ -369,7 +381,7 @@ namespace Debug {
|
|||||||
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];
|
||||||
|
|
||||||
sprintf(buf, "%d", (int)e.type);
|
sprintf(buf, "%d (%d)", (int)e.type, i);
|
||||||
Debug::Draw::text(vec3(e.x, e.y, e.z), e.flags.active ? vec4(0, 0, 0.8, 1) : vec4(0.8, 0, 0, 1), buf);
|
Debug::Draw::text(vec3(e.x, e.y, e.z), e.flags.active ? vec4(0, 0, 0.8, 1) : vec4(0.8, 0, 0, 1), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +393,24 @@ namespace Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void path(TR::Level &level, Enemy *enemy) {
|
||||||
|
Enemy::Path *path = enemy->path;
|
||||||
|
|
||||||
|
if (!path || !enemy->target) return;
|
||||||
|
for (int i = 0; i < path->count; i++) {
|
||||||
|
TR::Box &b = level.boxes[path->boxes[i]];
|
||||||
|
if (i == path->index)
|
||||||
|
glColor4f(0.5, 0.5, 0.0, 0.5);
|
||||||
|
else
|
||||||
|
glColor4f(0.0, 0.5, 0.0, 0.5);
|
||||||
|
debugBox(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::setDepthTest(false);
|
||||||
|
Draw::point(enemy->waypoint, vec4(1.0));
|
||||||
|
Core::setDepthTest(true);
|
||||||
|
}
|
||||||
|
|
||||||
void zones(const TR::Level &level, Lara *lara) {
|
void zones(const TR::Level &level, Lara *lara) {
|
||||||
Core::setDepthTest(false);
|
Core::setDepthTest(false);
|
||||||
for (int i = 0; i < level.roomsCount; i++)
|
for (int i = 0; i < level.roomsCount; i++)
|
||||||
@@ -446,12 +476,13 @@ namespace Debug {
|
|||||||
|
|
||||||
if (!level.meshOffsets[sm->mesh]) continue;
|
if (!level.meshOffsets[sm->mesh]) continue;
|
||||||
const TR::Mesh &mesh = level.meshes[level.meshOffsets[sm->mesh]];
|
const TR::Mesh &mesh = level.meshes[level.meshOffsets[sm->mesh]];
|
||||||
|
/*
|
||||||
{
|
{
|
||||||
char buf[255];
|
char buf[255];
|
||||||
sprintf(buf, "flags %d", (int)mesh.flags.value);
|
sprintf(buf, "flags %d", (int)mesh.flags.value);
|
||||||
Debug::Draw::text(offset + (box.min + box.max) * 0.5f, vec4(0.5, 0.5, 1.0, 1), buf);
|
Debug::Draw::text(offset + (box.min + box.max) * 0.5f, vec4(0.5, 0.5, 1.0, 1), buf);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,76 +493,29 @@ namespace Debug {
|
|||||||
if (!controller) continue;
|
if (!controller) continue;
|
||||||
|
|
||||||
mat4 matrix = controller->getMatrix();
|
mat4 matrix = controller->getMatrix();
|
||||||
Box box = controller->animation.getBoundingBox(vec3(0.0f), 0);
|
Basis basis(matrix);
|
||||||
|
|
||||||
|
TR::Model *m = controller->getModel();
|
||||||
|
if (!m) continue;
|
||||||
|
|
||||||
|
Box box = controller->getBoundingBoxLocal();
|
||||||
Debug::Draw::box(matrix, box.min, box.max, vec4(1.0));
|
Debug::Draw::box(matrix, box.min, box.max, vec4(1.0));
|
||||||
|
|
||||||
|
Sphere spheres[34];
|
||||||
|
ASSERT(m->mCount <= 34);
|
||||||
|
controller->getSpheres(spheres);
|
||||||
|
|
||||||
for (int j = 0; j < level.modelsCount; j++) {
|
for (int joint = 0; joint < m->mCount; joint++) {
|
||||||
TR::Model &m = level.models[j];
|
Sphere &sphere = spheres[joint];
|
||||||
TR::Node *node = m.node < level.nodesDataSize ? (TR::Node*)&level.nodesData[m.node] : NULL;
|
Debug::Draw::sphere(sphere.center, sphere.radius, vec4(0, 1, 1, 0.5f));
|
||||||
|
/*
|
||||||
if (!node) continue; // ???
|
{ //if (e.id != 0) {
|
||||||
if (e.type == m.type) {
|
char buf[255];
|
||||||
ASSERT(m.animation < 0xFFFF);
|
sprintf(buf, "(%d) radius %d flags %d", (int)e.type, (int)mesh->radius, (int)mesh->flags.value);
|
||||||
|
Debug::Draw::text(sphere.center, vec4(0.5, 1, 0.5, 1), buf);
|
||||||
int fSize = sizeof(TR::AnimFrame) + m.mCount * sizeof(uint16) * 2;
|
|
||||||
|
|
||||||
TR::Animation *anim = controller->animation;
|
|
||||||
TR::AnimFrame *frame = (TR::AnimFrame*)&level.frameData[(anim->frameOffset + (controller ? int((controller->animation.time * 30.0f / anim->frameRate)) * fSize : 0) >> 1)];
|
|
||||||
|
|
||||||
//mat4 m;
|
|
||||||
//m.identity();
|
|
||||||
//m.translate(vec3(frame->x, frame->y, frame->z).lerp(vec3(frameB->x, frameB->y, frameB->z), k));
|
|
||||||
|
|
||||||
int sIndex = 0;
|
|
||||||
mat4 stack[20];
|
|
||||||
mat4 joint;
|
|
||||||
|
|
||||||
joint.identity();
|
|
||||||
if (frame) joint.translate(frame->pos);
|
|
||||||
|
|
||||||
for (int k = 0; k < m.mCount; k++) {
|
|
||||||
|
|
||||||
if (k > 0 && node) {
|
|
||||||
TR::Node &t = node[k - 1];
|
|
||||||
|
|
||||||
if (t.flags & 0x01) joint = stack[--sIndex];
|
|
||||||
if (t.flags & 0x02) stack[sIndex++] = joint;
|
|
||||||
|
|
||||||
ASSERT(sIndex >= 0 && sIndex < 20);
|
|
||||||
|
|
||||||
joint.translate(vec3(t.x, t.y, t.z));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 a = frame ? frame->getAngle(k) : vec3(0.0f);
|
|
||||||
|
|
||||||
mat4 rot;
|
|
||||||
rot.identity();
|
|
||||||
rot.rotateY(a.y);
|
|
||||||
rot.rotateX(a.x);
|
|
||||||
rot.rotateZ(a.z);
|
|
||||||
|
|
||||||
joint = joint * rot;
|
|
||||||
|
|
||||||
int offset = level.meshOffsets[m.mStart + k];
|
|
||||||
TR::Mesh *mesh = (TR::Mesh*)&level.meshes[offset];
|
|
||||||
//if (!mesh->flags) continue;
|
|
||||||
Debug::Draw::sphere(matrix * joint * mesh->center, mesh->radius, vec4(0, 1, 1, 0.5f));
|
|
||||||
|
|
||||||
{ //if (e.id != 0) {
|
|
||||||
char buf[255];
|
|
||||||
sprintf(buf, "(%d) radius %d flags %d", (int)e.type, (int)mesh->radius, (int)mesh->flags.value);
|
|
||||||
Debug::Draw::text(matrix * joint * mesh->center, vec4(0.5, 1, 0.5, 1), buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Debug::Draw::box(matrix, frame->box.min(), frame->box.max(), vec4(1.0));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
766
src/enemy.h
766
src/enemy.h
@@ -4,9 +4,77 @@
|
|||||||
#include "character.h"
|
#include "character.h"
|
||||||
|
|
||||||
struct Enemy : Character {
|
struct Enemy : Character {
|
||||||
bool bitten;
|
|
||||||
|
|
||||||
Enemy(IGame *game, int entity, int health) : Character(game, entity, health), bitten(false) {}
|
struct Path {
|
||||||
|
int16 index;
|
||||||
|
int16 count;
|
||||||
|
uint16 *boxes;
|
||||||
|
TR::Level *level;
|
||||||
|
|
||||||
|
Path(TR::Level *level, uint16 *boxes, int count) : index(0), count(count), boxes(new uint16[count]), level(level) {
|
||||||
|
memcpy(this->boxes, boxes, count * sizeof(boxes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
~Path() {
|
||||||
|
delete[] boxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getNextPoint(TR::Level *level, vec3 &point) {
|
||||||
|
if (index >= count - 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TR::Box &a = level->boxes[boxes[index++]];
|
||||||
|
TR::Box &b = level->boxes[boxes[index]];
|
||||||
|
|
||||||
|
int minX = max(a.minX, b.minX);
|
||||||
|
int minZ = max(a.minZ, b.minZ);
|
||||||
|
int maxX = min(a.maxX, b.maxX);
|
||||||
|
int maxZ = min(a.maxZ, b.maxZ);
|
||||||
|
|
||||||
|
point.x = float(minX + 512) + randf() * (maxX - minX - 1024);
|
||||||
|
point.y = float((a.floor + b.floor) / 2);
|
||||||
|
point.z = float(minZ + 512) + randf() * (maxZ - minZ - 1024);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AI {
|
||||||
|
AI_FIXED, AI_RANDOM
|
||||||
|
} ai;
|
||||||
|
|
||||||
|
enum Mood {
|
||||||
|
MOOD_SLEEP, MOOD_STALK, MOOD_ATTACK, MOOD_ESCAPE
|
||||||
|
} mood;
|
||||||
|
|
||||||
|
bool wound;
|
||||||
|
int nextState;
|
||||||
|
|
||||||
|
int targetBox;
|
||||||
|
vec3 waypoint;
|
||||||
|
|
||||||
|
float thinkTime;
|
||||||
|
float aggression;
|
||||||
|
int radius;
|
||||||
|
int stepHeight;
|
||||||
|
int dropHeight;
|
||||||
|
|
||||||
|
Character *target;
|
||||||
|
Path *path;
|
||||||
|
|
||||||
|
int jointChest;
|
||||||
|
int jointHead;
|
||||||
|
|
||||||
|
Enemy(IGame *game, int entity, int health, int radius, float aggression) : Character(game, entity, health), ai(AI_RANDOM), mood(MOOD_SLEEP), wound(false), nextState(0), targetBox(-1), thinkTime(0.0f), aggression(aggression), radius(radius), target(NULL), path(NULL) {
|
||||||
|
stepHeight = 256;
|
||||||
|
dropHeight = -256;
|
||||||
|
|
||||||
|
jointChest = jointHead = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~Enemy() {
|
||||||
|
delete path;
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool activate(ActionCommand *cmd) {
|
virtual bool activate(ActionCommand *cmd) {
|
||||||
#ifdef LEVEL_EDITOR
|
#ifdef LEVEL_EDITOR
|
||||||
@@ -20,9 +88,10 @@ struct Enemy : Character {
|
|||||||
|
|
||||||
for (int i = 0; i < level->entitiesCount; i++)
|
for (int i = 0; i < level->entitiesCount; i++)
|
||||||
if (level->entities[i].type == TR::Entity::LARA) {
|
if (level->entities[i].type == TR::Entity::LARA) {
|
||||||
target = i;
|
target = (Character*)level->entities[i].controller;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
ASSERT(target);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -31,22 +100,71 @@ struct Enemy : Character {
|
|||||||
velocity = getDir() * animation.getSpeed();
|
velocity = getDir() * animation.getSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkPoint(int x, int z) {
|
||||||
|
TR::Box &a = level->boxes[box];
|
||||||
|
if (a.contains(x, z))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
TR::Overlap *o = &level->overlaps[a.overlap.index];
|
||||||
|
do {
|
||||||
|
TR::Box &b = level->boxes[o->boxIndex];
|
||||||
|
if (!b.contains(x, z))
|
||||||
|
continue;
|
||||||
|
if (getZones()[o->boxIndex] == zone) {
|
||||||
|
int d = a.floor - b.floor;
|
||||||
|
if (d <= stepHeight && d >= dropHeight)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} while (!(o++)->end);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clipByBox(vec3 &pos) {
|
||||||
|
int px = int(pos.x);
|
||||||
|
int pz = int(pos.z);
|
||||||
|
int nx = px;
|
||||||
|
int nz = pz;
|
||||||
|
|
||||||
|
TR::Box &a = level->boxes[box];
|
||||||
|
|
||||||
|
if (!checkPoint(px - radius, pz)) nx = a.minX + radius;
|
||||||
|
if (!checkPoint(px + radius, pz)) nx = a.maxX - radius;
|
||||||
|
if (!checkPoint(px, pz - radius)) nz = a.minZ + radius;
|
||||||
|
if (!checkPoint(px, pz + radius)) nz = a.maxZ - radius;
|
||||||
|
|
||||||
|
if (px != nx) pos.x = float(nx);
|
||||||
|
if (pz != nz) pos.z = float(nz);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void updatePosition() {
|
virtual void updatePosition() {
|
||||||
if (!getEntity().flags.active) return;
|
if (!getEntity().flags.active) return;
|
||||||
|
|
||||||
vec3 p = pos;
|
vec3 p = pos;
|
||||||
pos += velocity * Core::deltaTime * 30.0f;
|
pos += velocity * Core::deltaTime * 30.0f;
|
||||||
|
|
||||||
|
clipByBox(pos);
|
||||||
|
|
||||||
TR::Level::FloorInfo info;
|
TR::Level::FloorInfo info;
|
||||||
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
|
level->getFloorInfo(getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, info);
|
||||||
if (pos.y - info.floor > 1024) {
|
|
||||||
pos = p;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (stand) {
|
|
||||||
case STAND_GROUND : pos.y = float(info.floor); break;
|
int dx, dz;
|
||||||
case STAND_AIR : pos.y = clamp(pos.y, float(info.ceiling), float(info.floor)); break;
|
TR::Room::Sector &s = level->getSector(info.roomNext != TR::NO_ROOM ? info.roomNext : getRoomIndex(), int(pos.x), int(pos.z), dx, dz);
|
||||||
default : ;
|
if (s.boxIndex != 0xFFFF && zone == getZones()[s.boxIndex]) {
|
||||||
}
|
switch (stand) {
|
||||||
|
case STAND_GROUND : {
|
||||||
|
float fallSpeed = 2048.0f * Core::deltaTime;
|
||||||
|
decrease(info.floor - pos.y, pos.y, fallSpeed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STAND_AIR :
|
||||||
|
pos.y = clamp(pos.y, float(info.ceiling), float(info.floor));
|
||||||
|
break;
|
||||||
|
default : ;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
pos = p;
|
||||||
|
|
||||||
updateEntity();
|
updateEntity();
|
||||||
checkRoom();
|
checkRoom();
|
||||||
@@ -88,11 +206,7 @@ struct Enemy : Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) {
|
bool getTargetInfo(int height, vec3 *pos, float *angleX, float *angleY, float *dist) {
|
||||||
if (target == -1) return false;
|
vec3 p = waypoint;
|
||||||
Character *character = (Character*)level->entities[target].controller;
|
|
||||||
if (character->health <= 0) return false;
|
|
||||||
|
|
||||||
vec3 p = character->pos;
|
|
||||||
p.y -= height;
|
p.y -= height;
|
||||||
if (pos) *pos = p;
|
if (pos) *pos = p;
|
||||||
vec3 a = p - this->pos;
|
vec3 a = p - this->pos;
|
||||||
@@ -109,11 +223,16 @@ struct Enemy : Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int turn(float delta, float speed) {
|
int turn(float delta, float speed) {
|
||||||
speed *= Core::deltaTime;
|
float w = speed * Core::deltaTime;
|
||||||
decrease(delta, angle.y, speed);
|
|
||||||
if (speed != 0.0f) {
|
updateTilt(delta, w, speed * 0.1f);
|
||||||
velocity = velocity.rotateY(-speed);
|
|
||||||
return speed < 0 ? LEFT : RIGHT;
|
if (delta != 0.0f) {
|
||||||
|
decrease(delta, angle.y, w);
|
||||||
|
if (speed != 0.0f) {
|
||||||
|
velocity = velocity.rotateY(-w);
|
||||||
|
return speed < 0 ? LEFT : RIGHT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -128,21 +247,230 @@ struct Enemy : Character {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
virtual void hit(int damage, Controller *enemy = NULL) {
|
||||||
|
Character::hit(damage, enemy);
|
||||||
|
wound = true;
|
||||||
|
};
|
||||||
|
|
||||||
void bite(const vec3 &pos, int damage) {
|
void bite(const vec3 &pos, int damage) {
|
||||||
if (bitten) return;
|
ASSERT(target);
|
||||||
bitten = true;
|
target->hit(damage, this);
|
||||||
ASSERT(target > -1);
|
Sprite::add(game, TR::Entity::BLOOD, target->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED);
|
||||||
Character *c = (Character*)level->entities[target].controller;
|
}
|
||||||
c->hit(damage, this);
|
|
||||||
Sprite::add(game, TR::Entity::BLOOD, c->getRoomIndex(), (int)pos.x, (int)pos.y, (int)pos.z, Sprite::FRAME_ANIMATED);
|
#define STALK_BOX (1024 * 3)
|
||||||
|
#define ESCAPE_BOX (1024 * 5)
|
||||||
|
#define ATTACK_BOX STALK_BOX
|
||||||
|
|
||||||
|
Mood getMoodFixed() {
|
||||||
|
bool inZone = zone == target->zone;
|
||||||
|
|
||||||
|
if (mood == MOOD_SLEEP || mood == MOOD_STALK)
|
||||||
|
return inZone ? MOOD_ATTACK : (wound ? MOOD_ESCAPE : mood);
|
||||||
|
|
||||||
|
if (mood == MOOD_ATTACK)
|
||||||
|
return inZone ? mood : MOOD_SLEEP;
|
||||||
|
|
||||||
|
return inZone ? MOOD_ATTACK : mood;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mood getMoodRandom() {
|
||||||
|
bool inZone = zone == target->zone;
|
||||||
|
bool brave = rand() < (mood != MOOD_ESCAPE ? 0x7800 : 0x0100) && inZone;
|
||||||
|
|
||||||
|
if (mood == MOOD_SLEEP || mood == MOOD_STALK) {
|
||||||
|
if (wound && !brave)
|
||||||
|
return MOOD_ESCAPE;
|
||||||
|
if (inZone) {
|
||||||
|
int dx = abs(int(pos.x - target->pos.x));
|
||||||
|
int dz = abs(int(pos.z - target->pos.z));
|
||||||
|
return ((dx <= ATTACK_BOX && dz <= ATTACK_BOX) || (mood == MOOD_STALK && targetBox == -1)) ? MOOD_ATTACK : MOOD_STALK;
|
||||||
|
}
|
||||||
|
return mood;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mood == MOOD_ATTACK)
|
||||||
|
return (wound && !brave) ? MOOD_ESCAPE : (!inZone ? MOOD_SLEEP : mood);
|
||||||
|
|
||||||
|
return brave ? MOOD_STALK : mood;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool think(bool fixedLogic) {
|
||||||
|
thinkTime += Core::deltaTime;
|
||||||
|
if (thinkTime < 1.0f / 30.0f)
|
||||||
|
return false;
|
||||||
|
thinkTime -= 1.0f / 30.0f;
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
mood = MOOD_SLEEP;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetBoxOld = targetBox;
|
||||||
|
|
||||||
|
// update mood
|
||||||
|
bool inZone = zone == target->zone;
|
||||||
|
|
||||||
|
if (mood != MOOD_ATTACK && targetBox > -1 && !checkBox(targetBox)) {
|
||||||
|
if (!inZone)
|
||||||
|
mood = MOOD_SLEEP;
|
||||||
|
targetBox = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mood = target->health <= 0 ? MOOD_SLEEP : (ai == AI_FIXED ? getMoodFixed() : getMoodRandom());
|
||||||
|
|
||||||
|
// set behavior and target
|
||||||
|
int box;
|
||||||
|
|
||||||
|
switch (mood) {
|
||||||
|
case MOOD_SLEEP :
|
||||||
|
if (targetBox == -1 && checkBox(box = getRandomZoneBox()) && isStalkBox(box)) {
|
||||||
|
mood = MOOD_STALK;
|
||||||
|
gotoBox(box);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MOOD_STALK :
|
||||||
|
if ((targetBox == -1 || !isStalkBox(targetBox)) && checkBox(box = getRandomZoneBox())) {
|
||||||
|
if (isStalkBox(box))
|
||||||
|
gotoBox(box);
|
||||||
|
else
|
||||||
|
if (targetBox == -1) {
|
||||||
|
if (!inZone)
|
||||||
|
mood = MOOD_SLEEP;
|
||||||
|
gotoBox(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MOOD_ATTACK :
|
||||||
|
if (randf() > aggression)
|
||||||
|
break;
|
||||||
|
targetBox = -1;
|
||||||
|
break;
|
||||||
|
case MOOD_ESCAPE :
|
||||||
|
if (targetBox == -1 && checkBox(box = getRandomZoneBox())) {
|
||||||
|
if (isEscapeBox(box))
|
||||||
|
gotoBox(box);
|
||||||
|
else
|
||||||
|
if (inZone && isStalkBox(box)) {
|
||||||
|
mood = MOOD_STALK;
|
||||||
|
gotoBox(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetBox == -1)
|
||||||
|
gotoBox(target->box);
|
||||||
|
|
||||||
|
if (path && this->box != path->boxes[path->index - 1] && this->box != path->boxes[path->index])
|
||||||
|
targetBoxOld = -1;
|
||||||
|
|
||||||
|
if (targetBoxOld != targetBox) {
|
||||||
|
if (findPath(stepHeight, dropHeight))
|
||||||
|
nextWaypoint();
|
||||||
|
else
|
||||||
|
targetBox = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetBox != -1 && path) {
|
||||||
|
vec3 d = pos - waypoint;
|
||||||
|
|
||||||
|
if (fabsf(d.x) < 512 && fabsf(d.y) < 512 && fabsf(d.z) < 512)
|
||||||
|
nextWaypoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextWaypoint() {
|
||||||
|
if (!path->getNextPoint(level, waypoint))
|
||||||
|
waypoint = target->pos;
|
||||||
|
if (flying) {
|
||||||
|
if (target->stand != STAND_ONWATER)
|
||||||
|
waypoint.y -= 765.0f;
|
||||||
|
else
|
||||||
|
waypoint.y -= 64.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 getRandomZoneBox() {
|
||||||
|
return game->getRandomBox(zone, getZones());
|
||||||
|
}
|
||||||
|
|
||||||
|
void gotoBox(int box) {
|
||||||
|
targetBox = box;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkBox(int box) {
|
||||||
|
if (zone != getZones()[box])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TR::Entity &e = getEntity();
|
||||||
|
TR::Box &b = game->getLevel()->boxes[box];
|
||||||
|
TR::Entity::Type type = e.type;
|
||||||
|
|
||||||
|
if (type == TR::Entity::ENEMY_REX || type == TR::Entity::ENEMY_MUTANT_1 || type == TR::Entity::ENEMY_CENTAUR) {
|
||||||
|
if (b.overlap.blockable)
|
||||||
|
return false;
|
||||||
|
} else
|
||||||
|
if (b.overlap.block)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return e.x < int(b.minX) || e.x > int(b.maxX) || e.z < int(b.minZ) || e.z > int(b.maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isStalkBox(int box) {
|
||||||
|
TR::Entity &t = target->getEntity();
|
||||||
|
TR::Box &b = game->getLevel()->boxes[box];
|
||||||
|
|
||||||
|
int x = (b.minX + b.maxX) / 2 - t.x;
|
||||||
|
if (abs(x) > STALK_BOX) return false;
|
||||||
|
|
||||||
|
int z = (b.minZ + b.maxZ) / 2 - t.z;
|
||||||
|
if (abs(z) > STALK_BOX) return false;
|
||||||
|
|
||||||
|
// TODO: check for some quadrant shit
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEscapeBox(int box) {
|
||||||
|
TR::Entity &e = getEntity();
|
||||||
|
TR::Entity &t = target->getEntity();
|
||||||
|
TR::Box &b = game->getLevel()->boxes[box];
|
||||||
|
|
||||||
|
int x = (b.minX + b.maxX) / 2 - t.x;
|
||||||
|
if (abs(x) < ESCAPE_BOX) return false;
|
||||||
|
|
||||||
|
int z = (b.minZ + b.maxZ) / 2 - t.z;
|
||||||
|
if (abs(z) < ESCAPE_BOX) return false;
|
||||||
|
|
||||||
|
return !((e.x > t.x) ^ (x > 0)) || !((e.z > t.z) ^ (z > 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool findPath(int ascend, int descend) {
|
||||||
|
delete path;
|
||||||
|
path = NULL;
|
||||||
|
|
||||||
|
uint16 *boxes;
|
||||||
|
uint16 count = game->findPath(ascend, descend, box, targetBox, getZones(), &boxes);
|
||||||
|
if (count) {
|
||||||
|
path = new Path(level, boxes, count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define WOLF_TURN_FAST (DEG2RAD * 150)
|
||||||
|
#define WOLF_TURN_SLOW (DEG2RAD * 60)
|
||||||
|
|
||||||
|
#define WOLF_DIST_STALK STALK_BOX
|
||||||
|
#define WOLF_DIST_BITE 345
|
||||||
|
#define WOLF_DIST_ATTACK (1024 + 512)
|
||||||
|
|
||||||
#define WOLF_TURN_FAST PI
|
|
||||||
#define WOLF_TURN_SLOW (PI / 3.0f)
|
|
||||||
#define WOLF_TILT_MAX (PI / 6.0f)
|
|
||||||
#define WOLF_TILT_SPEED WOLF_TILT_MAX
|
|
||||||
|
|
||||||
struct Wolf : Enemy {
|
struct Wolf : Enemy {
|
||||||
|
|
||||||
@@ -153,70 +481,110 @@ struct Wolf : Enemy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
STATE_STOP = 1,
|
STATE_NONE ,
|
||||||
STATE_WALK = 2,
|
STATE_STOP ,
|
||||||
STATE_RUN = 3,
|
STATE_WALK ,
|
||||||
STATE_STALKING = 5,
|
STATE_RUN ,
|
||||||
STATE_JUMP = 6,
|
STATE_JUMP , // unused
|
||||||
STATE_HOWL = 7,
|
STATE_STALK ,
|
||||||
STATE_SLEEP = 8,
|
STATE_ATTACK ,
|
||||||
STATE_GROWL = 9,
|
STATE_HOWL ,
|
||||||
STATE_TURN = 10,
|
STATE_SLEEP ,
|
||||||
STATE_DEATH = 11,
|
STATE_GROWL ,
|
||||||
STATE_BITE = 12,
|
STATE_TURN , // unused
|
||||||
|
STATE_DEATH ,
|
||||||
|
STATE_BITE ,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
Wolf(IGame *game, int entity) : Enemy(game, entity, 6, 341, 0.25f) {
|
||||||
JOINT_CHEST = 2,
|
dropHeight = -1024;
|
||||||
JOINT_HEAD = 3
|
jointChest = 2;
|
||||||
};
|
jointHead = 3;
|
||||||
|
nextState = STATE_NONE;
|
||||||
Wolf(IGame *game, int entity) : Enemy(game, entity, 6) {}
|
}
|
||||||
|
|
||||||
virtual int getStateGround() {
|
virtual int getStateGround() {
|
||||||
TR::Entity &e = getEntity();
|
TR::Entity &e = getEntity();
|
||||||
if (!e.flags.active)
|
if (!e.flags.active)
|
||||||
return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP;
|
return (state == STATE_STOP || state == STATE_SLEEP) ? STATE_SLEEP : STATE_STOP;
|
||||||
|
|
||||||
|
if (!think(false))
|
||||||
|
return state;
|
||||||
|
|
||||||
|
float angle, dist;
|
||||||
|
getTargetInfo(0, NULL, NULL, &angle, NULL);
|
||||||
|
|
||||||
|
dist = (target && target->health > 0) ? (pos - target->pos).length() : +INF;
|
||||||
|
|
||||||
|
bool inZone = target ? target->zone == zone : false;
|
||||||
|
|
||||||
|
if (nextState == state)
|
||||||
|
nextState = STATE_NONE;
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_SLEEP : return (target > -1 && level->entities[target].room == getRoomIndex()) ? STATE_STOP : state;
|
case STATE_SLEEP :
|
||||||
case STATE_STOP : return target > -1 ? STATE_HOWL : STATE_SLEEP;
|
if (mood == MOOD_ESCAPE || inZone) {
|
||||||
case STATE_HOWL : return state;
|
nextState = STATE_GROWL;
|
||||||
case STATE_GROWL : return target > -1 ? (randf() > 0.5f ? STATE_STALKING : STATE_RUN) : STATE_STOP;
|
return STATE_STOP;
|
||||||
case STATE_RUN :
|
}
|
||||||
case STATE_STALKING : {
|
if (randf() < 0.0001f) {
|
||||||
if (state == STATE_STALKING && health < 6) return STATE_RUN;
|
nextState = STATE_WALK;
|
||||||
|
return STATE_STOP;
|
||||||
float angleY, dist;
|
|
||||||
if (getTargetInfo(0, NULL, NULL, &angleY, &dist)) {
|
|
||||||
float w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
|
|
||||||
input = turn(angleY, w); // also set input mask (left, right) for tilt control
|
|
||||||
|
|
||||||
if ((state == STATE_STALKING && dist < 512)) {
|
|
||||||
bitten = false;
|
|
||||||
return STATE_BITE;
|
|
||||||
}
|
|
||||||
if ((state == STATE_RUN && dist > 512 && dist < 1024)) {
|
|
||||||
bitten = false;
|
|
||||||
return STATE_JUMP;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
target = -1;
|
|
||||||
return STATE_GROWL;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
case STATE_STOP : return nextState != STATE_NONE ? nextState : STATE_WALK;
|
||||||
|
case STATE_WALK :
|
||||||
|
if (mood != MOOD_SLEEP) {
|
||||||
|
nextState = STATE_NONE;
|
||||||
|
return STATE_STALK;
|
||||||
|
}
|
||||||
|
if (randf() < 0.0001f) {
|
||||||
|
nextState = STATE_SLEEP;
|
||||||
|
return STATE_STOP;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_GROWL :
|
||||||
|
if (nextState != STATE_NONE) return nextState;
|
||||||
|
if (mood == MOOD_ESCAPE) return STATE_RUN;
|
||||||
|
if (dist < WOLF_DIST_BITE) return STATE_BITE;
|
||||||
|
if (mood == MOOD_STALK) return STATE_STALK;
|
||||||
|
if (mood == MOOD_SLEEP) return STATE_STOP;
|
||||||
|
return STATE_RUN;
|
||||||
|
case STATE_STALK :
|
||||||
|
if (mood == MOOD_ESCAPE) return STATE_RUN;
|
||||||
|
if (dist < WOLF_DIST_BITE) return STATE_BITE;
|
||||||
|
if (dist > WOLF_DIST_STALK) return STATE_RUN;
|
||||||
|
if (mood == MOOD_ATTACK) return STATE_RUN;
|
||||||
|
if (randf() < 0.012f) {
|
||||||
|
nextState = STATE_HOWL;
|
||||||
|
return STATE_GROWL;
|
||||||
|
}
|
||||||
|
if (mood == MOOD_SLEEP) return STATE_GROWL;
|
||||||
|
break;
|
||||||
|
case STATE_RUN :
|
||||||
|
if (dist < WOLF_DIST_ATTACK) {
|
||||||
|
if (dist < WOLF_DIST_ATTACK * 0.5f && fabsf(angle) < PI * 0.5f) {
|
||||||
|
nextState = STATE_NONE;
|
||||||
|
return STATE_ATTACK;
|
||||||
|
}
|
||||||
|
nextState = STATE_STALK;
|
||||||
|
return STATE_GROWL;
|
||||||
|
}
|
||||||
|
if (mood == MOOD_STALK && dist < WOLF_DIST_STALK) {
|
||||||
|
nextState = STATE_STALK;
|
||||||
|
return STATE_GROWL;
|
||||||
|
}
|
||||||
|
if (mood == MOOD_SLEEP) return STATE_GROWL;
|
||||||
|
break;
|
||||||
|
case STATE_ATTACK :
|
||||||
|
case STATE_BITE :
|
||||||
|
if (nextState == STATE_NONE && target->health > 0 && collide(target)) {
|
||||||
|
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_ATTACK ? 50 : 100);
|
||||||
|
nextState = state == STATE_ATTACK ? STATE_RUN : STATE_GROWL;
|
||||||
|
}
|
||||||
|
return state == STATE_ATTACK ? STATE_RUN : state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((state == STATE_JUMP || state == STATE_BITE) && !bitten) {
|
|
||||||
float dist;
|
|
||||||
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
|
|
||||||
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).pos, state == STATE_BITE ? 100 : 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == STATE_JUMP)
|
|
||||||
return STATE_RUN;
|
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,18 +598,30 @@ struct Wolf : Enemy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual void updatePosition() {
|
virtual void updatePosition() {
|
||||||
updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX);
|
float angleY = 0.0f;
|
||||||
|
if (state == STATE_RUN || state == STATE_WALK || state == STATE_STALK)
|
||||||
|
getTargetInfo(0, NULL, NULL, &angleY, NULL);
|
||||||
|
|
||||||
|
turn(angleY, state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW);
|
||||||
|
|
||||||
if (state == STATE_DEATH) {
|
if (state == STATE_DEATH) {
|
||||||
animation.overrideMask = 0;
|
animation.overrideMask = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Enemy::updatePosition();
|
Enemy::updatePosition();
|
||||||
setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD);
|
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_STALK, jointChest, jointHead);
|
||||||
lookAt(target, JOINT_CHEST, JOINT_HEAD);
|
lookAt(target ? target->entity : -1, jointChest, jointHead);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define BEAR_DIST_EAT 768
|
||||||
|
#define BEAR_DIST_HOWL 2048
|
||||||
|
#define BEAR_DIST_BITE 1024
|
||||||
|
#define BEAR_DIST_ATTACK 600
|
||||||
|
|
||||||
|
#define BEAR_TURN_FAST (DEG2RAD * 150)
|
||||||
|
#define BEAR_TURN_SLOW (DEG2RAD * 60)
|
||||||
|
|
||||||
struct Bear : Enemy {
|
struct Bear : Enemy {
|
||||||
|
|
||||||
@@ -251,130 +631,185 @@ struct Bear : Enemy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
STATE_WALK = 0,
|
STATE_NONE = -1,
|
||||||
STATE_STOP = 1,
|
STATE_WALK ,
|
||||||
STATE_HIND = 2,
|
STATE_STOP ,
|
||||||
STATE_RUN = 3,
|
STATE_HIND ,
|
||||||
STATE_HOWL = 4,
|
STATE_RUN ,
|
||||||
STATE_GROWL = 5,
|
STATE_HOWL ,
|
||||||
STATE_BITE = 6,
|
STATE_GROWL ,
|
||||||
STATE_ATTACK = 7,
|
STATE_BITE ,
|
||||||
STATE_EAT = 8,
|
STATE_ATTACK ,
|
||||||
STATE_DEATH = 9,
|
STATE_EAT ,
|
||||||
|
STATE_DEATH ,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
Bear(IGame *game, int entity) : Enemy(game, entity, 20, 341, 0.5f) {
|
||||||
JOINT_CHEST = 2,
|
jointChest = 13;
|
||||||
JOINT_HEAD = 3
|
jointHead = 14;
|
||||||
};
|
nextState = STATE_NONE;
|
||||||
|
}
|
||||||
Bear(IGame *game, int entity) : Enemy(game, entity, 20) {}
|
|
||||||
|
|
||||||
virtual int getStateGround() {
|
virtual int getStateGround() {
|
||||||
|
if (!getEntity().flags.active)
|
||||||
|
return state;
|
||||||
|
|
||||||
|
if (!think(false))
|
||||||
|
return state;
|
||||||
|
|
||||||
|
if (nextState == state)
|
||||||
|
nextState = STATE_NONE;
|
||||||
|
|
||||||
|
float dist = target ? (pos - target->pos).length() : +INF;
|
||||||
|
|
||||||
|
bool targetDead = target->health <= 0;
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_STOP : return STATE_RUN;
|
|
||||||
case STATE_GROWL : return state;
|
|
||||||
case STATE_WALK :
|
case STATE_WALK :
|
||||||
case STATE_RUN :
|
if (targetDead && collide(target))
|
||||||
case STATE_HIND : {
|
return STATE_STOP; // eat lara! >:E
|
||||||
if (state == STATE_HIND && health < 6) return STATE_RUN;
|
else
|
||||||
|
if (mood != MOOD_SLEEP) {
|
||||||
float angleY, dist;
|
if (mood == MOOD_ESCAPE)
|
||||||
if (getTargetInfo(0, NULL, NULL, &angleY, &dist)) {
|
nextState = STATE_NONE;
|
||||||
float w = state == STATE_RUN ? WOLF_TURN_FAST : WOLF_TURN_SLOW;
|
return STATE_STOP;
|
||||||
input = turn(angleY, w); // also set input mask (left, right) for tilt control
|
} else if (randf() < 0.003f) {
|
||||||
|
nextState = STATE_GROWL;
|
||||||
if ((state == STATE_HIND && dist < 512)) {
|
return STATE_STOP;
|
||||||
bitten = false;
|
|
||||||
return STATE_ATTACK;
|
|
||||||
}
|
|
||||||
if ((state == STATE_RUN && dist > 512 && dist < 1024)) {
|
|
||||||
bitten = false;
|
|
||||||
return STATE_BITE;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case STATE_STOP :
|
||||||
|
if (targetDead)
|
||||||
|
return dist <= BEAR_DIST_EAT ? STATE_EAT : STATE_WALK;
|
||||||
|
else
|
||||||
|
return nextState != STATE_NONE ? nextState : (mood == MOOD_SLEEP ? STATE_WALK : STATE_RUN);
|
||||||
|
case STATE_HIND :
|
||||||
|
if (collide(target)) {
|
||||||
|
return STATE_HOWL;
|
||||||
|
}
|
||||||
|
if (mood == MOOD_ESCAPE) {
|
||||||
|
nextState = STATE_NONE;
|
||||||
|
return STATE_HOWL;
|
||||||
|
}
|
||||||
|
if (mood == MOOD_SLEEP || randf() < 0.003f) {
|
||||||
|
nextState = STATE_GROWL;
|
||||||
|
return STATE_HOWL;
|
||||||
|
}
|
||||||
|
if (dist > BEAR_DIST_HOWL || randf() < 0.05f) {
|
||||||
|
nextState = STATE_STOP;
|
||||||
|
return STATE_HOWL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_RUN :
|
||||||
|
if (collide(target))
|
||||||
|
target->hit(3, this);
|
||||||
|
if (targetDead || mood == MOOD_SLEEP)
|
||||||
|
return STATE_STOP;
|
||||||
|
if (dist < BEAR_DIST_HOWL && randf() < 0.025f) {
|
||||||
|
nextState = STATE_HOWL;
|
||||||
|
return STATE_STOP;
|
||||||
|
} else if (dist < BEAR_DIST_BITE) {
|
||||||
|
nextState = STATE_NONE;
|
||||||
|
return STATE_BITE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_HOWL :
|
||||||
|
if (nextState != STATE_NONE) return nextState;
|
||||||
|
if (mood == MOOD_SLEEP || mood == MOOD_ESCAPE) return STATE_STOP;
|
||||||
|
if (dist < BEAR_DIST_ATTACK) return STATE_ATTACK;
|
||||||
|
return STATE_HIND;
|
||||||
|
case STATE_BITE :
|
||||||
|
case STATE_ATTACK :
|
||||||
|
if (nextState == STATE_NONE && collide(target)) {
|
||||||
|
bite(animation.getJoints(getMatrix(), jointHead, true).pos, state == STATE_BITE ? 200 : 400);
|
||||||
|
nextState = state == STATE_BITE ? STATE_STOP : STATE_HOWL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((state == STATE_ATTACK || state == STATE_BITE) && !bitten) {
|
|
||||||
float dist;
|
|
||||||
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
|
|
||||||
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).pos, state == STATE_BITE ? 100 : 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int getStateDeath() {
|
virtual int getStateDeath() {
|
||||||
return state == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
|
switch (state) {
|
||||||
|
case STATE_HIND : return STATE_HOWL;
|
||||||
|
case STATE_RUN :
|
||||||
|
case STATE_WALK : return STATE_STOP;
|
||||||
|
case STATE_HOWL :
|
||||||
|
case STATE_STOP : return STATE_DEATH;
|
||||||
|
}
|
||||||
|
return state;// == STATE_DEATH ? state : animation.setAnim(ANIM_DEATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void updatePosition() {
|
virtual void updatePosition() {
|
||||||
updateTilt(state == STATE_RUN, WOLF_TILT_SPEED, WOLF_TILT_MAX);
|
float angleY = 0.0f;
|
||||||
Enemy::updatePosition();
|
if (state == STATE_RUN || state == STATE_WALK || state == STATE_HIND)
|
||||||
/*
|
getTargetInfo(0, NULL, NULL, &angleY, NULL);
|
||||||
|
|
||||||
|
turn(angleY, state == STATE_RUN ? BEAR_TURN_FAST : BEAR_TURN_SLOW);
|
||||||
|
|
||||||
if (state == STATE_DEATH) {
|
if (state == STATE_DEATH) {
|
||||||
animation.overrideMask = 0;
|
animation.overrideMask = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Enemy::updatePosition();
|
Enemy::updatePosition();
|
||||||
setOverrides(state == STATE_STALKING || state == STATE_RUN, JOINT_CHEST, JOINT_HEAD);
|
setOverrides(state == STATE_RUN || state == STATE_WALK || state == STATE_HIND, jointChest, jointHead);
|
||||||
lookAt(target, JOINT_CHEST, JOINT_HEAD);
|
lookAt(target ? target->entity : -1, jointChest, jointHead);
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#define BAT_TURN_SPEED PI
|
#define BAT_TURN_SPEED (DEG2RAD * 300)
|
||||||
#define BAT_LIFT_SPEED 512.0f
|
#define BAT_LIFT_SPEED 512.0f
|
||||||
|
|
||||||
struct Bat : Enemy {
|
struct Bat : Enemy {
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ANIM_DEATH = 4,
|
ANIM_DEATH = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
STATE_AWAKE = 1,
|
STATE_NONE,
|
||||||
STATE_FLY = 2,
|
STATE_AWAKE,
|
||||||
STATE_ATTACK = 3,
|
STATE_FLY,
|
||||||
STATE_CIRCLING = 4,
|
STATE_ATTACK,
|
||||||
STATE_DEATH = 5,
|
STATE_CIRCLING,
|
||||||
|
STATE_DEATH,
|
||||||
};
|
};
|
||||||
|
|
||||||
Bat(IGame *game, int entity) : Enemy(game, entity, 1) { stand = STAND_AIR; }
|
Bat(IGame *game, int entity) : Enemy(game, entity, 1, 102, 0.03f) {
|
||||||
|
stand = STAND_AIR;
|
||||||
|
stepHeight = 20 * 1024;
|
||||||
|
dropHeight = -20 * 1024;
|
||||||
|
jointHead = 4;
|
||||||
|
}
|
||||||
|
|
||||||
virtual int getStateAir() {
|
virtual int getStateAir() {
|
||||||
if (!getEntity().flags.active) {
|
if (!getEntity().flags.active) {
|
||||||
animation.time = 0.0f;
|
animation.time = 0.0f;
|
||||||
animation.dir = 0.0f;
|
animation.dir = 0.0f;
|
||||||
return STATE_AWAKE;
|
return STATE_AWAKE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!think(false))
|
||||||
|
return state;
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_AWAKE : return STATE_FLY;
|
case STATE_AWAKE : return STATE_FLY;
|
||||||
case STATE_ATTACK :
|
case STATE_ATTACK :
|
||||||
case STATE_FLY : {
|
if (!collide(target)) {
|
||||||
vec3 p;
|
mood = MOOD_SLEEP;
|
||||||
float angleY, dist;
|
|
||||||
if (getTargetInfo(765, &p, NULL, &angleY, &dist)) {
|
|
||||||
turn(angleY, BAT_TURN_SPEED);
|
|
||||||
lift(p.y - pos.y, BAT_LIFT_SPEED);
|
|
||||||
|
|
||||||
if (dist < 128) {
|
|
||||||
if (state == STATE_ATTACK && !(animation.frameIndex % 15))
|
|
||||||
bite(pos, 2); // TODO: bite position
|
|
||||||
else
|
|
||||||
bitten = false;
|
|
||||||
return STATE_ATTACK;
|
|
||||||
} else
|
|
||||||
return STATE_FLY;
|
|
||||||
} else {
|
|
||||||
turn(PI, BAT_TURN_SPEED); // circling
|
|
||||||
return STATE_FLY;
|
return STATE_FLY;
|
||||||
|
} else
|
||||||
|
bite(animation.getJoints(getMatrix(), jointHead, true).pos, 2);
|
||||||
|
break;
|
||||||
|
case STATE_FLY :
|
||||||
|
if (collide(target)) {
|
||||||
|
mood = MOOD_ATTACK;
|
||||||
|
return STATE_ATTACK;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@@ -391,6 +826,15 @@ struct Bat : Enemy {
|
|||||||
velocity = vec3(0.0f, velocity.y + GRAVITY * Core::deltaTime, 0.0f);
|
velocity = vec3(0.0f, velocity.y + GRAVITY * Core::deltaTime, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void updatePosition() {
|
||||||
|
float angleY = 0.0f;
|
||||||
|
if (state == STATE_FLY || state == STATE_ATTACK)
|
||||||
|
getTargetInfo(0, NULL, NULL, &angleY, NULL);
|
||||||
|
turn(angleY, BAT_TURN_SPEED);
|
||||||
|
if (flying)
|
||||||
|
lift(waypoint.y - pos.y, BAT_LIFT_SPEED);
|
||||||
|
Enemy::updatePosition();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
126
src/lara.h
126
src/lara.h
@@ -216,6 +216,10 @@ struct Lara : Character {
|
|||||||
int roomPrev; // water out from room
|
int roomPrev; // water out from room
|
||||||
vec2 rotFactor;
|
vec2 rotFactor;
|
||||||
|
|
||||||
|
float hitTime;
|
||||||
|
int hitDir;
|
||||||
|
vec3 collisionOffset;
|
||||||
|
|
||||||
struct Braid {
|
struct Braid {
|
||||||
Lara *lara;
|
Lara *lara;
|
||||||
vec3 offset;
|
vec3 offset;
|
||||||
@@ -386,6 +390,9 @@ struct Lara : Character {
|
|||||||
animation.setAnim(ANIM_STAND);
|
animation.setAnim(ANIM_STAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hitDir = -1;
|
||||||
|
hitTime = 0.0f;
|
||||||
|
|
||||||
getEntity().flags.active = 1;
|
getEntity().flags.active = 1;
|
||||||
initMeshOverrides();
|
initMeshOverrides();
|
||||||
|
|
||||||
@@ -888,8 +895,9 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void updateOverrides() {
|
void updateOverrides() {
|
||||||
|
int overrideMask = 0;
|
||||||
// head & chest
|
// head & chest
|
||||||
animation.overrideMask |= BODY_CHEST | BODY_HEAD;
|
overrideMask |= BODY_CHEST | BODY_HEAD;
|
||||||
|
|
||||||
animation.overrides[ 7] = animation.getJointRot( 7);
|
animation.overrides[ 7] = animation.getJointRot( 7);
|
||||||
animation.overrides[14] = animation.getJointRot(14);
|
animation.overrides[14] = animation.getJointRot(14);
|
||||||
@@ -902,6 +910,7 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// arms
|
||||||
if (!emptyHands()) {
|
if (!emptyHands()) {
|
||||||
// right arm
|
// right arm
|
||||||
Arm *arm = &arms[0];
|
Arm *arm = &arms[0];
|
||||||
@@ -914,10 +923,38 @@ struct Lara : Character {
|
|||||||
animation.overrides[12] = arm->animation.getJointRot(12);
|
animation.overrides[12] = arm->animation.getJointRot(12);
|
||||||
animation.overrides[13] = arm->animation.getJointRot(13);
|
animation.overrides[13] = arm->animation.getJointRot(13);
|
||||||
|
|
||||||
animation.overrideMask |= (BODY_ARM_R | BODY_ARM_L);
|
overrideMask |= (BODY_ARM_R | BODY_ARM_L);
|
||||||
} else
|
} else
|
||||||
animation.overrideMask &= ~(BODY_ARM_R | BODY_ARM_L);
|
overrideMask &= ~(BODY_ARM_R | BODY_ARM_L);
|
||||||
|
|
||||||
|
// update hit anim
|
||||||
|
if (hitDir >= 0) {
|
||||||
|
Animation hitAnim = Animation(level, getModel());
|
||||||
|
switch (hitDir) {
|
||||||
|
case 0 : hitAnim.setAnim(ANIM_HIT_FRONT, 0, false); break;
|
||||||
|
case 1 : hitAnim.setAnim(ANIM_HIT_LEFT, 0, false); break;
|
||||||
|
case 2 : hitAnim.setAnim(ANIM_HIT_BACK , 0, false); break;
|
||||||
|
case 3 : hitAnim.setAnim(ANIM_HIT_RIGHT, 0, false); break;
|
||||||
|
}
|
||||||
|
hitTime = min(hitTime, hitAnim.timeMax - EPS);
|
||||||
|
hitAnim.time = hitTime;
|
||||||
|
hitAnim.updateInfo();
|
||||||
|
|
||||||
|
overrideMask &= ~(BODY_CHEST | BODY_HEAD);
|
||||||
|
int hitMask = (BODY_UPPER | BODY_LOWER | BODY_HEAD) & ~overrideMask;
|
||||||
|
int index = 0;
|
||||||
|
while (hitMask) {
|
||||||
|
if (hitMask & 1)
|
||||||
|
animation.overrides[index] = hitAnim.getJointRot(index);
|
||||||
|
index++;
|
||||||
|
hitMask >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitTime += Core::deltaTime;
|
||||||
|
overrideMask = BODY_UPPER | BODY_LOWER | BODY_HEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.overrideMask = overrideMask;
|
||||||
|
|
||||||
lookAt(viewTarget);
|
lookAt(viewTarget);
|
||||||
|
|
||||||
@@ -1000,16 +1037,16 @@ struct Lara : Character {
|
|||||||
|
|
||||||
int getTarget() {
|
int getTarget() {
|
||||||
vec3 dir = getDir().normal();
|
vec3 dir = getDir().normal();
|
||||||
float dist = TARGET_MAX_DIST;// * TARGET_MAX_DIST;
|
float dist = TARGET_MAX_DIST;
|
||||||
|
|
||||||
int index = -1;
|
int index = -1;
|
||||||
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.flags.active || !e.isEnemy()) continue;
|
if (!e.flags.active || !e.isEnemy()) continue;
|
||||||
Character *controller = (Character*)e.controller;
|
Character *enemy = (Character*)e.controller;
|
||||||
if (controller->health <= 0) continue;
|
if (enemy->health <= 0) continue;
|
||||||
|
|
||||||
vec3 p = controller->pos;
|
vec3 p = enemy->getBoundingBox().center();
|
||||||
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
|
||||||
|
|
||||||
@@ -1078,8 +1115,6 @@ struct Lara : Character {
|
|||||||
|
|
||||||
virtual void hit(int damage, Controller *enemy = NULL) {
|
virtual void hit(int damage, Controller *enemy = NULL) {
|
||||||
health -= damage;
|
health -= damage;
|
||||||
if (enemy && health > 0)
|
|
||||||
playSound(TR::SND_HIT, pos, Sound::PAN | Sound::REPLAY);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool waterOut() {
|
bool waterOut() {
|
||||||
@@ -1729,6 +1764,12 @@ struct Lara : Character {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void update() {
|
||||||
|
collisionOffset = vec3(0.0f);
|
||||||
|
checkCollisions();
|
||||||
|
Character::update();
|
||||||
|
}
|
||||||
|
|
||||||
virtual void updateAnimation(bool commands) {
|
virtual void updateAnimation(bool commands) {
|
||||||
Controller::updateAnimation(commands);
|
Controller::updateAnimation(commands);
|
||||||
updateWeapon();
|
updateWeapon();
|
||||||
@@ -1858,7 +1899,7 @@ struct Lara : Character {
|
|||||||
vTilt *= rotFactor.y;
|
vTilt *= rotFactor.y;
|
||||||
updateTilt(state == STATE_RUN || stand == STAND_UNDERWATER, vTilt.x, vTilt.y);
|
updateTilt(state == STATE_RUN || stand == STAND_UNDERWATER, vTilt.x, vTilt.y);
|
||||||
|
|
||||||
if (velocity.length() >= 1.0f)
|
if ((velocity + collisionOffset).length2() >= 1.0f)
|
||||||
move();
|
move();
|
||||||
|
|
||||||
if (getEntity().type != TR::Entity::LARA) {
|
if (getEntity().type != TR::Entity::LARA) {
|
||||||
@@ -1879,32 +1920,47 @@ struct Lara : Character {
|
|||||||
return getEntity().type == TR::Entity::LARA ? pos : chestOffset;
|
return getEntity().type == TR::Entity::LARA ? pos : chestOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void move() {
|
void checkCollisions() {
|
||||||
//TR::Entity &e = getEntity();
|
if (state == STATE_DEATH || stand != STAND_GROUND) {
|
||||||
//TR::Level::FloorInfo info;
|
hitDir = -1;
|
||||||
|
return;
|
||||||
//float f, c;
|
|
||||||
//bool canPassGap = true;
|
|
||||||
/*
|
|
||||||
if (velocity != 0.0f) {
|
|
||||||
vec3 dir = velocity.normal() * 128.0f;
|
|
||||||
vec3 p = pos + dir;
|
|
||||||
level->getFloorInfo(e.room, (int)p.x, (int)p.y, (int)p.z, info);
|
|
||||||
if (info.floor < p.y - (256 + 128) || info.ceiling > p.y - 768) { // wall
|
|
||||||
vec3 axis = dir.axisXZ();
|
|
||||||
vec3 normal = (p - vec3(int(p.x / 1024.0f) * 1024.0f + 512.0f, p.y, int(p.z / 1024.0f) * 1024.0f + 512.0f)).axisXZ();
|
|
||||||
LOG("%f %f = %f %f = %f\n", axis.x, axis.z, normal.x, normal.z, abs(axis.dot(normal)));
|
|
||||||
if (abs(axis.dot(normal)) > EPS) {
|
|
||||||
canPassGap = false;
|
|
||||||
} else {
|
|
||||||
updateEntity();
|
|
||||||
checkRoom();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
vec3 vel = velocity * Core::deltaTime * 30.0f;
|
// check enemies
|
||||||
|
for (int i = 0; i < level->entitiesCount; i++) {
|
||||||
|
TR::Entity &e = level->entities[i];
|
||||||
|
if (!e.flags.active || !e.isEnemy()) continue;
|
||||||
|
Character *enemy = (Character*)e.controller;
|
||||||
|
if (enemy->health <= 0) continue;
|
||||||
|
|
||||||
|
vec3 dir = pos - enemy->pos;
|
||||||
|
vec3 p = dir.rotateY(-enemy->angle.y);
|
||||||
|
|
||||||
|
Box enemyBox = enemy->getBoundingBoxLocal();
|
||||||
|
if (!enemyBox.contains(p))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// get shift
|
||||||
|
p = enemyBox.closestPoint2D(p);
|
||||||
|
p = (p.rotateY(enemy->angle.y) + enemy->pos) - pos;
|
||||||
|
collisionOffset += vec3(p.x, 0.0f, p.z);
|
||||||
|
|
||||||
|
// get hit dir
|
||||||
|
if (hitDir == -1) {
|
||||||
|
if (health > 0)
|
||||||
|
playSound(TR::SND_HIT, pos, Sound::PAN);
|
||||||
|
hitTime = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitDir = angleQuadrant(dir.rotateY(angle.y + PI * 0.5f).angleY());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitDir = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void move() {
|
||||||
|
vec3 vel = velocity * Core::deltaTime * 30.0f + collisionOffset;
|
||||||
vec3 opos(pos), offset(0.0f);
|
vec3 opos(pos), offset(0.0f);
|
||||||
|
|
||||||
float radius = stand == STAND_UNDERWATER ? LARA_RADIUS_WATER : LARA_RADIUS;
|
float radius = stand == STAND_UNDERWATER ? LARA_RADIUS_WATER : LARA_RADIUS;
|
||||||
|
28
src/level.h
28
src/level.h
@@ -34,6 +34,7 @@ struct Level : IGame {
|
|||||||
ShaderCache *shaderCache;
|
ShaderCache *shaderCache;
|
||||||
AmbientCache *ambientCache;
|
AmbientCache *ambientCache;
|
||||||
WaterCache *waterCache;
|
WaterCache *waterCache;
|
||||||
|
ZoneCache *zoneCache;
|
||||||
|
|
||||||
Sound::Sample *sndAmbient;
|
Sound::Sample *sndAmbient;
|
||||||
Sound::Sample *sndUnderwater;
|
Sound::Sample *sndUnderwater;
|
||||||
@@ -51,6 +52,15 @@ struct Level : IGame {
|
|||||||
return camera;
|
return camera;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual uint16 getRandomBox(uint16 zone, uint16 *zones) {
|
||||||
|
ZoneCache::Item *item = zoneCache->getBoxes(zone, zones);
|
||||||
|
return item->boxes[int(randf() * item->count)];
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual uint16 findPath(int ascend, int descend, int boxStart, int boxEnd, uint16 *zones, uint16 **boxes) {
|
||||||
|
return zoneCache->findPath(ascend, descend, boxStart, boxEnd, zones, boxes);
|
||||||
|
}
|
||||||
|
|
||||||
virtual void setClipParams(float clipSign, float clipHeight) {
|
virtual void setClipParams(float clipSign, float clipHeight) {
|
||||||
params->clipSign = clipSign;
|
params->clipSign = clipSign;
|
||||||
params->clipHeight = clipHeight;
|
params->clipHeight = clipHeight;
|
||||||
@@ -144,7 +154,7 @@ struct Level : IGame {
|
|||||||
case TR::Entity::ENEMY_CENTAUR :
|
case TR::Entity::ENEMY_CENTAUR :
|
||||||
case TR::Entity::ENEMY_MUMMY :
|
case TR::Entity::ENEMY_MUMMY :
|
||||||
case TR::Entity::ENEMY_LARSON :
|
case TR::Entity::ENEMY_LARSON :
|
||||||
entity.controller = new Enemy(this, i, 100);
|
entity.controller = new Enemy(this, i, 100, 10, 0.0f);
|
||||||
break;
|
break;
|
||||||
case TR::Entity::DOOR_1 :
|
case TR::Entity::DOOR_1 :
|
||||||
case TR::Entity::DOOR_2 :
|
case TR::Entity::DOOR_2 :
|
||||||
@@ -223,6 +233,7 @@ struct Level : IGame {
|
|||||||
|
|
||||||
ambientCache = Core::settings.ambient ? new AmbientCache(this) : NULL;
|
ambientCache = Core::settings.ambient ? new AmbientCache(this) : NULL;
|
||||||
waterCache = Core::settings.water ? new WaterCache(this) : NULL;
|
waterCache = Core::settings.water ? new WaterCache(this) : NULL;
|
||||||
|
zoneCache = new ZoneCache(this);
|
||||||
shadow = Core::settings.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL;
|
shadow = Core::settings.shadows ? new Texture(SHADOW_TEX_SIZE, SHADOW_TEX_SIZE, Texture::SHADOW, false) : NULL;
|
||||||
|
|
||||||
initReflections();
|
initReflections();
|
||||||
@@ -253,6 +264,7 @@ struct Level : IGame {
|
|||||||
delete shadow;
|
delete shadow;
|
||||||
delete ambientCache;
|
delete ambientCache;
|
||||||
delete waterCache;
|
delete waterCache;
|
||||||
|
delete zoneCache;
|
||||||
|
|
||||||
delete atlas;
|
delete atlas;
|
||||||
delete cube;
|
delete cube;
|
||||||
@@ -809,7 +821,7 @@ struct Level : IGame {
|
|||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
Core::setDepthTest(false);
|
Core::setDepthTest(false);
|
||||||
glBegin(GL_LINES);
|
glBegin(GL_LINES);
|
||||||
glColor3f(1, 1, 1);
|
glColor3f(1, 1, 1);
|
||||||
@@ -817,11 +829,11 @@ struct Level : IGame {
|
|||||||
glVertex3fv((GLfloat*)&lara->mainLightPos);
|
glVertex3fv((GLfloat*)&lara->mainLightPos);
|
||||||
glEnd();
|
glEnd();
|
||||||
Core::setDepthTest(true);
|
Core::setDepthTest(true);
|
||||||
|
*/
|
||||||
// Debug::Draw::sphere(lara->mainLightPos, lara->mainLightColor.w, vec4(1, 1, 0, 1));
|
// Debug::Draw::sphere(lara->mainLightPos, lara->mainLightColor.w, vec4(1, 1, 0, 1));
|
||||||
|
|
||||||
Box bbox = lara->getBoundingBox();
|
// Box bbox = lara->getBoundingBox();
|
||||||
Debug::Draw::box(bbox.min, bbox.max, vec4(1, 0, 1, 1));
|
// Debug::Draw::box(bbox.min, bbox.max, vec4(1, 0, 1, 1));
|
||||||
|
|
||||||
Core::setBlending(bmAlpha);
|
Core::setBlending(bmAlpha);
|
||||||
// Debug::Level::rooms(level, lara->pos, lara->getEntity().room);
|
// Debug::Level::rooms(level, lara->pos, lara->getEntity().room);
|
||||||
@@ -832,8 +844,10 @@ struct Level : IGame {
|
|||||||
// Core::setDepthTest(true);
|
// Core::setDepthTest(true);
|
||||||
// Debug::Level::meshes(level);
|
// Debug::Level::meshes(level);
|
||||||
// Debug::Level::entities(level);
|
// Debug::Level::entities(level);
|
||||||
Debug::Level::zones(level, lara);
|
// Debug::Level::zones(level, lara);
|
||||||
Debug::Level::blocks(level);
|
// Debug::Level::blocks(level);
|
||||||
|
// Debug::Level::path(level, (Enemy*)level.entities[86].controller);
|
||||||
|
// Debug::Level::debugOverlaps(level, lara->box);
|
||||||
|
|
||||||
Core::setBlending(bmNone);
|
Core::setBlending(bmNone);
|
||||||
|
|
||||||
|
67
src/utils.h
67
src/utils.h
@@ -102,7 +102,7 @@ int angleQuadrant(float angle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float decrease(float delta, float &value, float &speed) {
|
float decrease(float delta, float &value, float &speed) {
|
||||||
if (speed > 0.0f && fabsf(delta) > 0.01f) {
|
if (speed > 0.0f && fabsf(delta) > 0.001f) {
|
||||||
if (delta > 0) speed = min(delta, speed);
|
if (delta > 0) speed = min(delta, speed);
|
||||||
if (delta < 0) speed = max(delta, -speed);
|
if (delta < 0) speed = max(delta, -speed);
|
||||||
value += speed;
|
value += speed;
|
||||||
@@ -228,9 +228,11 @@ struct vec3 {
|
|||||||
return vec3(x*c - z*s, y, x*s + z*c);
|
return vec3(x*c - z*s, y, x*s + z*c);
|
||||||
}
|
}
|
||||||
|
|
||||||
float angle(const vec3 &v) {
|
float angle(const vec3 &v) const {
|
||||||
return dot(v) / (length() * v.length());
|
return dot(v) / (length() * v.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float angleY() const { return atan2f(z, x); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vec4 {
|
struct vec4 {
|
||||||
@@ -806,24 +808,61 @@ struct Box {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 closestPoint2D(const vec3 &v) const {
|
||||||
|
float ax = v.x - min.x;
|
||||||
|
float bx = max.x - v.x;
|
||||||
|
float az = v.z - min.z;
|
||||||
|
float bz = max.z - v.z;
|
||||||
|
|
||||||
|
vec3 p = v;
|
||||||
|
if (ax <= bx && ax <= az && ax <= bz)
|
||||||
|
p.x -= ax;
|
||||||
|
else if (bx <= ax && bx <= az && bx <= bz)
|
||||||
|
p.x += bx;
|
||||||
|
else if (az <= ax && az <= bx && az <= bz)
|
||||||
|
p.z -= az;
|
||||||
|
else
|
||||||
|
p.z += bz;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
bool intersect(const Box &box) const {
|
bool intersect(const Box &box) const {
|
||||||
return !((max.x < box.min.x || min.x > box.max.x) || (max.y < box.min.y || min.y > box.max.y) || (max.z < box.min.z || min.z > box.max.z));
|
return !((max.x < box.min.x || min.x > box.max.x) || (max.y < box.min.y || min.y > box.max.y) || (max.z < box.min.z || min.z > box.max.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool intersect(const vec3 &rayPos, const vec3 &rayDir, float &t) const {
|
bool intersect(const vec3 &rayPos, const vec3 &rayDir, float &t) const {
|
||||||
float t1 = INF, t0 = -t1;
|
float t1 = INF, t0 = -t1;
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
if (rayDir[i] != 0) {
|
if (rayDir[i] != 0) {
|
||||||
float lo = (min[i] - rayPos[i]) / rayDir[i];
|
float lo = (min[i] - rayPos[i]) / rayDir[i];
|
||||||
float hi = (max[i] - rayPos[i]) / rayDir[i];
|
float hi = (max[i] - rayPos[i]) / rayDir[i];
|
||||||
t0 = ::max(t0, ::min(lo, hi));
|
t0 = ::max(t0, ::min(lo, hi));
|
||||||
t1 = ::min(t1, ::max(lo, hi));
|
t1 = ::min(t1, ::max(lo, hi));
|
||||||
} else
|
} else
|
||||||
if (rayPos[i] < min[i] || rayPos[i] > max[i])
|
if (rayPos[i] < min[i] || rayPos[i] > max[i])
|
||||||
return false;
|
return false;
|
||||||
t = t0;
|
t = t0;
|
||||||
return (t0 <= t1) && (t1 > 0);
|
return (t0 <= t1) && (t1 > 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sphere {
|
||||||
|
vec3 center;
|
||||||
|
float radius;
|
||||||
|
|
||||||
|
Sphere() {}
|
||||||
|
Sphere(const vec3 ¢er, float radius) : center(center), radius(radius) {}
|
||||||
|
|
||||||
|
bool intersect(const Sphere &s) {
|
||||||
|
float d = (center - s.center).length2();
|
||||||
|
float r = (radius + s.radius);
|
||||||
|
return d < r * r;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user