/* 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 "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 *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 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 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 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::_indexedParticles = nullptr; /*static*/ int TeParticle::getIndex(const Common::String &name) { int retval = -1; Common::Array *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 *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 *parts = indexedParticles(); parts->clear(); } /*static*/ void TeParticle::updateAll(int val) { Common::Array *parts = indexedParticles(); for (uint i = 0; i < parts->size(); i++) (*parts)[i]->update(val); } /*static*/ Common::Array *TeParticle::indexedParticles() { if (_indexedParticles == nullptr) _indexedParticles = new Common::Array(); return _indexedParticles; } /*static*/ void TeParticle::cleanup() { delete _indexedParticles; _indexedParticles = nullptr; } } // end namespace Tetraedge