scummvm/engines/tetraedge/te/te_particle.cpp
2024-08-04 12:47:37 +03:00

240 lines
7.6 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "tetraedge/te/te_particle.h"
#include "tetraedge/tetraedge.h"
#include "tetraedge/te/te_core.h"
namespace Tetraedge {
static const char TE_PARTICLE_RANDOM_TABLE[] = "http://www.arkham-development.com/";
TeParticle::TeParticle(TeScene *scene) : _scene(scene), _size(0),
_colorTime(0), _time(0), _period(0), _particlePerPeriod(0),
_enabled(false), _startLoop(0), _gravity(0), _randomDir(false),
_lastTime(0) {
indexedParticles()->push_back(this);
_elementsPending.reserve(32);
}
TeParticle::~TeParticle() {
_elements.clear();
_elementsPending.clear();
Common::Array<TeParticle *> *parts = indexedParticles();
for (uint i = 0; i < parts->size(); i++) {
if ((*parts)[i] == this) {
parts->remove_at(i);
break;
}
}
}
bool TeParticle::loadTexture(const Common::String &filename) {
// Path for these textures includes '/' so convert to Path object first.
const Common::Path path(filename);
_texture = Te3DTexture::makeInstance();
return _texture->load(g_engine->getCore()->findFile(path));
}
void TeParticle::setOrientation(const TeVector3f32 &orientation) {
// Divergence from original.. orientation is only ever used
// to calculate this matrix during update(), so just do it now.
// TODO: isn't this equivalent to using fromEuler?
_orientMatrix = TeMatrix4x4();
_orientMatrix.rotate(TeQuaternion::fromAxisAndAngle(TeVector3f32(1, 0, 0),
orientation.x() * M_PI / 180));
_orientMatrix.rotate(TeQuaternion::fromAxisAndAngle(TeVector3f32(0, -1, 0),
orientation.y() * M_PI / 180));
_orientMatrix.rotate(TeQuaternion::fromAxisAndAngle(TeVector3f32(0, 0, 1),
orientation.z() * M_PI / 180));
}
static const double PART_EPSILON = 4.656613e-10;
// A kind of weird randomizer, but that's what the original does..
static double _particleRandom() {
int i = g_engine->getRandomNumber(INT_MAX);
byte b = TE_PARTICLE_RANDOM_TABLE[g_engine->getRandomNumber(0x21)];
return (float)(i ^ b) * -2 * PART_EPSILON + 1;
}
void TeParticle::update(int val) {
if (val <= 0) {
_realTimer.timeElapsed();
return;
}
for (int i = 0; i < val; i++) {
float timeOffset = (val < 2 ? _realTimer.timeFromLastTimeElapsed() / 1000.0f : _period);
_lastTime += timeOffset;
_lastTime = CLIP(_lastTime, 0, _period * 10);
if (_enabled && _period <= _lastTime) {
// Diverge from original.. precalculate these values to avoid
// redoing the math on every particle.
Common::Array<TeVector3f32> quad(4);
quad[0] = _matrix.mult3x3(TeVector3f32(-1, -1, 0));
quad[1] = _matrix.mult3x3(TeVector3f32(-1, 1, 0));
quad[2] = _matrix.mult3x3(TeVector3f32( 1, -1, 0));
quad[3] = _matrix.mult3x3(TeVector3f32( 1, 1, 0));
const TeQuaternion norot = TeQuaternion::fromEuler(TeVector3f32(0, 0, 0));
_lastTime = fmod(_lastTime, _period);
for (int p = 0; p < _particlePerPeriod; p++) {
TeIntrusivePtr<TeElement> elem;
if (_elementsPending.empty()) {
elem = new TeElement();
_elements.push_back(elem);
} else {
elem = _elementsPending.back();
_elements.push_back(elem);
_elementsPending.pop_back();
elem->_elapsedTime = 0;
elem->_yOffset = 0;
}
const TeVector3f32 posoffset(_particleRandom() * _volumeSize.x(),
_particleRandom() * _volumeSize.y(),
_particleRandom() * _volumeSize.z());
elem->setPositionFast(_position + (_orientMatrix * posoffset));
if (!_randomDir) {
elem->_direction = TeVector3f32(0, 0, 0);
} else {
const TeVector3f32 dirmod(_particleRandom(), _particleRandom(), _particleRandom());
if (_direction.length() >= 0.001f) {
elem->_direction = dirmod * _direction.length() / 2;
} else {
elem->_direction = dirmod / 4;
}
}
elem->setQuad(_texture, quad, TeColor(255, 0, 0, 0));
elem->setRotation(norot);
float sz = _size / 10;
elem->setScale(TeVector3f32(sz, sz, sz));
elem->_elapsedTime = 0;
_scene->models().push_back(elem.get());
if (_colorTime == 0) {
elem->setColor(_startColor);
} else {
elem->setColor(TeColor(0, 0, 0, 0));
}
}
}
for (auto eiter = _elements.begin(); eiter != _elements.end(); eiter++) {
TeIntrusivePtr<TeElement> elem = *eiter;
TeVector3f32 newpos = elem->position() + (_direction + elem->_direction) * timeOffset / 1000;
elem->setPositionFast(newpos);
float newTime = elem->_elapsedTime + timeOffset;
elem->_elapsedTime = newTime;
if (newTime <= _time) {
float alpha = _startColor.a();
if (newTime < _colorTime && _colorTime) {
alpha = (alpha * newTime) / _colorTime;
}
float endScale = newTime / _time;
float startScale = 1.0 - endScale;
elem->setColor(TeColor(_endColor.r() * endScale + _startColor.r() * startScale,
_endColor.g() * endScale + _startColor.g() * startScale,
_endColor.b() * endScale + _startColor.b() * startScale,
_endColor.a() * endScale + alpha * startScale));
for (int j = -1; j < timeOffset / 100; j++) {
elem->_yOffset += _gravity / 1000;
elem->setPositionFast(elem->position() + TeVector3f32(0, elem->_yOffset, 0));
}
} else {
// This element is done.
_elementsPending.push_back(elem);
eiter = _elements.erase(eiter);
for (uint j = 0; j < _scene->models().size(); j++) {
if (_scene->models()[j].get() == elem.get()) {
_scene->models().remove_at(j);
break;
}
}
}
}
}
//debug("Updated particle texture %s, %d active %d pending",
// _texture->getAccessName().c_str(), _elements.size(),
// _elementsPending.size());
_realTimer.timeElapsed();
}
/*static*/
Common::Array<TeParticle *> *TeParticle::_indexedParticles = nullptr;
/*static*/
int TeParticle::getIndex(const Common::String &name) {
int retval = -1;
Common::Array<TeParticle *> *parts = indexedParticles();
for (uint i = 0; i < parts->size(); i++) {
if ((*parts)[i]->_name == name) {
retval = i;
break;
}
}
return retval;
}
/*static*/
TeParticle *TeParticle::getIndexedParticle(int idx) {
Common::Array<TeParticle *> *parts = indexedParticles();
if (idx >= (int)(parts->size()))
error("Invalid particle %d requested (of %d)", idx, parts->size());
return (*parts)[idx];
}
/*static*/
void TeParticle::deleteAll() {
// Divergence from original - we keep auto pointers
// to the particles in the scene so don't delete
// them here.
Common::Array<TeParticle *> *parts = indexedParticles();
parts->clear();
}
/*static*/
void TeParticle::updateAll(int val) {
Common::Array<TeParticle *> *parts = indexedParticles();
for (uint i = 0; i < parts->size(); i++)
(*parts)[i]->update(val);
}
/*static*/
Common::Array<TeParticle *> *TeParticle::indexedParticles() {
if (_indexedParticles == nullptr)
_indexedParticles = new Common::Array<TeParticle *>();
return _indexedParticles;
}
/*static*/
void TeParticle::cleanup() {
delete _indexedParticles;
_indexedParticles = nullptr;
}
} // end namespace Tetraedge