/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * This code is based on Broken Sword 2.5 engine * * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer * * Licensed under GNU GPL v2 * */ #include "sword25/gfx/renderobject.h" #include "sword25/kernel/outputpersistenceblock.h" #include "sword25/kernel/inputpersistenceblock.h" #include "sword25/gfx/renderobjectregistry.h" #include "sword25/gfx/renderobjectmanager.h" #include "sword25/gfx/graphicengine.h" #include "sword25/gfx/bitmap.h" #include "sword25/gfx/staticbitmap.h" #include "sword25/gfx/dynamicbitmap.h" #include "sword25/gfx/animation.h" #include "sword25/gfx/panel.h" #include "sword25/gfx/text.h" #include "sword25/gfx/animationtemplate.h" namespace Sword25 { int RenderObject::_nextGlobalVersion = 0; RenderObject::RenderObject(RenderObjectPtr parentPtr, TYPES type, uint handle) : _managerPtr(0), _parentPtr(parentPtr), _x(0), _y(0), _z(0), _oldX(-1), _oldY(-1), _oldZ(-1), _width(0), _height(0), _visible(true), _oldVisible(false), _childChanged(true), _type(type), _initSuccess(false), _refreshForced(true), _handle(0), _version(++_nextGlobalVersion), _isSolid(false) { // Renderobject registrieren, abhängig vom Handle-Parameter entweder mit beliebigem oder vorgegebenen Handle. if (handle == 0) _handle = RenderObjectRegistry::instance().registerObject(this); else _handle = RenderObjectRegistry::instance().registerObject(this, handle); if (_handle == 0) return; updateAbsolutePos(); // Dieses Objekt zu den Kindern der Elternobjektes hinzufügen, falls nicht Wurzel (ParentPtr ungültig) und dem // selben RenderObjektManager zuweisen. if (_parentPtr.isValid()) { _managerPtr = _parentPtr->getManager(); _parentPtr->addObject(this->getHandle()); } else { if (getType() != TYPE_ROOT) { error("Tried to create a non-root render object and has no parent. All non-root render objects have to have a parent."); return; } } updateObjectState(); _initSuccess = true; } RenderObject::~RenderObject() { // Objekt aus dem Elternobjekt entfernen. if (_parentPtr.isValid()) _parentPtr->detatchChildren(this->getHandle()); deleteAllChildren(); // Objekt deregistrieren. RenderObjectRegistry::instance().deregisterObject(this); } void RenderObject::preRender(RenderObjectQueue *renderQueue) { // Objektänderungen validieren validateObject(); if (!_visible) return; // Falls notwendig, wird die Renderreihenfolge der Kinderobjekte aktualisiert. if (_childChanged) { sortRenderObjects(); _childChanged = false; } renderQueue->add(this); RENDEROBJECT_ITER it = _children.begin(); for (; it != _children.end(); ++it) (*it)->preRender(renderQueue); } bool RenderObject::render(RectangleList *updateRects, const Common::Array &updateRectsMinZ) { // Falls das Objekt nicht sichtbar ist, muss gar nichts gezeichnet werden if (!_visible) return true; // Objekt zeichnen. bool needRender = false; int index = 0; // Only draw if the bounding box intersects any update rectangle and // the object is in front of the minimum Z value. for (RectangleList::iterator rectIt = updateRects->begin(); !needRender && rectIt != updateRects->end(); ++rectIt, ++index) needRender = (_bbox.contains(*rectIt) || _bbox.intersects(*rectIt)) && getAbsoluteZ() >= updateRectsMinZ[index]; if (needRender) doRender(updateRects); // Dann müssen die Kinder gezeichnet werden RENDEROBJECT_ITER it = _children.begin(); for (; it != _children.end(); ++it) if (!(*it)->render(updateRects, updateRectsMinZ)) return false; return true; } void RenderObject::validateObject() { // Die Veränderungen in den Objektvariablen aufheben _oldBbox = _bbox; _oldVisible = _visible; _oldX = _x; _oldY = _y; _oldZ = _z; _refreshForced = false; } bool RenderObject::updateObjectState() { // Falls sich das Objekt verändert hat, muss der interne Zustand neu berechnet werden und evtl. Update-Regions für den nächsten Frame // registriert werden. if ((calcBoundingBox() != _oldBbox) || (_visible != _oldVisible) || (_x != _oldX) || (_y != _oldY) || (_z != _oldZ) || _refreshForced) { // Renderrang des Objektes neu bestimmen, da sich dieser verändert haben könnte if (_parentPtr.isValid()) _parentPtr->signalChildChange(); // Die Bounding-Box neu berechnen und Update-Regions registrieren. updateBoxes(); ++_version; // Änderungen Validieren validateObject(); } // Dann muss der Objektstatus der Kinder aktualisiert werden. RENDEROBJECT_ITER it = _children.begin(); for (; it != _children.end(); ++it) if (!(*it)->updateObjectState()) return false; return true; } void RenderObject::updateBoxes() { // Bounding-Box aktualisieren _bbox = calcBoundingBox(); } Common::Rect RenderObject::calcBoundingBox() const { // Die Bounding-Box mit der Objektgröße initialisieren. Common::Rect bbox(0, 0, _width, _height); // Die absolute Position der Bounding-Box berechnen. bbox.translate(_absoluteX, _absoluteY); // Die Bounding-Box am Elternobjekt clippen. if (_parentPtr.isValid()) { bbox.clip(_parentPtr->getBbox()); } return bbox; } void RenderObject::calcAbsolutePos(int32 &x, int32 &y, int32 &z) const { x = calcAbsoluteX(); y = calcAbsoluteY(); z = calcAbsoluteZ(); } int32 RenderObject::calcAbsoluteX() const { if (_parentPtr.isValid()) return _parentPtr->getAbsoluteX() + _x; else return _x; } int32 RenderObject::calcAbsoluteY() const { if (_parentPtr.isValid()) return _parentPtr->getAbsoluteY() + _y; else return _y; } int32 RenderObject::calcAbsoluteZ() const { if (_parentPtr.isValid()) return _parentPtr->getAbsoluteZ() + _z; else return _z; } void RenderObject::deleteAllChildren() { // Es ist nicht notwendig die Liste zu iterieren, da jedes Kind für sich DetatchChildren an diesem Objekt aufruft und sich somit // selber entfernt. Daher muss immer nur ein beliebiges Element (hier das letzte) gelöscht werden, bis die Liste leer ist. while (!_children.empty()) { RenderObjectPtr curPtr = _children.back(); curPtr.erase(); } } bool RenderObject::addObject(RenderObjectPtr pObject) { if (!pObject.isValid()) { error("Tried to add a null object to a renderobject."); return false; } // Objekt in die Kinderliste einfügen. _children.push_back(pObject); // Sicherstellen, dass vor dem nächsten Rendern die Renderreihenfolge aktualisiert wird. if (_parentPtr.isValid()) _parentPtr->signalChildChange(); return true; } bool RenderObject::detatchChildren(RenderObjectPtr pObject) { // Kinderliste durchgehen und Objekt entfernen falls vorhanden RENDEROBJECT_ITER it = _children.begin(); for (; it != _children.end(); ++it) if (*it == pObject) { _children.erase(it); return true; } error("Tried to detach children from a render object that isn't its parent."); return false; } void RenderObject::sortRenderObjects() { Common::sort(_children.begin(), _children.end(), greater); } void RenderObject::updateAbsolutePos() { calcAbsolutePos(_absoluteX, _absoluteY, _absoluteZ); RENDEROBJECT_ITER it = _children.begin(); for (; it != _children.end(); ++it) (*it)->updateAbsolutePos(); } bool RenderObject::getObjectIntersection(RenderObjectPtr pObject, Common::Rect &result) { result = pObject->getBbox(); result.clip(_bbox); return result.isValidRect(); } void RenderObject::setPos(int x, int y) { _x = x; _y = y; updateAbsolutePos(); } void RenderObject::setX(int x) { _x = x; updateAbsolutePos(); } void RenderObject::setY(int y) { _y = y; updateAbsolutePos(); } void RenderObject::setZ(int z) { if (z < 0) error("Tried to set a negative Z value (%d).", z); else { _z = z; updateAbsolutePos(); } } void RenderObject::setVisible(bool visible) { _visible = visible; } RenderObjectPtr RenderObject::addAnimation(const Common::String &filename) { RenderObjectPtr aniPtr((new Animation(this->getHandle(), filename))->getHandle()); if (aniPtr.isValid() && aniPtr->getInitSuccess()) return aniPtr; else { if (aniPtr.isValid()) aniPtr.erase(); return RenderObjectPtr(); } } RenderObjectPtr RenderObject::addAnimation(const AnimationTemplate &animationTemplate) { Animation *aniPtr = new Animation(this->getHandle(), animationTemplate); if (aniPtr && aniPtr->getInitSuccess()) return aniPtr->getHandle(); else { delete aniPtr; return RenderObjectPtr(); } } RenderObjectPtr RenderObject::addBitmap(const Common::String &filename) { RenderObjectPtr bitmapPtr((new StaticBitmap(this->getHandle(), filename))->getHandle()); if (bitmapPtr.isValid() && bitmapPtr->getInitSuccess()) return RenderObjectPtr(bitmapPtr); else { if (bitmapPtr.isValid()) bitmapPtr.erase(); return RenderObjectPtr(); } } RenderObjectPtr RenderObject::addDynamicBitmap(uint width, uint height) { RenderObjectPtr bitmapPtr((new DynamicBitmap(this->getHandle(), width, height))->getHandle()); if (bitmapPtr.isValid() && bitmapPtr->getInitSuccess()) return bitmapPtr; else { if (bitmapPtr.isValid()) bitmapPtr.erase(); return RenderObjectPtr(); } } RenderObjectPtr RenderObject::addPanel(int width, int height, uint color) { RenderObjectPtr panelPtr((new Panel(this->getHandle(), width, height, color))->getHandle()); if (panelPtr.isValid() && panelPtr->getInitSuccess()) return panelPtr; else { if (panelPtr.isValid()) panelPtr.erase(); return RenderObjectPtr(); } } RenderObjectPtr RenderObject::addText(const Common::String &font, const Common::String &text) { RenderObjectPtr textPtr((new Text(this->getHandle()))->getHandle()); if (textPtr.isValid() && textPtr->getInitSuccess() && textPtr->setFont(font)) { textPtr->setText(text); return textPtr; } else { if (textPtr.isValid()) textPtr.erase(); return RenderObjectPtr(); } } bool RenderObject::persist(OutputPersistenceBlock &writer) { // Typ und Handle werden als erstes gespeichert, damit beim Laden ein Objekt vom richtigen Typ mit dem richtigen Handle erzeugt werden kann. writer.write(static_cast(_type)); writer.write(_handle); // Restliche Objekteigenschaften speichern. writer.write(_x); writer.write(_y); writer.write(_absoluteX); writer.write(_absoluteY); writer.write(_z); writer.write(_width); writer.write(_height); writer.write(_visible); writer.write(_childChanged); writer.write(_initSuccess); writer.write((int32)_bbox.left); writer.write((int32)_bbox.top); writer.write((int32)_bbox.right); writer.write((int32)_bbox.bottom); writer.write((int32)_oldBbox.left); writer.write((int32)_oldBbox.top); writer.write((int32)_oldBbox.right); writer.write((int32)_oldBbox.bottom); writer.write(_oldX); writer.write(_oldY); writer.write(_oldZ); writer.write(_oldVisible); writer.write(_parentPtr.isValid() ? _parentPtr->getHandle() : 0); writer.write(_refreshForced); return true; } bool RenderObject::unpersist(InputPersistenceBlock &reader) { // Typ und Handle wurden schon von RecreatePersistedRenderObject() ausgelesen. Jetzt werden die restlichen Objekteigenschaften ausgelesen. reader.read(_x); reader.read(_y); reader.read(_absoluteX); reader.read(_absoluteY); reader.read(_z); reader.read(_width); reader.read(_height); reader.read(_visible); reader.read(_childChanged); reader.read(_initSuccess); reader.read(_bbox.left); reader.read(_bbox.top); reader.read(_bbox.right); reader.read(_bbox.bottom); reader.read(_oldBbox.left); reader.read(_oldBbox.top); reader.read(_oldBbox.right); reader.read(_oldBbox.bottom); reader.read(_oldX); reader.read(_oldY); reader.read(_oldZ); reader.read(_oldVisible); uint32 parentHandle; reader.read(parentHandle); _parentPtr = RenderObjectPtr(parentHandle); reader.read(_refreshForced); updateAbsolutePos(); updateObjectState(); return reader.isGood(); } bool RenderObject::persistChildren(OutputPersistenceBlock &writer) { bool result = true; // Kinderanzahl speichern. writer.write((uint32)_children.size()); // Rekursiv alle Kinder speichern. RENDEROBJECT_LIST::iterator it = _children.begin(); while (it != _children.end()) { result &= (*it)->persist(writer); ++it; } return result; } bool RenderObject::unpersistChildren(InputPersistenceBlock &reader) { bool result = true; // Kinderanzahl einlesen. uint32 childrenCount; reader.read(childrenCount); if (!reader.isGood()) return false; // Alle Kinder rekursiv wieder herstellen. for (uint32 i = 0; i < childrenCount; ++i) { if (!recreatePersistedRenderObject(reader).isValid()) return false; } return result && reader.isGood(); } RenderObjectPtr RenderObject::recreatePersistedRenderObject(InputPersistenceBlock &reader) { RenderObjectPtr result; // Typ und Handle auslesen. uint32 type; uint32 handle; reader.read(type); reader.read(handle); if (!reader.isGood()) return result; switch (type) { case TYPE_PANEL: result = (new Panel(reader, this->getHandle(), handle))->getHandle(); break; case TYPE_STATICBITMAP: result = (new StaticBitmap(reader, this->getHandle(), handle))->getHandle(); break; case TYPE_DYNAMICBITMAP: // Videos are not normally saved: this probably indicates a bug, thus die here. //result = (new DynamicBitmap(reader, this->getHandle(), handle))->getHandle(); error("Request to recreate a video. This is either a corrupted saved game, or a bug"); break; case TYPE_TEXT: result = (new Text(reader, this->getHandle(), handle))->getHandle(); break; case TYPE_ANIMATION: result = (new Animation(reader, this->getHandle(), handle))->getHandle(); break; default: error("Cannot recreate render object of unknown type %d.", type); } return result; } bool RenderObject::greater(const RenderObjectPtr lhs, const RenderObjectPtr rhs) { // Das Objekt mit dem kleinem Z-Wert müssen zuerst gerendert werden. if (lhs->_z != rhs->_z) return lhs->_z < rhs->_z; // Falls der Z-Wert gleich ist, wird das weiter oben gelegenen Objekte zuerst gezeichnet. return lhs->_y < rhs->_y; } } // End of namespace Sword25