/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "math/matrix3.h"
#include "common/algorithm.h"
#include "common/config-manager.h"
#include "twp/twp.h"
#include "twp/detection.h"
#include "twp/lighting.h"
#include "twp/object.h"
#include "twp/resmanager.h"
#include "twp/room.h"
#include "twp/scenegraph.h"
namespace Twp {
#define DEFAULT_FPS 10.f
#define NUMOBJECTSBYROW 4
#define MARGIN 8.f
#define MARGINBOTTOM 10.f
#define BACKOFFSET 7.f
#define ARROWWIDTH 56.f
#define ARROWHEIGHT 86.f
#define BACKWIDTH 137.f
#define BACKHEIGHT 75.f
class ShakeInventory : public Motor {
public:
ShakeInventory(Math::Vector2d &shakeOffset, float amount) : _shakeOffset(shakeOffset), _amount(amount) {}
protected:
void onUpdate(float elapsed) override {
_shakeTime += 40.f * elapsed;
_elapsed += elapsed;
_shakeOffset = Math::Vector2d(_amount * cos(_shakeTime + 0.3f), _amount * sin(_shakeTime));
}
private:
Math::Vector2d &_shakeOffset;
const float _amount;
float _shakeTime = 0.f;
float _elapsed = 0.f;
};
static float _getFps(float fps, float animFps) {
if (fps != 0.f)
return fps;
return (animFps < 1e-3) ? DEFAULT_FPS : animFps;
}
Node::Node(const Common::String &name, const Math::Vector2d &scale, const Color &color)
: _name(name),
_color(color),
_computedColor(color),
_scale(scale) {
}
Node::~Node() {
remove();
if (_children.empty())
return;
for (size_t i = 0; i < _children.size(); i++) {
_children[i]->_parent = nullptr;
}
}
void Node::addChild(Node *child) {
if (child->_parent == this)
return;
if (child->_parent) {
child->_pos -= getAbsPos();
child->remove();
}
_children.push_back(child);
child->_parent = this;
child->updateColor();
child->updateAlpha();
}
const Node *Node::getRoot() const {
const Node *result = this;
while (result->_parent != NULL) {
result = result->_parent;
}
return result;
}
int Node::find(Node *other) {
for (size_t i = 0; i < _children.size(); i++) {
if (_children[i] == other) {
return i;
}
}
return -1;
}
void Node::removeChild(Node *child) {
int i = find(child);
if (i != -1) {
_children.remove_at(i);
}
child->_parent = nullptr;
}
void Node::clear() {
if (_children.empty())
return;
Common::Array children(_children);
for (size_t i = 0; i < children.size(); i++) {
children[i]->remove();
}
_children.clear();
}
void Node::remove() {
if (_parent)
_parent->removeChild(this);
}
void Node::setColor(const Color &c) {
_color.rgba.r = c.rgba.r;
_color.rgba.g = c.rgba.g;
_color.rgba.b = c.rgba.b;
_computedColor.rgba.r = c.rgba.r;
_computedColor.rgba.g = c.rgba.g;
_computedColor.rgba.b = c.rgba.b;
updateColor();
}
void Node::setAlpha(float alpha) {
_color.rgba.a = alpha;
_computedColor.rgba.a = alpha;
updateAlpha();
}
void Node::updateColor() {
Color parentColor = !_parent ? Color(1.f, 1.f, 1.f) : _parent->_computedColor;
updateColor(parentColor);
}
void Node::updateAlpha() {
float parentOpacity = !_parent ? 1.f : _parent->_computedColor.rgba.a;
updateAlpha(parentOpacity);
}
void Node::updateColor(const Color &parentColor) {
_computedColor.rgba.r = _color.rgba.r * parentColor.rgba.r;
_computedColor.rgba.g = _color.rgba.g * parentColor.rgba.g;
_computedColor.rgba.b = _color.rgba.b * parentColor.rgba.b;
onColorUpdated(_computedColor);
for (size_t i = 0; i < _children.size(); i++) {
Node *child = _children[i];
child->updateColor(_computedColor);
}
}
void Node::updateAlpha(float parentAlpha) {
_computedColor.rgba.a = _color.rgba.a * parentAlpha;
onColorUpdated(_computedColor);
for (size_t i = 0; i < _children.size(); i++) {
Node *child = _children[i];
child->updateAlpha(_computedColor.rgba.a);
}
}
void Node::setAnchor(const Math::Vector2d &anchor) {
if (_anchor != anchor) {
_anchorNorm = anchor / _size;
_anchor = anchor;
}
}
void Node::setAnchorNorm(const Math::Vector2d &anchorNorm) {
if (_anchorNorm != anchorNorm) {
_anchorNorm = anchorNorm;
_anchor = _size * _anchorNorm;
}
}
void Node::setSize(const Math::Vector2d &size) {
if (_size != size) {
_size = size;
_anchor = size * _anchorNorm;
}
}
// this structure is used to have a stable sort
typedef struct {
size_t index;
Node *node;
} NodeSort;
static int cmpNodes(const NodeSort &x, const NodeSort &y) {
if (y.node->getZSort() == x.node->getZSort()) {
return x.index < y.index;
}
return x.node->getZSort() > y.node->getZSort();
}
void Node::onDrawChildren(const Math::Matrix4 &trsf) {
// use this "stable sort" until there is something better available
Common::Array children;
for (size_t i = 0; i < _children.size(); i++) {
children.push_back({i, _children[i]});
}
Common::sort(children.begin(), children.end(), cmpNodes);
_children.clear();
_children.reserve(children.size());
for (size_t i = 0; i < children.size(); i++) {
_children.push_back(children[i].node);
}
for (auto &child : _children) {
child->draw(trsf);
}
}
void Node::draw(const Math::Matrix4 &parent) {
if (_visible) {
Math::Matrix4 trsf = getTrsf(parent);
Math::Matrix4 myTrsf(trsf);
myTrsf.translate(Math::Vector3d(-_anchor.getX(), _anchor.getY(), 0.f));
drawCore(myTrsf);
onDrawChildren(trsf);
}
}
Math::Vector2d Node::getAbsPos() const {
return !_parent ? _pos : _parent->getAbsPos() + _pos + _offset;
}
Math::Matrix4 Node::getTrsf(const Math::Matrix4 &parentTrsf) {
return parentTrsf * getLocalTrsf();
}
Math::Matrix4 Node::getLocalTrsf() {
Math::Vector2d p = _pos + _offset + _shakeOffset;
Math::Matrix4 m1;
m1.translate(Math::Vector3d(p.getX(), p.getY(), 0.f));
Math::Matrix3 mRot;
mRot.buildAroundZ(Math::Angle(-_rotation + _rotationOffset));
Math::Matrix4 m2;
m2.setRotation(mRot);
scale(m2, getScale());
Math::Matrix4 m3;
m3.translate(Math::Vector3d(_renderOffset.getX(), _renderOffset.getY(), 0.f));
return m1 * m2 * m3;
}
Rectf Node::getRect() const {
Math::Vector2d size = _size * getScale();
return Rectf::fromPosAndSize(getAbsPos(), Math::Vector2d(-size.getX(), size.getY()) * _anchorNorm * _size);
}
ParallaxNode::ParallaxNode(const Math::Vector2d ¶llax, const Common::String &sheet, const Common::StringArray &frames)
: Node("parallax"),
_parallax(parallax),
_sheet(sheet),
_frames(frames) {
}
ParallaxNode::~ParallaxNode() {}
Math::Matrix4 ParallaxNode::getTrsf(const Math::Matrix4 &parentTrsf) {
Gfx &gfx = g_twp->getGfx();
Math::Matrix4 trsf = Node::getTrsf(parentTrsf);
Math::Vector2d camPos = gfx.cameraPos();
Math::Vector2d p = Math::Vector2d(-camPos.getX() * _parallax.getX(), -camPos.getY() * _parallax.getY());
trsf.translate(Math::Vector3d(p.getX(), p.getY(), 0.0f));
return trsf;
}
void ParallaxNode::onDrawChildren(const Math::Matrix4 &trsf) {
Node::onDrawChildren(trsf);
}
void ParallaxNode::drawCore(const Math::Matrix4 &trsf) {
Gfx &gfx = g_twp->getGfx();
SpriteSheet *sheet = g_twp->_resManager->spriteSheet(_sheet);
Texture *texture = g_twp->_resManager->texture(sheet->meta.image);
// enable debug lighting ?
if (_zOrder == 0 && g_twp->_lighting->_debug) {
g_twp->getGfx().use(g_twp->_lighting.get());
} else {
g_twp->getGfx().use(nullptr);
}
Math::Matrix4 t = trsf;
float x = 0.f;
for (size_t i = 0; i < _frames.size(); i++) {
const SpriteSheetFrame &frame = sheet->getFrame(_frames[i]);
g_twp->_lighting->setSpriteOffset({0.f, static_cast(-frame.frame.height())});
g_twp->_lighting->setSpriteSheetFrame(frame, *texture, false);
Math::Matrix4 myTrsf = t;
myTrsf.translate(Math::Vector3d(x + frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top, 0.0f));
gfx.drawSprite(frame.frame, *texture, getColor(), myTrsf);
t = trsf;
x += frame.frame.width();
}
g_twp->getGfx().use(nullptr);
}
Anim::Anim(Object *obj)
: Node("anim") {
_obj = obj;
_zOrder = 1000;
}
void Anim::clearFrames() {
_frames.clear();
}
void Anim::setAnim(const ObjectAnimation *anim, float fps, bool loop, bool instant) {
_anim = anim;
_disabled = false;
setName(anim->name);
_sheet = anim->sheet;
_frames = anim->frames;
_frameIndex = instant && _frames.size() > 0 ? _frames.size() - 1 : 0;
_frameDuration = 1.0 / _getFps(fps, anim->fps);
_loop = loop || anim->loop;
_instant = instant;
if (_obj)
setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == (size_t)-1);
clear();
for (size_t i = 0; i < _anim->layers.size(); i++) {
const ObjectAnimation &layer = _anim->layers[i];
Common::SharedPtr node(new Anim(_obj));
_anims.push_back(node);
node->setAnim(&layer, fps, loop, instant);
addChild(node.get());
}
}
void Anim::trigSound() {
if (_anim && (_anim->triggers.size() > 0) && (_frameIndex < _anim->triggers.size())) {
const Common::String &trigger = _anim->triggers[_frameIndex];
if ((trigger.size() > 0) && trigger != "null") {
_obj->trig(trigger);
}
}
}
void Anim::update(float elapsed) {
if (_anim)
setVisible(Twp::find(_obj->_hiddenLayers, _anim->name) == (size_t)-1);
if (_instant)
disable();
else if (_frames.size() != 0) {
_elapsed += elapsed;
if (_elapsed > _frameDuration) {
_elapsed = 0;
if (_frameIndex < _frames.size() - 1) {
_frameIndex++;
trigSound();
} else if (_loop) {
_frameIndex = 0;
trigSound();
} else {
disable();
}
}
if (_anim && _anim->offsets.size() > 0) {
Math::Vector2d off = _frameIndex < _anim->offsets.size() ? _anim->offsets[_frameIndex] : Math::Vector2d();
if (_obj->getFacing() == Facing::FACE_LEFT) {
off.setX(-off.getX());
}
_offset = Common::move(off);
}
} else if (_children.size() != 0) {
bool disabled = true;
for (size_t i = 0; i < _children.size(); i++) {
Anim *layer = static_cast(_children[i]);
layer->update(elapsed);
disabled = disabled && layer->_disabled;
}
if (disabled) {
disable();
}
} else {
disable();
}
}
void Anim::drawCore(const Math::Matrix4 &t) {
Math::Matrix4 trsf(t);
if (_frameIndex < _frames.size()) {
const Common::String &frame = _frames[_frameIndex];
if (frame == "null")
return;
bool flipX = _obj->getFacing() == Facing::FACE_LEFT;
if (_sheet.size() == 0) {
_sheet = _obj->_sheet;
if (_sheet.size() == 0 && _obj->_room) {
_sheet = _obj->_room->_sheet;
}
}
if (_sheet == "raw") {
Texture *texture = g_twp->_resManager->texture(frame);
Math::Vector3d pos(-texture->width / 2.f, -texture->height / 2.f, 0.f);
trsf.translate(pos);
g_twp->getGfx().drawSprite(Common::Rect(texture->width, texture->height), *texture, getComputedColor(), trsf, flipX);
} else {
const SpriteSheet *sheet = g_twp->_resManager->spriteSheet(_sheet);
const SpriteSheetFrame *sf = sheet->frame(frame);
if (!sf)
return;
Texture *texture = g_twp->_resManager->texture(sheet->meta.image);
if (_obj->_lit) {
g_twp->getGfx().use(g_twp->_lighting.get());
Math::Vector2d p = getAbsPos() + _obj->_node->getRenderOffset();
const float left = flipX ? (-1.f + sf->sourceSize.getX()) / 2.f - sf->spriteSourceSize.left : sf->spriteSourceSize.left - sf->sourceSize.getX() / 2.f;
const float top = -sf->sourceSize.getY() / 2.f + sf->spriteSourceSize.top;
g_twp->_lighting->setSpriteOffset({p.getX() + left, -p.getY() + top});
g_twp->_lighting->setSpriteSheetFrame(*sf, *texture, flipX);
} else {
g_twp->getGfx().use(nullptr);
}
const float x = flipX ? (1.f - sf->sourceSize.getX()) / 2.f + sf->frame.width() + sf->spriteSourceSize.left : sf->sourceSize.getX() / 2.f - sf->spriteSourceSize.left;
const float y = sf->sourceSize.getY() / 2.f - sf->spriteSourceSize.height() - sf->spriteSourceSize.top;
Math::Vector3d pos(int(-x), int(y), 0.f);
trsf.translate(pos);
g_twp->getGfx().drawSprite(sf->frame, *texture, getComputedColor(), trsf, flipX);
g_twp->getGfx().use(nullptr);
}
}
}
ActorNode::ActorNode(Common::SharedPtr