Index: ps/trunk/source/graphics/CameraController.cpp
===================================================================
--- ps/trunk/source/graphics/CameraController.cpp (revision 23768)
+++ ps/trunk/source/graphics/CameraController.cpp (revision 23769)
@@ -1,718 +1,718 @@
/* Copyright (C) 2020 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 "CameraController.h"
#include "graphics/HFTracer.h"
#include "graphics/Terrain.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "lib/input.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "maths/Quaternion.h"
#include "ps/ConfigDB.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Joystick.h"
#include "ps/Pyrogenesis.h"
#include "ps/TouchInput.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
extern int g_xres, g_yres;
// Maximum distance outside the edge of the map that the camera's
// focus point can be moved
static const float CAMERA_EDGE_MARGIN = 2.0f * TERRAIN_TILE_SIZE;
CCameraController::CCameraController(CCamera& camera)
: ICameraController(camera),
m_ConstrainCamera(true),
m_FollowEntity(INVALID_ENTITY),
m_FollowFirstPerson(false),
// Dummy values (these will be filled in by the config file)
m_ViewScrollSpeed(0),
m_ViewScrollSpeedModifier(1),
m_ViewRotateXSpeed(0),
m_ViewRotateXMin(0),
m_ViewRotateXMax(0),
m_ViewRotateXDefault(0),
m_ViewRotateYSpeed(0),
m_ViewRotateYSpeedWheel(0),
m_ViewRotateYDefault(0),
m_ViewRotateSpeedModifier(1),
m_ViewDragSpeed(0),
m_ViewZoomSpeed(0),
m_ViewZoomSpeedWheel(0),
m_ViewZoomMin(0),
m_ViewZoomMax(0),
m_ViewZoomDefault(0),
m_ViewZoomSpeedModifier(1),
m_ViewFOV(DEGTORAD(45.f)),
m_ViewNear(2.f),
m_ViewFar(4096.f),
m_JoystickPanX(-1),
m_JoystickPanY(-1),
m_JoystickRotateX(-1),
m_JoystickRotateY(-1),
m_JoystickZoomIn(-1),
m_JoystickZoomOut(-1),
m_HeightSmoothness(0.5f),
m_HeightMin(16.f),
m_PosX(0, 0, 0.01f),
m_PosY(0, 0, 0.01f),
m_PosZ(0, 0, 0.01f),
m_Zoom(0, 0, 0.1f),
m_RotateX(0, 0, 0.001f),
m_RotateY(0, 0, 0.001f)
{
SViewPort vp;
vp.m_X = 0;
vp.m_Y = 0;
vp.m_Width = g_xres;
vp.m_Height = g_yres;
m_Camera.SetViewPort(vp);
SetCameraProjection();
SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
m_Camera.UpdateFrustum();
}
CCameraController::~CCameraController() = default;
void CCameraController::LoadConfig()
{
CFG_GET_VAL("view.scroll.speed", m_ViewScrollSpeed);
CFG_GET_VAL("view.scroll.speed.modifier", m_ViewScrollSpeedModifier);
CFG_GET_VAL("view.rotate.x.speed", m_ViewRotateXSpeed);
CFG_GET_VAL("view.rotate.x.min", m_ViewRotateXMin);
CFG_GET_VAL("view.rotate.x.max", m_ViewRotateXMax);
CFG_GET_VAL("view.rotate.x.default", m_ViewRotateXDefault);
CFG_GET_VAL("view.rotate.y.speed", m_ViewRotateYSpeed);
CFG_GET_VAL("view.rotate.y.speed.wheel", m_ViewRotateYSpeedWheel);
CFG_GET_VAL("view.rotate.y.default", m_ViewRotateYDefault);
CFG_GET_VAL("view.rotate.speed.modifier", m_ViewRotateSpeedModifier);
CFG_GET_VAL("view.drag.speed", m_ViewDragSpeed);
CFG_GET_VAL("view.zoom.speed", m_ViewZoomSpeed);
CFG_GET_VAL("view.zoom.speed.wheel", m_ViewZoomSpeedWheel);
CFG_GET_VAL("view.zoom.min", m_ViewZoomMin);
CFG_GET_VAL("view.zoom.max", m_ViewZoomMax);
CFG_GET_VAL("view.zoom.default", m_ViewZoomDefault);
CFG_GET_VAL("view.zoom.speed.modifier", m_ViewZoomSpeedModifier);
CFG_GET_VAL("joystick.camera.pan.x", m_JoystickPanX);
CFG_GET_VAL("joystick.camera.pan.y", m_JoystickPanY);
CFG_GET_VAL("joystick.camera.rotate.x", m_JoystickRotateX);
CFG_GET_VAL("joystick.camera.rotate.y", m_JoystickRotateY);
CFG_GET_VAL("joystick.camera.zoom.in", m_JoystickZoomIn);
CFG_GET_VAL("joystick.camera.zoom.out", m_JoystickZoomOut);
CFG_GET_VAL("view.height.smoothness", m_HeightSmoothness);
CFG_GET_VAL("view.height.min", m_HeightMin);
#define SETUP_SMOOTHNESS(CFG_PREFIX, SMOOTHED_VALUE) \
{ \
float smoothness = SMOOTHED_VALUE.GetSmoothness(); \
CFG_GET_VAL(CFG_PREFIX ".smoothness", smoothness); \
SMOOTHED_VALUE.SetSmoothness(smoothness); \
}
SETUP_SMOOTHNESS("view.pos", m_PosX);
SETUP_SMOOTHNESS("view.pos", m_PosY);
SETUP_SMOOTHNESS("view.pos", m_PosZ);
SETUP_SMOOTHNESS("view.zoom", m_Zoom);
SETUP_SMOOTHNESS("view.rotate.x", m_RotateX);
SETUP_SMOOTHNESS("view.rotate.y", m_RotateY);
#undef SETUP_SMOOTHNESS
CFG_GET_VAL("view.near", m_ViewNear);
CFG_GET_VAL("view.far", m_ViewFar);
CFG_GET_VAL("view.fov", m_ViewFOV);
// Convert to radians
m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
m_ViewFOV = DEGTORAD(m_ViewFOV);
}
void CCameraController::SetViewport(const SViewPort& vp)
{
m_Camera.SetViewPort(vp);
SetCameraProjection();
}
void CCameraController::Update(const float deltaRealTime)
{
// Calculate mouse movement
static int mouse_last_x = 0;
static int mouse_last_y = 0;
int mouse_dx = g_mouse_x - mouse_last_x;
int mouse_dy = g_mouse_y - mouse_last_y;
mouse_last_x = g_mouse_x;
mouse_last_y = g_mouse_y;
if (HotkeyIsPressed("camera.rotate.cw"))
m_RotateY.AddSmoothly(m_ViewRotateYSpeed * deltaRealTime);
if (HotkeyIsPressed("camera.rotate.ccw"))
m_RotateY.AddSmoothly(-m_ViewRotateYSpeed * deltaRealTime);
if (HotkeyIsPressed("camera.rotate.up"))
m_RotateX.AddSmoothly(-m_ViewRotateXSpeed * deltaRealTime);
if (HotkeyIsPressed("camera.rotate.down"))
m_RotateX.AddSmoothly(m_ViewRotateXSpeed * deltaRealTime);
float moveRightward = 0.f;
float moveForward = 0.f;
if (HotkeyIsPressed("camera.pan"))
{
moveRightward += m_ViewDragSpeed * mouse_dx;
moveForward += m_ViewDragSpeed * -mouse_dy;
}
if (g_mouse_active)
{
if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres)
moveRightward += m_ViewScrollSpeed * deltaRealTime;
else if (g_mouse_x <= 3 && g_mouse_x >= 0)
moveRightward -= m_ViewScrollSpeed * deltaRealTime;
if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres)
moveForward -= m_ViewScrollSpeed * deltaRealTime;
else if (g_mouse_y <= 3 && g_mouse_y >= 0)
moveForward += m_ViewScrollSpeed * deltaRealTime;
}
if (HotkeyIsPressed("camera.right"))
moveRightward += m_ViewScrollSpeed * deltaRealTime;
if (HotkeyIsPressed("camera.left"))
moveRightward -= m_ViewScrollSpeed * deltaRealTime;
if (HotkeyIsPressed("camera.up"))
moveForward += m_ViewScrollSpeed * deltaRealTime;
if (HotkeyIsPressed("camera.down"))
moveForward -= m_ViewScrollSpeed * deltaRealTime;
if (g_Joystick.IsEnabled())
{
// This could all be improved with extra speed and sensitivity settings
// (maybe use pow to allow finer control?), and inversion settings
moveRightward += g_Joystick.GetAxisValue(m_JoystickPanX) * m_ViewScrollSpeed * deltaRealTime;
moveForward -= g_Joystick.GetAxisValue(m_JoystickPanY) * m_ViewScrollSpeed * deltaRealTime;
m_RotateX.AddSmoothly(g_Joystick.GetAxisValue(m_JoystickRotateX) * m_ViewRotateXSpeed * deltaRealTime);
m_RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m_JoystickRotateY) * m_ViewRotateYSpeed * deltaRealTime);
// Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1
m_Zoom.AddSmoothly((g_Joystick.GetAxisValue(m_JoystickZoomIn) + 1.0f) / 2.0f * m_ViewZoomSpeed * deltaRealTime);
m_Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m_JoystickZoomOut) + 1.0f) / 2.0f * m_ViewZoomSpeed * deltaRealTime);
}
if (moveRightward || moveForward)
{
// Break out of following mode when the user starts scrolling
m_FollowEntity = INVALID_ENTITY;
float s = sin(m_RotateY.GetSmoothedValue());
float c = cos(m_RotateY.GetSmoothedValue());
m_PosX.AddSmoothly(c * moveRightward);
m_PosZ.AddSmoothly(-s * moveRightward);
m_PosX.AddSmoothly(s * moveForward);
m_PosZ.AddSmoothly(c * moveForward);
}
if (m_FollowEntity)
{
CmpPtr cmpPosition(*(g_Game->GetSimulation2()), m_FollowEntity);
CmpPtr cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
if (cmpPosition && cmpPosition->IsInWorld() &&
- cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE)
+ cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == LosVisibility::VISIBLE)
{
// Get the most recent interpolated position
float frameOffset = g_Game->GetSimulation2()->GetLastFrameOffset();
CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
CVector3D pos = transform.GetTranslation();
if (m_FollowFirstPerson)
{
float x, z, angle;
cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle);
float height = 4.f;
m_Camera.m_Orientation.SetIdentity();
m_Camera.m_Orientation.RotateX(static_cast(M_PI) / 24.f);
m_Camera.m_Orientation.RotateY(angle);
m_Camera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z);
m_Camera.UpdateFrustum();
return;
}
else
{
// Move the camera to match the unit
CCamera targetCam = m_Camera;
SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = pos - pivot;
m_PosX.AddSmoothly(delta.X);
m_PosY.AddSmoothly(delta.Y);
m_PosZ.AddSmoothly(delta.Z);
}
}
else
{
// The unit disappeared (died or garrisoned etc), so stop following it
m_FollowEntity = INVALID_ENTITY;
}
}
if (HotkeyIsPressed("camera.zoom.in"))
m_Zoom.AddSmoothly(-m_ViewZoomSpeed * deltaRealTime);
if (HotkeyIsPressed("camera.zoom.out"))
m_Zoom.AddSmoothly(m_ViewZoomSpeed * deltaRealTime);
if (m_ConstrainCamera)
m_Zoom.ClampSmoothly(m_ViewZoomMin, m_ViewZoomMax);
float zoomDelta = -m_Zoom.Update(deltaRealTime);
if (zoomDelta)
{
CVector3D forwards = m_Camera.GetOrientation().GetIn();
m_PosX.AddSmoothly(forwards.X * zoomDelta);
m_PosY.AddSmoothly(forwards.Y * zoomDelta);
m_PosZ.AddSmoothly(forwards.Z * zoomDelta);
}
if (m_ConstrainCamera)
m_RotateX.ClampSmoothly(DEGTORAD(m_ViewRotateXMin), DEGTORAD(m_ViewRotateXMax));
FocusHeight(true);
// Ensure the ViewCamera focus is inside the map with the chosen margins
// if not so - apply margins to the camera
if (m_ConstrainCamera)
{
CCamera targetCam = m_Camera;
SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
CTerrain* pTerrain = g_Game->GetWorld()->GetTerrain();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
CVector3D desiredPivot = pivot;
CmpPtr cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
if (cmpRangeManager && cmpRangeManager->GetLosCircular())
{
// Clamp to a circular region around the center of the map
float r = pTerrain->GetMaxX() / 2;
CVector3D center(r, desiredPivot.Y, r);
float dist = (desiredPivot - center).Length();
if (dist > r - CAMERA_EDGE_MARGIN)
desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN);
}
else
{
// Clamp to the square edges of the map
desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN);
}
// Update the position so that pivot is within the margin
m_PosX.SetValueSmoothly(desiredPivot.X + delta.X);
m_PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z);
}
m_PosX.Update(deltaRealTime);
m_PosY.Update(deltaRealTime);
m_PosZ.Update(deltaRealTime);
// Handle rotation around the Y (vertical) axis
{
CCamera targetCam = m_Camera;
SetupCameraMatrixSmooth(&targetCam.m_Orientation);
float rotateYDelta = m_RotateY.Update(deltaRealTime);
if (rotateYDelta)
{
// We've updated RotateY, and need to adjust Pos so that it's still
// facing towards the original focus point (the terrain in the center
// of the screen).
CVector3D upwards(0.0f, 1.0f, 0.0f);
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
CQuaternion q;
q.FromAxisAngle(upwards, rotateYDelta);
CVector3D d = q.Rotate(delta) - delta;
m_PosX.Add(d.X);
m_PosY.Add(d.Y);
m_PosZ.Add(d.Z);
}
}
// Handle rotation around the X (sideways, relative to camera) axis
{
CCamera targetCam = m_Camera;
SetupCameraMatrixSmooth(&targetCam.m_Orientation);
float rotateXDelta = m_RotateX.Update(deltaRealTime);
if (rotateXDelta)
{
CVector3D rightwards = targetCam.GetOrientation().GetLeft() * -1.0f;
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
CQuaternion q;
q.FromAxisAngle(rightwards, rotateXDelta);
CVector3D d = q.Rotate(delta) - delta;
m_PosX.Add(d.X);
m_PosY.Add(d.Y);
m_PosZ.Add(d.Z);
}
}
/* This is disabled since it doesn't seem necessary:
// Ensure the camera's near point is never inside the terrain
if (m_ConstrainCamera)
{
CMatrix3D target;
target.SetIdentity();
target.RotateX(m_RotateX.GetValue());
target.RotateY(m_RotateY.GetValue());
target.Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear;
float ground = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
float limit = ground + 16.f;
if (nearPoint.Y < limit)
m_PosY.AddSmoothly(limit - nearPoint.Y);
}
*/
m_RotateY.Wrap(-static_cast(M_PI), static_cast(M_PI));
// Update the camera matrix
SetCameraProjection();
SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
m_Camera.UpdateFrustum();
}
CVector3D CCameraController::GetSmoothPivot(CCamera& camera) const
{
return camera.GetOrientation().GetTranslation() + camera.GetOrientation().GetIn() * m_Zoom.GetSmoothedValue();
}
CVector3D CCameraController::GetCameraPivot() const
{
return GetSmoothPivot(m_Camera);
}
CVector3D CCameraController::GetCameraPosition() const
{
return CVector3D(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
}
CVector3D CCameraController::GetCameraRotation() const
{
// The angle of rotation around the Z axis is not used.
return CVector3D(m_RotateX.GetValue(), m_RotateY.GetValue(), 0.0f);
}
float CCameraController::GetCameraZoom() const
{
return m_Zoom.GetValue();
}
void CCameraController::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
{
m_PosX.SetValue(pos.X);
m_PosY.SetValue(pos.Y);
m_PosZ.SetValue(pos.Z);
m_RotateX.SetValue(rotX);
m_RotateY.SetValue(rotY);
m_Zoom.SetValue(zoom);
FocusHeight(false);
SetupCameraMatrixNonSmooth(&m_Camera.m_Orientation);
m_Camera.UpdateFrustum();
// Break out of following mode so the camera really moves to the target
m_FollowEntity = INVALID_ENTITY;
}
void CCameraController::MoveCameraTarget(const CVector3D& target)
{
// Maintain the same orientation and level of zoom, if we can
// (do this by working out the point the camera is looking at, saving
// the difference between that position and the camera point, and restoring
// that difference to our new target)
CCamera targetCam = m_Camera;
SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = target - pivot;
m_PosX.SetValueSmoothly(delta.X + m_PosX.GetValue());
m_PosZ.SetValueSmoothly(delta.Z + m_PosZ.GetValue());
FocusHeight(false);
// Break out of following mode so the camera really moves to the target
m_FollowEntity = INVALID_ENTITY;
}
void CCameraController::ResetCameraTarget(const CVector3D& target)
{
CMatrix3D orientation;
orientation.SetIdentity();
orientation.RotateX(DEGTORAD(m_ViewRotateXDefault));
orientation.RotateY(DEGTORAD(m_ViewRotateYDefault));
CVector3D delta = orientation.GetIn() * m_ViewZoomDefault;
m_PosX.SetValue(target.X - delta.X);
m_PosY.SetValue(target.Y - delta.Y);
m_PosZ.SetValue(target.Z - delta.Z);
m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
m_Zoom.SetValue(m_ViewZoomDefault);
FocusHeight(false);
SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
m_Camera.UpdateFrustum();
// Break out of following mode so the camera really moves to the target
m_FollowEntity = INVALID_ENTITY;
}
void CCameraController::FollowEntity(entity_id_t entity, bool firstPerson)
{
m_FollowEntity = entity;
m_FollowFirstPerson = firstPerson;
}
entity_id_t CCameraController::GetFollowedEntity()
{
return m_FollowEntity;
}
void CCameraController::SetCameraProjection()
{
m_Camera.SetPerspectiveProjection(m_ViewNear, m_ViewFar, m_ViewFOV);
}
void CCameraController::ResetCameraAngleZoom()
{
CCamera targetCam = m_Camera;
SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
// Compute the zoom adjustment to get us back to the default
CVector3D forwards = targetCam.GetOrientation().GetIn();
CVector3D pivot = GetSmoothPivot(targetCam);
CVector3D delta = pivot - targetCam.GetOrientation().GetTranslation();
float dist = delta.Dot(forwards);
m_Zoom.AddSmoothly(m_ViewZoomDefault - dist);
// Reset orientations to default
m_RotateX.SetValueSmoothly(DEGTORAD(m_ViewRotateXDefault));
m_RotateY.SetValueSmoothly(DEGTORAD(m_ViewRotateYDefault));
}
void CCameraController::SetupCameraMatrixSmooth(CMatrix3D* orientation)
{
orientation->SetIdentity();
orientation->RotateX(m_RotateX.GetSmoothedValue());
orientation->RotateY(m_RotateY.GetSmoothedValue());
orientation->Translate(m_PosX.GetSmoothedValue(), m_PosY.GetSmoothedValue(), m_PosZ.GetSmoothedValue());
}
void CCameraController::SetupCameraMatrixSmoothRot(CMatrix3D* orientation)
{
orientation->SetIdentity();
orientation->RotateX(m_RotateX.GetSmoothedValue());
orientation->RotateY(m_RotateY.GetSmoothedValue());
orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
}
void CCameraController::SetupCameraMatrixNonSmooth(CMatrix3D* orientation)
{
orientation->SetIdentity();
orientation->RotateX(m_RotateX.GetValue());
orientation->RotateY(m_RotateY.GetValue());
orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
}
void CCameraController::FocusHeight(bool smooth)
{
/*
The camera pivot height is moved towards ground level.
To prevent excessive zoom when looking over a cliff,
the target ground level is the maximum of the ground level at the camera's near and pivot points.
The ground levels are filtered to achieve smooth camera movement.
The filter radius is proportional to the zoom level.
The camera height is clamped to prevent map penetration.
*/
if (!m_ConstrainCamera)
return;
CCamera targetCam = m_Camera;
SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
const CVector3D position = targetCam.GetOrientation().GetTranslation();
const CVector3D forwards = targetCam.GetOrientation().GetIn();
// horizontal view radius
const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m_Zoom.GetSmoothedValue();
const float near_radius = radius * m_HeightSmoothness;
const float pivot_radius = radius * m_HeightSmoothness;
const CVector3D nearPoint = position + forwards * m_ViewNear;
const CVector3D pivotPoint = position + forwards * m_Zoom.GetSmoothedValue();
const float ground = std::max(
g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z),
g_Renderer.GetWaterManager()->m_WaterHeight);
// filter ground levels for smooth camera movement
const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
// filtered maximum visible ground level in view
const float filtered_ground = std::max(
std::max(filtered_near_ground, filtered_pivot_ground),
g_Renderer.GetWaterManager()->m_WaterHeight);
// target camera height above pivot point
const float pivot_height = -forwards.Y * (m_Zoom.GetSmoothedValue() - m_ViewNear);
// minimum camera height above filtered ground level
const float min_height = (m_HeightMin + ground - filtered_ground);
const float target_height = std::max(pivot_height, min_height);
const float height = (nearPoint.Y - filtered_ground);
const float diff = target_height - height;
if (fabsf(diff) < 0.0001f)
return;
if (smooth)
m_PosY.AddSmoothly(diff);
else
m_PosY.Add(diff);
}
InReaction CCameraController::HandleEvent(const SDL_Event_* ev)
{
switch (ev->ev.type)
{
case SDL_HOTKEYPRESS:
{
std::string hotkey = static_cast(ev->ev.user.data1);
if (hotkey == "camera.reset")
{
ResetCameraAngleZoom();
return IN_HANDLED;
}
return IN_PASS;
}
case SDL_HOTKEYDOWN:
{
std::string hotkey = static_cast(ev->ev.user.data1);
// Mouse wheel must be treated using events instead of polling,
// because SDL auto-generates a sequence of mousedown/mouseup events
// and we never get to see the "down" state inside Update().
if (hotkey == "camera.zoom.wheel.in")
{
m_Zoom.AddSmoothly(-m_ViewZoomSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.zoom.wheel.out")
{
m_Zoom.AddSmoothly(m_ViewZoomSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.rotate.wheel.cw")
{
m_RotateY.AddSmoothly(m_ViewRotateYSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.rotate.wheel.ccw")
{
m_RotateY.AddSmoothly(-m_ViewRotateYSpeedWheel);
return IN_HANDLED;
}
else if (hotkey == "camera.scroll.speed.increase")
{
m_ViewScrollSpeed *= m_ViewScrollSpeedModifier;
return IN_HANDLED;
}
else if (hotkey == "camera.scroll.speed.decrease")
{
m_ViewScrollSpeed /= m_ViewScrollSpeedModifier;
return IN_HANDLED;
}
else if (hotkey == "camera.rotate.speed.increase")
{
m_ViewRotateXSpeed *= m_ViewRotateSpeedModifier;
m_ViewRotateYSpeed *= m_ViewRotateSpeedModifier;
return IN_HANDLED;
}
else if (hotkey == "camera.rotate.speed.decrease")
{
m_ViewRotateXSpeed /= m_ViewRotateSpeedModifier;
m_ViewRotateYSpeed /= m_ViewRotateSpeedModifier;
return IN_HANDLED;
}
else if (hotkey == "camera.zoom.speed.increase")
{
m_ViewZoomSpeed *= m_ViewZoomSpeedModifier;
return IN_HANDLED;
}
else if (hotkey == "camera.zoom.speed.decrease")
{
m_ViewZoomSpeed /= m_ViewZoomSpeedModifier;
return IN_HANDLED;
}
return IN_PASS;
}
}
return IN_PASS;
}
Index: ps/trunk/source/graphics/tests/test_LOSTexture.h
===================================================================
--- ps/trunk/source/graphics/tests/test_LOSTexture.h (revision 23768)
+++ ps/trunk/source/graphics/tests/test_LOSTexture.h (revision 23769)
@@ -1,90 +1,93 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2020 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 "lib/self_test.h"
#include "graphics/LOSTexture.h"
#include "lib/timer.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
class TestLOSTexture : public CxxTest::TestSuite
{
public:
void test_basic()
{
CSimulation2 sim(NULL, g_ScriptRuntime, NULL);
CLOSTexture tex(sim);
const ssize_t size = 8;
u32 inputData[size*size] = {
2, 2, 2, 0, 0, 0, 0, 0,
2, 2, 2, 0, 0, 0, 0, 0,
2, 2, 2, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 2
};
- std::vector inputDataVec(inputData, inputData+size*size);
+ Grid inputDataVec(size, size);
- // LOS_MASK should be cmpRanageManager->GetSharedLosMask(1),
+ for (u8 i = 0; i < size; ++i)
+ for (u8 j = 0; j < size; ++j)
+ inputDataVec.set(i, j, inputData[i + j * size]);
+
+ // LosState::MASK should be cmpRanageManager->GetSharedLosMask(1),
// but that would mean adding a huge mock component for this and it
- // should always be LOS_MASK for player 1 (as the other players are bit-shifted).
- ICmpRangeManager::CLosQuerier los(ICmpRangeManager::LOS_MASK, inputDataVec, size);
+ // should always be LosState::MASK for player 1 (as the other players are bit-shifted).
+ ICmpRangeManager::CLosQuerier los((u32)LosState::MASK, inputDataVec, size);
std::vector losData;
size_t pitch;
losData.resize(tex.GetBitmapSize(size, size, &pitch));
tex.GenerateBitmap(los, &losData[0], size, size, pitch);
// for (size_t i = 0; i < losData.size(); ++i)
// printf("%s %3d", i % (size_t)sqrt(losData.size()) ? "" : "\n", losData[i]);
TS_ASSERT_EQUALS(losData[0], 104);
}
void test_perf_DISABLED()
{
CSimulation2 sim(NULL, g_ScriptRuntime, NULL);
CLOSTexture tex(sim);
const ssize_t size = 257;
- std::vector inputDataVec;
- inputDataVec.resize(size*size);
+ Grid inputDataVec(size, size);
- // LOS_MASK should be cmpRanageManager->GetSharedLosMask(1),
+ // LosState::MASK should be cmpRanageManager->GetSharedLosMask(1),
// but that would mean adding a huge mock component for this and it
- // should always be LOS_MASK for player 1 (as the other players are bit-shifted).
- ICmpRangeManager::CLosQuerier los(ICmpRangeManager::LOS_MASK, inputDataVec, size);
+ // should always be LosState::MASK for player 1 (as the other players are bit-shifted).
+ ICmpRangeManager::CLosQuerier los((u32)LosState::MASK, inputDataVec, size);
size_t reps = 128;
double t = timer_Time();
for (size_t i = 0; i < reps; ++i)
{
std::vector losData;
size_t pitch;
losData.resize(tex.GetBitmapSize(size, size, &pitch));
tex.GenerateBitmap(los, &losData[0], size, size, pitch);
}
double dt = timer_Time() - t;
printf("\n# %f secs\n", dt/reps);
}
};
Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 23768)
+++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 23769)
@@ -1,720 +1,720 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2020 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 "CMiniMap.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniPatch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "gui/CGUI.h"
#include "gui/GUIManager.h"
#include "gui/GUIMatrix.h"
#include "lib/bits.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/ParamNode.h"
#include
extern bool g_GameRestarted;
// Set max drawn entities to UINT16_MAX for now, which is more than enough
// TODO: we should be cleverer about drawing them to reduce clutter
const u16 MAX_ENTITIES_DRAWN = 65535;
static unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color>>8) & 0xff) * x);
unsigned int b = unsigned(float((color>>16) & 0xff) * x);
return (0xff000000 | b | g<<8 | r<<16);
}
const CStr CMiniMap::EventNameWorldClick = "WorldClick";
CMiniMap::CMiniMap(CGUI& pGUI) :
IGUIObject(pGUI),
m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f),
m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW),
m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0)
{
m_Clicking = false;
m_MouseHovering = false;
// Register Relax NG validator
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
// Get the maximum height for unit passage in water.
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
else
m_ShallowPassageHeight = 0.0f;
m_AttributePos.type = GL_FLOAT;
m_AttributePos.elems = 2;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.type = GL_UNSIGNED_BYTE;
m_AttributeColor.elems = 4;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_VertexArray.Layout();
m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
*index++ = i;
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
}
m_HalfBlinkDuration = blinkDuration/2;
}
CMiniMap::~CMiniMap()
{
Destroy();
}
void CMiniMap::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
switch (Message.type)
{
case GUIM_MOUSE_PRESS_LEFT:
if (m_MouseHovering)
{
if (!CMiniMap::FireWorldClickEvent(SDL_BUTTON_LEFT, 1))
{
SetCameraPos();
m_Clicking = true;
}
}
break;
case GUIM_MOUSE_RELEASE_LEFT:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
case GUIM_MOUSE_DBLCLICK_LEFT:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
case GUIM_MOUSE_ENTER:
m_MouseHovering = true;
break;
case GUIM_MOUSE_LEAVE:
m_Clicking = false;
m_MouseHovering = false;
break;
case GUIM_MOUSE_RELEASE_RIGHT:
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
break;
case GUIM_MOUSE_DBLCLICK_RIGHT:
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
break;
case GUIM_MOUSE_MOTION:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
break;
case GUIM_MOUSE_WHEEL_DOWN:
case GUIM_MOUSE_WHEEL_UP:
Message.Skip();
break;
default:
break;
}
}
bool CMiniMap::IsMouseOver() const
{
// Get the mouse position.
const CPos& mousePos = m_pGUI.GetMousePos();
// Get the position of the center of the minimap.
CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0);
// Take the magnitude of the difference of the mouse position and minimap center.
double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2));
// If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap.
if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0)
return true;
else
return false;
}
void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const
{
// Determine X and Z according to proportion of mouse position and minimap
const CPos& mousePos = m_pGUI.GetMousePos();
float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth();
float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight();
float angle = GetAngle();
// Scale world coordinates for shrunken square map
x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5);
z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5);
}
void CMiniMap::SetCameraPos()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
GetMouseWorldCoordinates(target.X, target.Z);
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
float CMiniMap::GetAngle() const
{
CVector3D cameraIn = m_Camera->GetOrientation().GetIn();
return -atan2(cameraIn.X, cameraIn.Z);
}
bool CMiniMap::FireWorldClickEvent(int button, int UNUSED(clicks))
{
JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
float x, z;
GetMouseWorldCoordinates(x, z);
JS::RootedValue coords(cx);
ScriptInterface::CreateObject(cx, &coords, "x", x, "z", z);
JS::RootedValue buttonJs(cx);
ScriptInterface::ToJSVal(cx, &buttonJs, button);
JS::AutoValueVector paramData(cx);
paramData.append(coords);
paramData.append(buttonJs);
return ScriptEventWithReturn(EventNameWorldClick, paramData);
}
// This sets up and draws the rectangle on the minimap
// which represents the view of the camera in the world.
void CMiniMap::DrawViewRect(CMatrix3D transform) const
{
// Compute the camera frustum intersected with a fixed-height plane.
// Use the water height as a fixed base height, which should be the lowest we can go
float h = g_Renderer.GetWaterManager()->m_WaterHeight;
const float width = m_CachedActualSize.GetWidth();
const float height = m_CachedActualSize.GetHeight();
const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize);
CVector3D hitPt[4];
hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h);
hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h);
hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h);
hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h);
float ViewRect[4][2];
for (int i = 0; i < 4; ++i)
{
// convert to minimap space
ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize);
ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize);
}
float viewVerts[] = {
ViewRect[0][0], -ViewRect[0][1],
ViewRect[1][0], -ViewRect[1][1],
ViewRect[2][0], -ViewRect[2][1],
ViewRect[3][0], -ViewRect[3][1]
};
// Enable Scissoring to restrict the rectangle to only the minimap.
glScissor(
m_CachedActualSize.left * g_GuiScale,
g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale,
width * g_GuiScale,
height * g_GuiScale);
glEnable(GL_SCISSOR_TEST);
glLineWidth(2.0f);
CShaderDefines lineDefines;
lineDefines.Add(str_MINIMAP_LINE, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f);
shader->VertexPointer(2, GL_FLOAT, 0, viewVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_LINE_LOOP, 0, 4);
tech->EndPass();
glLineWidth(1.0f);
glDisable(GL_SCISSOR_TEST);
}
struct MinimapUnitVertex
{
// This struct is copyable for convenience and because to move is to copy for primitives.
u8 r, g, b, a;
float x, y;
};
// Adds a vertex to the passed VertexArray
static void inline addVertex(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.x;
(*attrPos)[1] = v.y;
++attrPos;
}
void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const
{
// Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
// Scale square maps to fit in circular minimap area
const float s = sin(angle) * m_MapScale;
const float c = cos(angle) * m_MapScale;
const float m = coordMax / 2.f;
float quadTex[] = {
m*(-c + s + 1.f), m*(-c + -s + 1.f),
m*(c + s + 1.f), m*(-c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(-c + -s + 1.f), m*(c + -s + 1.f),
m*(-c + s + 1.f), m*(-c + -s + 1.f)
};
float quadVerts[] = {
x, y, z,
x2, y, z,
x2, y2, z,
x2, y2, z,
x, y2, z,
x, y, z
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(3, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// TODO: render the minimap in a framebuffer and just draw the frambuffer texture
// most of the time, updating the framebuffer twice a frame.
// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling
// (those operations cause a gpu sync, which slows down the way gpu works)
void CMiniMap::Draw()
{
PROFILE3("render minimap");
// The terrain isn't actually initialized until the map is loaded, which
// happens when the game is started, so abort until then.
if (!g_Game || !g_Game->IsGameStarted())
return;
CSimulation2* sim = g_Game->GetSimulation2();
CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
// Set our globals in case they hadn't been set before
m_Camera = g_Game->GetView()->GetCamera();
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
m_MapSize = m_Terrain->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize);
m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f);
if (!m_TerrainTexture || g_GameRestarted)
CreateTextures();
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
static double last_time;
const double cur_time = timer_Time();
const bool doUpdate = cur_time - last_time > 0.5;
if (doUpdate)
{
last_time = cur_time;
if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight)
RebuildTerrainTexture();
}
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
const float z = GetBufferedZ();
const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
const float angle = GetAngle();
const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f);
// Disable depth updates to prevent apparent z-fighting-related issues
// with some drivers causing units to get drawn behind the texture.
glDepthMask(0);
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines);
tech->BeginPass();
shader = tech->GetShader();
// Draw the main textured quad
shader->BindTexture(str_baseTex, m_TerrainTexture);
const CMatrix3D baseTransform = GetDefaultGuiMatrix();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, baseTextureTransform);
DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z);
// Draw territory boundaries
glEnable(GL_BLEND);
CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *territoryTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
// Draw the LOS quad in black, using alpha values from the LOS texture
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
CShaderDefines losDefines;
losDefines.Add(str_MINIMAP_LOS, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->BindTexture(str_baseTex, losTexture.GetTexture());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *losTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
glDisable(GL_BLEND);
PROFILE_START("minimap units");
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_pointSize, 3.f);
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Center the minimap on the origin of the axis of rotation.
unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f);
// Rotate the map.
unitMatrix.RotateZ(angle);
// Scale square maps to fit.
unitMatrix.Scale(unitScale, unitScale, 1.f);
// Move the minimap back to it's starting position.
unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f);
// Move the minimap to it's final location.
unitMatrix.Translate(x, y, z);
// Apply the gui matrix.
unitMatrix *= GetDefaultGuiMatrix();
// Load the transform into the shader.
shader->Uniform(str_transform, unitMatrix);
const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap);
if (doUpdate)
{
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
if (cur_time > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = cur_time + m_HalfBlinkDuration;
}
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
- ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
- if (vis != ICmpRangeManager::VIS_HIDDEN)
+ LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
+ if (vis != LosVisibility::HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat() * sx;
v.y = -posZ.ToFloat() * sy;
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
addVertex(v, attrColor, attrPos);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (size_t v = 0; v < pingingVertices.size(); ++v)
{
addVertex(pingingVertices[v], attrColor, attrPos);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
m_VertexArray.Upload();
}
m_VertexArray.PrepareForRendering();
if (m_EntitiesDrawn > 0)
{
#if !CONFIG2_GLES
if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER)
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
u8* indexBase = m_IndexArray.Bind();
u8* base = m_VertexArray.Bind();
const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind();
#if !CONFIG2_GLES
if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER)
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
}
tech->EndPass();
DrawViewRect(unitMatrix);
PROFILE_END("minimap units");
// Reset depth mask
glDepthMask(1);
}
void CMiniMap::CreateTextures()
{
Destroy();
// Create terrain texture
glGenTextures(1, &m_TerrainTexture);
g_Renderer.BindTexture(0, m_TerrainTexture);
// Initialise texture with solid black, for the areas we don't
// overwrite with glTexSubImage2D later
u32* texData = new u32[m_TextureSize * m_TextureSize];
for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i)
texData[i] = 0xFF000000;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
delete[] texData;
m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Rebuild and upload both of them
RebuildTerrainTexture();
}
void CMiniMap::RebuildTerrainTexture()
{
u32 x = 0;
u32 y = 0;
u32 w = m_MapSize - 1;
u32 h = m_MapSize - 1;
m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
m_TerrainDirty = false;
for (u32 j = 0; j < h; ++j)
{
u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
for (u32 i = 0; i < w; ++i)
{
float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i, (int)j+1)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
) / 4.0f;
if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
{
// shallow water
*dataPtr++ = 0xffc09870;
}
else if (avgHeight < m_WaterHeight)
{
// Set water as constant color for consistency on different maps
*dataPtr++ = 0xffa07850;
}
else
{
int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
int val = (hmap / 3) + 170;
u32 color = 0xFFFFFFFF;
CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j);
if (mp)
{
CTerrainTextureEntry* tex = mp->GetTextureEntry();
if (tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if(!tex->GetTexture()->TryLoad())
m_TerrainDirty = true;
color = tex->GetBaseColor();
}
}
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
}
}
}
// Upload the texture
g_Renderer.BindTexture(0, m_TerrainTexture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
}
void CMiniMap::Destroy()
{
if (m_TerrainTexture)
{
glDeleteTextures(1, &m_TerrainTexture);
m_TerrainTexture = 0;
}
SAFE_ARRAY_DELETE(m_TerrainData);
}
Index: ps/trunk/source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRangeManager.cpp (revision 23768)
+++ ps/trunk/source/simulation2/components/CCmpRangeManager.cpp (revision 23769)
@@ -1,2465 +1,2461 @@
/* Copyright (C) 2020 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 "simulation2/system/Component.h"
#include "ICmpRangeManager.h"
#include "ICmpTerrain.h"
#include "simulation2/system/EntityMap.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpFogging.h"
#include "simulation2/components/ICmpMirage.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpTerritoryManager.h"
#include "simulation2/components/ICmpVisibility.h"
#include "simulation2/components/ICmpVision.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/helpers/MapEdgeTiles.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/helpers/Spatial.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
#define LOS_TILES_RATIO 8
#define DEBUG_RANGE_MANAGER_BOUNDS 0
-/**
- * Representation of a range query.
- */
-struct Query
-{
- bool enabled;
- bool parabolic;
- CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
- entity_pos_t minRange;
- entity_pos_t maxRange;
- entity_pos_t elevationBonus;
- u32 ownersMask;
- i32 interface;
- std::vector lastMatch;
- u8 flagsMask;
-};
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
* into a 32-bit mask for quick set-membership tests.
*/
static inline u32 CalcOwnerMask(player_id_t owner)
{
if (owner >= -1 && owner < 31)
return 1 << (1+owner);
else
return 0; // owner was invalid
}
/**
* Returns LOS mask for given player.
*/
static inline u32 CalcPlayerLosMask(player_id_t player)
{
if (player > 0 && player <= 16)
- return ICmpRangeManager::LOS_MASK << (2*(player-1));
+ return (u32)LosState::MASK << (2*(player-1));
return 0;
}
/**
* Returns shared LOS mask for given list of players.
*/
static u32 CalcSharedLosMask(std::vector players)
{
u32 playerMask = 0;
for (size_t i = 0; i < players.size(); i++)
playerMask |= CalcPlayerLosMask(players[i]);
return playerMask;
}
/**
* Add/remove a player to/from mask, which is a 1-bit mask representing a list of players.
* Returns true if the mask is modified.
*/
static bool SetPlayerSharedDirtyVisibilityBit(u16& mask, player_id_t player, bool enable)
{
if (player <= 0 || player > 16)
return false;
u16 oldMask = mask;
if (enable)
mask |= (0x1 << (player - 1));
else
mask &= ~(0x1 << (player - 1));
return oldMask != mask;
}
/**
* Computes the 2-bit visibility for one player, given the total 32-bit visibilities
*/
-static inline u8 GetPlayerVisibility(u32 visibilities, player_id_t player)
+static inline LosVisibility GetPlayerVisibility(u32 visibilities, player_id_t player)
{
if (player > 0 && player <= 16)
- return (visibilities >> (2 *(player-1))) & 0x3;
- return 0;
+ return static_cast( (visibilities >> (2 *(player-1))) & 0x3 );
+ return LosVisibility::HIDDEN;
}
/**
* Test whether the visibility is dirty for a given LoS tile and a given player
*/
static inline bool IsVisibilityDirty(u16 dirty, player_id_t player)
{
if (player > 0 && player <= 16)
return (dirty >> (player - 1)) & 0x1;
return false;
}
/**
* Test whether a player share this vision
*/
static inline bool HasVisionSharing(u16 visionSharing, player_id_t player)
{
return visionSharing & 1 << (player-1);
}
/**
* Computes the shared vision mask for the player
*/
static inline u16 CalcVisionSharingMask(player_id_t player)
{
return 1 << (player-1);
}
/**
+ * Representation of a range query.
+ */
+struct Query
+{
+ bool enabled;
+ bool parabolic;
+ CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
+ entity_pos_t minRange;
+ entity_pos_t maxRange;
+ entity_pos_t elevationBonus;
+ u32 ownersMask;
+ i32 interface;
+ std::vector lastMatch;
+ u8 flagsMask;
+};
+
+/**
* Checks whether v is in a parabolic range of (0,0,0)
* The highest point of the paraboloid is (0,range/2,0)
* and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
*
* Avoids sqrting and overflowing.
*/
static bool InParabolicRange(CFixedVector3D v, fixed range)
{
u64 xx = SQUARE_U64_FIXED(v.X); // xx <= 2^62
u64 zz = SQUARE_U64_FIXED(v.Z);
i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
i32 y = v.Y.GetInternalValue();
i32 c = range.GetInternalValue();
i32 c_2 = c >> 1;
i64 c2 = MUL_I64_I32_I32(c_2 - y, c);
if (d2 <= c2)
return true;
return false;
}
struct EntityParabolicRangeOutline
{
entity_id_t source;
CFixedVector3D position;
entity_pos_t range;
std::vector outline;
};
static std::map ParabolicRangesOutlines;
/**
* Representation of an entity, with the data needed for queries.
*/
enum FlagMasks
{
// flags used for queries
None = 0x00,
Normal = 0x01,
Injured = 0x02,
AllQuery = Normal | Injured,
// 0x04 reserved for future use
// general flags
InWorld = 0x08,
RetainInFog = 0x10,
RevealShore = 0x20,
ScriptedVisibility = 0x40,
SharedVision = 0x80
};
struct EntityData
{
EntityData() :
visibilities(0), size(0), visionSharing(0),
owner(-1), flags(FlagMasks::Normal)
{ }
entity_pos_t x, z;
entity_pos_t visionRange;
u32 visibilities; // 2-bit visibility, per player
u32 size;
u16 visionSharing; // 1-bit per player
i8 owner;
u8 flags; // See the FlagMasks enum
template
inline bool HasFlag() const { return (flags & mask) != 0; }
template
inline void SetFlag(bool val) { flags = val ? (flags | mask) : (flags & ~mask); }
inline void SetFlag(u8 mask, bool val) { flags = val ? (flags | mask) : (flags & ~mask); }
};
cassert(sizeof(EntityData) == 24);
/**
* Serialization helper template for Query
*/
struct SerializeQuery
{
template
void Common(S& serialize, const char* UNUSED(name), Query& value)
{
serialize.Bool("enabled", value.enabled);
serialize.Bool("parabolic",value.parabolic);
serialize.NumberFixed_Unbounded("min range", value.minRange);
serialize.NumberFixed_Unbounded("max range", value.maxRange);
serialize.NumberFixed_Unbounded("elevation bonus", value.elevationBonus);
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
serialize.NumberI32_Unbounded("interface", value.interface);
SerializeVector()(serialize, "last match", value.lastMatch);
serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
}
void operator()(ISerializer& serialize, const char* name, Query& value, const CSimContext& UNUSED(context))
{
Common(serialize, name, value);
uint32_t id = value.source.GetId();
serialize.NumberU32_Unbounded("source", id);
}
void operator()(IDeserializer& deserialize, const char* name, Query& value, const CSimContext& context)
{
Common(deserialize, name, value);
uint32_t id;
deserialize.NumberU32_Unbounded("source", id);
value.source = context.GetComponentManager().LookupEntityHandle(id, true);
// the referenced entity might not have been deserialized yet,
// so tell LookupEntityHandle to allocate the handle if necessary
}
};
/**
* Serialization helper template for EntityData
*/
struct SerializeEntityData
{
template
void operator()(S& serialize, const char* UNUSED(name), EntityData& value)
{
serialize.NumberFixed_Unbounded("x", value.x);
serialize.NumberFixed_Unbounded("z", value.z);
serialize.NumberFixed_Unbounded("vision", value.visionRange);
serialize.NumberU32_Unbounded("visibilities", value.visibilities);
serialize.NumberU32_Unbounded("size", value.size);
serialize.NumberU16_Unbounded("vision sharing", value.visionSharing);
serialize.NumberI8_Unbounded("owner", value.owner);
serialize.NumberU8_Unbounded("flags", value.flags);
}
};
/**
* Functor for sorting entities by distance from a source point.
* It must only be passed entities that are in 'entities'
* and are currently in the world.
*/
struct EntityDistanceOrdering
{
EntityDistanceOrdering(const EntityMap& entities, const CFixedVector2D& source) :
m_EntityData(entities), m_Source(source)
{
}
bool operator()(entity_id_t a, entity_id_t b) const
{
const EntityData& da = m_EntityData.find(a)->second;
const EntityData& db = m_EntityData.find(b)->second;
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
return (vecA.CompareLength(vecB) < 0);
}
const EntityMap& m_EntityData;
CFixedVector2D m_Source;
private:
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
};
/**
* Range manager implementation.
* Maintains a list of all entities (and their positions and owners), which is used for
* queries.
*
* LOS implementation is based on the model described in GPG2.
* (TODO: would be nice to make it cleverer, so e.g. mountains and walls
* can block vision)
*/
class CCmpRangeManager : public ICmpRangeManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_Create);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
componentManager.SubscribeGloballyToMessageType(MT_VisionRangeChanged);
componentManager.SubscribeGloballyToMessageType(MT_VisionSharingChanged);
componentManager.SubscribeToMessageType(MT_Deserialized);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
}
DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
bool m_DebugOverlayEnabled;
bool m_DebugOverlayDirty;
std::vector m_DebugOverlayLines;
// Deserialization flag. A lot of different functions are called by Deserialize()
// and we don't want to pass isDeserializing bool arguments to all of them...
bool m_Deserializing;
// World bounds (entities are expected to be within this range)
entity_pos_t m_WorldX0;
entity_pos_t m_WorldZ0;
entity_pos_t m_WorldX1;
entity_pos_t m_WorldZ1;
// Range query state:
tag_t m_QueryNext; // next allocated id
std::map m_Queries;
EntityMap m_EntityData;
FastSpatialSubdivision m_Subdivision; // spatial index of m_EntityData
std::vector m_SubdivisionResults;
// LOS state:
static const player_id_t MAX_LOS_PLAYER_ID = 16;
- std::vector m_LosRevealAll;
+ using LosTile = std::pair;
+
+ std::array m_LosRevealAll;
bool m_LosCircular;
i32 m_TerrainVerticesPerSide;
// Cache for visibility tracking
i32 m_LosTilesPerSide;
bool m_GlobalVisibilityUpdate;
- std::vector m_GlobalPlayerVisibilityUpdate;
- std::vector m_DirtyVisibility;
- std::vector > m_LosTiles;
+ std::array m_GlobalPlayerVisibilityUpdate;
+ Grid m_DirtyVisibility;
+ Grid> m_LosTiles;
// List of entities that must be updated, regardless of the status of their tile
std::vector m_ModifiedEntities;
// Counts of units seeing vertex, per vertex, per player (starting with player 0).
// Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers
// of units in a very small area.
// (Note we use vertexes, not tiles, to better match the renderer.)
// Lazily constructed when it's needed, to save memory in smaller games.
- std::vector > m_LosPlayerCounts;
+ std::array, MAX_LOS_PLAYER_ID> m_LosPlayerCounts;
- // 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
- std::vector m_LosState;
+ // 2-bit LosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
+ Grid m_LosState;
// Special static visibility data for the "reveal whole map" mode
// (TODO: this is usually a waste of memory)
- std::vector m_LosStateRevealed;
+ Grid m_LosStateRevealed;
// Shared LOS masks, one per player.
- std::vector m_SharedLosMasks;
+ std::array m_SharedLosMasks;
// Shared dirty visibility masks, one per player.
- std::vector m_SharedDirtyVisibilityMasks;
+ std::array m_SharedDirtyVisibilityMasks;
// Cache explored vertices per player (not serialized)
u32 m_TotalInworldVertices;
std::vector m_ExploredVertices;
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_QueryNext = 1;
m_DebugOverlayEnabled = false;
m_DebugOverlayDirty = true;
m_Deserializing = false;
m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero();
// Initialise with bogus values (these will get replaced when
// SetBounds is called)
ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024));
m_SubdivisionResults.reserve(4096);
// The whole map should be visible to Gaia by default, else e.g. animals
// will get confused when trying to run from enemies
- m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false);
m_LosRevealAll[0] = true;
- m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0);
- m_SharedDirtyVisibilityMasks.resize(MAX_LOS_PLAYER_ID + 2, 0);
m_GlobalVisibilityUpdate = true;
- m_GlobalPlayerVisibilityUpdate.resize(MAX_LOS_PLAYER_ID);
m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
}
virtual void Deinit()
{
}
template
void SerializeCommon(S& serialize)
{
serialize.NumberFixed_Unbounded("world x0", m_WorldX0);
serialize.NumberFixed_Unbounded("world z0", m_WorldZ0);
serialize.NumberFixed_Unbounded("world x1", m_WorldX1);
serialize.NumberFixed_Unbounded("world z1", m_WorldZ1);
serialize.NumberU32_Unbounded("query next", m_QueryNext);
SerializeMap()(serialize, "queries", m_Queries, GetSimContext());
SerializeEntityMap()(serialize, "entity data", m_EntityData);
- SerializeVector()(serialize, "los reveal all", m_LosRevealAll);
+ SerializeArray()(serialize, "los reveal all", m_LosRevealAll);
serialize.Bool("los circular", m_LosCircular);
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
serialize.Bool("global visibility update", m_GlobalVisibilityUpdate);
- SerializeVector()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
- SerializeRepetitiveVector()(serialize, "dirty visibility", m_DirtyVisibility);
+ SerializeArray()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
+ SerializedGridCompressed()(serialize, "dirty visibility", m_DirtyVisibility);
SerializeVector()(serialize, "modified entities", m_ModifiedEntities);
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
// since they can be recomputed from the entity data when deserializing;
// m_LosState must be serialized since it depends on the history of exploration
- SerializeRepetitiveVector()(serialize, "los state", m_LosState);
- SerializeVector()(serialize, "shared los masks", m_SharedLosMasks);
- SerializeVector()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
+ SerializedGridCompressed()(serialize, "los state", m_LosState);
+ SerializeArray()(serialize, "shared los masks", m_SharedLosMasks);
+ SerializeArray()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
}
virtual void Serialize(ISerializer& serialize)
{
SerializeCommon(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
SerializeCommon(deserialize);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Deserialized:
{
// Reinitialize subdivisions and LOS data after all
// other components have been deserialized.
m_Deserializing = true;
ResetDerivedData();
m_Deserializing = false;
break;
}
case MT_Create:
{
const CMessageCreate& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
// Ignore local entities - we shouldn't let them influence anything
if (ENTITY_IS_LOCAL(ent))
break;
// Ignore non-positional entities
CmpPtr cmpPosition(GetSimContext(), ent);
if (!cmpPosition)
break;
// The newly-created entity will have owner -1 and position out-of-world
// (any initialisation of those values will happen later), so we can just
// use the default-constructed EntityData here
EntityData entdata;
// Store the LOS data, if any
CmpPtr cmpVision(GetSimContext(), ent);
if (cmpVision)
{
entdata.visionRange = cmpVision->GetRange();
entdata.SetFlag(cmpVision->GetRevealShore());
}
CmpPtr cmpVisibility(GetSimContext(), ent);
if (cmpVisibility)
entdata.SetFlag(cmpVisibility->GetRetainInFog());
// Store the size
CmpPtr cmpObstruction(GetSimContext(), ent);
if (cmpObstruction)
entdata.size = cmpObstruction->GetSize().ToInt_RoundToInfinity();
// Remember this entity
m_EntityData.insert(ent, entdata);
break;
}
case MT_PositionChanged:
{
const CMessagePositionChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (msgData.inWorld)
{
if (it->second.HasFlag())
{
CFixedVector2D from(it->second.x, it->second.z);
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Move(ent, from, to, it->second.size);
if (it->second.HasFlag())
SharingLosMove(it->second.visionSharing, it->second.visionRange, from, to);
else
LosMove(it->second.owner, it->second.visionRange, from, to);
- i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
- i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
+ LosTile oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
+ LosTile newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
if (oldLosTile != newLosTile)
{
RemoveFromTile(oldLosTile, ent);
AddToTile(newLosTile, ent);
}
}
else
{
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Add(ent, to, it->second.size);
if (it->second.HasFlag())
SharingLosAdd(it->second.visionSharing, it->second.visionRange, to);
else
LosAdd(it->second.owner, it->second.visionRange, to);
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
}
it->second.SetFlag(true);
it->second.x = msgData.x;
it->second.z = msgData.z;
}
else
{
if (it->second.HasFlag())
{
CFixedVector2D from(it->second.x, it->second.z);
m_Subdivision.Remove(ent, from, it->second.size);
if (it->second.HasFlag())
SharingLosRemove(it->second.visionSharing, it->second.visionRange, from);
else
LosRemove(it->second.owner, it->second.visionRange, from);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
it->second.SetFlag(false);
it->second.x = entity_pos_t::Zero();
it->second.z = entity_pos_t::Zero();
}
RequestVisibilityUpdate(ent);
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (it->second.HasFlag())
{
// Entity vision is taken into account in VisionSharingChanged
// when sharing component activated
if (!it->second.HasFlag())
{
CFixedVector2D pos(it->second.x, it->second.z);
LosRemove(it->second.owner, it->second.visionRange, pos);
LosAdd(msgData.to, it->second.visionRange, pos);
}
if (it->second.HasFlag())
{
RevealShore(it->second.owner, false);
RevealShore(msgData.to, true);
}
}
ENSURE(-128 <= msgData.to && msgData.to <= 127);
it->second.owner = (i8)msgData.to;
break;
}
case MT_Destroy:
{
const CMessageDestroy& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (it->second.HasFlag())
{
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z), it->second.size);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
// This will be called after Ownership's OnDestroy, so ownership will be set
// to -1 already and we don't have to do a LosRemove here
ENSURE(it->second.owner == -1);
m_EntityData.erase(it);
break;
}
case MT_VisionRangeChanged:
{
const CMessageVisionRangeChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
CmpPtr cmpVision(GetSimContext(), ent);
if (!cmpVision)
break;
entity_pos_t oldRange = it->second.visionRange;
entity_pos_t newRange = msgData.newRange;
// If the range changed and the entity's in-world, we need to manually adjust it
// but if it's not in-world, we only need to set the new vision range
it->second.visionRange = newRange;
if (it->second.HasFlag())
{
CFixedVector2D pos(it->second.x, it->second.z);
if (it->second.HasFlag())
{
SharingLosRemove(it->second.visionSharing, oldRange, pos);
SharingLosAdd(it->second.visionSharing, newRange, pos);
}
else
{
LosRemove(it->second.owner, oldRange, pos);
LosAdd(it->second.owner, newRange, pos);
}
}
break;
}
case MT_VisionSharingChanged:
{
const CMessageVisionSharingChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
ENSURE(msgData.player > 0 && msgData.player < MAX_LOS_PLAYER_ID+1);
u16 visionChanged = CalcVisionSharingMask(msgData.player);
if (!it->second.HasFlag())
{
// Activation of the Vision Sharing
ENSURE(it->second.owner == (i8)msgData.player);
it->second.visionSharing = visionChanged;
it->second.SetFlag(true);
break;
}
if (it->second.HasFlag())
{
entity_pos_t range = it->second.visionRange;
CFixedVector2D pos(it->second.x, it->second.z);
if (msgData.add)
LosAdd(msgData.player, range, pos);
else
LosRemove(msgData.player, range, pos);
}
if (msgData.add)
it->second.visionSharing |= visionChanged;
else
it->second.visionSharing &= ~visionChanged;
break;
}
case MT_Update:
{
m_DebugOverlayDirty = true;
ExecuteActiveQueries();
UpdateVisibilityData();
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
{
m_WorldX0 = x0;
m_WorldZ0 = z0;
m_WorldX1 = x1;
m_WorldZ1 = z1;
m_TerrainVerticesPerSide = (i32)vertices;
ResetDerivedData();
}
virtual void Verify()
{
// Ignore if map not initialised yet
if (m_WorldX1.IsZero())
return;
// Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
// does not affect the incrementally-computed state
- std::vector > oldPlayerCounts = m_LosPlayerCounts;
- std::vector oldStateRevealed = m_LosStateRevealed;
+ std::array, MAX_LOS_PLAYER_ID> oldPlayerCounts = m_LosPlayerCounts;
+ Grid oldStateRevealed = m_LosStateRevealed;
FastSpatialSubdivision oldSubdivision = m_Subdivision;
- std::vector > oldLosTiles = m_LosTiles;
+ Grid > oldLosTiles = m_LosTiles;
m_Deserializing = true;
ResetDerivedData();
m_Deserializing = false;
if (oldPlayerCounts != m_LosPlayerCounts)
{
- for (size_t i = 0; i < oldPlayerCounts.size(); ++i)
+ for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id)
{
- debug_printf("%d: ", (int)i);
- for (size_t j = 0; j < oldPlayerCounts[i].size(); ++j)
- debug_printf("%d ", oldPlayerCounts[i][j]);
- debug_printf("\n");
- }
- for (size_t i = 0; i < m_LosPlayerCounts.size(); ++i)
- {
- debug_printf("%d: ", (int)i);
- for (size_t j = 0; j < m_LosPlayerCounts[i].size(); ++j)
- debug_printf("%d ", m_LosPlayerCounts[i][j]);
- debug_printf("\n");
+ debug_printf("player %li\n", id);
+ for (size_t i = 0; i < oldPlayerCounts[id].width(); ++i)
+ {
+ for (size_t j = 0; j < oldPlayerCounts[id].height(); ++j)
+ debug_printf("%i ", oldPlayerCounts[id].get(i,j));
+ debug_printf("\n");
+ }
+ }
+ for (size_t id = 0; id < m_LosPlayerCounts.size(); ++id)
+ {
+ debug_printf("player %li\n", id);
+ for (size_t i = 0; i < m_LosPlayerCounts[id].width(); ++i)
+ {
+ for (size_t j = 0; j < m_LosPlayerCounts[id].height(); ++j)
+ debug_printf("%i ", m_LosPlayerCounts[id].get(i,j));
+ debug_printf("\n");
+ }
}
debug_warn(L"inconsistent player counts");
}
if (oldStateRevealed != m_LosStateRevealed)
debug_warn(L"inconsistent revealed");
if (oldSubdivision != m_Subdivision)
debug_warn(L"inconsistent subdivs");
if (oldLosTiles != m_LosTiles)
debug_warn(L"inconsistent los tiles");
}
FastSpatialSubdivision* GetSubdivision()
{
return &m_Subdivision;
}
// Reinitialise subdivisions and LOS data, based on entity data
void ResetDerivedData()
{
ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
ResetSubdivisions(m_WorldX1, m_WorldZ1);
m_LosTilesPerSide = (m_TerrainVerticesPerSide - 1)/LOS_TILES_RATIO;
- m_LosPlayerCounts.clear();
- m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
+ for (size_t player_id = 0; player_id < m_LosPlayerCounts.size(); ++player_id)
+ m_LosPlayerCounts[player_id].reset();
+
m_ExploredVertices.clear();
m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0);
+
if (m_Deserializing)
{
// recalc current exploration stats.
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
if (!LosIsOffWorld(i, j))
for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
- m_ExploredVertices.at(k) += ((m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*(k-1)))) > 0);
- }
- else
- {
- m_LosState.clear();
- m_LosState.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
- }
- m_LosStateRevealed.clear();
- m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
+ m_ExploredVertices.at(k) += ((m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(k-1)))) > 0);
+ } else
+ m_LosState.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
+
+ m_LosStateRevealed.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
if (!m_Deserializing)
{
- m_DirtyVisibility.clear();
- m_DirtyVisibility.resize(m_LosTilesPerSide*m_LosTilesPerSide);
+ m_DirtyVisibility.resize(m_LosTilesPerSide, m_LosTilesPerSide);
}
- ENSURE(m_DirtyVisibility.size() == (size_t)(m_LosTilesPerSide*m_LosTilesPerSide));
+ ENSURE(m_DirtyVisibility.width() == m_LosTilesPerSide);
+ ENSURE(m_DirtyVisibility.height() == m_LosTilesPerSide);
- m_LosTiles.clear();
- m_LosTiles.resize(m_LosTilesPerSide*m_LosTilesPerSide);
+ m_LosTiles.resize(m_LosTilesPerSide, m_LosTilesPerSide);
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
if (it->second.HasFlag())
{
if (it->second.HasFlag())
SharingLosAdd(it->second.visionSharing, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
else
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first);
if (it->second.HasFlag())
RevealShore(it->second.owner, true);
}
m_TotalInworldVertices = 0;
for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j)
for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
- m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0;
+ m_LosStateRevealed.get(i, j) = 0;
else
{
- m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0xFFFFFFFFu;
+ m_LosStateRevealed.get(i, j) = 0xFFFFFFFFu;
m_TotalInworldVertices++;
}
}
}
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
{
m_Subdivision.Reset(x1, z1);
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
if (it->second.HasFlag())
m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z), it->second.size);
}
virtual tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector& owners, int requiredInterface, u8 flags)
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags);
return id;
}
virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
const std::vector& owners, int requiredInterface, u8 flags)
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, elevationBonus, owners, requiredInterface, flags);
return id;
}
virtual void DestroyActiveQuery(tag_t tag)
{
if (m_Queries.find(tag) == m_Queries.end())
{
LOGERROR("CCmpRangeManager: DestroyActiveQuery called with invalid tag %u", tag);
return;
}
m_Queries.erase(tag);
}
virtual void EnableActiveQuery(tag_t tag)
{
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR("CCmpRangeManager: EnableActiveQuery called with invalid tag %u", tag);
return;
}
Query& q = it->second;
q.enabled = true;
}
virtual void DisableActiveQuery(tag_t tag)
{
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR("CCmpRangeManager: DisableActiveQuery called with invalid tag %u", tag);
return;
}
Query& q = it->second;
q.enabled = false;
}
virtual bool IsActiveQueryEnabled(tag_t tag) const
{
std::map::const_iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR("CCmpRangeManager: IsActiveQueryEnabled called with invalid tag %u", tag);
return false;
}
const Query& q = it->second;
return q.enabled;
}
virtual std::vector ExecuteQueryAroundPos(const CFixedVector2D& pos,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector& owners, int requiredInterface)
{
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
std::vector r;
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector ExecuteQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector& owners, int requiredInterface)
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
std::vector r;
CmpPtr cmpSourcePosition(q.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
return r;
}
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector ResetActiveQuery(tag_t tag)
{
PROFILE("ResetActiveQuery");
std::vector r;
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR("CCmpRangeManager: ResetActiveQuery called with invalid tag %u", tag);
return r;
}
Query& q = it->second;
q.enabled = true;
CmpPtr cmpSourcePosition(q.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
q.lastMatch = r;
return r;
}
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
PerformQuery(q, r, pos);
q.lastMatch = r;
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector GetEntitiesByPlayer(player_id_t player) const
{
return GetEntitiesByMask(CalcOwnerMask(player));
}
virtual std::vector GetNonGaiaEntities() const
{
return GetEntitiesByMask(~3); // bit 0 for owner=-1 and bit 1 for gaia
}
virtual std::vector GetGaiaAndNonGaiaEntities() const
{
return GetEntitiesByMask(~1); // bit 0 for owner=-1
}
std::vector GetEntitiesByMask(u32 ownerMask) const
{
std::vector entities;
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
// Check owner and add to list if it matches
if (CalcOwnerMask(it->second.owner) & ownerMask)
entities.push_back(it->first);
}
return entities;
}
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;
m_DebugOverlayDirty = true;
if (!enabled)
m_DebugOverlayLines.clear();
}
/**
* Update all currently-enabled active queries.
*/
void ExecuteActiveQueries()
{
PROFILE3("ExecuteActiveQueries");
// Store a queue of all messages before sending any, so we can assume
// no entities will move until we've finished checking all the ranges
std::vector > messages;
std::vector results;
std::vector added;
std::vector removed;
for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& query = it->second;
if (!query.enabled)
continue;
results.clear();
CmpPtr cmpSourcePosition(query.source);
if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
{
results.reserve(query.lastMatch.size());
PerformQuery(query, results, cmpSourcePosition->GetPosition2D());
}
// Compute the changes vs the last match
added.clear();
removed.clear();
// Return the 'added' list sorted by distance from the entity
// (Don't bother sorting 'removed' because they might not even have positions or exist any more)
std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
std::back_inserter(added));
std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
std::back_inserter(removed));
if (added.empty() && removed.empty())
continue;
if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
messages.resize(messages.size() + 1);
std::pair& back = messages.back();
back.first = query.source.GetId();
back.second.tag = it->first;
back.second.added.swap(added);
back.second.removed.swap(removed);
query.lastMatch.swap(results);
}
CComponentManager& cmpMgr = GetSimContext().GetComponentManager();
for (size_t i = 0; i < messages.size(); ++i)
cmpMgr.PostMessage(messages[i].first, messages[i].second);
}
/**
* Returns whether the given entity matches the given query (ignoring maxRange)
*/
bool TestEntityQuery(const Query& q, entity_id_t id, const EntityData& entity) const
{
// Quick filter to ignore entities with the wrong owner
if (!(CalcOwnerMask(entity.owner) & q.ownersMask))
return false;
// Ignore entities not present in the world
if (!entity.HasFlag())
return false;
// Ignore entities that don't match the current flags
if (!((entity.flags & FlagMasks::AllQuery) & q.flagsMask))
return false;
// Ignore self
if (id == q.source.GetId())
return false;
// Ignore if it's missing the required interface
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface))
return false;
return true;
}
/**
* Returns a list of distinct entity IDs that match the given query, sorted by ID.
*/
void PerformQuery(const Query& q, std::vector& r, CFixedVector2D pos)
{
// Special case: range -1.0 means check all entities ignoring distance
if (q.maxRange == entity_pos_t::FromInt(-1))
{
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
if (!TestEntityQuery(q, it->first, it->second))
continue;
r.push_back(it->first);
}
}
// Not the entire world, so check a parabolic range, or a regular range
else if (q.parabolic)
{
// elevationBonus is part of the 3D position, as the source is really that much heigher
CmpPtr cmpSourcePosition(q.source);
CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ;
// Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
m_SubdivisionResults.clear();
m_Subdivision.GetNear(m_SubdivisionResults, pos, q.maxRange * 2);
for (size_t i = 0; i < m_SubdivisionResults.size(); ++i)
{
EntityMap::const_iterator it = m_EntityData.find(m_SubdivisionResults[i]);
ENSURE(it != m_EntityData.end());
if (!TestEntityQuery(q, it->first, it->second))
continue;
CmpPtr cmpSecondPosition(GetSimContext(), m_SubdivisionResults[i]);
if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
continue;
CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
// Restrict based on precise distance
if (!InParabolicRange(
CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
- pos3d,
q.maxRange))
continue;
if (!q.minRange.IsZero())
{
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
if (distVsMin < 0)
continue;
}
r.push_back(it->first);
}
std::sort(r.begin(), r.end());
}
// check a regular range (i.e. not the entire world, and not parabolic)
else
{
// Get a quick list of entities that are potentially in range
m_SubdivisionResults.clear();
m_Subdivision.GetNear(m_SubdivisionResults, pos, q.maxRange);
for (size_t i = 0; i < m_SubdivisionResults.size(); ++i)
{
EntityMap::const_iterator it = m_EntityData.find(m_SubdivisionResults[i]);
ENSURE(it != m_EntityData.end());
if (!TestEntityQuery(q, it->first, it->second))
continue;
// Restrict based on precise distance
int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
if (distVsMax > 0)
continue;
if (!q.minRange.IsZero())
{
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
if (distVsMin < 0)
continue;
}
r.push_back(it->first);
}
std::sort(r.begin(), r.end());
}
}
virtual entity_pos_t GetElevationAdaptedRange(const CFixedVector3D& pos1, const CFixedVector3D& rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle) const
{
entity_pos_t r = entity_pos_t::Zero();
CFixedVector3D pos(pos1);
pos.Y += elevationBonus;
entity_pos_t orientation = rot.Y;
entity_pos_t maxAngle = orientation + angle/2;
entity_pos_t minAngle = orientation - angle/2;
int numberOfSteps = 16;
if (angle == entity_pos_t::Zero())
numberOfSteps = 1;
std::vector coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
entity_pos_t part = entity_pos_t::FromInt(numberOfSteps);
for (int i = 0; i < numberOfSteps; ++i)
r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
return r;
}
virtual std::vector getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps) const
{
std::vector r;
CmpPtr cmpTerrain(GetSystemEntity());
if (!cmpTerrain)
return r;
// angle = 0 goes in the positive Z direction
u64 precisionSquared = SQUARE_U64_FIXED(entity_pos_t::FromInt(static_cast(TERRAIN_TILE_SIZE)) / 8);
CmpPtr cmpWaterManager(GetSystemEntity());
entity_pos_t waterLevel = cmpWaterManager ? cmpWaterManager->GetWaterLevel(pos.X, pos.Z) : entity_pos_t::Zero();
entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
for (int i = 0; i < numberOfSteps; ++i)
{
entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
entity_pos_t sin;
entity_pos_t cos;
entity_pos_t minDistance = entity_pos_t::Zero();
entity_pos_t maxDistance = cutoff;
sincos_approx(angle, sin, cos);
CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(), entity_pos_t::Zero());
CFixedVector2D maxVector = CFixedVector2D(sin, cos).Multiply(cutoff);
entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X, pos.Z+maxVector.Y);
// use water level to display range on water
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
if (InParabolicRange(CFixedVector3D(maxVector.X, targetHeight-thisHeight, maxVector.Y), maxRange))
{
r.push_back(maxVector.X);
r.push_back(maxVector.Y);
continue;
}
// Loop until vectors come close enough
while ((maxVector - minVector).CompareLengthSquared(precisionSquared) > 0)
{
// difference still bigger than precision, bisect to get smaller difference
entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
CFixedVector2D newVector = CFixedVector2D(sin, cos).Multiply(newDistance);
// get the height of the ground
targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X, pos.Z+newVector.Y);
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
if (InParabolicRange(CFixedVector3D(newVector.X, targetHeight-thisHeight, newVector.Y), maxRange))
{
// new vector is in parabolic range, so this is a new minVector
minVector = newVector;
minDistance = newDistance;
}
else
{
// new vector is out parabolic range, so this is a new maxVector
maxVector = newVector;
maxDistance = newDistance;
}
}
r.push_back(maxVector.X);
r.push_back(maxVector.Y);
}
r.push_back(r[0]);
r.push_back(r[1]);
return r;
}
Query ConstructQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector& owners, int requiredInterface, u8 flagsMask) const
{
// Min range must be non-negative
if (minRange < entity_pos_t::Zero())
LOGWARNING("CCmpRangeManager: Invalid min range %f in query for entity %u", minRange.ToDouble(), source);
// Max range must be non-negative, or else -1
if (maxRange < entity_pos_t::Zero() && maxRange != entity_pos_t::FromInt(-1))
LOGWARNING("CCmpRangeManager: Invalid max range %f in query for entity %u", maxRange.ToDouble(), source);
Query q;
q.enabled = false;
q.parabolic = false;
q.source = GetSimContext().GetComponentManager().LookupEntityHandle(source);
q.minRange = minRange;
q.maxRange = maxRange;
q.elevationBonus = entity_pos_t::Zero();
q.ownersMask = 0;
for (size_t i = 0; i < owners.size(); ++i)
q.ownersMask |= CalcOwnerMask(owners[i]);
if (q.ownersMask == 0)
LOGWARNING("CCmpRangeManager: No owners in query for entity %u", source);
q.interface = requiredInterface;
q.flagsMask = flagsMask;
return q;
}
Query ConstructParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
const std::vector& owners, int requiredInterface, u8 flagsMask) const
{
Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
q.parabolic = true;
q.elevationBonus = elevationBonus;
return q;
}
void RenderSubmit(SceneCollector& collector)
{
if (!m_DebugOverlayEnabled)
return;
static CColor disabledRingColor(1, 0, 0, 1); // red
static CColor enabledRingColor(0, 1, 0, 1); // green
static CColor subdivColor(0, 0, 1, 1); // blue
static CColor rayColor(1, 1, 0, 0.2f);
if (m_DebugOverlayDirty)
{
m_DebugOverlayLines.clear();
for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& q = it->second;
CmpPtr cmpSourcePosition(q.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
continue;
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
// Draw the max range circle
if (!q.parabolic)
{
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColor : disabledRingColor);
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
}
else
{
// elevation bonus is part of the 3D position. As if the unit is really that much higher
CFixedVector3D pos = cmpSourcePosition->GetPosition();
pos.Y += q.elevationBonus;
std::vector coords;
// Get the outline from cache if possible
if (ParabolicRangesOutlines.find(q.source.GetId()) != ParabolicRangesOutlines.end())
{
EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source.GetId()];
if (e.position == pos && e.range == q.maxRange)
{
// outline is cached correctly, use it
coords = e.outline;
}
else
{
// outline was cached, but important parameters changed
// (position, elevation, range)
// update it
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
e.outline = coords;
e.range = q.maxRange;
e.position = pos;
ParabolicRangesOutlines[q.source.GetId()] = e;
}
}
else
{
// outline wasn't cached (first time you enable the range overlay
// or you created a new entiy)
// cache a new outline
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
EntityParabolicRangeOutline e;
e.source = q.source.GetId();
e.range = q.maxRange;
e.position = pos;
e.outline = coords;
ParabolicRangesOutlines[q.source.GetId()] = e;
}
CColor thiscolor = q.enabled ? enabledRingColor : disabledRingColor;
// draw the outline (piece by piece)
for (size_t i = 3; i < coords.size(); i += 2)
{
std::vector c;
c.push_back((coords[i-3]+pos.X).ToFloat());
c.push_back((coords[i-2]+pos.Z).ToFloat());
c.push_back((coords[i-1]+pos.X).ToFloat());
c.push_back((coords[i]+pos.Z).ToFloat());
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = thiscolor;
SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
}
}
// Draw the min range circle
if (!q.minRange.IsZero())
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.minRange.ToFloat(), m_DebugOverlayLines.back(), true);
// Draw a ray from the source to each matched entity
for (size_t i = 0; i < q.lastMatch.size(); ++i)
{
CmpPtr cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
continue;
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
std::vector coords;
coords.push_back(pos.X.ToFloat());
coords.push_back(pos.Y.ToFloat());
coords.push_back(targetPos.X.ToFloat());
coords.push_back(targetPos.Y.ToFloat());
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = rayColor;
SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
}
}
// render subdivision grid
float divSize = m_Subdivision.GetDivisionSize();
int size = m_Subdivision.GetWidth();
for (int x = 0; x < size; ++x)
{
for (int y = 0; y < size; ++y)
{
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = subdivColor;
float xpos = x*divSize + divSize/2;
float zpos = y*divSize + divSize/2;
SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f,
m_DebugOverlayLines.back(), false, 1.0f);
}
}
m_DebugOverlayDirty = false;
}
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
collector.Submit(&m_DebugOverlayLines[i]);
}
virtual u8 GetEntityFlagMask(const std::string& identifier) const
{
if (identifier == "normal")
return FlagMasks::Normal;
if (identifier == "injured")
return FlagMasks::Injured;
LOGWARNING("CCmpRangeManager: Invalid flag identifier %s", identifier.c_str());
return FlagMasks::None;
}
virtual void SetEntityFlag(entity_id_t ent, const std::string& identifier, bool value)
{
EntityMap::iterator it = m_EntityData.find(ent);
// We don't have this entity
if (it == m_EntityData.end())
return;
u8 flag = GetEntityFlagMask(identifier);
if (flag == FlagMasks::None)
LOGWARNING("CCmpRangeManager: Invalid flag identifier %s for entity %u", identifier.c_str(), ent);
else
it->second.SetFlag(flag, value);
}
// ****************************************************************
// LOS implementation:
virtual CLosQuerier GetLosQuerier(player_id_t player) const
{
if (GetLosRevealAll(player))
return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_TerrainVerticesPerSide);
else
return CLosQuerier(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
}
virtual void ActivateScriptedVisibility(entity_id_t ent, bool status)
{
EntityMap::iterator it = m_EntityData.find(ent);
if (it != m_EntityData.end())
it->second.SetFlag(status);
}
- ELosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const
+ LosVisibility ComputeLosVisibility(CEntityHandle ent, player_id_t player) const
{
// Entities not with positions in the world are never visible
if (ent.GetId() == INVALID_ENTITY)
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
CmpPtr cmpPosition(ent);
if (!cmpPosition || !cmpPosition->IsInWorld())
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
// Mirage entities, whatever the situation, are visible for one specific player
CmpPtr cmpMirage(ent);
if (cmpMirage && cmpMirage->GetPlayer() != player)
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
// Reveal flag makes all positioned entities visible and all mirages useless
if (GetLosRevealAll(player))
{
if (LosIsOffWorld(i, j) || cmpMirage)
- return VIS_HIDDEN;
- else
- return VIS_VISIBLE;
+ return LosVisibility::HIDDEN;
+ return LosVisibility::VISIBLE;
}
// Get visible regions
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
CmpPtr cmpVisibility(ent);
// Possibly ask the scripted Visibility component
EntityMap::const_iterator it = m_EntityData.find(ent.GetId());
if (it != m_EntityData.end())
{
if (it->second.HasFlag() && cmpVisibility)
return cmpVisibility->GetVisibility(player, los.IsVisible(i, j), los.IsExplored(i, j));
}
else
{
if (cmpVisibility && cmpVisibility->IsActivated())
return cmpVisibility->GetVisibility(player, los.IsVisible(i, j), los.IsExplored(i, j));
}
// Else, default behavior
if (los.IsVisible(i, j))
{
if (cmpMirage)
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
- return VIS_VISIBLE;
+ return LosVisibility::VISIBLE;
}
if (!los.IsExplored(i, j))
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
// Invisible if the 'retain in fog' flag is not set, and in a non-visible explored region
// Try using the 'retainInFog' flag in m_EntityData to save a script call
if (it != m_EntityData.end())
{
if (!it->second.HasFlag())
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
}
else
{
if (!(cmpVisibility && cmpVisibility->GetRetainInFog()))
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
}
if (cmpMirage)
- return VIS_FOGGED;
+ return LosVisibility::FOGGED;
CmpPtr cmpOwnership(ent);
if (!cmpOwnership)
- return VIS_FOGGED;
+ return LosVisibility::FOGGED;
if (cmpOwnership->GetOwner() == player)
{
CmpPtr cmpFogging(ent);
if (!(cmpFogging && cmpFogging->IsMiraged(player)))
- return VIS_FOGGED;
+ return LosVisibility::FOGGED;
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
}
// Fogged entities are hidden in two cases:
// - They were not scouted
// - A mirage replaces them
CmpPtr cmpFogging(ent);
if (cmpFogging && cmpFogging->IsActivated() &&
(!cmpFogging->WasSeen(player) || cmpFogging->IsMiraged(player)))
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
- return VIS_FOGGED;
+ return LosVisibility::FOGGED;
}
- ELosVisibility ComputeLosVisibility(entity_id_t ent, player_id_t player) const
+ LosVisibility ComputeLosVisibility(entity_id_t ent, player_id_t player) const
{
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
return ComputeLosVisibility(handle, player);
}
- virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const
+ virtual LosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player) const
{
entity_id_t entId = ent.GetId();
// Entities not with positions in the world are never visible
if (entId == INVALID_ENTITY)
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
CmpPtr cmpPosition(ent);
if (!cmpPosition || !cmpPosition->IsInWorld())
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
// Gaia and observers do not have a visibility cache
if (player <= 0)
return ComputeLosVisibility(ent, player);
CFixedVector2D pos = cmpPosition->GetPosition2D();
- i32 n = PosToLosTilesHelper(pos.X, pos.Y);
- if (IsVisibilityDirty(m_DirtyVisibility[n], player))
+ if (IsVisibilityDirty(m_DirtyVisibility[PosToLosTilesHelper(pos.X, pos.Y)], player))
return ComputeLosVisibility(ent, player);
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), entId) != m_ModifiedEntities.end())
return ComputeLosVisibility(ent, player);
EntityMap::const_iterator it = m_EntityData.find(entId);
if (it == m_EntityData.end())
return ComputeLosVisibility(ent, player);
- return static_cast(GetPlayerVisibility(it->second.visibilities, player));
+ return static_cast(GetPlayerVisibility(it->second.visibilities, player));
}
- virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const
+ virtual LosVisibility GetLosVisibility(entity_id_t ent, player_id_t player) const
{
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
return GetLosVisibility(handle, player);
}
- virtual ELosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const
+ virtual LosVisibility GetLosVisibilityPosition(entity_pos_t x, entity_pos_t z, player_id_t player) const
{
int i = (x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
// Reveal flag makes all positioned entities visible and all mirages useless
if (GetLosRevealAll(player))
{
if (LosIsOffWorld(i, j))
- return VIS_HIDDEN;
+ return LosVisibility::HIDDEN;
else
- return VIS_VISIBLE;
+ return LosVisibility::VISIBLE;
}
// Get visible regions
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
if (los.IsVisible(i,j))
- return VIS_VISIBLE;
+ return LosVisibility::VISIBLE;
if (los.IsExplored(i,j))
- return VIS_FOGGED;
- return VIS_HIDDEN;
+ return LosVisibility::FOGGED;
+ return LosVisibility::HIDDEN;
+ }
+
+ LosTile PosToLosTilesHelper(u16 x, u16 z) const
+ {
+ return LosTile{ Clamp(x/LOS_TILES_RATIO, 0, m_LosTilesPerSide - 1), Clamp(z/LOS_TILES_RATIO, 0, m_LosTilesPerSide - 1) };
}
- i32 PosToLosTilesHelper(entity_pos_t x, entity_pos_t z) const
+ LosTile PosToLosTilesHelper(entity_pos_t x, entity_pos_t z) const
{
i32 i = Clamp(
(x/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
i32 j = Clamp(
(z/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
- return j*m_LosTilesPerSide + i;
+ return std::make_pair(i, j);
}
- void AddToTile(i32 tile, entity_id_t ent)
+ void AddToTile(LosTile tile, entity_id_t ent)
{
m_LosTiles[tile].insert(ent);
}
- void RemoveFromTile(i32 tile, entity_id_t ent)
+ void RemoveFromTile(LosTile tile, entity_id_t ent)
{
std::set::const_iterator tileIt = m_LosTiles[tile].find(ent);
if (tileIt != m_LosTiles[tile].end())
m_LosTiles[tile].erase(tileIt);
}
void UpdateVisibilityData()
{
PROFILE("UpdateVisibilityData");
- for (i32 n = 0; n < m_LosTilesPerSide * m_LosTilesPerSide; ++n)
- {
- for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
- if (IsVisibilityDirty(m_DirtyVisibility[n], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
- for (const entity_id_t& ent : m_LosTiles[n])
- UpdateVisibility(ent, player);
+ for (u16 i = 0; i < m_LosTilesPerSide; ++i)
+ for (u16 j = 0; j < m_LosTilesPerSide; ++j)
+ {
+ LosTile pos{i, j};
+ for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
+ if (IsVisibilityDirty(m_DirtyVisibility[pos], player) || m_GlobalPlayerVisibilityUpdate[player-1] == 1 || m_GlobalVisibilityUpdate)
+ for (const entity_id_t& ent : m_LosTiles[pos])
+ UpdateVisibility(ent, player);
- m_DirtyVisibility[n] = 0;
- }
+ m_DirtyVisibility[pos] = 0;
+ }
- std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), 0);
+ std::fill(m_GlobalPlayerVisibilityUpdate.begin(), m_GlobalPlayerVisibilityUpdate.end(), false);
m_GlobalVisibilityUpdate = false;
// Calling UpdateVisibility can modify m_ModifiedEntities, so be careful:
// infinite loops could be triggered by feedback between entities and their mirages.
std::map attempts;
while (!m_ModifiedEntities.empty())
{
entity_id_t ent = m_ModifiedEntities.back();
m_ModifiedEntities.pop_back();
++attempts[ent];
ENSURE(attempts[ent] < 100 && "Infinite loop in UpdateVisibilityData");
UpdateVisibility(ent);
}
}
virtual void RequestVisibilityUpdate(entity_id_t ent)
{
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), ent) == m_ModifiedEntities.end())
m_ModifiedEntities.push_back(ent);
}
void UpdateVisibility(entity_id_t ent, player_id_t player)
{
EntityMap::iterator itEnts = m_EntityData.find(ent);
if (itEnts == m_EntityData.end())
return;
- u8 oldVis = GetPlayerVisibility(itEnts->second.visibilities, player);
- u8 newVis = ComputeLosVisibility(itEnts->first, player);
+ LosVisibility oldVis = GetPlayerVisibility(itEnts->second.visibilities, player);
+ LosVisibility newVis = ComputeLosVisibility(itEnts->first, player);
if (oldVis == newVis)
return;
- itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2 * (player - 1))) | (newVis << 2 * (player - 1));
+ itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2 * (player - 1))) | ((u8)newVis << 2 * (player - 1));
- CMessageVisibilityChanged msg(player, ent, oldVis, newVis);
+ CMessageVisibilityChanged msg(player, ent, static_cast(oldVis), static_cast(newVis));
GetSimContext().GetComponentManager().PostMessage(ent, msg);
}
void UpdateVisibility(entity_id_t ent)
{
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
UpdateVisibility(ent, player);
}
virtual void SetLosRevealAll(player_id_t player, bool enabled)
{
if (player == -1)
m_LosRevealAll[MAX_LOS_PLAYER_ID+1] = enabled;
else
{
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID);
m_LosRevealAll[player] = enabled;
}
// On next update, update the visibility of every entity in the world
m_GlobalVisibilityUpdate = true;
}
virtual bool GetLosRevealAll(player_id_t player) const
{
// Special player value can force reveal-all for every player
if (m_LosRevealAll[MAX_LOS_PLAYER_ID+1] || player == -1)
return true;
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID+1);
// Otherwise check the player-specific flag
if (m_LosRevealAll[player])
return true;
return false;
}
virtual void SetLosCircular(bool enabled)
{
m_LosCircular = enabled;
ResetDerivedData();
}
virtual bool GetLosCircular() const
{
return m_LosCircular;
}
virtual void SetSharedLos(player_id_t player, const std::vector& players)
{
m_SharedLosMasks[player] = CalcSharedLosMask(players);
// Units belonging to any of 'players' can now trigger visibility updates for 'player'.
// If shared LOS partners have been removed, we disable visibility updates from them
// in order to improve performance. That also allows us to properly determine whether
// 'player' needs a global visibility update for this turn.
bool modified = false;
for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
{
bool inList = std::find(players.begin(), players.end(), p) != players.end();
if (SetPlayerSharedDirtyVisibilityBit(m_SharedDirtyVisibilityMasks[p], player, inList))
modified = true;
}
if (modified && (size_t)player <= m_GlobalPlayerVisibilityUpdate.size())
m_GlobalPlayerVisibilityUpdate[player-1] = 1;
}
virtual u32 GetSharedLosMask(player_id_t player) const
{
return m_SharedLosMasks[player];
}
void ExploreAllTiles(player_id_t p)
{
for (u16 j = 0; j < m_TerrainVerticesPerSide; ++j)
for (u16 i = 0; i < m_TerrainVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
continue;
u32 &explored = m_ExploredVertices.at(p);
- explored += !(m_LosState[i + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
- m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
+ explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(p-1))));
+ m_LosState.get(i, j) |= ((u32)LosState::EXPLORED << (2*(p-1)));
}
SeeExploredEntities(p);
}
virtual void ExploreTerritories()
{
PROFILE3("ExploreTerritories");
CmpPtr cmpTerritoryManager(GetSystemEntity());
const Grid& grid = cmpTerritoryManager->GetTerritoryGrid();
// Territory data is stored per territory-tile (typically a multiple of terrain-tiles).
// LOS data is stored per terrain-tile vertex.
// For each territory-tile, if it is owned by a valid player then update the LOS
// for every vertex inside/around that tile, to mark them as explored.
// Currently this code doesn't support territory-tiles smaller than terrain-tiles
// (it will get scale==0 and break), or a non-integer multiple, so check that first
cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE >= Pathfinding::NAVCELLS_PER_TILE);
cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE % Pathfinding::NAVCELLS_PER_TILE == 0);
int scale = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / Pathfinding::NAVCELLS_PER_TILE;
ENSURE(grid.m_W*scale == m_TerrainVerticesPerSide-1 && grid.m_H*scale == m_TerrainVerticesPerSide-1);
for (u16 j = 0; j < grid.m_H; ++j)
for (u16 i = 0; i < grid.m_W; ++i)
{
u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
if (p > 0 && p <= MAX_LOS_PLAYER_ID)
{
u32& explored = m_ExploredVertices.at(p);
for (int tj = j * scale; tj <= (j+1) * scale; ++tj)
for (int ti = i * scale; ti <= (i+1) * scale; ++ti)
{
if (LosIsOffWorld(ti, tj))
continue;
- u32& losState = m_LosState[ti + tj * m_TerrainVerticesPerSide];
- if (!(losState & (LOS_EXPLORED << (2*(p-1)))))
+ u32& losState = m_LosState.get(ti, tj);
+ if (!(losState & ((u32)LosState::EXPLORED << (2*(p-1)))))
{
++explored;
- losState |= (LOS_EXPLORED << (2*(p-1)));
+ losState |= ((u32)LosState::EXPLORED << (2*(p-1)));
}
}
}
}
for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
SeeExploredEntities(p);
}
/**
* Force any entity in explored territory to appear for player p.
* This is useful for miraging entities inside the territory borders at the beginning of a game,
* or if the "Explore Map" option has been set.
*/
void SeeExploredEntities(player_id_t p) const
{
// Warning: Code related to fogging (like ForceMiraging) shouldn't be
// invoked while iterating through m_EntityData.
// Otherwise, by deleting mirage entities and so on, that code will
// change the indexes in the map, leading to segfaults.
// So we just remember what entities to mirage and do that later.
std::vector miragableEntities;
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
CmpPtr cmpPosition(GetSimContext(), it->first);
if (!cmpPosition || !cmpPosition->IsInWorld())
continue;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
CLosQuerier los(GetSharedLosMask(p), m_LosState, m_TerrainVerticesPerSide);
if (!los.IsExplored(i,j) || los.IsVisible(i,j))
continue;
CmpPtr cmpFogging(GetSimContext(), it->first);
if (cmpFogging)
miragableEntities.push_back(it->first);
}
for (std::vector::iterator it = miragableEntities.begin(); it != miragableEntities.end(); ++it)
{
CmpPtr cmpFogging(GetSimContext(), *it);
ENSURE(cmpFogging && "Impossible to retrieve Fogging component, previously achieved");
cmpFogging->ForceMiraging(p);
}
}
virtual void RevealShore(player_id_t p, bool enable)
{
if (p <= 0 || p > MAX_LOS_PLAYER_ID)
return;
// Maximum distance to the shore
const u16 maxdist = 10;
CmpPtr cmpPathfinder(GetSystemEntity());
const Grid& shoreGrid = cmpPathfinder->ComputeShoreGrid(true);
ENSURE(shoreGrid.m_W == m_TerrainVerticesPerSide-1 && shoreGrid.m_H == m_TerrainVerticesPerSide-1);
- std::vector& counts = m_LosPlayerCounts.at(p);
- ENSURE(!counts.empty());
- u16* countsData = &counts[0];
+ Grid& counts = m_LosPlayerCounts.at(p);
+ ENSURE(!counts.blank());
for (u16 j = 0; j < shoreGrid.m_H; ++j)
for (u16 i = 0; i < shoreGrid.m_W; ++i)
{
u16 shoredist = shoreGrid.get(i, j);
if (shoredist > maxdist)
continue;
// Maybe we could be more clever and don't add dummy strips of one tile
if (enable)
- LosAddStripHelper(p, i, i, j, countsData);
+ LosAddStripHelper(p, i, i, j, counts);
else
- LosRemoveStripHelper(p, i, i, j, countsData);
+ LosRemoveStripHelper(p, i, i, j, counts);
}
}
/**
* Returns whether the given vertex is outside the normal bounds of the world
* (i.e. outside the range of a circular map)
*/
inline bool LosIsOffWorld(ssize_t i, ssize_t j) const
{
if (m_LosCircular)
{
// With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2:
ssize_t dist2 = (i - m_TerrainVerticesPerSide/2)*(i - m_TerrainVerticesPerSide/2)
+ (j - m_TerrainVerticesPerSide/2)*(j - m_TerrainVerticesPerSide/2);
ssize_t r = m_TerrainVerticesPerSide / 2 - MAP_EDGE_TILES + 1;
// subtract a bit from the radius to ensure nice
// SoD blurring around the edges of the map
return (dist2 >= r*r);
}
else
{
// With a square map, the outermost edge of the map should be off-world,
// so the SoD texture blends out nicely
return i < MAP_EDGE_TILES || j < MAP_EDGE_TILES ||
i >= m_TerrainVerticesPerSide - MAP_EDGE_TILES ||
j >= m_TerrainVerticesPerSide - MAP_EDGE_TILES;
}
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
- inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
+ inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid& counts)
{
if (i1 < i0)
return;
- i32 idx0 = j*m_TerrainVerticesPerSide + i0;
- i32 idx1 = j*m_TerrainVerticesPerSide + i1;
u32 &explored = m_ExploredVertices.at(owner);
- for (i32 idx = idx0; idx <= idx1; ++idx)
+ for (i32 i = i0; i <= i1; ++i)
{
// Increasing from zero to non-zero - move from unexplored/explored to visible+explored
- if (counts[idx] == 0)
+ if (counts.get(i, j) == 0)
{
- i32 i = i0 + idx - idx0;
if (!LosIsOffWorld(i, j))
{
- explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1))));
- m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
+ explored += !(m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*(owner-1))));
+ m_LosState.get(i, j) |= (((int)LosState::VISIBLE | (u32)LosState::EXPLORED) << (2*(owner-1)));
}
MarkVisibilityDirtyAroundTile(owner, i, j);
}
- ASSERT(counts[idx] < 65535);
- counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units
+ ENSURE(counts.get(i, j) < std::numeric_limits::max());
+ counts.get(i, j) = (u16)(counts.get(i, j) + 1); // ignore overflow; the player should never have 64K units
}
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
- inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
+ inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, Grid& counts)
{
if (i1 < i0)
return;
- i32 idx0 = j*m_TerrainVerticesPerSide + i0;
- i32 idx1 = j*m_TerrainVerticesPerSide + i1;
- for (i32 idx = idx0; idx <= idx1; ++idx)
+ for (i32 i = i0; i <= i1; ++i)
{
- ASSERT(counts[idx] > 0);
- counts[idx] = (u16)(counts[idx] - 1);
+ ASSERT(counts.get(i, j) > 0);
+ counts.get(i, j) = (u16)(counts.get(i, j) - 1);
// Decreasing from non-zero to zero - move from visible+explored to explored
- if (counts[idx] == 0)
+ if (counts.get(i, j) == 0)
{
// (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
- m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
+ m_LosState.get(i, j) &= ~((int)LosState::VISIBLE << (2*(owner-1)));
- i32 i = i0 + idx - idx0;
MarkVisibilityDirtyAroundTile(owner, i, j);
}
}
}
inline void MarkVisibilityDirtyAroundTile(u8 owner, i32 i, i32 j)
{
// If we're still in the deserializing process, we must not modify m_DirtyVisibility
if (m_Deserializing)
return;
// Mark the LoS tiles around the updated vertex
// 1: left-up, 2: right-up, 3: left-down, 4: right-down
- int n1 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
- int n2 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
- int n3 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
- int n4 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
+ LosTile n1 = PosToLosTilesHelper(i-1, j-1);
+ LosTile n2 = PosToLosTilesHelper(i-1, j);
+ LosTile n3 = PosToLosTilesHelper(i, j-1);
+ LosTile n4 = PosToLosTilesHelper(i, j);
u16 sharedDirtyVisibilityMask = m_SharedDirtyVisibilityMasks[owner];
if (j > 0 && i > 0)
m_DirtyVisibility[n1] |= sharedDirtyVisibilityMask;
if (n2 != n1 && j > 0 && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n2] |= sharedDirtyVisibilityMask;
if (n3 != n1 && j < m_TerrainVerticesPerSide && i > 0)
m_DirtyVisibility[n3] |= sharedDirtyVisibilityMask;
if (n4 != n1 && j < m_TerrainVerticesPerSide && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n4] |= sharedDirtyVisibilityMask;
}
/**
* Update the LOS state of tiles within a given circular range,
* either adding or removing visibility depending on the template parameter.
* Assumes owner is in the valid range.
*/
template
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelper");
- std::vector& counts = m_LosPlayerCounts.at(owner);
+ Grid& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
- if (counts.empty())
- counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
-
- u16* countsData = &counts[0];
+ if (counts.blank())
+ counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
// Compute the circular region as a series of strips.
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
// to get smoother behaviour as a unit moves rather than jumping a whole tile
// at once.
// To avoid the cost of sqrt when computing the outline of the circle,
// we loop from the bottom to the top and estimate the width of the current
// strip based on the previous strip, then adjust each end of the strip
// inwards or outwards until it's the widest that still falls within the circle.
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
// (so that we never render the sharp edge of the map)
i32 j0 = ((pos.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1 = ((pos.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(j0, 1);
i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
// Translate world coordinates into fractional tile-space coordinates
entity_pos_t x = pos.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y = pos.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
entity_pos_t r2 = r.Square();
// Compute the integers on either side of x
i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
// Initialise the strip (i0, i1) to a rough guess
i32 i0 = xfloor;
i32 i1 = xceil;
for (i32 j = j0clamp; j <= j1clamp; ++j)
{
// Adjust i0 and i1 to be the outermost values that don't exceed
// the circle's radius (i.e. require dy^2 + dx^2 <= r^2).
// When moving the points inwards, clamp them to xceil+1 or xfloor-1
// so they don't accidentally shoot off in the wrong direction forever.
entity_pos_t dy = entity_pos_t::FromInt(j) - y;
entity_pos_t dy2 = dy.Square();
while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2)
--i0;
while (i0 < xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2)
++i0;
while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2)
++i1;
while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
--i1;
#if DEBUG_RANGE_MANAGER_BOUNDS
if (i0 <= i1)
{
ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2);
ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2);
}
ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2);
ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2);
#endif
// Clamp the strip to exclude the 1-tile border,
// then add or remove the strip as requested
i32 i0clamp = std::max(i0, 1);
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
if (adding)
- LosAddStripHelper(owner, i0clamp, i1clamp, j, countsData);
+ LosAddStripHelper(owner, i0clamp, i1clamp, j, counts);
else
- LosRemoveStripHelper(owner, i0clamp, i1clamp, j, countsData);
+ LosRemoveStripHelper(owner, i0clamp, i1clamp, j, counts);
}
}
/**
* Update the LOS state of tiles within a given circular range,
* by removing visibility around the 'from' position
* and then adding visibility around the 'to' position.
*/
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelperIncremental");
- std::vector& counts = m_LosPlayerCounts.at(owner);
+ Grid& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
- if (counts.empty())
- counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
-
- u16* countsData = &counts[0];
+ if (counts.blank())
+ counts.resize(m_TerrainVerticesPerSide, m_TerrainVerticesPerSide);
// See comments in LosUpdateHelper.
// This does exactly the same, except computing the strips for
// both circles simultaneously.
// (The idea is that the circles will be heavily overlapping,
// so we can compute the difference between the removed/added strips
// and only have to touch tiles that have a net change.)
i32 j0_from = ((from.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_from = ((from.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0_to = ((to.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_to = ((to.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-2);
entity_pos_t x_from = from.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y_from = from.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t x_to = to.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y_to = to.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
entity_pos_t r2 = r.Square();
i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
i32 i0_from = xfloor_from;
i32 i1_from = xceil_from;
i32 i0_to = xfloor_to;
i32 i1_to = xceil_to;
for (i32 j = j0clamp; j <= j1clamp; ++j)
{
entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from;
entity_pos_t dy2_from = dy_from.Square();
while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2)
--i0_from;
while (i0_from < xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2)
++i0_from;
while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2)
++i1_from;
while (i1_from > xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2)
--i1_from;
entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to;
entity_pos_t dy2_to = dy_to.Square();
while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2)
--i0_to;
while (i0_to < xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2)
++i0_to;
while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2)
++i1_to;
while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
--i1_to;
#if DEBUG_RANGE_MANAGER_BOUNDS
if (i0_from <= i1_from)
{
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2);
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2);
}
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2);
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2);
if (i0_to <= i1_to)
{
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2);
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2);
}
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2);
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2);
#endif
// Check whether this strip moved at all
if (!(i0_to == i0_from && i1_to == i1_from))
{
i32 i0clamp_from = std::max(i0_from, 1);
i32 i1clamp_from = std::min(i1_from, m_TerrainVerticesPerSide-2);
i32 i0clamp_to = std::max(i0_to, 1);
i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2);
// Check whether one strip is negative width,
// and we can just add/remove the entire other strip
if (i1clamp_from < i0clamp_from)
{
- LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, countsData);
+ LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, counts);
}
else if (i1clamp_to < i0clamp_to)
{
- LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, countsData);
+ LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, counts);
}
else
{
// There are four possible regions of overlap between the two strips
// (remove before add, remove after add, add before remove, add after remove).
// Process each of the regions as its own strip.
// (If this produces negative-width strips then they'll just get ignored
// which is fine.)
// (If the strips don't actually overlap (which is very rare with normal unit
// movement speeds), the region between them will be both added and removed,
// so we have to do the add first to avoid overflowing to -1 and triggering
// assertion failures.)
- LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, countsData);
- LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, countsData);
- LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, countsData);
- LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, countsData);
+ LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, counts);
+ LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, counts);
+ LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, counts);
+ LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, counts);
}
}
}
}
void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper((u8)owner, visionRange, pos);
}
void SharingLosAdd(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero())
return;
for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
if (HasVisionSharing(visionSharing, i))
LosAdd(i, visionRange, pos);
}
void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper((u8)owner, visionRange, pos);
}
void SharingLosRemove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero())
return;
for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
if (HasVisionSharing(visionSharing, i))
LosRemove(i, visionRange, pos);
}
void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
if ((from - to).CompareLength(visionRange) > 0)
{
// If it's a very large move, then simply remove and add to the new position
LosUpdateHelper((u8)owner, visionRange, from);
LosUpdateHelper((u8)owner, visionRange, to);
}
else
// Otherwise use the version optimised for mostly-overlapping circles
LosUpdateHelperIncremental((u8)owner, visionRange, from, to);
}
void SharingLosMove(u16 visionSharing, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (visionRange.IsZero())
return;
for (player_id_t i = 1; i < MAX_LOS_PLAYER_ID+1; ++i)
if (HasVisionSharing(visionSharing, i))
LosMove(i, visionRange, from, to);
}
virtual u8 GetPercentMapExplored(player_id_t player) const
{
return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices;
}
virtual u8 GetUnionPercentMapExplored(const std::vector& players) const
{
u32 exploredVertices = 0;
std::vector::const_iterator playerIt;
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
{
if (LosIsOffWorld(i, j))
continue;
for (playerIt = players.begin(); playerIt != players.end(); ++playerIt)
- if (m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*((*playerIt)-1))))
+ if (m_LosState.get(i, j) & ((u32)LosState::EXPLORED << (2*((*playerIt)-1))))
{
exploredVertices += 1;
break;
}
}
return exploredVertices * 100 / m_TotalInworldVertices;
}
};
REGISTER_COMPONENT_TYPE(RangeManager)
#undef LOS_TILES_RATIO
#undef DEBUG_RANGE_MANAGER_BOUNDS
Index: ps/trunk/source/simulation2/components/CCmpSoundManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpSoundManager.cpp (revision 23768)
+++ ps/trunk/source/simulation2/components/CCmpSoundManager.cpp (revision 23769)
@@ -1,105 +1,105 @@
-/* Copyright (C) 2018 Wildfire Games.
+/* Copyright (C) 2020 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 "simulation2/system/Component.h"
#include "ICmpSoundManager.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpOwnership.h"
#include "soundmanager/ISoundManager.h"
class CCmpSoundManager : public ICmpSoundManager
{
public:
static void ClassInit(CComponentManager& UNUSED(componentManager) )
{
}
DEFAULT_COMPONENT_ALLOCATOR(SoundManager)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// Do nothing here - sounds are purely local, and don't need to be preserved across saved games etc
// (If we add music support in here then we might want to save the music state, though)
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void PlaySoundGroup(const std::wstring& name, entity_id_t source)
{
if (!g_SoundManager || (source == INVALID_ENTITY))
return;
int currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
CmpPtr cmpRangeManager(GetSystemEntity());
- if (!cmpRangeManager || (cmpRangeManager->GetLosVisibility(source, currentPlayer) != ICmpRangeManager::VIS_VISIBLE))
+ if (!cmpRangeManager || (cmpRangeManager->GetLosVisibility(source, currentPlayer) != LosVisibility::VISIBLE))
return;
CmpPtr cmpPosition(GetSimContext(), source);
if (!cmpPosition || !cmpPosition->IsInWorld())
return;
bool playerOwned = false;
CmpPtr cmpOwnership(GetSimContext(), source);
if (cmpOwnership)
playerOwned = cmpOwnership->GetOwner() == currentPlayer;
CVector3D sourcePos = CVector3D(cmpPosition->GetPosition());
g_SoundManager->PlayAsGroup(name, sourcePos, source, playerOwned);
}
virtual void PlaySoundGroupAtPosition(const std::wstring& name, const CFixedVector3D& sourcePos)
{
if (!g_SoundManager)
return;
g_SoundManager->PlayAsGroup(name, CVector3D(sourcePos), INVALID_ENTITY, false);
}
virtual void StopMusic()
{
if (!g_SoundManager)
return;
g_SoundManager->Pause(true);
}
};
REGISTER_COMPONENT_TYPE(SoundManager)
Index: ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp (revision 23768)
+++ ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp (revision 23769)
@@ -1,473 +1,473 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2020 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 "simulation2/system/Component.h"
#include "ICmpUnitRenderer.h"
#include "simulation2/MessageTypes.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpSelectable.h"
#include "ICmpVisibility.h"
#include "ICmpVisual.h"
#include "graphics/Frustum.h"
#include "graphics/ModelAbstract.h"
#include "graphics/ObjectEntry.h"
#include "graphics/Overlay.h"
#include "graphics/Unit.h"
#include "maths/BoundingSphere.h"
#include "maths/Matrix3D.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "renderer/RenderingOptions.h"
#include "renderer/Scene.h"
#include "tools/atlas/GameInterface/GameLoop.h"
/**
* Efficiently(ish) renders all the units in the world.
*
* The class maintains a list of all units that currently exist, and the data
* needed for frustum-culling them. To minimise the amount of work done per
* frame (despite a unit's interpolated position changing every frame), the
* culling data is only updated once per turn: we store the position at the
* start of the turn, and the position at the end of the turn, and assume the
* unit might be anywhere between those two points (linearly).
*
* (Note this is a slightly invalid assumption: units don't always move linearly,
* since their interpolated position depends on terrain and water. But over a
* single turn it's probably going to be a good enough approximation, and will
* only break for units that both start and end the turn off-screen.)
*
* We want to ignore rotation entirely, since it's a complex function of
* interpolated position and terrain. So we store a bounding sphere, which
* is rotation-independent, instead of a bounding box.
*/
class CCmpUnitRenderer : public ICmpUnitRenderer
{
public:
struct SUnit
{
CEntityHandle entity;
CUnit* actor;
int flags;
/**
* m_FrameNumber from when the model's transform was last updated.
* This is used to avoid recomputing it multiple times per frame
* if a model is visible in multiple cull groups.
*/
int lastTransformFrame;
/**
* Worst-case bounding shape, relative to position. Needs to account
* for all possible animations, orientations, etc.
*/
CBoundingSphere boundsApprox;
/**
* Cached LOS visibility status.
*/
- ICmpRangeManager::ELosVisibility visibility;
+ LosVisibility visibility;
bool visibilityDirty;
/**
* Whether the unit has a valid position. If false, pos0 and pos1
* are meaningless.
*/
bool inWorld;
/**
* World-space positions to interpolate between.
*/
CVector3D pos0;
CVector3D pos1;
/**
* Bounds encompassing the unit's bounds when it is anywhere between
* pos0 and pos1.
*/
CBoundingSphere sweptBounds;
/**
* For debug overlay.
*/
bool culled;
};
std::vector m_Units;
std::vector m_UnitTagsFree;
int m_FrameNumber;
float m_FrameOffset;
bool m_EnableDebugOverlays;
std::vector m_DebugSpheres;
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(UnitRenderer)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_FrameNumber = 0;
m_FrameOffset = 0.0f;
m_EnableDebugOverlays = false;
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_TurnStart:
{
TurnStart();
break;
}
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
break;
}
}
}
SUnit* LookupUnit(tag_t tag)
{
if (tag.n < 1 || tag.n - 1 >= m_Units.size())
return NULL;
return &m_Units[tag.n - 1];
}
virtual tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags)
{
ENSURE(actor != NULL);
tag_t tag;
if (!m_UnitTagsFree.empty())
{
tag = m_UnitTagsFree.back();
m_UnitTagsFree.pop_back();
}
else
{
m_Units.push_back(SUnit());
tag.n = m_Units.size();
}
SUnit* unit = LookupUnit(tag);
unit->entity = entity;
unit->actor = actor;
unit->lastTransformFrame = -1;
unit->flags = flags;
unit->boundsApprox = boundsApprox;
unit->inWorld = false;
unit->visibilityDirty = true;
unit->pos0 = unit->pos1 = CVector3D();
return tag;
}
virtual void RemoveUnit(tag_t tag)
{
SUnit* unit = LookupUnit(tag);
unit->actor = NULL;
unit->inWorld = false;
m_UnitTagsFree.push_back(tag);
}
void RecomputeSweptBounds(SUnit* unit)
{
// Compute the bounding sphere of the capsule formed by
// sweeping boundsApprox from pos0 to pos1
CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter();
float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius();
unit->sweptBounds = CBoundingSphere(mid, radius);
}
virtual void UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox)
{
SUnit* unit = LookupUnit(tag);
unit->actor = actor;
unit->boundsApprox = boundsApprox;
RecomputeSweptBounds(unit);
}
virtual void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1)
{
SUnit* unit = LookupUnit(tag);
unit->inWorld = inWorld;
unit->pos0 = pos0;
unit->pos1 = pos1;
unit->visibilityDirty = true;
RecomputeSweptBounds(unit);
}
void TurnStart();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
void UpdateVisibility(SUnit& unit) const;
virtual float GetFrameOffset() const
{
return m_FrameOffset;
}
virtual void SetDebugOverlay(bool enabled)
{
m_EnableDebugOverlays = enabled;
}
virtual void PickAllEntitiesAtPoint(std::vector >& outEntities, const CVector3D& origin, const CVector3D& dir, bool allowEditorSelectables) const
{
// First, make a rough test with the worst-case bounding boxes to pick all
// entities/models that could possibly be hit by the ray.
std::vector candidates;
for (const SUnit& unit : m_Units)
{
if (!unit.actor || !unit.inWorld)
continue;
if (unit.sweptBounds.RayIntersect(origin, dir))
candidates.push_back(&unit);
}
// Now make a more precise test to get rid of the remaining false positives
float tmin, tmax;
CVector3D center;
for (size_t i = 0; i< candidates.size(); ++i)
{
const SUnit& unit = *candidates[i];
CmpPtr cmpVisual(unit.entity);
if (!cmpVisual)
continue;
CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox();
if (selectionBox.IsEmpty())
{
if (!allowEditorSelectables)
continue;
// Fall back to using old AABB selection method for decals
// see: http://trac.wildfiregames.com/ticket/1032
// Decals are flat objects without a selectionShape defined,
// but they should still be selectable in the editor to move them
// around or delete them after they are placed.
// Check campaigns/labels/ in the Actors tab of atlas for examples.
CBoundingBoxAligned aABBox = cmpVisual->GetBounds();
if (aABBox.IsEmpty())
continue;
if (!aABBox.RayIntersect(origin, dir, tmin, tmax))
continue;
aABBox.GetCenter(center);
}
else
{
if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
continue;
center = selectionBox.m_Center;
}
outEntities.emplace_back(unit.entity, center);
}
}
};
void CCmpUnitRenderer::TurnStart()
{
PROFILE3("UnitRenderer::TurnStart");
// Assume units have stopped moving after the previous turn. If that assumption is not
// correct, we will get a UpdateUnitPos to tell us about its movement in the new turn.
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
unit.pos0 = unit.pos1;
unit.sweptBounds = CBoundingSphere(unit.pos1, unit.boundsApprox.GetRadius());
// Visibility must be recomputed on the first frame during this turn
unit.visibilityDirty = true;
}
}
void CCmpUnitRenderer::Interpolate(float frameTime, float frameOffset)
{
PROFILE3("UnitRenderer::Interpolate");
++m_FrameNumber;
m_FrameOffset = frameOffset;
// TODO: we shouldn't update all the animations etc for units that are off-screen
// (but need to be careful about e.g. sounds triggered by animations of off-screen
// units)
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (unit.actor)
unit.actor->UpdateModel(frameTime);
}
m_DebugSpheres.clear();
if (m_EnableDebugOverlays)
{
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (!(unit.actor && unit.inWorld))
continue;
SOverlaySphere sphere;
sphere.m_Center = unit.sweptBounds.GetCenter();
sphere.m_Radius = unit.sweptBounds.GetRadius();
if (unit.culled)
sphere.m_Color = CColor(1.0f, 0.5f, 0.5f, 0.5f);
else
sphere.m_Color = CColor(0.5f, 0.5f, 1.0f, 0.5f);
m_DebugSpheres.push_back(sphere);
}
}
}
void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
// TODO: need a coarse culling pass based on some kind of spatial data
// structure - that's the main point of this design. Once we've got a
// rough list of possibly-visible units, then we can do the more precise
// culling. (And once it's cheap enough, we can do multiple culling passes
// per frame - one for shadow generation, one for water reflections, etc.)
PROFILE3("UnitRenderer::RenderSubmit");
for (size_t i = 0; i < m_Units.size(); ++i)
{
SUnit& unit = m_Units[i];
unit.culled = true;
if (!unit.actor)
continue;
if (unit.visibilityDirty)
UpdateVisibility(unit);
- if (unit.visibility == ICmpRangeManager::VIS_HIDDEN)
+ if (unit.visibility == LosVisibility::HIDDEN)
continue;
if (!g_AtlasGameLoop->running && !g_RenderingOptions.GetRenderActors() && (unit.flags & ACTOR_ONLY))
continue;
if (!g_AtlasGameLoop->running && (unit.flags & VISIBLE_IN_ATLAS_ONLY))
continue;
if (culling && !frustum.IsSphereVisible(unit.sweptBounds.GetCenter(), unit.sweptBounds.GetRadius()))
continue;
unit.culled = false;
CModelAbstract& unitModel = unit.actor->GetModel();
if (unit.lastTransformFrame != m_FrameNumber)
{
CmpPtr cmpPosition(unit.entity);
if (!cmpPosition)
continue;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset));
unitModel.SetTransform(transform);
unit.lastTransformFrame = m_FrameNumber;
}
if (culling && !frustum.IsBoxVisible(unitModel.GetWorldBoundsRec()))
continue;
collector.SubmitRecursive(&unitModel);
}
for (size_t i = 0; i < m_DebugSpheres.size(); ++i)
collector.Submit(&m_DebugSpheres[i]);
}
void CCmpUnitRenderer::UpdateVisibility(SUnit& unit) const
{
if (unit.inWorld)
{
// The 'always visible' flag means we should always render the unit
// (regardless of whether the LOS system thinks it's visible)
CmpPtr cmpVisibility(unit.entity);
if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
- unit.visibility = ICmpRangeManager::VIS_VISIBLE;
+ unit.visibility = LosVisibility::VISIBLE;
else
{
CmpPtr cmpRangeManager(GetSystemEntity());
unit.visibility = cmpRangeManager->GetLosVisibility(unit.entity,
GetSimContext().GetCurrentDisplayedPlayer());
}
}
else
- unit.visibility = ICmpRangeManager::VIS_HIDDEN;
+ unit.visibility = LosVisibility::HIDDEN;
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr cmpSelectable(unit.entity);
if (cmpSelectable)
- cmpSelectable->SetVisibility(unit.visibility != ICmpRangeManager::VIS_HIDDEN);
+ cmpSelectable->SetVisibility(unit.visibility != LosVisibility::HIDDEN);
unit.visibilityDirty = false;
}
REGISTER_COMPONENT_TYPE(UnitRenderer)
Index: ps/trunk/source/simulation2/components/ICmpRangeManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpRangeManager.cpp (revision 23768)
+++ ps/trunk/source/simulation2/components/ICmpRangeManager.cpp (revision 23769)
@@ -1,77 +1,77 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2020 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 "ICmpRangeManager.h"
#include "simulation2/system/InterfaceScripted.h"
namespace {
- std::string VisibilityToString(ICmpRangeManager::ELosVisibility visibility)
+ std::string VisibilityToString(LosVisibility visibility)
{
switch (visibility)
{
- case ICmpRangeManager::VIS_HIDDEN: return "hidden";
- case ICmpRangeManager::VIS_FOGGED: return "fogged";
- case ICmpRangeManager::VIS_VISIBLE: return "visible";
+ case LosVisibility::HIDDEN: return "hidden";
+ case LosVisibility::FOGGED: return "fogged";
+ case LosVisibility::VISIBLE: return "visible";
default: return "error"; // should never happen
}
}
}
std::string ICmpRangeManager::GetLosVisibility_wrapper(entity_id_t ent, int player) const
{
return VisibilityToString(GetLosVisibility(ent, player));
}
std::string ICmpRangeManager::GetLosVisibilityPosition_wrapper(entity_pos_t x, entity_pos_t z, int player) const
{
return VisibilityToString(GetLosVisibilityPosition(x, z, player));
}
BEGIN_INTERFACE_WRAPPER(RangeManager)
DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector, int)
DEFINE_INTERFACE_METHOD_5("ExecuteQueryAroundPos", std::vector, ICmpRangeManager, ExecuteQueryAroundPos, CFixedVector2D, entity_pos_t, entity_pos_t, std::vector, int)
DEFINE_INTERFACE_METHOD_6("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector, int, u8)
DEFINE_INTERFACE_METHOD_7("CreateActiveParabolicQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveParabolicQuery, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t, std::vector, int, u8)
DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_CONST_1("IsActiveQueryEnabled", bool, ICmpRangeManager, IsActiveQueryEnabled, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_3("SetEntityFlag", void, ICmpRangeManager, SetEntityFlag, entity_id_t, std::string, bool)
DEFINE_INTERFACE_METHOD_CONST_1("GetEntityFlagMask", u8, ICmpRangeManager, GetEntityFlagMask, std::string)
DEFINE_INTERFACE_METHOD_CONST_1("GetEntitiesByPlayer", std::vector, ICmpRangeManager, GetEntitiesByPlayer, player_id_t)
DEFINE_INTERFACE_METHOD_CONST_0("GetNonGaiaEntities", std::vector, ICmpRangeManager, GetNonGaiaEntities)
DEFINE_INTERFACE_METHOD_CONST_0("GetGaiaAndNonGaiaEntities", std::vector, ICmpRangeManager, GetGaiaAndNonGaiaEntities)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
DEFINE_INTERFACE_METHOD_1("ExploreAllTiles", void, ICmpRangeManager, ExploreAllTiles, player_id_t)
DEFINE_INTERFACE_METHOD_0("ExploreTerritories", void, ICmpRangeManager, ExploreTerritories)
DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool)
DEFINE_INTERFACE_METHOD_CONST_1("GetLosRevealAll", bool, ICmpRangeManager, GetLosRevealAll, player_id_t)
DEFINE_INTERFACE_METHOD_CONST_5("GetElevationAdaptedRange", entity_pos_t, ICmpRangeManager, GetElevationAdaptedRange, CFixedVector3D, CFixedVector3D, entity_pos_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_2("ActivateScriptedVisibility", void, ICmpRangeManager, ActivateScriptedVisibility, entity_id_t, bool)
DEFINE_INTERFACE_METHOD_CONST_2("GetLosVisibility", std::string, ICmpRangeManager, GetLosVisibility_wrapper, entity_id_t, player_id_t)
DEFINE_INTERFACE_METHOD_CONST_3("GetLosVisibilityPosition", std::string, ICmpRangeManager, GetLosVisibilityPosition_wrapper, entity_pos_t, entity_pos_t, player_id_t)
DEFINE_INTERFACE_METHOD_1("RequestVisibilityUpdate", void, ICmpRangeManager, RequestVisibilityUpdate, entity_id_t)
DEFINE_INTERFACE_METHOD_1("SetLosCircular", void, ICmpRangeManager, SetLosCircular, bool)
DEFINE_INTERFACE_METHOD_CONST_0("GetLosCircular", bool, ICmpRangeManager, GetLosCircular)
DEFINE_INTERFACE_METHOD_2("SetSharedLos", void, ICmpRangeManager, SetSharedLos, player_id_t, std::vector)
DEFINE_INTERFACE_METHOD_CONST_1("GetPercentMapExplored", u8, ICmpRangeManager, GetPercentMapExplored, player_id_t)
DEFINE_INTERFACE_METHOD_CONST_1("GetUnionPercentMapExplored", u8, ICmpRangeManager, GetUnionPercentMapExplored, std::vector