Mercurial > fife-parpg
diff engine/core/view/camera.cpp @ 0:4a0efb7baf70
* Datasets becomes the new trunk and retires after that :-)
author | mvbarracuda@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sun, 29 Jun 2008 18:44:17 +0000 |
parents | |
children | ab09325f901e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/core/view/camera.cpp Sun Jun 29 18:44:17 2008 +0000 @@ -0,0 +1,558 @@ +/*************************************************************************** + * Copyright (C) 2005-2008 by the FIFE team * + * http://www.fifengine.de * + * This file is part of FIFE. * + * * + * FIFE 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 St, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +// Standard C++ library includes + +// 3rd party library includes +#include <SDL.h> + +// FIFE includes +// These includes are split up in two parts, separated by one empty line +// First block: files included from the FIFE root src directory +// Second block: files included from the same folder + +#include "model/metamodel/grids/cellgrid.h" +#include "model/metamodel/action.h" +#include "model/metamodel/timeprovider.h" +#include "model/structures/map.h" +#include "model/structures/layer.h" +#include "model/structures/instancetree.h" +#include "model/structures/instance.h" +#include "model/structures/location.h" +#include "util/log/logger.h" +#include "util/math/fife_math.h" +#include "util/time/timemanager.h" +#include "video/renderbackend.h" +#include "video/image.h" +#include "video/imagepool.h" +#include "video/animation.h" +#include "video/animationpool.h" + +#include "camera.h" +#include "visual.h" + + +namespace FIFE { + static Logger _log(LM_CAMERA); + + Camera::Camera(const std::string& id, + Layer *layer, + Rect viewport, + ExactModelCoordinate emc, + RenderBackend* renderbackend, + ImagePool* ipool, + AnimationPool* apool): + m_id(id), + m_matrix(), + m_inverse_matrix(), + m_tilt(0), + m_rotation(0), + m_zoom(1), + m_location(), + m_prev_origo(ScreenPoint(0,0,0)), + m_cur_origo(ScreenPoint(0,0,0)), + m_viewport(), + m_screen_cell_width(1), + m_screen_cell_height(1), + m_reference_scale(1), + m_enabled(true), + m_attachedto(NULL), + m_image_dimensions(), + m_iswarped(false), + m_renderers(), + m_pipeline(), + m_updated(false), + m_renderbackend(renderbackend), + m_ipool(ipool), + m_apool(apool), + m_layer_to_instances() { + m_location.setLayer(layer); + m_location.setExactLayerCoordinates(emc); + m_viewport = viewport; + } + + Camera::~Camera() { + std::map<std::string, RendererBase*>::iterator r_it = m_renderers.begin(); + for(; r_it != m_renderers.end(); ++r_it) { + delete r_it->second; + } + m_renderers.clear(); + } + + void Camera::setTilt(double tilt) { + m_tilt = tilt; + updateReferenceScale(); + updateMatrices(); + m_iswarped = true; + } + + double Camera::getTilt() const { + return m_tilt; + } + + void Camera::setRotation(double rotation) { + m_rotation = rotation; + updateReferenceScale(); + updateMatrices(); + m_iswarped = true; + } + + double Camera::getRotation() const { + return m_rotation; + } + + void Camera::setZoom(double zoom) { + m_zoom = zoom; + if (m_zoom < 0.001) { + m_zoom = 0.001; + } + updateMatrices(); + m_iswarped = true; + } + + double Camera::getZoom() const { + return m_zoom; + } + + void Camera::setCellImageDimensions(unsigned int width, unsigned int height) { + m_screen_cell_width = width; + m_screen_cell_height = height; + updateReferenceScale(); + updateMatrices(); + m_iswarped = true; + } + + void Camera::setLocation(const Location& location) { + // initialize first set properly + if ((m_prev_origo == m_cur_origo) && (m_prev_origo == ScreenPoint(0,0,0))) { + m_cur_origo = toScreenCoordinates(ExactModelCoordinate(0,0,0)); + m_prev_origo = m_cur_origo; + } + m_location = location; + + CellGrid* cg = NULL; + if (m_location.getLayer()) { + cg = m_location.getLayer()->getCellGrid(); + } else { + throw Exception("Location without layer given to Camera::setLocation"); + } + if (!cg) { + throw Exception("Camera layer has no cellgrid specified"); + } + + updateMatrices(); + + m_prev_origo = m_cur_origo; + m_cur_origo = toScreenCoordinates(ExactModelCoordinate(0,0,0)); + } + + Point Camera::getCellImageDimensions() { + return getCellImageDimensions(m_location.getLayer()); + } + + Point Camera::getCellImageDimensions(Layer* layer) { + if (layer == m_location.getLayer()) { + return Point( m_screen_cell_width, m_screen_cell_height ); + } + std::map<Layer*, Point>::iterator it = m_image_dimensions.find(layer); + if (it != m_image_dimensions.end()) { + return it->second; + } + Point p; + CellGrid* cg = layer->getCellGrid(); + assert(cg); + DoublePoint dimensions = getLogicalCellDimensions(layer); + p.x = static_cast<int>(round(m_reference_scale * dimensions.x)); + p.y = static_cast<int>(round(m_reference_scale * dimensions.y)); + m_image_dimensions[layer] = p; + return p; + } + + Location Camera::getLocation() const { + return m_location; + } + + Location& Camera::getLocationRef() { + return m_location; + } + + void Camera::setViewPort(const Rect& viewport) { + m_viewport = viewport; + } + + const Rect& Camera::getViewPort() const { + return m_viewport; + } + + void Camera::setEnabled(bool enabled) { + m_enabled = enabled; + } + + bool Camera::isEnabled() { + return m_enabled; + } + + void Camera::updateMatrices() { + double scale = m_reference_scale; + m_matrix.loadScale(scale, scale, scale); + if (m_location.getLayer()) { + CellGrid* cg = m_location.getLayer()->getCellGrid(); + if (cg) { + ExactModelCoordinate pt = m_location.getMapCoordinates(); + m_matrix.applyTranslate( -pt.x *m_reference_scale,-pt.y *m_reference_scale, 0); + } + } + scale = m_zoom; + m_matrix.applyScale(scale, scale, scale); + m_matrix.applyRotate(-m_rotation, 0.0, 0.0, 1.0); + m_matrix.applyRotate(-m_tilt, 1.0, 0.0, 0.0); + m_inverse_matrix = m_matrix.inverse(); + } + + void Camera::calculateZValue(ScreenPoint& screen_coords) { + int dy = -(screen_coords.y - toScreenCoordinates(m_location.getMapCoordinates()).y); + screen_coords.z = static_cast<int>(tan(m_tilt * (M_PI / 180.0)) * static_cast<double>(dy)); + } + + ExactModelCoordinate Camera::toMapCoordinates(ScreenPoint screen_coords, bool z_calculated) { + if (!z_calculated) { + calculateZValue(screen_coords); + } + screen_coords.x -= m_viewport.w / 2; + screen_coords.y -= m_viewport.h / 2; + + return m_inverse_matrix * intPt2doublePt(screen_coords); + } + + ScreenPoint Camera::toScreenCoordinates(ExactModelCoordinate elevation_coords) { + ExactModelCoordinate p = elevation_coords; + ScreenPoint pt = doublePt2intPt( m_matrix* p ); + pt.x += m_viewport.w / 2; + pt.y += m_viewport.h / 2; + return pt; + } + + DoublePoint Camera::getLogicalCellDimensions(Layer* layer) { + CellGrid* cg = NULL; + if (layer) { + cg = layer->getCellGrid(); + } + assert(cg); + + ModelCoordinate cell(0,0); + std::vector<ExactModelCoordinate> vertices; + cg->getVertices(vertices, cell); + + DoubleMatrix mtx; + mtx.loadRotate(m_rotation, 0.0, 0.0, 1.0); + mtx.applyRotate(m_tilt, 1.0, 0.0, 0.0); + double x1, x2, y1, y2; + for (unsigned int i = 0; i < vertices.size(); i++) { + vertices[i] = cg->toMapCoordinates(vertices[i]); + vertices[i] = mtx * vertices[i]; + if (i == 0) { + x1 = x2 = vertices[0].x; + y1 = y2 = vertices[0].y; + } else { + x1 = std::min(vertices[i].x, x1); + x2 = std::max(vertices[i].x, x2); + y1 = std::min(vertices[i].y, y1); + y2 = std::max(vertices[i].y, y2); + } + } + return DoublePoint( x2 - x1, y2 - y1 ); + } + + void Camera::updateReferenceScale() { + DoublePoint dim = getLogicalCellDimensions(m_location.getLayer()); + m_reference_scale = static_cast<double>(m_screen_cell_width) / dim.x; + + FL_DBG(_log, "Updating reference scale"); + FL_DBG(_log, LMsg(" tilt=") << m_tilt << " rot=" << m_rotation); + FL_DBG(_log, LMsg(" m_screen_cell_width=") << m_screen_cell_width); + } + + void Camera::getMatchingInstances(ScreenPoint screen_coords, Layer& layer, std::list<Instance*>& instances) { + instances.clear(); + const std::vector<Instance*>& layer_instances = m_layer_to_instances[&layer]; + std::vector<Instance*>::const_iterator instance_it = layer_instances.end(); + while (instance_it != layer_instances.begin()) { + --instance_it; + Instance* i = (*instance_it); + InstanceVisual* visual = i->getVisual<InstanceVisual>(); + InstanceVisualCacheItem& vc = visual->getCacheItem(this); + if ((vc.dimensions.contains(Point(screen_coords.x, screen_coords.y)))) { + assert(vc.image); + Uint8 r, g, b, a; + int x = screen_coords.x - vc.dimensions.x; + int y = screen_coords.y - vc.dimensions.y; + if (m_zoom != 1.0) { + double fx = static_cast<double>(x); + double fy = static_cast<double>(y); + double fow = static_cast<double>(vc.image->getWidth()); + double foh = static_cast<double>(vc.image->getHeight()); + double fsw = static_cast<double>(vc.dimensions.w); + double fsh = static_cast<double>(vc.dimensions.h); + x = static_cast<int>(round(fx / fsw * fow)); + y = static_cast<int>(round(fy / fsh * foh)); + } + vc.image->getPixelRGBA(x, y, &r, &b, &g, &a); + // instance is hit with mouse if not totally transparent + if (a != 0) { + instances.push_back(i); + } + } + } + } + + void Camera::getMatchingInstances(Location& loc, std::list<Instance*>& instances, bool use_exactcoordinates) { + instances.clear(); + const std::vector<Instance*>& layer_instances = m_layer_to_instances[loc.getLayer()]; + std::vector<Instance*>::const_iterator instance_it = layer_instances.end(); + while (instance_it != layer_instances.begin()) { + --instance_it; + Instance* i = (*instance_it); + if (use_exactcoordinates) { + if (i->getLocationRef().getExactLayerCoordinatesRef() == loc.getExactLayerCoordinatesRef()) { + instances.push_back(i); + } + } else { + if (i->getLocationRef().getLayerCoordinates() == loc.getLayerCoordinates()) { + instances.push_back(i); + } + } + } + } + + void Camera::attach(Instance *instance) { + m_attachedto = instance; + } + + void Camera::detach() { + m_attachedto = NULL; + } + + void Camera::update() { + if( !m_attachedto ) { + return; + } + Location loc(m_location); + loc.setExactLayerCoordinates( m_attachedto->getLocationRef().getExactLayerCoordinates(m_location.getLayer()) ); + setLocation(loc); + updateMatrices(); + } + + void Camera::refresh() { + updateMatrices(); + m_iswarped = true; + } + + void Camera::resetUpdates() { + m_iswarped = false; + m_prev_origo = m_cur_origo; + } + + bool pipelineSort(const RendererBase* lhs, const RendererBase* rhs) { + return (lhs->getPipelinePosition() < rhs->getPipelinePosition()); + } + + void Camera::addRenderer(RendererBase* renderer) { + renderer->setRendererListener(this); + m_renderers[renderer->getName()] = renderer; + if (renderer->isEnabled()) { + m_pipeline.push_back(renderer); + } + m_pipeline.sort(pipelineSort); + } + + void Camera::onRendererPipelinePositionChanged(RendererBase* renderer) { + m_pipeline.sort(pipelineSort); + } + + void Camera::onRendererEnabledChanged(RendererBase* renderer) { + assert(m_renderers[renderer->getName()]); + if (renderer->isEnabled()) { + FL_LOG(_log, LMsg("Enabling renderer ") << renderer->getName()); + m_pipeline.push_back(renderer); + m_pipeline.sort(pipelineSort); + } else { + m_pipeline.remove(renderer); + } + } + + RendererBase* Camera::getRenderer(const std::string& name) { + return m_renderers[name]; + } + + class InstanceDistanceSort { + public: + Camera* cam; + inline bool operator()(const Instance* lhs, const Instance* rhs) { + InstanceVisual* liv = lhs->getVisual<InstanceVisual>(); + InstanceVisual* riv = rhs->getVisual<InstanceVisual>(); + InstanceVisualCacheItem& lic = liv->getCacheItem(cam); + InstanceVisualCacheItem& ric = riv->getCacheItem(cam); + if (lic.screenpoint.z == ric.screenpoint.z) { + return liv->getStackPosition() < riv->getStackPosition(); + } + return lic.screenpoint.z < ric.screenpoint.z; + } + }; + + void Camera::resetRenderers() { + std::map<std::string, RendererBase*>::iterator r_it = m_renderers.begin(); + for (; r_it != m_renderers.end(); ++r_it) { + Map* map = m_location.getMap(); + r_it->second->reset(); + r_it->second->activateAllLayers(map); + } + } + + void Camera::render() { + ScreenPoint cammove = getLatestMovement(); + + Map* map = m_location.getMap(); + if (!map) { + FL_ERR(_log, "No map for camera found"); + return; + } + //if ((!map->isChanged()) && (!m_iswarped) && (cammove == ScreenPoint(0,0,0))) { + // return; + //} + + const unsigned long curtime = TimeManager::instance()->getTime(); + + // update each layer + m_renderbackend->pushClipArea(getViewPort()); + + m_layer_to_instances.clear(); + + const std::list<Layer*>& layers = map->getLayers(); + std::list<Layer*>::const_iterator layer_it = layers.begin(); + for (;layer_it != layers.end(); ++layer_it) { + + // sort instances on layer based on stack position + camera distance. done only once + // here instead passing it to each renderer. + // instances are checked first if their image intersects with the viewport. + // this reduces processing load during sorting later + std::vector<Instance*> allinstances((*layer_it)->getInstances()); + std::vector<Instance*>::const_iterator instance_it = allinstances.begin(); + std::vector<Instance*>& instances_to_render = m_layer_to_instances[*layer_it]; + for (;instance_it != allinstances.end(); ++instance_it) { + Instance* instance = *instance_it; + InstanceVisual* visual = instance->getVisual<InstanceVisual>(); + InstanceVisualCacheItem& vc = visual->getCacheItem(this); + + // use cached values if there is no need to do full recalculation + ScreenPoint drawpt; + int angle = 0; + if (m_updated && (!m_iswarped) && (!(instance->getChangeInfo() & (ICHANGE_LOC | ICHANGE_ROTATION))) && (vc.image)) { + int pos_estimate_x = vc.screenpoint.x - cammove.x; + int pos_estimate_y = vc.screenpoint.y - cammove.y; + int pos_estimate_z = vc.screenpoint.z - cammove.z; + angle = vc.facing_angle; + //std::cout << "orig x = " << drawpt.x << ", est x = " << pos_estimate_x << "\n"; + //std::cout << "orig y = " << drawpt.y << ", est y = " << pos_estimate_y << "\n"; + drawpt.x = pos_estimate_x; + drawpt.y = pos_estimate_y; + drawpt.z = pos_estimate_z; + //drawpt.z = toScreenCoordinates( instance->getLocationRef().getMapCoordinates() ).z; + } else { + drawpt = toScreenCoordinates( instance->getLocationRef().getMapCoordinates() ); + vc.facing_angle = angle = getAngleBetween(instance->getLocationRef(), instance->getFacingLocation()); + } + angle += instance->getRotation(); + + Image* image = NULL; + Action* action = instance->getCurrentAction(); + if (action) { + FL_DBG(_log, "Instance has action"); + int animation_id = action->getVisual<ActionVisual>()->getAnimationIndexByAngle(angle); + + Animation& animation = m_apool->getAnimation(animation_id); + int animtime = scaleTime(instance->getTotalTimeMultiplier(), instance->getActionRuntime()) % animation.getDuration(); + image = animation.getFrameByTimestamp(animtime); + } else { + FL_DBG(_log, "No action"); + int imageid = vc.getStaticImageIndexByAngle(angle, instance); + FL_DBG(_log, LMsg("Instance does not have action, using static image with id ") << imageid); + if (imageid >= 0) { + image = &m_ipool->getImage(imageid); + } else { + // there was no static image for instance, trying default action + action = instance->getObject()->getDefaultAction(); + if (action) { + int animation_id = action->getVisual<ActionVisual>()->getAnimationIndexByAngle(angle); + Animation& animation = m_apool->getAnimation(animation_id); + int animtime = scaleTime(instance->getTotalTimeMultiplier(), curtime) % animation.getDuration(); + image = animation.getFrameByTimestamp(animtime); + } + } + } + if (image) { + vc.image = image; + vc.screenpoint = drawpt; + + int w = image->getWidth(); + int h = image->getHeight(); + drawpt.x -= w / 2; + drawpt.x += image->getXShift(); + drawpt.y -= h / 2; + drawpt.y += image->getYShift(); + Rect r = Rect(drawpt.x, drawpt.y, w, h); + + vc.dimensions = r; + if (m_zoom != 1.0) { + // NOTE: Due to image alignment, there is additional additions and substractions on image dimensions + // There's probabaly some better solution for this, but works "good enough" for now. + // In case additions / substractions are removed, gaps appear between tiles. + r.w = static_cast<unsigned int>(ceil(static_cast<double>(vc.dimensions.w) * m_zoom)) + 2; + r.h = static_cast<unsigned int>(ceil(static_cast<double>(vc.dimensions.h) * m_zoom)) + 2; + int xo = static_cast<int>(ceil(static_cast<double>(vc.image->getXShift()) * m_zoom)) - vc.image->getXShift(); + int yo = static_cast<int>(ceil(static_cast<double>(vc.image->getYShift()) * m_zoom)) - vc.image->getYShift(); + r.x = vc.dimensions.x - static_cast<unsigned int>(ceil(static_cast<double>(r.w - vc.dimensions.w) / 2)) + xo - 1; + r.y = vc.dimensions.y - static_cast<unsigned int>(ceil(static_cast<double>(r.h - vc.dimensions.h) / 2)) + yo - 1; + vc.dimensions = r; + } + + if (vc.dimensions.intersects(getViewPort())) { + instances_to_render.push_back(instance); + } + } + } + + InstanceDistanceSort ids; + ids.cam = this; + std::stable_sort(instances_to_render.begin(), instances_to_render.end(), ids); + + std::list<RendererBase*>::iterator r_it = m_pipeline.begin(); + for (; r_it != m_pipeline.end(); ++r_it) { + if ((*r_it)->isActivedLayer(*layer_it)) { + (*r_it)->render(this, *layer_it, instances_to_render); + } + } + } + m_renderbackend->popClipArea(); + resetUpdates(); + m_updated = true; + } + +}