Mercurial > fife-parpg
view engine/core/view/camera.cpp @ 203:44919eb74c40
making header files self-sufficient so they can be compiled in isolation.
author | vtchill@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sat, 14 Mar 2009 02:42:49 +0000 |
parents | 56ac89189bc4 |
children | 09f40db3f10c |
line wrap: on
line source
/*************************************************************************** * 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 Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, 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, &g, &b, &a); // instance is hit with mouse if not totally transparent if (a != 0) { instances.push_back(i); } } } } void Camera::getMatchingInstances(Rect screen_rect, 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.intersects(screen_rect))) { assert(vc.image); Uint8 r, g, b, a; for(int xx = screen_rect.x; xx < screen_rect.x + screen_rect.w; xx++) { for(int yy = screen_rect.y; yy < screen_rect.y + screen_rect.h; yy++) { if ((vc.dimensions.contains(Point(xx, yy)))) { int x = xx - vc.dimensions.x; int y = yy - 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, &g, &b, &a); // instance is hit with mouse if not totally transparent if (a != 0) { instances.push_back(i); goto found_non_transparent_pixel; } } } } found_non_transparent_pixel:; } } } 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) { // fail if the layers aren't the same if (m_location.getLayer()->getId() != instance->getLocation().getLayer()->getId()) { FL_WARN(_log, "Tried to attach camera to instance on different layer."); return ; } 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; //} // 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>(); if(!visual->isVisible()) continue; 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 | ICHANGE_FACING_LOC))) && (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); unsigned int animtime = 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); unsigned int animtime = instance->getRuntime() % 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; } }