/* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "graphics/GameView.h" #include "graphics/Model.h" #include "graphics/Sprite.h" #include "graphics/Terrain.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "lib/res/graphics/ogl_tex.h" #include "maths/MathUtil.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Player.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "scripting/ScriptableComplex.inl" #include "Aura.h" #include "Collision.h" #include "Entity.h" #include "EntityFormation.h" #include "EntityTemplate.h" #include "EntityTemplateCollection.h" #include "EventHandlers.h" #include "Formation.h" #include "FormationManager.h" #include "ProductionQueue.h" #include "TechnologyCollection.h" #include "TerritoryManager.h" #include void CEntity::Render() { if( !m_visible ) return; if( !m_orderQueue.empty() ) { std::deque::iterator it; CBoundingObject* destinationCollisionObject; float x0, y0, x, y; x = m_orderQueue.front().m_target_location.x; y = m_orderQueue.front().m_target_location.y; for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) { if( it->m_type == CEntityOrder::ORDER_PATROL ) break; x = it->m_target_location.x; y = it->m_target_location.y; } destinationCollisionObject = GetContainingObject( CVector2D( x, y ) ); glShadeModel( GL_FLAT ); glBegin( GL_LINE_STRIP ); glVertex3f( m_position.X, m_position.Y + 0.25f, m_position.Z ); x = m_position.X; y = m_position.Z; for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) { x0 = x; y0 = y; x = it->m_target_location.x; y = it->m_target_location.y; rayIntersectionResults r; CVector2D fwd( x - x0, y - y0 ); float l = fwd.Length(); fwd = fwd.Normalize(); CVector2D rgt = fwd.beta(); if( GetRayIntersection( CVector2D( x0, y0 ), fwd, rgt, l, m_bounds->m_radius, destinationCollisionObject, &r ) ) { glEnd(); glBegin( GL_LINES ); glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( x0 + fwd.x * r.distance, GetAnchorLevel( x0 + fwd.x * r.distance, y0 + fwd.y * r.distance ) + 0.25f, y0 + fwd.y * r.distance ); glVertex3f( r.position.x, GetAnchorLevel( r.position.x, r.position.y ) + 0.25f, r.position.y ); glEnd(); glBegin( GL_LINE_STRIP ); glVertex3f( x0, GetAnchorLevel( x0, y0 ), y0 ); } switch( it->m_type ) { case CEntityOrder::ORDER_GOTO: glColor3f( 1.0f, 0.0f, 0.0f ); break; case CEntityOrder::ORDER_GOTO_COLLISION: glColor3f( 1.0f, 0.5f, 0.5f ); break; case CEntityOrder::ORDER_GOTO_NOPATHING: case CEntityOrder::ORDER_GOTO_SMOOTHED: glColor3f( 0.5f, 0.5f, 0.5f ); break; case CEntityOrder::ORDER_PATROL: glColor3f( 0.0f, 1.0f, 0.0f ); break; default: continue; } glVertex3f( x, GetAnchorLevel( x, y ) + 0.25f, y ); } glEnd(); glShadeModel( GL_SMOOTH ); } glColor3f( 1.0f, 1.0f, 1.0f ); if( GetCollisionObject( this ) ) glColor3f( 0.5f, 0.5f, 1.0f ); m_bounds->Render( GetAnchorLevel( m_position.X, m_position.Z ) + 0.25f ); //m_position.Y + 0.25f ); } void CEntity::RenderSelectionOutline( float alpha ) { if( !m_bounds || !m_visible ) return; if( GetCollisionObject( m_bounds, m_player, &m_base->m_socket ) ) { glColor4f( 1.0f, 0.5f, 0.5f, alpha ); // We're colliding with another unit; colour outline pink } else { const SPlayerColour& col = m_player->GetColour(); glColor3f( col.r, col.g, col.b ); // Colour outline with player colour } glBegin( GL_LINE_LOOP ); CVector3D pos = m_graphics_position; switch( m_bounds->m_type ) { case CBoundingObject::BOUND_CIRCLE: { float radius = ((CBoundingCircle*)m_bounds)->m_radius; for( int i = 0; i < SELECTION_CIRCLE_POINTS; i++ ) { float ang = i * 2 * (float)M_PI / SELECTION_CIRCLE_POINTS; float x = pos.X + radius * sin( ang ); float y = pos.Z + radius * cos( ang ); #ifdef SELECTION_TERRAIN_CONFORMANCE glVertex3f( x, GetAnchorLevel( x, y ) + 0.25f, y ); #else glVertex3f( x, pos.Y + 0.25f, y ); #endif } break; } case CBoundingObject::BOUND_OABB: { CVector2D p, q; CVector2D u, v; q.x = pos.X; q.y = pos.Z; float d = ((CBoundingBox*)m_bounds)->m_d; float w = ((CBoundingBox*)m_bounds)->m_w; u.x = sin( m_graphics_orientation.Y ); u.y = cos( m_graphics_orientation.Y ); v.x = u.y; v.y = -u.x; #ifdef SELECTION_TERRAIN_CONFORMANCE for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) { p = q + u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) { p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) - v * w; glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) { p = q - u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) { p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) + v * w; glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } #else p = q + u * h + v * w; glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); p = q + u * h - v * w; glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); p = q - u * h + v * w; glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); p = q + u * h + v * w; glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y ); #endif break; } } glEnd(); } void CEntity::RenderAuras() { if( !(m_bounds && m_visible && !m_auras.empty()) ) return; const SPlayerColour& playerCol = m_player->GetColour(); glPushMatrix(); glTranslatef(m_graphics_position.X, m_graphics_position.Y, m_graphics_position.Z); size_t i=0; for ( AuraTable::iterator it=m_auras.begin(); it!=m_auras.end(); ++it, ++i ) { CVector4D color = it->second->m_color; glColor4f(color.m_X, color.m_Y, color.m_Z, color.m_W); #ifdef SELECTION_TERRAIN_CONFORMANCE //This starts to break when the radius is bigger if ( it->second->m_radius < 15.0f ) { glBegin(GL_TRIANGLE_FAN); glVertex3f(0.0f, GetAnchorLevel(m_graphics_position.X, m_graphics_position.Z)-m_graphics_position.Y+.5f, 0.0f); for ( int j=0; jsecond->m_radius < 15.0f ) { for ( int j=0; jGetView()->GetCamera(); float sx, sy; CVector3D above; above.X = m_position.X; above.Z = m_position.Z; above.Y = GetAnchorLevel(m_position.X, m_position.Z) + height; camera.GetScreenCoordinates(above, sx, sy); return CVector2D( sx, sy ); } void CEntity::DrawRect( CVector3D& centre, CVector3D& up, CVector3D& right, float x1, float y1, float x2, float y2 ) { glBegin(GL_QUADS); const int X[] = {1,1,0,0}; // which X and Y to choose at each vertex const int Y[] = {0,1,1,0}; for( int i=0; i<4; i++ ) { CVector3D vec = centre + right * (X[i] ? x1 : x2) + up * (Y[i] ? y1 : y2); glTexCoord2f( X[i], Y[i] ); glVertex3fv( &vec.X ); } glEnd(); } void CEntity::DrawBar( CVector3D& centre, CVector3D& up, CVector3D& right, float x1, float y1, float x2, float y2, SColour col1, SColour col2, float currVal, float maxVal ) { // Figure out fraction that should be col1 float fraction; if(maxVal == 0) fraction = 1.0f; else fraction = clamp( currVal / maxVal, 0.0f, 1.0f ); /*// Draw the border at full size ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] ); DrawRect( centre, up, right, x1, y1, x2, y2 ); ogl_tex_bind( 0 ); // Make the bar contents slightly smaller than the border x1 += m_base->m_barBorderSize; y1 += m_base->m_barBorderSize; x2 -= m_base->m_barBorderSize; y2 -= m_base->m_barBorderSize;*/ // Draw the bar contents float xMid = x2 * fraction + x1 * (1.0f - fraction); glColor3fv( &col1.r ); DrawRect( centre, up, right, x1, y1, xMid, y2 ); glColor3fv( &col2.r ); DrawRect( centre, up, right, xMid, y1, x2, y2 ); } void CEntity::RenderBars() { if( !m_base->m_barsEnabled || !m_bounds || !m_visible) return; SnapToGround(); CVector3D centre = m_graphics_position; centre.Y += m_base->m_barOffset; CVector3D up = g_Game->GetView()->GetCamera()->m_Orientation.GetUp(); CVector3D right = -g_Game->GetView()->GetCamera()->m_Orientation.GetLeft(); float w = m_base->m_barWidth; float h = m_base->m_barHeight; float borderSize = m_base->m_barBorderSize; // Draw the health and stamina bars; if the unit has no stamina, the health bar is // drawn centered, otherwise it's offset slightly up and the stamina bar is offset // slightly down so that they overlap over an area of size borderSize. bool hasStamina = (m_staminaMax > 0); float backgroundW = w+2*borderSize; float backgroundH = hasStamina ? 2*h+2*borderSize : h+2*borderSize; /* ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] ); DrawRect( centre, up, right, -backgroundW/2, -backgroundH/2, backgroundW/2, backgroundH/2 ); */ ogl_tex_bind( 0 ); float off = hasStamina ? h/2 : 0; DrawBar( centre, up, right, -w/2, off-h/2, w/2, off+h/2, SColour(0,1,0), SColour(1,0,0), m_healthCurr, m_healthMax ); if( hasStamina ) { DrawBar( centre, up, right, -w/2, -h, w/2, 0, SColour(0,0,1), SColour(0.4f,0.4f,0.1f), m_staminaCurr, m_staminaMax ); } // Draw the rank icon /* CSelectedEntities::MapFilenameToHandle::iterator it = g_Selection.m_unitUITextures.find( m_rankName ); if( it != g_Selection.m_unitUITextures.end() ) { float size = 2*h + borderSize; ogl_tex_bind( it->second ); DrawRect( centre, up, right, w/2+borderSize, -size/2, w/2+borderSize+size, size/2 ); ogl_tex_bind( 0 ); } */ } void CEntity::RenderBarBorders() { if( !m_visible ) return; /* if ( m_base->m_staminaBarHeight >= 0 && g_Selection.m_unitUITextures.find(m_base->m_healthBorderName) != g_Selection.m_unitUITextures.end() ) { ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_healthBorderName] ); CVector2D pos = GetScreenCoords( m_base->m_healthBarHeight ); float left = pos.x - m_base->m_healthBorderWidth/2; float right = pos.x + m_base->m_healthBorderWidth/2; pos.y = g_yres - pos.y; float bottom = pos.y + m_base->m_healthBorderHeight/2; float top = pos.y - m_base->m_healthBorderHeight/2; glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 ); glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 ); glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 ); glEnd(); } if ( m_base->m_staminaBarHeight >= 0 && g_Selection.m_unitUITextures.find(m_base->m_staminaBorderName) != g_Selection.m_unitUITextures.end() ) { ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_staminaBorderName] ); CVector2D pos = GetScreenCoords( m_base->m_staminaBarHeight ); float left = pos.x - m_base->m_staminaBorderWidth/2; float right = pos.x + m_base->m_staminaBorderWidth/2; pos.y = g_yres - pos.y; float bottom = pos.y + m_base->m_staminaBorderHeight/2; float top = pos.y - m_base->m_staminaBorderHeight/2; glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 ); glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 ); glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 ); glEnd(); } */ } void CEntity::RenderHealthBar() { if( !m_bounds || !m_visible ) return; if( m_base->m_healthBarHeight < 0 ) return; // negative bar height means don't display health bar float fraction; if(m_healthMax == 0) fraction = 1.0f; else fraction = clamp(m_healthCurr / m_healthMax, 0.0f, 1.0f); CVector2D pos = GetScreenCoords( m_base->m_healthBarHeight ); float x1 = pos.x - m_base->m_healthBarSize/2; float x2 = pos.x + m_base->m_healthBarSize/2; float y = g_yres - pos.y; glLineWidth( m_base->m_healthBarWidth ); glBegin(GL_LINES); // green part of bar glColor3f( 0, 1, 0 ); glVertex3f( x1, y, 0 ); glColor3f( 0, 1, 0 ); glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 ); // red part of bar glColor3f( 1, 0, 0 ); glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 ); glColor3f( 1, 0, 0 ); glVertex3f( x2, y, 0 ); glEnd(); glLineWidth(1.0f); } void CEntity::RenderStaminaBar() { if( !m_bounds || !m_visible ) return; if( m_base->m_staminaBarHeight < 0 ) return; // negative bar height means don't display stamina bar float fraction; if(m_staminaMax == 0) fraction = 1.0f; else fraction = clamp(m_staminaCurr / m_staminaMax, 0.0f, 1.0f); CVector2D pos = GetScreenCoords( m_base->m_staminaBarHeight ); float x1 = pos.x - m_base->m_staminaBarSize/2; float x2 = pos.x + m_base->m_staminaBarSize/2; float y = g_yres - pos.y; glLineWidth( m_base->m_staminaBarWidth ); glBegin(GL_LINES); // blue part of bar glColor3f( 0.1f, 0.1f, 1 ); glVertex3f( x1, y, 0 ); glColor3f( 0.1f, 0.1f, 1 ); glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 ); // purple part of bar glColor3f( 0.3f, 0, 0.3f ); glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 ); glColor3f( 0.3f, 0, 0.3f ); glVertex3f( x2, y, 0 ); glEnd(); glLineWidth(1.0f); } void CEntity::RenderRank() { if( !m_bounds || !m_visible ) return; if( m_base->m_rankHeight < 0 ) return; // negative height means don't display rank //Check for valid texture /* if( g_Selection.m_unitUITextures.find( m_rankName ) == g_Selection.m_unitUITextures.end() ) return; */ CCamera *camera = g_Game->GetView()->GetCamera(); float sx, sy; CVector3D above; above.X = m_position.X; above.Z = m_position.Z; above.Y = GetAnchorLevel(m_position.X, m_position.Z) + m_base->m_rankHeight; camera->GetScreenCoordinates(above, sx, sy); int size = m_base->m_rankWidth/2; float x1 = sx + m_base->m_healthBarSize/2; float x2 = sx + m_base->m_healthBarSize/2 + 2*size; float y1 = g_yres - (sy - size); //top float y2 = g_yres - (sy + size); //bottom // ogl_tex_bind(g_Selection.m_unitUITextures[m_rankName]); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); glBegin(GL_QUADS); glTexCoord2f(1.0f, 0.0f); glVertex3f( x2, y2, 0 ); glTexCoord2f(1.0f, 1.0f); glVertex3f( x2, y1, 0 ); glTexCoord2f(0.0f, 1.0f); glVertex3f( x1, y1, 0 ); glTexCoord2f(0.0f, 0.0f); glVertex3f( x1, y2, 0 ); glEnd(); } void CEntity::RenderRallyPoint() { if( !m_visible ) return; /* if ( !entf_get(ENTF_HAS_RALLY_POINT) || g_Selection.m_unitUITextures.find(m_base->m_rallyName) == g_Selection.m_unitUITextures.end() ) { return; } */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); CSprite sprite; CTexture tex; // tex.SetHandle( g_Selection.m_unitUITextures[m_base->m_rallyName] ); sprite.SetTexture(&tex); // Place the sprite slightly above ground/water level. float terrainHeight = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(m_rallyPoint); float height = std::max(terrainHeight, g_Renderer.GetWaterManager()->m_WaterHeight) + 0.5f; CVector3D rally(m_rallyPoint.x, height, m_rallyPoint.y); sprite.SetTranslation(rally); sprite.Render(); }