Index: source/graphics/Camera.cpp
===================================================================
--- source/graphics/Camera.cpp (revision 24796)
+++ source/graphics/Camera.cpp (working copy)
@@ -1,436 +1,436 @@
/* Copyright (C) 2021 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 .
*/
/*
* CCamera holds a view and a projection matrix. It also has a frustum
* which can be used to cull objects for rendering.
*/
#include "precompiled.h"
#include "Camera.h"
#include "graphics/HFTracer.h"
#include "graphics/Terrain.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "maths/Vector4D.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
CCamera::CCamera()
: m_NearPlane(0.0f), m_FarPlane(0.0f), m_FOV(0.0f), m_ProjType(CUSTOM)
{
// Set viewport to something anything should handle, but should be initialised
// to window size before use.
m_ViewPort.m_X = 0;
m_ViewPort.m_Y = 0;
m_ViewPort.m_Width = 800;
m_ViewPort.m_Height = 600;
}
CCamera::~CCamera() = default;
void CCamera::SetProjection(const CMatrix3D& matrix)
{
m_ProjType = CUSTOM;
m_ProjMat = matrix;
}
void CCamera::SetProjectionFromCamera(const CCamera& camera)
{
m_ProjType = camera.m_ProjType;
m_NearPlane = camera.m_NearPlane;
m_FarPlane = camera.m_FarPlane;
if (m_ProjType == PERSPECTIVE)
{
m_FOV = camera.m_FOV;
}
m_ProjMat = camera.m_ProjMat;
}
void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov)
{
m_ProjType = PERSPECTIVE;
m_NearPlane = nearp;
m_FarPlane = farp;
m_FOV = fov;
m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane);
}
// Updates the frustum planes. Should be called
// everytime the view or projection matrices are
// altered.
void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor)
{
CMatrix3D MatFinal;
CMatrix3D MatView;
m_Orientation.GetInverse(MatView);
MatFinal = m_ProjMat * MatView;
m_ViewFrustum.SetNumPlanes(6);
// get the RIGHT plane
- m_ViewFrustum.m_aPlanes[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11;
- m_ViewFrustum.m_aPlanes[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12;
- m_ViewFrustum.m_aPlanes[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13;
- m_ViewFrustum.m_aPlanes[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14;
+ m_ViewFrustum.m_aPlanes[0].m_Norm.Xref() = scissor[1].getX()*MatFinal._41 - MatFinal._11;
+ m_ViewFrustum.m_aPlanes[0].m_Norm.Yref() = scissor[1].getX()*MatFinal._42 - MatFinal._12;
+ m_ViewFrustum.m_aPlanes[0].m_Norm.Zref() = scissor[1].getX()*MatFinal._43 - MatFinal._13;
+ m_ViewFrustum.m_aPlanes[0].m_Dist = scissor[1].getX()*MatFinal._44 - MatFinal._14;
// get the LEFT plane
- m_ViewFrustum.m_aPlanes[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11;
- m_ViewFrustum.m_aPlanes[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12;
- m_ViewFrustum.m_aPlanes[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13;
- m_ViewFrustum.m_aPlanes[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14;
+ m_ViewFrustum.m_aPlanes[1].m_Norm.Xref() = -scissor[0].getX()*MatFinal._41 + MatFinal._11;
+ m_ViewFrustum.m_aPlanes[1].m_Norm.Yref() = -scissor[0].getX()*MatFinal._42 + MatFinal._12;
+ m_ViewFrustum.m_aPlanes[1].m_Norm.Zref() = -scissor[0].getX()*MatFinal._43 + MatFinal._13;
+ m_ViewFrustum.m_aPlanes[1].m_Dist = -scissor[0].getX()*MatFinal._44 + MatFinal._14;
// get the BOTTOM plane
- m_ViewFrustum.m_aPlanes[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21;
- m_ViewFrustum.m_aPlanes[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22;
- m_ViewFrustum.m_aPlanes[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23;
- m_ViewFrustum.m_aPlanes[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24;
+ m_ViewFrustum.m_aPlanes[2].m_Norm.Xref() = -scissor[0].getY()*MatFinal._41 + MatFinal._21;
+ m_ViewFrustum.m_aPlanes[2].m_Norm.Yref() = -scissor[0].getY()*MatFinal._42 + MatFinal._22;
+ m_ViewFrustum.m_aPlanes[2].m_Norm.Zref() = -scissor[0].getY()*MatFinal._43 + MatFinal._23;
+ m_ViewFrustum.m_aPlanes[2].m_Dist = -scissor[0].getY()*MatFinal._44 + MatFinal._24;
// get the TOP plane
- m_ViewFrustum.m_aPlanes[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21;
- m_ViewFrustum.m_aPlanes[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22;
- m_ViewFrustum.m_aPlanes[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23;
- m_ViewFrustum.m_aPlanes[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24;
+ m_ViewFrustum.m_aPlanes[3].m_Norm.Xref() = scissor[1].getY()*MatFinal._41 - MatFinal._21;
+ m_ViewFrustum.m_aPlanes[3].m_Norm.Yref() = scissor[1].getY()*MatFinal._42 - MatFinal._22;
+ m_ViewFrustum.m_aPlanes[3].m_Norm.Zref() = scissor[1].getY()*MatFinal._43 - MatFinal._23;
+ m_ViewFrustum.m_aPlanes[3].m_Dist = scissor[1].getY()*MatFinal._44 - MatFinal._24;
// get the FAR plane
- m_ViewFrustum.m_aPlanes[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31;
- m_ViewFrustum.m_aPlanes[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32;
- m_ViewFrustum.m_aPlanes[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33;
- m_ViewFrustum.m_aPlanes[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34;
+ m_ViewFrustum.m_aPlanes[4].m_Norm.Xref() = scissor[1].getZ()*MatFinal._41 - MatFinal._31;
+ m_ViewFrustum.m_aPlanes[4].m_Norm.Yref() = scissor[1].getZ()*MatFinal._42 - MatFinal._32;
+ m_ViewFrustum.m_aPlanes[4].m_Norm.Zref() = scissor[1].getZ()*MatFinal._43 - MatFinal._33;
+ m_ViewFrustum.m_aPlanes[4].m_Dist = scissor[1].getZ()*MatFinal._44 - MatFinal._34;
// get the NEAR plane
- m_ViewFrustum.m_aPlanes[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31;
- m_ViewFrustum.m_aPlanes[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32;
- m_ViewFrustum.m_aPlanes[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33;
- m_ViewFrustum.m_aPlanes[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34;
+ m_ViewFrustum.m_aPlanes[5].m_Norm.Xref() = -scissor[0].getZ()*MatFinal._41 + MatFinal._31;
+ m_ViewFrustum.m_aPlanes[5].m_Norm.Yref() = -scissor[0].getZ()*MatFinal._42 + MatFinal._32;
+ m_ViewFrustum.m_aPlanes[5].m_Norm.Zref() = -scissor[0].getZ()*MatFinal._43 + MatFinal._33;
+ m_ViewFrustum.m_aPlanes[5].m_Dist = -scissor[0].getZ()*MatFinal._44 + MatFinal._34;
for (size_t i = 0; i < 6; ++i)
m_ViewFrustum.m_aPlanes[i].Normalize();
}
void CCamera::ClipFrustum(const CPlane& clipPlane)
{
CPlane normClipPlane = clipPlane;
normClipPlane.Normalize();
m_ViewFrustum.AddPlane(normClipPlane);
}
void CCamera::SetViewPort(const SViewPort& viewport)
{
m_ViewPort.m_X = viewport.m_X;
m_ViewPort.m_Y = viewport.m_Y;
m_ViewPort.m_Width = viewport.m_Width;
m_ViewPort.m_Height = viewport.m_Height;
}
float CCamera::GetAspectRatio() const
{
return static_cast(m_ViewPort.m_Width) / static_cast(m_ViewPort.m_Height);
}
void CCamera::GetViewQuad(float dist, Quad& quad) const
{
ENSURE(m_ProjType == PERSPECTIVE);
const float y = dist * tanf(m_FOV * 0.5f);
const float x = y * GetAspectRatio();
- quad[0].X = -x;
- quad[0].Y = -y;
- quad[0].Z = dist;
- quad[1].X = x;
- quad[1].Y = -y;
- quad[1].Z = dist;
- quad[2].X = x;
- quad[2].Y = y;
- quad[2].Z = dist;
- quad[3].X = -x;
- quad[3].Y = y;
- quad[3].Z = dist;
+ quad[0].Xref() = -x;
+ quad[0].Yref() = -y;
+ quad[0].Zref() = dist;
+ quad[1].Xref() = x;
+ quad[1].Yref() = -y;
+ quad[1].Zref() = dist;
+ quad[2].Xref() = x;
+ quad[2].Yref() = y;
+ quad[2].Zref() = dist;
+ quad[3].Xref() = -x;
+ quad[3].Yref() = y;
+ quad[3].Zref() = dist;
}
void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const
{
// Coordinates relative to the camera plane.
const float dx = static_cast(px) / g_Renderer.GetWidth();
const float dy = 1.0f - static_cast(py) / g_Renderer.GetHeight();
Quad points;
GetViewQuad(m_FarPlane, points);
// Transform from camera space to world space.
for (CVector3D& point : points)
point = m_Orientation.Transform(point);
// Get world space position of mouse point at the far clipping plane.
CVector3D basisX = points[1] - points[0];
CVector3D basisY = points[3] - points[0];
CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy);
origin = m_Orientation.GetTranslation();
// Build direction for the camera origin to the target point.
dir = targetPoint - origin;
dir.Normalize();
}
void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const
{
CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse();
- CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f));
+ CVector4D screenspace = transform.Transform(CVector4D(world.getX(), world.getY(), world.getZ(), 1.0f));
x = screenspace.X / screenspace.W;
y = screenspace.Y / screenspace.W;
x = (x + 1) * 0.5f * g_Renderer.GetWidth();
y = (1 - y) * 0.5f * g_Renderer.GetHeight();
}
CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const
{
CHFTracer tracer(g_Game->GetWorld()->GetTerrain());
int x, z;
CVector3D origin, dir, delta, terrainPoint, waterPoint;
BuildCameraRay(px, py, origin, dir);
bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint);
if (!aboveWater)
{
if (gotTerrain)
return terrainPoint;
// Off the edge of the world?
// Work out where it /would/ hit, if the map were extended out to infinity with average height.
return GetWorldCoordinates(px, py, 50.0f);
}
CPlane plane;
plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal
CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane
bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint );
// Clamp the water intersection to within the map's bounds, so that
// we'll always return a valid position on the map
ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
if (gotWater)
{
- waterPoint.X = Clamp(waterPoint.X, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
- waterPoint.Z = Clamp(waterPoint.Z, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
+ waterPoint.Xref() = Clamp(waterPoint.getX(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
+ waterPoint.Zref() = Clamp(waterPoint.getZ(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
}
if (gotTerrain)
{
if (gotWater)
{
// Intersecting both heightmap and water plane; choose the closest of those
if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared())
return terrainPoint;
else
return waterPoint;
}
else
{
// Intersecting heightmap but parallel to water plane
return terrainPoint;
}
}
else
{
if (gotWater)
{
// Only intersecting water plane
return waterPoint;
}
else
{
// Not intersecting terrain or water; just return 0,0,0.
return CVector3D(0.f, 0.f, 0.f);
}
}
}
CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const
{
CPlane plane;
plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h
CVector3D origin, dir, delta, currentTarget;
BuildCameraRay(px, py, origin, dir);
if (plane.FindRayIntersection(origin, dir, ¤tTarget))
return currentTarget;
// No intersection with the infinite plane - nothing sensible can be returned,
// so just choose an arbitrary point on the plane
return CVector3D(0.f, h, 0.f);
}
CVector3D CCamera::GetFocus() const
{
// Basically the same as GetWorldCoordinates
CHFTracer tracer(g_Game->GetWorld()->GetTerrain());
int x, z;
CVector3D origin, dir, delta, terrainPoint, waterPoint;
origin = m_Orientation.GetTranslation();
dir = m_Orientation.GetIn();
bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint);
CPlane plane;
plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal
CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane
bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint );
// Clamp the water intersection to within the map's bounds, so that
// we'll always return a valid position on the map
ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
if (gotWater)
{
- waterPoint.X = Clamp(waterPoint.X, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
- waterPoint.Z = Clamp(waterPoint.Z, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
+ waterPoint.Xref() = Clamp(waterPoint.getX(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
+ waterPoint.Zref() = Clamp(waterPoint.getZ(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE));
}
if (gotTerrain)
{
if (gotWater)
{
// Intersecting both heightmap and water plane; choose the closest of those
if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared())
return terrainPoint;
else
return waterPoint;
}
else
{
// Intersecting heightmap but parallel to water plane
return terrainPoint;
}
}
else
{
if (gotWater)
{
// Only intersecting water plane
return waterPoint;
}
else
{
// Not intersecting terrain or water; just return 0,0,0.
return CVector3D(0.f, 0.f, 0.f);
}
}
}
void CCamera::LookAt(const CVector3D& camera, const CVector3D& target, const CVector3D& up)
{
CVector3D delta = target - camera;
LookAlong(camera, delta, up);
}
void CCamera::LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up)
{
orientation.Normalize();
up.Normalize();
CVector3D s = orientation.Cross(up);
- m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X;
- m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y;
- m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z;
+ m_Orientation._11 = -s.getX(); m_Orientation._12 = up.getX(); m_Orientation._13 = orientation.getX(); m_Orientation._14 = camera.getX();
+ m_Orientation._21 = -s.getY(); m_Orientation._22 = up.getY(); m_Orientation._23 = orientation.getY(); m_Orientation._24 = camera.getY();
+ m_Orientation._31 = -s.getZ(); m_Orientation._32 = up.getZ(); m_Orientation._33 = orientation.getZ(); m_Orientation._34 = camera.getZ();
m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f;
}
// Render the camera's frustum
void CCamera::Render(int intermediates) const
{
#if CONFIG2_GLES
#warning TODO: implement camera frustum for GLES
#else
Quad nearPoints;
Quad farPoints;
GetViewQuad(m_NearPlane, nearPoints);
GetViewQuad(m_FarPlane, farPoints);
for(int i = 0; i < 4; i++)
{
nearPoints[i] = m_Orientation.Transform(nearPoints[i]);
farPoints[i] = m_Orientation.Transform(farPoints[i]);
}
// near plane
glBegin(GL_POLYGON);
- glVertex3fv(&nearPoints[0].X);
- glVertex3fv(&nearPoints[1].X);
- glVertex3fv(&nearPoints[2].X);
- glVertex3fv(&nearPoints[3].X);
+ glVertex3fv(&nearPoints[0].Xref());
+ glVertex3fv(&nearPoints[1].Xref());
+ glVertex3fv(&nearPoints[2].Xref());
+ glVertex3fv(&nearPoints[3].Xref());
glEnd();
// far plane
glBegin(GL_POLYGON);
- glVertex3fv(&farPoints[0].X);
- glVertex3fv(&farPoints[1].X);
- glVertex3fv(&farPoints[2].X);
- glVertex3fv(&farPoints[3].X);
+ glVertex3fv(&farPoints[0].Xref());
+ glVertex3fv(&farPoints[1].Xref());
+ glVertex3fv(&farPoints[2].Xref());
+ glVertex3fv(&farPoints[3].Xref());
glEnd();
// connection lines
glBegin(GL_QUAD_STRIP);
- glVertex3fv(&nearPoints[0].X);
- glVertex3fv(&farPoints[0].X);
- glVertex3fv(&nearPoints[1].X);
- glVertex3fv(&farPoints[1].X);
- glVertex3fv(&nearPoints[2].X);
- glVertex3fv(&farPoints[2].X);
- glVertex3fv(&nearPoints[3].X);
- glVertex3fv(&farPoints[3].X);
- glVertex3fv(&nearPoints[0].X);
- glVertex3fv(&farPoints[0].X);
+ glVertex3fv(&nearPoints[0].Xref());
+ glVertex3fv(&farPoints[0].Xref());
+ glVertex3fv(&nearPoints[1].Xref());
+ glVertex3fv(&farPoints[1].Xref());
+ glVertex3fv(&nearPoints[2].Xref());
+ glVertex3fv(&farPoints[2].Xref());
+ glVertex3fv(&nearPoints[3].Xref());
+ glVertex3fv(&farPoints[3].Xref());
+ glVertex3fv(&nearPoints[0].Xref());
+ glVertex3fv(&farPoints[0].Xref());
glEnd();
// intermediate planes
CVector3D intermediatePoints[4];
for(int i = 0; i < intermediates; ++i)
{
float t = (i+1.0)/(intermediates+1.0);
for(int j = 0; j < 4; ++j)
intermediatePoints[j] = nearPoints[j]*t + farPoints[j]*(1.0-t);
glBegin(GL_POLYGON);
- glVertex3fv(&intermediatePoints[0].X);
- glVertex3fv(&intermediatePoints[1].X);
- glVertex3fv(&intermediatePoints[2].X);
- glVertex3fv(&intermediatePoints[3].X);
+ glVertex3fv(&intermediatePoints[0].Xref());
+ glVertex3fv(&intermediatePoints[1].Xref());
+ glVertex3fv(&intermediatePoints[2].Xref());
+ glVertex3fv(&intermediatePoints[3].Xref());
glEnd();
}
#endif
}
Index: source/graphics/CameraController.cpp
===================================================================
--- source/graphics/CameraController.cpp (revision 24796)
+++ source/graphics/CameraController.cpp (working copy)
@@ -1,719 +1,719 @@
/* Copyright (C) 2021 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"
#include "simulation2/helpers/Los.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()) == 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.m_Orientation.Translate(pos.getX(), pos.getY() + height, pos.getZ());
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);
+ m_PosX.AddSmoothly(delta.getX());
+ m_PosY.AddSmoothly(delta.getY());
+ m_PosZ.AddSmoothly(delta.getZ());
}
}
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);
+ m_PosX.AddSmoothly(forwards.getX() * zoomDelta);
+ m_PosY.AddSmoothly(forwards.getY() * zoomDelta);
+ m_PosZ.AddSmoothly(forwards.getZ() * 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);
+ CVector3D center(r, desiredPivot.getY(), 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);
+ desiredPivot.Xref() = Clamp(desiredPivot.getX(), pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
+ desiredPivot.Zref() = Clamp(desiredPivot.getZ(), 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.SetValueSmoothly(desiredPivot.getX() + delta.getX());
+ m_PosZ.SetValueSmoothly(desiredPivot.getZ() + delta.getZ());
}
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);
+ m_PosX.Add(d.getX());
+ m_PosY.Add(d.getY());
+ m_PosZ.Add(d.getZ());
}
}
// 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);
+ m_PosX.Add(d.getX());
+ m_PosY.Add(d.getY());
+ m_PosZ.Add(d.getZ());
}
}
/* 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_PosX.SetValue(pos.getX());
+ m_PosY.SetValue(pos.getY());
+ m_PosZ.SetValue(pos.getZ());
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());
+ m_PosX.SetValueSmoothly(delta.getX() + m_PosX.GetValue());
+ m_PosZ.SetValueSmoothly(delta.getZ() + 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_PosX.SetValue(target.getX() - delta.getX());
+ m_PosY.SetValue(target.getY() - delta.getY());
+ m_PosZ.SetValue(target.getZ() - delta.getZ());
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 radius = forwards.HorizontalLength() * 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_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.getX(), nearPoint.getZ()),
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);
+ const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.getX(), nearPoint.getZ(), near_radius);
+ const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.getX(), pivotPoint.getZ(), 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);
+ const float pivot_height = -forwards.getY() * (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 height = (nearPoint.getY() - 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: source/graphics/CinemaManager.cpp
===================================================================
--- source/graphics/CinemaManager.cpp (revision 24796)
+++ source/graphics/CinemaManager.cpp (working copy)
@@ -1,256 +1,256 @@
/* Copyright (C) 2021 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
#include
#include "graphics/CinemaManager.h"
-
+#include "graphics/Color.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "maths/Quaternion.h"
#include "maths/Vector3D.h"
#include "maths/Vector4D.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/Hotkey.h"
#include "ps/World.h"
#include "simulation2/components/ICmpCinemaManager.h"
#include "simulation2/components/ICmpOverlayRenderer.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpTerritoryManager.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/system/ComponentManager.h"
#include "simulation2/Simulation2.h"
#include "renderer/Renderer.h"
CCinemaManager::CCinemaManager()
: m_DrawPaths(false)
{
}
void CCinemaManager::Update(const float deltaRealTime) const
{
CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity());
if (!cmpCinemaManager)
return;
if (IsPlaying())
cmpCinemaManager->PlayQueue(deltaRealTime, g_Game->GetView()->GetCamera());
}
void CCinemaManager::Render() const
{
if (IsEnabled())
DrawBars();
else if (m_DrawPaths)
DrawPaths();
}
void CCinemaManager::DrawPaths() const
{
CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity());
if (!cmpCinemaManager)
return;
for (const std::pair& p : cmpCinemaManager->GetPaths())
{
- DrawSpline(p.second, CColor(0.2f, 0.2f, 1.f, 0.9f), 128, true);
- DrawNodes(p.second, CColor(0.1f, 1.f, 0.f, 1.f));
+ DrawSpline(p.second, RGBAcolor(0.2f, 0.2f, 1.f, 0.9f), 128, true);
+ DrawNodes(p.second, RGBAcolor(0.1f, 1.f, 0.f, 1.f));
if (p.second.GetTargetSpline().GetAllNodes().empty())
continue;
- DrawSpline(p.second.GetTargetSpline(), CColor(1.f, 0.3f, 0.4f, 0.9f), 128, true);
- DrawNodes(p.second.GetTargetSpline(), CColor(1.f, 0.1f, 0.f, 1.f));
+ DrawSpline(p.second.GetTargetSpline(), RGBAcolor(1.f, 0.3f, 0.4f, 0.9f), 128, true);
+ DrawNodes(p.second.GetTargetSpline(), RGBAcolor(1.f, 0.1f, 0.f, 1.f));
}
}
-void CCinemaManager::DrawSpline(const RNSpline& spline, const CColor& splineColor, int smoothness, bool lines) const
+void CCinemaManager::DrawSpline(const RNSpline& spline, const RGBAcolor& splineColor, int smoothness, bool lines) const
{
if (spline.GetAllNodes().size() < 2)
return;
if (spline.GetAllNodes().size() == 2 && lines)
smoothness = 2;
float start = spline.MaxDistance.ToFloat() / smoothness;
float time = 0;
#if CONFIG2_GLES
#warning TODO : implement CCinemaPath on GLES
#else
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
- glColor4f(splineColor.r, splineColor.g, splineColor.b, splineColor.a);
+ glColor4f(splineColor.getR(), splineColor.getG(), splineColor.getB(), splineColor.getA());
if (lines)
{
glLineWidth(1.8f);
glEnable(GL_LINE_SMOOTH);
glBegin(GL_LINE_STRIP);
for (int i = 0; i <= smoothness; ++i)
{
time = start * i / spline.MaxDistance.ToFloat();
CVector3D tmp = spline.GetPosition(time);
- glVertex3f(tmp.X, tmp.Y, tmp.Z);
+ glVertex3f(tmp.getX(), tmp.getY(), tmp.getZ());
}
glEnd();
// Height indicator
if (g_Game && g_Game->GetWorld() && g_Game->GetWorld()->GetTerrain())
{
glLineWidth(1.1f);
glBegin(GL_LINES);
for (int i = 0; i <= smoothness; ++i)
{
time = start * i / spline.MaxDistance.ToFloat();
CVector3D tmp = spline.GetPosition(time);
- float groundY = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(tmp.X, tmp.Z);
- glVertex3f(tmp.X, tmp.Y, tmp.Z);
- glVertex3f(tmp.X, groundY, tmp.Z);
+ float groundY = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(tmp.getX(), tmp.getZ());
+ glVertex3f(tmp.getX(), tmp.getY(), tmp.getZ());
+ glVertex3f(tmp.getX(), groundY, tmp.getZ());
}
glEnd();
}
glDisable(GL_LINE_SMOOTH);
glLineWidth(1.0f);
}
else
{
smoothness /= 2;
start = spline.MaxDistance.ToFloat() / smoothness;
glEnable(GL_POINT_SMOOTH);
glPointSize(3.0f);
glBegin(GL_POINTS);
for (int i = 0; i <= smoothness; ++i)
{
time = start * i / spline.MaxDistance.ToFloat();
CVector3D tmp = spline.GetPosition(time);
- glVertex3f(tmp.X, tmp.Y, tmp.Z);
+ glVertex3f(tmp.getX(), tmp.getY(), tmp.getZ());
}
glEnd();
glPointSize(1.0f);
glDisable(GL_POINT_SMOOTH);
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
#endif
}
-void CCinemaManager::DrawNodes(const RNSpline& spline, const CColor& nodeColor) const
+void CCinemaManager::DrawNodes(const RNSpline& spline, const RGBAcolor& nodeColor) const
{
#if CONFIG2_GLES
#warning TODO : implement CCinemaPath on GLES
#else
glDisable(GL_DEPTH_TEST);
glEnable(GL_POINT_SMOOTH);
glPointSize(7.0f);
- glColor4f(nodeColor.r, nodeColor.g, nodeColor.b, nodeColor.a);
+ glColor4f(nodeColor.getR(), nodeColor.getG(), nodeColor.getB(), nodeColor.getA());
glBegin(GL_POINTS);
for (const SplineData& node : spline.GetAllNodes())
- glVertex3f(node.Position.X.ToFloat(), node.Position.Y.ToFloat(), node.Position.Z.ToFloat());
+ glVertex3f(node.Position.getX().ToFloat(), node.Position.getY().ToFloat(), node.Position.getZ().ToFloat());
glEnd();
glPointSize(1.0f);
glDisable(GL_POINT_SMOOTH);
glEnable(GL_DEPTH_TEST);
#endif
}
void CCinemaManager::DrawBars() const
{
int height = (float)g_xres / 2.39f;
int shift = (g_yres - height) / 2;
if (shift <= 0)
return;
#if CONFIG2_GLES
#warning TODO : implement bars for GLES
#else
// Set up transform for GL bars
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
CMatrix3D transform;
transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f);
glLoadMatrixf(&transform._11);
glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glBegin(GL_QUADS);
glVertex2i(0, 0);
glVertex2i(g_xres, 0);
glVertex2i(g_xres, shift);
glVertex2i(0, shift);
glEnd();
glBegin(GL_QUADS);
glVertex2i(0, g_yres - shift);
glVertex2i(g_xres, g_yres - shift);
glVertex2i(g_xres, g_yres);
glVertex2i(0, g_yres);
glEnd();
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
// Restore transform
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
#endif
}
bool CCinemaManager::IsEnabled() const
{
CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity());
return cmpCinemaManager && cmpCinemaManager->IsEnabled();
}
bool CCinemaManager::IsPlaying() const
{
return IsEnabled() && g_Game && !g_Game->m_Paused;
}
bool CCinemaManager::GetPathsDrawing() const
{
return m_DrawPaths;
}
void CCinemaManager::SetPathsDrawing(const bool drawPath)
{
m_DrawPaths = drawPath;
}
Index: source/graphics/CinemaManager.h
===================================================================
--- source/graphics/CinemaManager.h (revision 24796)
+++ source/graphics/CinemaManager.h (working copy)
@@ -1,61 +1,61 @@
/* Copyright (C) 2021 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 .
*/
#ifndef INCLUDED_CINEMAMANAGER
#define INCLUDED_CINEMAMANAGER
#include "lib/input.h" // InReaction - can't forward-declare enum
-#include "graphics/Color.h"
#include "ps/CStr.h"
#include "simulation2/helpers/CinemaPath.h"
+class RGBAcolor;
/**
* Class for in game playing of cinematics. Should only be instantiated in CGameView.
*/
class CCinemaManager
{
public:
CCinemaManager();
~CCinemaManager() {}
/**
* Renders black bars and paths (if enabled)
*/
void Render() const;
void DrawBars() const;
void DrawPaths() const;
- void DrawSpline(const RNSpline& spline, const CColor& splineColor, int smoothness, bool lines) const;
- void DrawNodes(const RNSpline& spline, const CColor& nodesColor) const;
+ void DrawSpline(const RNSpline& spline, const RGBAcolor& splineColor, int smoothness, bool lines) const;
+ void DrawNodes(const RNSpline& spline, const RGBAcolor& nodesColor) const;
bool IsPlaying() const;
bool IsEnabled() const;
/**
* Updates CCinemManager and current path
* @param deltaRealTime Elapsed real time since the last frame.
*/
void Update(const float deltaRealTime) const;
bool GetPathsDrawing() const;
void SetPathsDrawing(const bool drawPath);
private:
bool m_DrawPaths;
};
#endif
Index: source/graphics/Color.cpp
===================================================================
--- source/graphics/Color.cpp (revision 24796)
+++ source/graphics/Color.cpp (working copy)
@@ -1,145 +1,158 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "graphics/Color.h"
#include "graphics/SColor.h"
#include "maths/MathUtil.h"
+#include "maths/Vector4D.h"
#include "lib/sse.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#if COMPILER_HAS_SSE
#include
#endif
-static SColor4ub ConvertRGBColorTo4ubFallback(const RGBColor& src)
+static SColor4ub ConvertRGBcolorTo4ubFallback(const RGBcolor& src)
{
SColor4ub result;
- result.R = Clamp(static_cast(src.X * 255), 0, 255);
- result.G = Clamp(static_cast(src.Y * 255), 0, 255);
- result.B = Clamp(static_cast(src.Z * 255), 0, 255);
+ result.R = static_cast(Clamp(static_cast(src.getR() * 256.0f), 0, 255));
+ result.G = static_cast(Clamp(static_cast(src.getG() * 256.0f), 0, 255));
+ result.B = static_cast(Clamp(static_cast(src.getB() * 256.0f), 0, 255));
result.A = 255;
return result;
}
// on IA32, this is replaced by an SSE assembly version in ia32.cpp
-SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src) = ConvertRGBColorTo4ubFallback;
+SColor4ub (*ConvertRGBcolorTo4ub)(const RGBcolor& src) = ConvertRGBcolorTo4ubFallback;
// Assembler-optimized function for color conversion
#if COMPILER_HAS_SSE
-static SColor4ub ConvertRGBColorTo4ubSSE(const RGBColor& src)
+static SColor4ub ConvertRGBcolorTo4ubSSE(const RGBcolor& src)
{
const __m128 zero = _mm_setzero_ps();
const __m128 _255 = _mm_set_ss(255.0f);
- __m128 r = _mm_load_ss(&src.X);
- __m128 g = _mm_load_ss(&src.Y);
- __m128 b = _mm_load_ss(&src.Z);
+ __m128 r = _mm_load_ss(src.pR());
+ __m128 g = _mm_load_ss(src.pG());
+ __m128 b = _mm_load_ss(src.pB());
// C = min(255, 255*max(C, 0)) ( == Clamp(255*C, 0, 255) )
r = _mm_max_ss(r, zero);
g = _mm_max_ss(g, zero);
b = _mm_max_ss(b, zero);
r = _mm_mul_ss(r, _255);
g = _mm_mul_ss(g, _255);
b = _mm_mul_ss(b, _255);
r = _mm_min_ss(r, _255);
g = _mm_min_ss(g, _255);
b = _mm_min_ss(b, _255);
// convert to integer and combine channels using bit logic
int ri = _mm_cvtss_si32(r);
int gi = _mm_cvtss_si32(g);
int bi = _mm_cvtss_si32(b);
return SColor4ub(ri, gi, bi, 0xFF);
}
#endif
void ColorActivateFastImpl()
{
#if COMPILER_HAS_SSE
if (HostHasSSE())
{
- ConvertRGBColorTo4ub = ConvertRGBColorTo4ubSSE;
+ ConvertRGBcolorTo4ub = ConvertRGBcolorTo4ubSSE;
return;
}
#endif
debug_printf("No SSE available. Slow fallback routines will be used.\n");
}
/**
* Important: This function does not modify the value if parsing fails.
*/
-bool CColor::ParseString(const CStr8& value, int defaultAlpha)
+bool RGBAcolor::ParseString(const CStr8& value, int defaultAlpha)
{
const size_t NUM_VALS = 4;
int values[NUM_VALS] = { 0, 0, 0, defaultAlpha };
std::stringstream stream;
stream.str(value);
// Parse each value
size_t i;
for (i = 0; i < NUM_VALS; ++i)
{
if (stream.eof())
break;
stream >> values[i];
if ((stream.rdstate() & std::stringstream::failbit) != 0)
{
- LOGWARNING("Unable to parse CColor parameters. Your input: '%s'", value.c_str());
+ LOGWARNING("Unable to parse RGBAcolor parameters. Your input: '%s'", value.c_str());
return false;
}
if (values[i] < 0 || values[i] > 255)
{
- LOGWARNING("Invalid value (<0 or >255) when parsing CColor parameters. Your input: '%s'", value.c_str());
+ LOGWARNING("Invalid value (<0 or >255) when parsing RGBAcolor parameters. Your input: '%s'", value.c_str());
return false;
}
}
if (i < 3)
{
- LOGWARNING("Not enough parameters when parsing as CColor. Your input: '%s'", value.c_str());
+ LOGWARNING("Not enough parameters when parsing as RGBAcolor. Your input: '%s'", value.c_str());
return false;
}
if (!stream.eof())
{
- LOGWARNING("Too many parameters when parsing as CColor. Your input: '%s'", value.c_str());
+ LOGWARNING("Too many parameters when parsing as RGBAcolor. Your input: '%s'", value.c_str());
return false;
}
- r = values[0] / 255.f;
- g = values[1] / 255.f;
- b = values[2] / 255.f;
- a = values[3] / 255.f;
+ R = values[0] / 255.f;
+ G = values[1] / 255.f;
+ B = values[2] / 255.f;
+ A = values[3] / 255.f;
return true;
}
-bool CColor::operator==(const CColor& color) const
+bool RGBAcolor::operator==(const RGBAcolor& color) const
{
return
- r == color.r &&
- g == color.g &&
- b == color.b &&
- a == color.a;
+ R == color.R &&
+ G == color.G &&
+ B == color.B &&
+ A == color.A;
}
+
+RGBAcolor::operator CVector4D() const { return CVector4D(R,G,B,A); }
+
+#if 0
+#include "lib/code_annotation.h"
+PRINT_SIZEOF_AS_ERROR(RGBcolor);
+#endif
+#if 0
+#include "lib/code_annotation.h"
+PRINT_SIZEOF_AS_ERROR(RGBAcolor);
+#endif
+
Index: source/graphics/Color.h
===================================================================
--- source/graphics/Color.h (revision 24796)
+++ source/graphics/Color.h (working copy)
@@ -1,86 +1,189 @@
/* Copyright (C) 2021 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 .
*/
-
+// (Note there's also a Color struct for the messaging system, somewhere...)
#ifndef INCLUDED_COLOR
#define INCLUDED_COLOR
+struct Color;
#include "graphics/SColor.h"
#include "maths/Vector3D.h"
#include "maths/Vector4D.h"
+#include "maths/MathUtil.h"
// Simple defines for 3 and 4 component floating point colors - just map to
// corresponding vector types.
-typedef CVector3D RGBColor;
-typedef CVector4D RGBAColor;
+class RGBcolor
+{
+ float R, G, B;
+public:
+ ///constructors, etc
+ RGBcolor() {}
+ RGBcolor(float x, float y, float z) : R(x), G(y), B(z) { }
+ ///get functions
+ float getR() const { return R; }
+ float getG() const { return G; }
+ float getB() const { return B; }
+ float MaxComponent() { return std::max({R, G, B}); }
+ float MinComponent() { return std::min({R, G, B}); }
+ u8 getR8() const { return static_cast( Clamp( int(R*256.0f), 0, 255 ) ); }
+ u8 getG8() const { return static_cast( Clamp( int(G*256.0f), 0, 255 ) ); }
+ u8 getB8() const { return static_cast( Clamp( int(B*256.0f), 0, 255 ) ); }
+ ///Not quite "set functions"; "get non-const ref" functions to minimize changes
+ float& Rref(){ return R; }
+ float& Gref(){ return G; }
+ float& Bref(){ return B; }
+ ///pointers for the stuuupid SSE library
+ float const * pR() const { return (((float*)this)+0); }
+ float const * pG() const { return (((float*)this)+1); }
+ float const * pB() const { return (((float*)this)+2); }
+ void clamp0to1()
+ {
+ if( R < 0.0f ) R = 0.0f;
+ if( 0.996f < R ) R = 0.996f;
+ if( G < 0.0f ) G = 0.0f;
+ if( 0.996f < G ) G = 0.996f;
+ if( B < 0.0f ) B = 0.0f;
+ if( 0.996f < B ) B = 0.996f;
+ }
+ RGBcolor clamped0to1() const
+ {
+ RGBcolor result(*this);
+ result.clamp0to1();
+ return result;
+ }
+ bool operator==(const RGBcolor& p) const
+ {
+ return (R == p.R && G == p.G && B == p.B);
+ }
+ bool operator!=(const RGBcolor& p) const
+ {
+ return (R != p.R || G != p.G || B != p.B);
+ }
+ RGBcolor operator+(const RGBcolor& c) const
+ {
+ return RGBcolor(R + c.getR(), G + c.getG(), B + c.getB());
+ }
+ RGBcolor operator-(const RGBcolor& c) const
+ {
+ return RGBcolor(R - c.getR(), G - c.getG(), B - c.getB());
+ }
+ RGBcolor const & operator+=(const RGBcolor& v)
+ {
+ R += v.getR(); G += v.getG(); B += v.getB();
+ return *this;
+ }
+ RGBcolor const & operator-=(const RGBcolor& v)
+ {
+ R -= v.getR(); G -= v.getG(); B -= v.getB();
+ return *this;
+ }
+ RGBcolor operator*(float value) const
+ {
+ return RGBcolor(R * value, G * value, B * value);
+ }
+ RGBcolor& operator*=(float value)
+ {
+ R *= value;
+ G *= value;
+ B *= value;
+ return *this;
+ }
+};
+//typedef CVector4D RGBAColor;
// Convert float RGB(A) colors to unsigned byte.
// Exposed as function pointer because it is set at init-time to
// one of several implementations depending on CPU caps.
-extern SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src);
+extern SColor4ub (*ConvertRGBcolorTo4ub)(const RGBcolor& src);
/**
* Detects CPU caps and activates the best possible codepath.
*/
extern void ColorActivateFastImpl();
class CStr8;
-struct CColor
+class RGBAcolor
{
- CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {}
- CColor(float cr, float cg, float cb, float ca) : r(cr), g(cg), b(cb), a(ca) {}
+protected: //CGUIColor inherits RGBAcolor and wants to manipulate the variables
+ float R, G, B, A;
+public:
+ RGBAcolor() : R(-1.f), G(-1.f), B(-1.f), A(1.f) {}
+ RGBAcolor(float cr, float cg, float cb, float ca) : R(cr), G(cg), B(cb), A(ca) {}
+ ///get functions
+ float getR() const { return R; }
+ float getG() const { return G; }
+ float getB() const { return B; }
+ float getA() const { return A; }
+ float MaxComponent() { return std::max({R, G, B}); } //alpha channel's component?
+ float MinComponent() { return std::min({R, G, B}); } //alpha channel's component?
+ u8 getR8() const { return static_cast( Clamp( int(R*256.0f), 0, 255 ) ); }
+ u8 getG8() const { return static_cast( Clamp( int(G*256.0f), 0, 255 ) ); }
+ u8 getB8() const { return static_cast( Clamp( int(B*256.0f), 0, 255 ) ); }
+ u8 getA8() const { return static_cast( Clamp( int(A*256.0f), 0, 255 ) ); }
+ ///Conversion to CVector4D is needed for the shader uniforms
+ operator CVector4D() const;
+ ///Not quite "set functions"; "get non-const ref" functions to minimize changes
+ float& Rref(){ return R; }
+ float& Gref(){ return G; }
+ float& Bref(){ return B; }
+ float& Aref(){ return A; }
/**
* Returns whether this has been set to a valid color.
+ * DanW58: I suppose values greater than 1 are valid in the case that
+ * color is being used for a light source, like the Sun, and not just
+ * reflective material color --which can never be greater than 1.0f
+ * It's funny then that the default constructor puts RGB at -1.0f...
+ * I've no idea why...
*/
operator bool() const
{
- return r >= 0 && g >= 0 && b >= 0 && a >= 0;
+ return R >= 0 && G >= 0 && B >= 0 && A >= 0;
}
/**
* Try to parse @p Value as a color. Returns true on success, false otherwise.
* Leaves the color unchanged if it failed.
- * @param value Should be "r g b" or "r g b a" where each value is an integer in [0,255].
- * @param defaultAlpha The alpha value that is used if the format of @p Value is "r g b".
+ * @param value Should be "R G B" or "R G B A" where each value is an integer in [0,255].
+ * @param defaultAlpha The alpha value that is used if the format of @p Value is "R G B".
*/
bool ParseString(const CStr8& value, int defaultAlpha = 255);
- bool operator==(const CColor& color) const;
- bool operator!=(const CColor& color) const
+ bool operator==(const RGBAcolor& color) const;
+ bool operator!=(const RGBAcolor& color) const
{
return !(*this == color);
}
// For passing to glColor[34]fv:
- const float* FloatArray() const { return &r; }
+ const float* FloatArray() const { return &R; }
// For passing to CRenderer:
SColor4ub AsSColor4ub() const
{
return SColor4ub(
- static_cast(r * 255.f),
- static_cast(g * 255.f),
- static_cast(b * 255.f),
- static_cast(a * 255.f)
+ static_cast(Clamp(R * 256.0f, 0, 255)),
+ static_cast(Clamp(G * 255.0f, 0, 255)),
+ static_cast(Clamp(B * 255.0f, 0, 255)),
+ static_cast(Clamp(A * 255.0f, 0, 255))
);
}
- float r, g, b, a;
};
#endif // INCLUDED_COLOR
Index: source/graphics/Decal.cpp
===================================================================
--- source/graphics/Decal.cpp (revision 24796)
+++ source/graphics/Decal.cpp (working copy)
@@ -1,115 +1,115 @@
/* Copyright (C) 2021 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 "Decal.h"
#include "graphics/Terrain.h"
#include "maths/MathUtil.h"
CModelAbstract* CModelDecal::Clone() const
{
CModelDecal* clone = new CModelDecal(m_Terrain, m_Decal);
return clone;
}
void CModelDecal::CalcVertexExtents(ssize_t& i0, ssize_t& j0, ssize_t& i1, ssize_t& j1)
{
CVector3D corner0(m_Decal.m_OffsetX + m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ + m_Decal.m_SizeZ/2);
CVector3D corner1(m_Decal.m_OffsetX + m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ - m_Decal.m_SizeZ/2);
CVector3D corner2(m_Decal.m_OffsetX - m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ - m_Decal.m_SizeZ/2);
CVector3D corner3(m_Decal.m_OffsetX - m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ + m_Decal.m_SizeZ/2);
corner0 = GetTransform().Transform(corner0);
corner1 = GetTransform().Transform(corner1);
corner2 = GetTransform().Transform(corner2);
corner3 = GetTransform().Transform(corner3);
- i0 = floor(std::min(std::min(corner0.X, corner1.X), std::min(corner2.X, corner3.X)) / TERRAIN_TILE_SIZE);
- j0 = floor(std::min(std::min(corner0.Z, corner1.Z), std::min(corner2.Z, corner3.Z)) / TERRAIN_TILE_SIZE);
- i1 = ceil(std::max(std::max(corner0.X, corner1.X), std::max(corner2.X, corner3.X)) / TERRAIN_TILE_SIZE);
- j1 = ceil(std::max(std::max(corner0.Z, corner1.Z), std::max(corner2.Z, corner3.Z)) / TERRAIN_TILE_SIZE);
+ i0 = floor(std::min(std::min(corner0.getX(), corner1.getX()), std::min(corner2.getX(), corner3.getX())) / TERRAIN_TILE_SIZE);
+ j0 = floor(std::min(std::min(corner0.getZ(), corner1.getZ()), std::min(corner2.getZ(), corner3.getZ())) / TERRAIN_TILE_SIZE);
+ i1 = ceil(std::max(std::max(corner0.getX(), corner1.getX()), std::max(corner2.getX(), corner3.getX())) / TERRAIN_TILE_SIZE);
+ j1 = ceil(std::max(std::max(corner0.getZ(), corner1.getZ()), std::max(corner2.getZ(), corner3.getZ())) / TERRAIN_TILE_SIZE);
i0 = Clamp(i0, static_cast(0), m_Terrain->GetVerticesPerSide() - 1);
j0 = Clamp(j0, static_cast(0), m_Terrain->GetVerticesPerSide() - 1);
i1 = Clamp(i1, static_cast(0), m_Terrain->GetVerticesPerSide() - 1);
j1 = Clamp(j1, static_cast(0), m_Terrain->GetVerticesPerSide() - 1);
}
void CModelDecal::CalcBounds()
{
ssize_t i0, j0, i1, j1;
CalcVertexExtents(i0, j0, i1, j1);
m_WorldBounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
}
void CModelDecal::SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
// Check if there's no intersection between the dirty range and this decal
ssize_t bi0, bj0, bi1, bj1;
CalcVertexExtents(bi0, bj0, bi1, bj1);
if (bi1 < i0 || bi0 > i1 || bj1 < j0 || bj0 > j1)
return;
SetDirty(RENDERDATA_UPDATE_VERTICES);
}
void CModelDecal::InvalidatePosition()
{
m_PositionValid = false;
}
void CModelDecal::ValidatePosition()
{
if (m_PositionValid)
{
ENSURE(!m_Parent || m_Parent->m_PositionValid);
return;
}
if (m_Parent && !m_Parent->m_PositionValid)
{
// Make sure we don't base our calculations on
// a parent animation state that is out of date.
m_Parent->ValidatePosition();
// Parent will recursively call our validation.
ENSURE(m_PositionValid);
return;
}
m_PositionValid = true;
}
void CModelDecal::SetTransform(const CMatrix3D& transform)
{
// Since decals are assumed to be horizontal and projected downwards
// onto the terrain, use just the Y-axis rotation and the translation
CMatrix3D newTransform;
newTransform.SetYRotation(transform.GetYRotation() + m_Decal.m_Angle);
newTransform.Translate(transform.GetTranslation());
CRenderableObject::SetTransform(newTransform);
InvalidatePosition();
}
void CModelDecal::RemoveShadows()
{
m_Decal.m_Material.AddShaderDefine(str_DISABLE_RECEIVE_SHADOWS, str_1);
m_Decal.m_Material.RecomputeCombinedShaderDefines();
}
Index: source/graphics/Frustum.cpp
===================================================================
--- source/graphics/Frustum.cpp (revision 24796)
+++ source/graphics/Frustum.cpp (working copy)
@@ -1,160 +1,160 @@
/* Copyright (C) 2009 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* CFrustum is a collection of planes which define a viewing space.
*/
/*
Usually associated with the camera, there are 6 planes which define the
view pyramid. But we allow more planes per frustum which may be used for
portal rendering, where a portal may have 3 or more edges.
*/
#include "precompiled.h"
#include "Frustum.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
CFrustum::CFrustum ()
{
m_NumPlanes = 0;
}
CFrustum::~CFrustum ()
{
}
void CFrustum::SetNumPlanes (size_t num)
{
m_NumPlanes = num;
//clip it
if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES)
{
debug_warn(L"CFrustum::SetNumPlanes: Too many planes");
m_NumPlanes = MAX_NUM_FRUSTUM_PLANES-1;
}
}
void CFrustum::AddPlane(const CPlane& plane)
{
if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES)
{
debug_warn(L"CFrustum::AddPlane: Too many planes");
return;
}
m_aPlanes[m_NumPlanes++] = plane;
}
void CFrustum::Transform(CMatrix3D& m)
{
for (size_t i = 0; i < m_NumPlanes; ++i)
{
CVector3D n = m.Rotate(m_aPlanes[i].m_Norm);
CVector3D p = m.Transform(m_aPlanes[i].m_Norm * -m_aPlanes[i].m_Dist);
m_aPlanes[i].Set(n, p);
m_aPlanes[i].Normalize();
}
}
bool CFrustum::IsPointVisible(const CVector3D& point) const
{
for (size_t i=0; i radius)
return false;
}
return true;
}
bool CFrustum::IsBoxVisible(const CVector3D& position, const CBoundingBoxAligned& bounds) const
{
//basically for every plane we calculate the furthest point
//in the box to that plane. If that point is beyond the plane
//then the box is not visible
CVector3D FarPoint;
CVector3D Min = position+bounds[0];
CVector3D Max = position+bounds[1];
for (size_t i=0; i 0.0f ? Max.X : Min.X;
- FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? Max.Y : Min.Y;
- FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? Max.Z : Min.Z;
+ FarPoint.Xref() = m_aPlanes[i].m_Norm.getX() > 0.0f ? Max.getX() : Min.getX();
+ FarPoint.Yref() = m_aPlanes[i].m_Norm.getY() > 0.0f ? Max.getY() : Min.getY();
+ FarPoint.Zref() = m_aPlanes[i].m_Norm.getZ() > 0.0f ? Max.getZ() : Min.getZ();
if (m_aPlanes[i].IsPointOnBackSide(FarPoint))
return false;
}
return true;
}
bool CFrustum::IsBoxVisible(const CBoundingBoxAligned& bounds) const
{
//Same as the previous one, but with the position = (0, 0, 0)
CVector3D FarPoint;
for (size_t i=0; i 0.0f ? bounds[1].X : bounds[0].X;
- FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? bounds[1].Y : bounds[0].Y;
- FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? bounds[1].Z : bounds[0].Z;
+ FarPoint.Xref() = m_aPlanes[i].m_Norm.getX() > 0.0f ? bounds[1].getX() : bounds[0].getX();
+ FarPoint.Yref() = m_aPlanes[i].m_Norm.getY() > 0.0f ? bounds[1].getY() : bounds[0].getY();
+ FarPoint.Zref() = m_aPlanes[i].m_Norm.getZ() > 0.0f ? bounds[1].getZ() : bounds[0].getZ();
if (m_aPlanes[i].IsPointOnBackSide(FarPoint))
return false;
}
return true;
}
Index: source/graphics/GameView.cpp
===================================================================
--- source/graphics/GameView.cpp (revision 24796)
+++ source/graphics/GameView.cpp (working copy)
@@ -1,415 +1,415 @@
/* Copyright (C) 2021 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 "GameView.h"
#include "graphics/CameraController.h"
#include "graphics/CinemaManager.h"
#include "graphics/ColladaManager.h"
#include "graphics/HFTracer.h"
#include "graphics/LOSTexture.h"
#include "graphics/LightEnv.h"
#include "graphics/Model.h"
#include "graphics/ObjectManager.h"
#include "graphics/Patch.h"
#include "graphics/SkeletonAnimManager.h"
#include "graphics/SmoothedValue.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "lib/input.h"
#include "lib/timer.h"
#include "lobby/IXmppClient.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "maths/Quaternion.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Joystick.h"
#include "ps/Loader.h"
#include "ps/LoaderThunks.h"
#include "ps/Profile.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"
#include
class CGameViewImpl
{
NONCOPYABLE(CGameViewImpl);
public:
CGameViewImpl(CGame* game)
: Game(game),
ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager),
ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()),
LOSTexture(*game->GetSimulation2()),
TerritoryTexture(*game->GetSimulation2()),
ViewCamera(),
CullCamera(),
LockCullCamera(false),
Culling(true),
CameraController(new CCameraController(ViewCamera))
{
}
CGame* Game;
CColladaManager ColladaManager;
CMeshManager MeshManager;
CSkeletonAnimManager SkeletonAnimManager;
CObjectManager ObjectManager;
CLOSTexture LOSTexture;
CTerritoryTexture TerritoryTexture;
/**
* this camera controls the eye position when rendering
*/
CCamera ViewCamera;
/**
* this camera controls the frustum that is used for culling
* and shadow calculations
*
* Note that all code that works with camera movements should only change
* m_ViewCamera. The render functions automatically sync the cull camera to
* the view camera depending on the value of m_LockCullCamera.
*/
CCamera CullCamera;
/**
* When @c true, the cull camera is locked in place.
* When @c false, the cull camera follows the view camera.
*
* Exposed to JS as gameView.lockCullCamera
*/
bool LockCullCamera;
/**
* When @c true, culling is enabled so that only models that have a chance of
* being visible are sent to the renderer.
* Otherwise, the entire world is sent to the renderer.
*
* Exposed to JS as gameView.culling
*/
bool Culling;
/**
* Cache global lighting environment. This is used to check whether the
* environment has changed during the last frame, so that vertex data can be updated etc.
*/
CLightEnv CachedLightEnv;
CCinemaManager CinemaManager;
/**
* Controller of the view's camera. We use a std::unique_ptr for an easy
* on the fly replacement. It's guaranteed that the pointer is never nulllptr.
*/
std::unique_ptr CameraController;
};
#define IMPLEMENT_BOOLEAN_SETTING(NAME) \
bool CGameView::Get##NAME##Enabled() const \
{ \
return m->NAME; \
} \
\
void CGameView::Set##NAME##Enabled(bool Enabled) \
{ \
m->NAME = Enabled; \
}
IMPLEMENT_BOOLEAN_SETTING(Culling);
IMPLEMENT_BOOLEAN_SETTING(LockCullCamera);
bool CGameView::GetConstrainCameraEnabled() const
{
return m->CameraController->GetConstrainCamera();
}
void CGameView::SetConstrainCameraEnabled(bool enabled)
{
m->CameraController->SetConstrainCamera(enabled);
}
#undef IMPLEMENT_BOOLEAN_SETTING
CGameView::CGameView(CGame *pGame):
m(new CGameViewImpl(pGame))
{
m->CullCamera = m->ViewCamera;
g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera);
}
CGameView::~CGameView()
{
UnloadResources();
delete m;
}
void CGameView::SetViewport(const SViewPort& vp)
{
m->CameraController->SetViewport(vp);
}
CObjectManager& CGameView::GetObjectManager()
{
return m->ObjectManager;
}
CCamera* CGameView::GetCamera()
{
return &m->ViewCamera;
}
CCinemaManager* CGameView::GetCinema()
{
return &m->CinemaManager;
};
CLOSTexture& CGameView::GetLOSTexture()
{
return m->LOSTexture;
}
CTerritoryTexture& CGameView::GetTerritoryTexture()
{
return m->TerritoryTexture;
}
int CGameView::Initialize()
{
m->CameraController->LoadConfig();
return 0;
}
void CGameView::RegisterInit()
{
// CGameView init
RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1);
RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60);
RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5);
}
void CGameView::BeginFrame()
{
if (m->LockCullCamera == false)
{
// Set up cull camera
m->CullCamera = m->ViewCamera;
}
g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera);
CheckLightEnv();
m->Game->CachePlayerColors();
}
void CGameView::Render()
{
g_Renderer.RenderScene(*this);
}
///////////////////////////////////////////////////////////
// This callback is part of the Scene interface
// Submit all objects visible in the given frustum
void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
{
{
PROFILE3("submit terrain");
CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f;
const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide();
// find out which patches will be drawn
for (ssize_t j=0; jGetPatch(i,j); // can't fail
// If the patch is underwater, calculate a bounding box that also contains the water plane
CBoundingBoxAligned bounds = patch->GetWorldBounds();
- if(bounds[1].Y < waterHeight)
- bounds[1].Y = waterHeight;
+ if(bounds[1].getY() < waterHeight)
+ bounds[1].Yref() = waterHeight;
if (!m->Culling || frustum.IsBoxVisible(bounds))
c->Submit(patch);
}
}
}
m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling);
}
void CGameView::CheckLightEnv()
{
if (m->CachedLightEnv == g_LightEnv)
return;
m->CachedLightEnv = g_LightEnv;
CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
if (!pTerrain)
return;
PROFILE("update light env");
pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR);
const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits();
for (size_t i = 0; i < units.size(); ++i)
units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR);
}
void CGameView::UnloadResources()
{
g_TexMan.UnloadTerrainTextures();
g_Renderer.UnloadAlphaMaps();
g_Renderer.GetWaterManager()->UnloadWaterTextures();
}
void CGameView::Update(const float deltaRealTime)
{
// If camera movement is being handled by the touch-input system,
// then we should stop to avoid conflicting with it
if (g_TouchInput.IsEnabled())
return;
if (!g_app_has_focus)
return;
m->CinemaManager.Update(deltaRealTime);
if (m->CinemaManager.IsEnabled())
return;
m->CameraController->Update(deltaRealTime);
}
CVector3D CGameView::GetCameraPivot() const
{
return m->CameraController->GetCameraPivot();
}
CVector3D CGameView::GetCameraPosition() const
{
return m->CameraController->GetCameraPosition();
}
CVector3D CGameView::GetCameraRotation() const
{
return m->CameraController->GetCameraRotation();
}
float CGameView::GetCameraZoom() const
{
return m->CameraController->GetCameraZoom();
}
void CGameView::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
{
m->CameraController->SetCamera(pos, rotX, rotY, zoom);
}
void CGameView::MoveCameraTarget(const CVector3D& target)
{
m->CameraController->MoveCameraTarget(target);
}
void CGameView::ResetCameraTarget(const CVector3D& target)
{
m->CameraController->ResetCameraTarget(target);
}
void CGameView::FollowEntity(entity_id_t entity, bool firstPerson)
{
m->CameraController->FollowEntity(entity, firstPerson);
}
entity_id_t CGameView::GetFollowedEntity()
{
return m->CameraController->GetFollowedEntity();
}
InReaction game_view_handler(const SDL_Event_* ev)
{
// put any events that must be processed even if inactive here
if (!g_app_has_focus || !g_Game || !g_Game->IsGameStarted() || g_Game->GetView()->GetCinema()->IsEnabled())
return IN_PASS;
CGameView *pView=g_Game->GetView();
return pView->HandleEvent(ev);
}
InReaction CGameView::HandleEvent(const SDL_Event_* ev)
{
switch(ev->ev.type)
{
case SDL_HOTKEYPRESS:
{
std::string hotkey = static_cast(ev->ev.user.data1);
if (hotkey == "wireframe")
{
if (g_XmppClient && g_rankedGame == true)
break;
else if (g_Renderer.GetModelRenderMode() == SOLID)
{
g_Renderer.SetTerrainRenderMode(EDGED_FACES);
g_Renderer.SetWaterRenderMode(EDGED_FACES);
g_Renderer.SetModelRenderMode(EDGED_FACES);
g_Renderer.SetOverlayRenderMode(EDGED_FACES);
}
else if (g_Renderer.GetModelRenderMode() == EDGED_FACES)
{
g_Renderer.SetTerrainRenderMode(WIREFRAME);
g_Renderer.SetWaterRenderMode(WIREFRAME);
g_Renderer.SetModelRenderMode(WIREFRAME);
g_Renderer.SetOverlayRenderMode(WIREFRAME);
}
else
{
g_Renderer.SetTerrainRenderMode(SOLID);
g_Renderer.SetWaterRenderMode(SOLID);
g_Renderer.SetModelRenderMode(SOLID);
g_Renderer.SetOverlayRenderMode(SOLID);
}
return IN_HANDLED;
}
}
}
return m->CameraController->HandleEvent(ev);
}
Index: source/graphics/HFTracer.cpp
===================================================================
--- source/graphics/HFTracer.cpp (revision 24796)
+++ source/graphics/HFTracer.cpp (working copy)
@@ -1,360 +1,360 @@
/* Copyright (C) 2021 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 .
*/
/*
* Determine intersection of rays with a heightfield.
*/
#include "precompiled.h"
#include "HFTracer.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Vector3D.h"
#include
// To cope well with points that are slightly off the edge of the map,
// we act as if there's an N-tile margin around the edges of the heightfield.
// (N shouldn't be too huge else it'll hurt performance a little when
// RayIntersect loops through it all.)
// CTerrain::CalcPosition implements clamp-to-edge behaviour so the tracer
// will have that behaviour.
static const int MARGIN_SIZE = 64;
///////////////////////////////////////////////////////////////////////////////
// CHFTracer constructor
CHFTracer::CHFTracer(CTerrain *pTerrain):
m_pTerrain(pTerrain),
m_Heightfield(m_pTerrain->GetHeightMap()),
m_MapSize(m_pTerrain->GetVerticesPerSide()),
m_CellSize((float)TERRAIN_TILE_SIZE),
m_HeightScale(HEIGHT_SCALE)
{
}
///////////////////////////////////////////////////////////////////////////////
// RayTriIntersect: intersect a ray with triangle defined by vertices
// v0,v1,v2; return true if ray hits triangle at distance less than dist,
// or false otherwise
static bool RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2,
const CVector3D& origin, const CVector3D& dir, float& dist)
{
const float EPSILON=0.00001f;
// calculate edge vectors
CVector3D edge0=v1-v0;
CVector3D edge1=v2-v0;
// begin calculating determinant - also used to calculate U parameter
CVector3D pvec=dir.Cross(edge1);
// if determinant is near zero, ray lies in plane of triangle
float det = edge0.Dot(pvec);
if (fabs(det)1.01f)
return false;
// prepare to test V parameter
CVector3D qvec=tvec.Cross(edge0);
// calculate V parameter and test bounds
float v=dir.Dot(qvec)*inv_det;
if (v<0.0f || u+v>1.0f)
return false;
// calculate distance to intersection point from ray origin
float d=edge1.Dot(qvec)*inv_det;
if (d>=0 && dCalcPosition(cx,cz,vpos[0]);
m_pTerrain->CalcPosition(cx+1,cz,vpos[1]);
m_pTerrain->CalcPosition(cx+1,cz+1,vpos[2]);
m_pTerrain->CalcPosition(cx,cz+1,vpos[3]);
dist=1.0e30f;
if (RayTriIntersect(vpos[0],vpos[1],vpos[2],origin,dir,dist)) {
res=true;
}
if (RayTriIntersect(vpos[0],vpos[2],vpos[3],origin,dir,dist)) {
res=true;
}
return res;
}
///////////////////////////////////////////////////////////////////////////////
// RayIntersect: intersect ray with this heightfield; return true if
// intersection occurs (and fill in grid coordinates of intersection), or false
// otherwise
bool CHFTracer::RayIntersect(const CVector3D& origin, const CVector3D& dir, int& x, int& z, CVector3D& ipt) const
{
// If the map is empty (which should never happen),
// return early before we crash when reading zero-sized heightmaps
if (!m_MapSize)
{
debug_warn(L"CHFTracer::RayIntersect called with zero-size map");
return false;
}
// intersect first against bounding box
CBoundingBoxAligned bound;
bound[0] = CVector3D(-MARGIN_SIZE * m_CellSize, 0, -MARGIN_SIZE * m_CellSize);
bound[1] = CVector3D((m_MapSize + MARGIN_SIZE) * m_CellSize, 65535 * m_HeightScale, (m_MapSize + MARGIN_SIZE) * m_CellSize);
float tmin,tmax;
if (!bound.RayIntersect(origin,dir,tmin,tmax)) {
// ray missed world bounds; no intersection
return false;
}
// project origin onto grid, if necessary, to get starting point for traversal
CVector3D traversalPt;
if (tmin>0) {
traversalPt=origin+dir*tmin;
} else {
traversalPt=origin;
}
// setup traversal variables
- int sx=dir.X<0 ? -1 : 1;
- int sz=dir.Z<0 ? -1 : 1;
+ int sx=dir.getX()<0 ? -1 : 1;
+ int sz=dir.getZ()<0 ? -1 : 1;
float invCellSize=1.0f/float(m_CellSize);
- float fcx=traversalPt.X*invCellSize;
+ float fcx=traversalPt.getX()*invCellSize;
int cx=(int)floor(fcx);
- float fcz=traversalPt.Z*invCellSize;
+ float fcz=traversalPt.getZ()*invCellSize;
int cz=(int)floor(fcz);
float invdx = 1.0e20f;
float invdz = 1.0e20f;
- if (fabs(dir.X) > 1.0e-20)
- invdx = float(1.0/fabs(dir.X));
- if (fabs(dir.Z) > 1.0e-20)
- invdz = float(1.0/fabs(dir.Z));
+ if (fabs(dir.getX()) > 1.0e-20)
+ invdx = float(1.0/fabs(dir.getX()));
+ if (fabs(dir.getZ()) > 1.0e-20)
+ invdz = float(1.0/fabs(dir.getZ()));
do {
// test current cell
if (cx >= -MARGIN_SIZE && cx < int(m_MapSize + MARGIN_SIZE - 1) && cz >= -MARGIN_SIZE && cz < int(m_MapSize + MARGIN_SIZE - 1))
{
float dist;
if (CellIntersect(cx,cz,origin,dir,dist)) {
x=cx;
z=cz;
ipt=origin+dir*dist;
return true;
}
}
else
{
// Degenerate case: y close to zero
// catch travelling off the map
if ((cx < -MARGIN_SIZE) && (sx < 0))
return false;
if ((cx >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sx > 0))
return false;
if ((cz < -MARGIN_SIZE) && (sz < 0))
return false;
if ((cz >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sz > 0))
return false;
}
// get coords of current cell
- fcx=traversalPt.X*invCellSize;
- fcz=traversalPt.Z*invCellSize;
+ fcx=traversalPt.getX()*invCellSize;
+ fcz=traversalPt.getZ()*invCellSize;
// get distance to next cell in x,z
float dx=(sx==-1) ? fcx-float(cx) : 1-(fcx-float(cx));
dx*=invdx;
float dz=(sz==-1) ? fcz-float(cz) : 1-(fcz-float(cz));
dz*=invdz;
// advance ..
float dist;
if (dx=0);
+ } while (traversalPt.getY()>=0);
// fell off end of heightmap with no intersection; return a miss
return false;
}
static bool TestTile(u16* heightmap, int stride, int i, int j, const CVector3D& pos, const CVector3D& dir, CVector3D& isct)
{
u16 y00 = heightmap[i + j*stride];
u16 y10 = heightmap[i+1 + j*stride];
u16 y01 = heightmap[i + (j+1)*stride];
u16 y11 = heightmap[i+1 + (j+1)*stride];
CVector3D p00( i * TERRAIN_TILE_SIZE, y00 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE);
CVector3D p10((i+1) * TERRAIN_TILE_SIZE, y10 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE);
CVector3D p01( i * TERRAIN_TILE_SIZE, y01 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE);
CVector3D p11((i+1) * TERRAIN_TILE_SIZE, y11 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE);
int mid1 = y00+y11;
int mid2 = y01+y10;
int triDir = (mid1 < mid2);
float dist = FLT_MAX;
if (triDir)
{
if (RayTriIntersect(p00, p10, p01, pos, dir, dist) || // lower-left triangle
RayTriIntersect(p11, p01, p10, pos, dir, dist)) // upper-right triangle
{
isct = pos + dir * dist;
return true;
}
}
else
{
if (RayTriIntersect(p00, p11, p01, pos, dir, dist) || // upper-left triangle
RayTriIntersect(p00, p10, p11, pos, dir, dist)) // lower-right triangle
{
isct = pos + dir * dist;
return true;
}
}
return false;
}
bool CHFTracer::PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out)
{
// (TODO: This largely duplicates RayIntersect - some refactoring might be
// nice in the future.)
// General approach:
// Given the ray defined by origin + dir * t, we increase t until it
// enters the patch's bounding box. The x,z coordinates identify which
// tile it is currently above/below. Do an intersection test vs the tile's
// two triangles. If it doesn't hit, do a 2D line rasterisation to find
// the next tiles the ray will pass through, and test each of them.
// Start by jumping to the point where the ray enters the bounding box
CBoundingBoxAligned bound = patch->GetWorldBounds();
float tmin, tmax;
if (!bound.RayIntersect(origin, dir, tmin, tmax))
{
// Ray missed patch; no intersection
return false;
}
int heightmapStride = patch->m_Parent->GetVerticesPerSide();
// Get heightmap, offset to start at this patch
u16* heightmap = patch->m_Parent->GetHeightMap() +
patch->m_X * PATCH_SIZE +
patch->m_Z * PATCH_SIZE * heightmapStride;
// Get patch-space position of ray origin and bbox entry point
CVector3D patchPos(
patch->m_X * PATCH_SIZE * TERRAIN_TILE_SIZE,
0.0f,
patch->m_Z * PATCH_SIZE * TERRAIN_TILE_SIZE);
CVector3D originPatch = origin - patchPos;
CVector3D entryPatch = originPatch + dir * tmin;
// We want to do a simple 2D line rasterisation (with the 3D ray projected
// down onto the Y plane). That will tell us which cells are intersected
// in 2D dimensions, then we can do a more precise 3D intersection test.
//
// WLOG, assume the ray has direction dir.x > 0, dir.z > 0, and starts in
// cell (i,j). The next cell intersecting the line must be either (i+1,j)
// or (i,j+1). To tell which, just check whether the point (i+1,j+1) is
// above or below the ray. Advance into that cell and repeat.
//
// (If the ray passes precisely through (i+1,j+1), we can pick either.
// If the ray is parallel to Y, only the first cell matters, then we can
// carry on rasterising in any direction (a bit of a waste of time but
// should be extremely rare, and it's safe and simple).)
// Work out which tile we're starting in
- int i = Clamp(static_cast(entryPatch.X / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1);
- int j = Clamp(static_cast(entryPatch.Z / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1);
+ int i = Clamp(static_cast(entryPatch.getX() / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1);
+ int j = Clamp(static_cast(entryPatch.getZ() / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1);
// Work out which direction the ray is going in
- int di = (dir.X >= 0 ? 1 : 0);
- int dj = (dir.Z >= 0 ? 1 : 0);
+ int di = (dir.getX() >= 0 ? 1 : 0);
+ int dj = (dir.getZ() >= 0 ? 1 : 0);
do
{
CVector3D isct;
if (TestTile(heightmap, heightmapStride, i, j, originPatch, dir, isct))
{
if (out)
*out = isct + patchPos;
return true;
}
// Get the vertex between the two possible next cells
float nx = (i + di) * (int)TERRAIN_TILE_SIZE;
float nz = (j + dj) * (int)TERRAIN_TILE_SIZE;
// Test which side of the ray the vertex is on, and advance into the
// appropriate cell, using a test that works for all 4 combinations
// of di,dj
- float dot = dir.Z * (nx - originPatch.X) - dir.X * (nz - originPatch.Z);
+ float dot = dir.getZ() * (nx - originPatch.getX()) - dir.getX() * (nz - originPatch.getZ());
if ((di == dj) == (dot > 0.0f))
j += dj*2-1;
else
i += di*2-1;
}
while (i >= 0 && j >= 0 && i < PATCH_SIZE && j < PATCH_SIZE);
// Ran off the edge of the patch, so no intersection
return false;
}
Index: source/graphics/LightEnv.cpp
===================================================================
--- source/graphics/LightEnv.cpp (revision 24796)
+++ source/graphics/LightEnv.cpp (working copy)
@@ -1,57 +1,57 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "graphics/LightEnv.h"
#include "maths/MathUtil.h"
-
+#include "maths/Vector3D.h"
CLightEnv::CLightEnv()
: m_Elevation(DEGTORAD(45)),
m_Rotation(DEGTORAD(315)),
m_SunColor(1.5, 1.5, 1.5),
m_AmbientColor(0x50/255.f, 0x60/255.f, 0x85/255.f),
m_FogColor(0xCC/255.f, 0xCC/255.f, 0xE5/255.f),
m_FogFactor(0.000f),
m_FogMax(0.5f),
m_Brightness(0.0f), m_Contrast(1.0f), m_Saturation(0.99f), m_Bloom(0.1999f)
{
CalculateSunDirection();
}
void CLightEnv::SetElevation(float f)
{
m_Elevation = f;
CalculateSunDirection();
}
void CLightEnv::SetRotation(float f)
{
m_Rotation = f;
CalculateSunDirection();
}
void CLightEnv::CalculateSunDirection()
{
- m_SunDir.Y = -sinf(m_Elevation);
- float scale = 1 + m_SunDir.Y;
- m_SunDir.X = scale * sinf(m_Rotation);
- m_SunDir.Z = scale * cosf(m_Rotation);
+ m_SunDir.Yref() = -sinf(m_Elevation);
+ float scale = 1 + m_SunDir.getY();
+ m_SunDir.Xref() = scale * sinf(m_Rotation);
+ m_SunDir.Zref() = scale * cosf(m_Rotation);
m_SunDir.Normalize();
}
Index: source/graphics/LightEnv.h
===================================================================
--- source/graphics/LightEnv.h (revision 24796)
+++ source/graphics/LightEnv.h (working copy)
@@ -1,125 +1,124 @@
/* Copyright (C) 2021 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 .
*/
/*
* CLightEnv, a class describing the current lights
*/
#ifndef INCLUDED_LIGHTENV
#define INCLUDED_LIGHTENV
-#include "graphics/Color.h"
-#include "maths/MathUtil.h"
-#include "maths/Vector3D.h"
+#include "graphics/Color.h" // HAS to be here, used in structs
class CMapWriter;
class CMapReader;
+class CVector3D;
/**
* Class CLightEnv: description of a lighting environment - contains all the
* necessary parameters for representation of the lighting within a scenario
*/
class CLightEnv
{
public:
- RGBColor m_SunColor;
- RGBColor m_AmbientColor;
- RGBColor m_FogColor;
+ RGBcolor m_SunColor;
+ RGBcolor m_AmbientColor;
+ RGBcolor m_FogColor;
float m_FogFactor;
float m_FogMax;
float m_Brightness, m_Contrast, m_Saturation, m_Bloom;
CLightEnv();
float GetElevation() const { return m_Elevation; }
float GetRotation() const { return m_Rotation; }
const CVector3D& GetSunDir() const { return m_SunDir; }
void SetElevation(float f);
void SetRotation(float f);
/**
* Calculate brightness of a point of a unit with the given normal vector,
* for rendering with CPU lighting.
* The resulting color contains both ambient and diffuse light.
* To cope with sun overbrightness, the color is scaled by 0.5.
*
* @param normal normal vector (must have length 1)
*/
- RGBColor EvaluateUnitScaled(const CVector3D& normal) const
+ RGBcolor EvaluateUnitScaled(const CVector3D& normal) const
{
float dot = -normal.Dot(m_SunDir);
- RGBColor color = m_AmbientColor;
+ RGBcolor color = m_AmbientColor;
if (dot > 0)
color += m_SunColor * dot;
return color * 0.5f;
}
// Comparison operators
bool operator==(const CLightEnv& o) const
{
return m_Elevation == o.m_Elevation &&
m_Rotation == o.m_Rotation &&
m_SunColor == o.m_SunColor &&
m_AmbientColor == o.m_AmbientColor &&
m_FogColor == o.m_FogColor &&
m_FogFactor == o.m_FogFactor &&
m_FogMax == o.m_FogMax &&
m_Brightness == o.m_Brightness &&
m_Contrast == o.m_Contrast &&
m_Saturation == o.m_Saturation &&
m_Bloom == o.m_Bloom;
}
bool operator!=(const CLightEnv& o) const
{
return !(*this == o);
}
private:
friend class CMapWriter;
friend class CMapReader;
friend class CXMLReader;
/**
* Height of sun above the horizon, in radians.
* For example, an elevation of M_PI/2 means the sun is straight up.
*/
float m_Elevation;
/**
* Direction of sun on the compass, in radians.
* For example, a rotation of zero means the sun is in the direction (0,0,-1)
* and a rotation of M_PI/2 means the sun is in the direction (1,0,0) (not taking
* elevation into account).
*/
float m_Rotation;
/**
* Vector corresponding to m_Elevation and m_Rotation.
* Updated by CalculateSunDirection.
*/
CVector3D m_SunDir;
void CalculateSunDirection();
};
#endif // INCLUDED_LIGHTENV
Index: source/graphics/MapReader.cpp
===================================================================
--- source/graphics/MapReader.cpp (revision 24796)
+++ source/graphics/MapReader.cpp (working copy)
@@ -1,1603 +1,1603 @@
/* Copyright (C) 2021 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 "MapReader.h"
#include "graphics/Camera.h"
#include "graphics/CinemaManager.h"
#include "graphics/Entity.h"
#include "graphics/GameView.h"
#include "graphics/MapGenerator.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/timer.h"
#include "lib/external_libraries/libsdl.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "ps/LoaderThunks.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/PostprocManager.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptContext.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpCinemaManager.h"
#include "simulation2/components/ICmpGarrisonHolder.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpTurretHolder.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/components/ICmpWaterManager.h"
#include
#if defined(_MSC_VER) && _MSC_VER > 1900
#pragma warning(disable: 4456) // Declaration hides previous local declaration.
#pragma warning(disable: 4458) // Declaration hides class member.
#endif
CMapReader::CMapReader()
: xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
{
cur_terrain_tex = 0; // important - resets generator state
}
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void CMapReader::LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::HandleValue settings, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
{
pTerrain = pTerrain_;
pLightEnv = pLightEnv_;
pGameView = pGameView_;
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pPostproc = pPostproc_;
pSimulation2 = pSimulation2_;
pSimContext = pSimContext_;
m_PlayerID = playerID_;
m_SkipEntities = skipEntities;
m_StartingCameraTarget = INVALID_ENTITY;
m_ScriptSettings.init(cx.GetGeneralJSContext(), settings);
filename_xml = pathname.ChangeExtension(L".xml");
// In some cases (particularly tests) we don't want to bother storing a large
// mostly-empty .pmp file, so we let the XML file specify basic terrain instead.
// If there's an .xml file and no .pmp, then we're probably in this XML-only mode
only_xml = false;
if (!VfsFileExists(pathname) && VfsFileExists(filename_xml))
{
only_xml = true;
}
file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp
if (!only_xml)
{
// [25ms]
unpacker.Read(pathname, "PSMP");
file_format_version = unpacker.GetVersion();
}
// check oldest supported version
if (file_format_version < FILE_READ_VERSION)
throw PSERROR_Game_World_MapLoadFailed("Could not load terrain file - too old version!");
// delete all existing entities
if (pSimulation2)
pSimulation2->ResetState();
// reset post effects
if (pPostproc)
pPostproc->SetPostEffect(L"default");
// load map or script settings script
if (settings.isUndefined())
RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50);
else
RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
// load player settings script (must be done before reading map)
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// unpack the data
if (!only_xml)
RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200);
// read the corresponding XML file
RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50);
// apply terrain data to the world
RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
// read entities
RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800);
// apply misc data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
// load map settings script (must be done after reading map)
RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
}
// LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
void CMapReader::LoadRandomMap(const CStrW& scriptFile, const ScriptContext& cx, JS::HandleValue settings, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
CSimulation2 *pSimulation2_, int playerID_)
{
m_ScriptFile = scriptFile;
pSimulation2 = pSimulation2_;
pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
m_ScriptSettings.init(cx.GetGeneralJSContext(), settings);
pTerrain = pTerrain_;
pLightEnv = pLightEnv_;
pGameView = pGameView_;
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pPostproc = pPostproc_;
m_PlayerID = playerID_;
m_SkipEntities = false;
m_StartingCameraTarget = INVALID_ENTITY;
// delete all existing entities
if (pSimulation2)
pSimulation2->ResetState();
only_xml = false;
// copy random map settings (before entity creation)
RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
// load player settings script (must be done before reading map)
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// load map generator with random map script
RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 20000);
// parse RMS results into terrain structure
RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500);
// parse RMS results into environment settings
RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5);
// parse RMS results into camera settings
RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5);
// apply terrain data to the world
RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
// parse RMS results into entities
RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000);
// apply misc data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
// load map settings script (must be done after reading map)
RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
}
// UnpackMap: unpack the given data from the raw data stream into local variables
int CMapReader::UnpackMap()
{
return UnpackTerrain();
}
// UnpackTerrain: unpack the terrain from the end of the input data stream
// - data: map size, heightmap, list of textures used by map, texture tile assignments
int CMapReader::UnpackTerrain()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
// first call to generator (this is skipped after first call,
// i.e. when the loop below was interrupted)
if (cur_terrain_tex == 0)
{
m_PatchesPerSide = (ssize_t)unpacker.UnpackSize();
// unpack heightmap [600us]
size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1;
m_Heightmap.resize(SQR(verticesPerSide));
unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16));
// unpack # textures
num_terrain_tex = unpacker.UnpackSize();
m_TerrainTextures.reserve(num_terrain_tex);
}
// unpack texture names; find handle for each texture.
// interruptible.
while (cur_terrain_tex < num_terrain_tex)
{
CStr texturename;
unpacker.UnpackString(texturename);
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
}
// unpack tile data [3ms]
ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE;
m_Tiles.resize(size_t(SQR(tilesPerSide)));
unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size());
// reset generator state.
cur_terrain_tex = 0;
return 0;
}
int CMapReader::ApplyTerrainData()
{
if (m_PatchesPerSide == 0)
{
// we'll probably crash when trying to use this map later
throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details.");
}
if (!only_xml)
{
// initialise the terrain
pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]);
// setup the textures on the minipatches
STileDesc* tileptr = &m_Tiles[0];
for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
mp.Priority = tileptr->m_Priority;
tileptr++;
}
}
}
}
}
CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->ReloadTerrain();
return 0;
}
// ApplyData: take all the input data, and rebuild the scene from it
int CMapReader::ApplyData()
{
// copy over the lighting parameters
if (pLightEnv)
*pLightEnv = m_LightEnv;
CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
if (pGameView && cmpPlayerManager)
{
// Default to global camera (with constraints)
pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
// TODO: Starting rotation?
CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
if (cmpPlayer && cmpPlayer->HasStartingCamera())
{
// Use player starting camera
CFixedVector3D pos = cmpPlayer->GetStartingCameraPos();
- pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
+ pGameView->ResetCameraTarget(CVector3D(pos.getX().ToFloat(), pos.getY().ToFloat(), pos.getZ().ToFloat()));
}
else if (m_StartingCameraTarget != INVALID_ENTITY)
{
// Point camera at entity
CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget);
if (cmpPosition)
{
CFixedVector3D pos = cmpPosition->GetPosition();
- pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
+ pGameView->ResetCameraTarget(CVector3D(pos.getX().ToFloat(), pos.getY().ToFloat(), pos.getZ().ToFloat()));
}
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname)
{
VfsPath filename_xml = pathname.ChangeExtension(L".xml");
CXeromyces xmb_file;
if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK)
return PSRETURN_File_ReadFailed;
// Define all the relevant elements used in the XML file
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(scenario);
EL(scriptsettings);
#undef AT
#undef EL
XMBElement root = xmb_file.GetRoot();
ENSURE(root.GetNodeName() == el_scenario);
XERO_ITER_EL(root, child)
{
int child_name = child.GetNodeName();
if (child_name == el_scriptsettings)
{
m_ScriptSettings = child.GetText();
}
}
return PSRETURN_OK;
}
void CMapSummaryReader::GetMapSettings(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
ScriptRequest rq(scriptInterface);
ScriptInterface::CreateObject(rq, ret);
if (m_ScriptSettings.empty())
return;
JS::RootedValue scriptSettingsVal(rq.cx);
scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal);
scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Holds various state data while reading maps, so that loading can be
// interrupted (e.g. to update the progress display) then later resumed.
class CXMLReader
{
NONCOPYABLE(CXMLReader);
public:
CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader)
: m_MapReader(mapReader), nodes(NULL, 0, NULL)
{
Init(xml_filename);
}
CStr ReadScriptSettings();
// read everything except for entities
void ReadXML();
// return semantics: see Loader.cpp!LoadFunc.
int ProgressiveReadEntities();
private:
CXeromyces xmb_file;
CMapReader& m_MapReader;
int el_entity;
int el_tracks;
int el_template, el_player;
int el_position, el_orientation, el_obstruction;
int el_garrison;
int el_turrets;
int el_actor;
int at_x;
int at_y;
int at_z;
int at_group, at_group2;
int at_angle;
int at_uid;
int at_seed;
int at_turret;
XMBElementList nodes; // children of root
// loop counters
size_t node_idx;
size_t entity_idx;
// # entities+nonentities processed and total (for progress calc)
int completed_jobs, total_jobs;
// maximum used entity ID, so we can safely allocate new ones
entity_id_t max_uid;
void Init(const VfsPath& xml_filename);
void ReadTerrain(XMBElement parent);
void ReadEnvironment(XMBElement parent);
void ReadCamera(XMBElement parent);
void ReadPaths(XMBElement parent);
void ReadTriggers(XMBElement parent);
int ReadEntities(XMBElement parent, double end_time);
};
void CXMLReader::Init(const VfsPath& xml_filename)
{
// must only assign once, so do it here
node_idx = entity_idx = 0;
if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK)
throw PSERROR_Game_World_MapLoadFailed("Could not read map XML file!");
// define the elements and attributes that are frequently used in the XML file,
// so we don't need to do lots of string construction and comparison when
// reading the data.
// (Needs to be synchronised with the list in CXMLReader - ugh)
#define EL(x) el_##x = xmb_file.GetElementID(#x)
#define AT(x) at_##x = xmb_file.GetAttributeID(#x)
EL(entity);
EL(tracks);
EL(template);
EL(player);
EL(position);
EL(garrison);
EL(turrets);
EL(orientation);
EL(obstruction);
EL(actor);
AT(x); AT(y); AT(z);
AT(group); AT(group2);
AT(angle);
AT(uid);
AT(seed);
AT(turret);
#undef AT
#undef EL
XMBElement root = xmb_file.GetRoot();
ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
nodes = root.GetChildNodes();
// find out total number of entities+nonentities
// (used when calculating progress)
completed_jobs = 0;
total_jobs = 0;
for (XMBElement node : nodes)
total_jobs += node.GetChildNodes().size();
// Find the maximum entity ID, so we can safely allocate new IDs without conflicts
max_uid = SYSTEM_ENTITY;
XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
XERO_ITER_EL(ents, ent)
{
CStr uid = ent.GetAttributes().GetNamedItem(at_uid);
max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt());
}
}
CStr CXMLReader::ReadScriptSettings()
{
XMBElement root = xmb_file.GetRoot();
ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
nodes = root.GetChildNodes();
XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings"));
return settings.GetText();
}
void CXMLReader::ReadTerrain(XMBElement parent)
{
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
AT(patches);
AT(texture);
AT(priority);
AT(height);
#undef AT
ssize_t patches = 9;
CStr texture = "grass1_spring";
int priority = 0;
u16 height = 16384;
XERO_ITER_ATTR(parent, attr)
{
if (attr.Name == at_patches)
patches = attr.Value.ToInt();
else if (attr.Name == at_texture)
texture = attr.Value;
else if (attr.Name == at_priority)
priority = attr.Value.ToInt();
else if (attr.Name == at_height)
height = (u16)attr.Value.ToInt();
}
m_MapReader.m_PatchesPerSide = patches;
// Load the texture
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture);
m_MapReader.pTerrain->Initialize(patches, NULL);
// Fill the heightmap
u16* heightmap = m_MapReader.pTerrain->GetHeightMap();
ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide();
for (ssize_t i = 0; i < SQR(verticesPerSide); ++i)
heightmap[i] = height;
// Fill the texture map
for (ssize_t pz = 0; pz < patches; ++pz)
{
for (ssize_t px = 0; px < patches; ++px)
{
CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail
for (ssize_t z = 0; z < PATCH_SIZE; ++z)
{
for (ssize_t x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex = texentry;
patch->m_MiniPatches[z][x].Priority = priority;
}
}
}
}
}
void CXMLReader::ReadEnvironment(XMBElement parent)
{
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(posteffect);
EL(skyset);
EL(suncolor);
EL(sunelevation);
EL(sunrotation);
EL(ambientcolor);
EL(water);
EL(waterbody);
EL(type);
EL(color);
EL(tint);
EL(height);
EL(waviness);
EL(murkiness);
EL(windangle);
EL(fog);
EL(fogcolor);
EL(fogfactor);
EL(fogthickness);
EL(postproc);
EL(brightness);
EL(contrast);
EL(saturation);
EL(bloom);
AT(r); AT(g); AT(b);
#undef AT
#undef EL
XERO_ITER_EL(parent, element)
{
int element_name = element.GetNodeName();
XMBAttributeList attrs = element.GetAttributes();
if (element_name == el_skyset)
{
if (m_MapReader.pSkyMan)
m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8());
}
else if (element_name == el_suncolor)
{
- m_MapReader.m_LightEnv.m_SunColor = RGBColor(
+ m_MapReader.m_LightEnv.m_SunColor = RGBcolor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_sunelevation)
{
m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_sunrotation)
{
m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_ambientcolor)
{
- m_MapReader.m_LightEnv.m_AmbientColor = RGBColor(
+ m_MapReader.m_LightEnv.m_AmbientColor = RGBcolor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_fog)
{
XERO_ITER_EL(element, fog)
{
int fog_element_name = fog.GetNodeName();
if (fog_element_name == el_fogcolor)
{
XMBAttributeList fogAttributes = fog.GetAttributes();
- m_MapReader.m_LightEnv.m_FogColor = RGBColor(
+ m_MapReader.m_LightEnv.m_FogColor = RGBcolor(
fogAttributes.GetNamedItem(at_r).ToFloat(),
fogAttributes.GetNamedItem(at_g).ToFloat(),
fogAttributes.GetNamedItem(at_b).ToFloat());
}
else if (fog_element_name == el_fogfactor)
{
m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat();
}
else if (fog_element_name == el_fogthickness)
{
m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat();
}
}
}
else if (element_name == el_postproc)
{
XERO_ITER_EL(element, postproc)
{
int post_element_name = postproc.GetNodeName();
if (post_element_name == el_brightness)
{
m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat();
}
else if (post_element_name == el_contrast)
{
m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat();
}
else if (post_element_name == el_saturation)
{
m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat();
}
else if (post_element_name == el_bloom)
{
m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat();
}
else if (post_element_name == el_posteffect)
{
if (m_MapReader.pPostproc)
m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8());
}
}
}
else if (element_name == el_water)
{
XERO_ITER_EL(element, waterbody)
{
ENSURE(waterbody.GetNodeName() == el_waterbody);
XERO_ITER_EL(waterbody, waterelement)
{
int water_element_name = waterelement.GetNodeName();
if (water_element_name == el_height)
{
CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText()));
continue;
}
// The rest are purely graphical effects, and should be ignored if
// graphics are disabled
if (!m_MapReader.pWaterMan)
continue;
if (water_element_name == el_type)
{
if (waterelement.GetText() == "default")
m_MapReader.pWaterMan->m_WaterType = L"ocean";
else
m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8();
}
#define READ_COLOR(el, out) \
else if (water_element_name == el) \
{ \
XMBAttributeList colorAttrs = waterelement.GetAttributes(); \
- out = CColor( \
+ out = RGBAcolor( \
colorAttrs.GetNamedItem(at_r).ToFloat(), \
colorAttrs.GetNamedItem(at_g).ToFloat(), \
colorAttrs.GetNamedItem(at_b).ToFloat(), \
1.f); \
}
#define READ_FLOAT(el, out) \
else if (water_element_name == el) \
{ \
out = waterelement.GetText().ToFloat(); \
} \
READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor)
READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint)
READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness)
READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness)
READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle)
#undef READ_FLOAT
#undef READ_COLOR
else
debug_warn(L"Invalid map XML data");
}
}
}
else
debug_warn(L"Invalid map XML data");
}
m_MapReader.m_LightEnv.CalculateSunDirection();
}
void CXMLReader::ReadCamera(XMBElement parent)
{
// defaults if we don't find player starting camera
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(declination);
EL(rotation);
EL(position);
AT(angle);
AT(x); AT(y); AT(z);
#undef AT
#undef EL
float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
CVector3D translation = CVector3D(100, 150, -100);
XERO_ITER_EL(parent, element)
{
int element_name = element.GetNodeName();
XMBAttributeList attrs = element.GetAttributes();
if (element_name == el_declination)
{
declination = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_rotation)
{
rotation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_position)
{
translation = CVector3D(
attrs.GetNamedItem(at_x).ToFloat(),
attrs.GetNamedItem(at_y).ToFloat(),
attrs.GetNamedItem(at_z).ToFloat());
}
else
debug_warn(L"Invalid map XML data");
}
if (m_MapReader.pGameView)
{
m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation);
m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation);
m_MapReader.pGameView->GetCamera()->UpdateFrustum();
}
}
void CXMLReader::ReadPaths(XMBElement parent)
{
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(path);
EL(rotation);
EL(node);
EL(position);
EL(target);
AT(name);
AT(timescale);
AT(orientation);
AT(mode);
AT(style);
AT(x);
AT(y);
AT(z);
AT(deltatime);
#undef EL
#undef AT
CmpPtr cmpCinemaManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
XERO_ITER_EL(parent, element)
{
int elementName = element.GetNodeName();
if (elementName == el_path)
{
CCinemaData pathData;
XMBAttributeList attrs = element.GetAttributes();
CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8());
pathData.m_Name = pathName;
pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale));
pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8();
pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8();
pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8();
TNSpline positionSpline, targetSpline;
fixed lastPositionTime = fixed::Zero();
fixed lastTargetTime = fixed::Zero();
XERO_ITER_EL(element, pathChild)
{
elementName = pathChild.GetNodeName();
attrs = pathChild.GetAttributes();
// Load node data used for spline
if (elementName == el_node)
{
lastPositionTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
XERO_ITER_EL(pathChild, nodeChild)
{
elementName = nodeChild.GetNodeName();
attrs = nodeChild.GetAttributes();
if (elementName == el_position)
{
CFixedVector3D position(fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
positionSpline.AddNode(position, CFixedVector3D(), lastPositionTime);
lastPositionTime = fixed::Zero();
}
else if (elementName == el_rotation)
{
// TODO: Implement rotation slerp/spline as another object
}
else if (elementName == el_target)
{
CFixedVector3D targetPosition(fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime);
lastTargetTime = fixed::Zero();
}
else
LOGWARNING("Invalid cinematic element for node child");
}
}
else
LOGWARNING("Invalid cinematic element for path child");
}
// Construct cinema path with data gathered
CCinemaPath path(pathData, positionSpline, targetSpline);
if (path.Empty())
{
LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8());
return;
}
if (!cmpCinemaManager)
continue;
if (!cmpCinemaManager->HasPath(pathName))
cmpCinemaManager->AddPath(path);
else
LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8());
}
else
LOGWARNING("Invalid path child with name '%s'", element.GetText());
}
}
void CXMLReader::ReadTriggers(XMBElement UNUSED(parent))
{
}
int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
XMBElementList entities = parent.GetChildNodes();
ENSURE(m_MapReader.pSimulation2);
CSimulation2& sim = *m_MapReader.pSimulation2;
CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY);
while (entity_idx < entities.size())
{
// all new state at this scope and below doesn't need to be
// wrapped, since we only yield after a complete iteration.
XMBElement entity = entities[entity_idx++];
ENSURE(entity.GetNodeName() == el_entity);
XMBAttributeList attrs = entity.GetAttributes();
CStr uid = attrs.GetNamedItem(at_uid);
ENSURE(!uid.empty());
int EntityUid = uid.ToInt();
CStrW TemplateName;
int PlayerID = 0;
std::vector Garrison;
std::vector> Turrets;
CFixedVector3D Position;
CFixedVector3D Orientation;
long Seed = -1;
// Obstruction control groups.
entity_id_t ControlGroup = INVALID_ENTITY;
entity_id_t ControlGroup2 = INVALID_ENTITY;
XERO_ITER_EL(entity, setting)
{
int element_name = setting.GetNodeName();
//
if (element_name == el_template)
{
TemplateName = setting.GetText().FromUTF8();
}
//
else if (element_name == el_player)
{
PlayerID = setting.GetText().ToInt();
}
//
else if (element_name == el_position)
{
XMBAttributeList positionAttrs = setting.GetAttributes();
Position = CFixedVector3D(
fixed::FromString(positionAttrs.GetNamedItem(at_x)),
fixed::FromString(positionAttrs.GetNamedItem(at_y)),
fixed::FromString(positionAttrs.GetNamedItem(at_z)));
}
//
else if (element_name == el_orientation)
{
XMBAttributeList orientationAttrs = setting.GetAttributes();
Orientation = CFixedVector3D(
fixed::FromString(orientationAttrs.GetNamedItem(at_x)),
fixed::FromString(orientationAttrs.GetNamedItem(at_y)),
fixed::FromString(orientationAttrs.GetNamedItem(at_z)));
// TODO: what happens if some attributes are missing?
}
//
else if (element_name == el_obstruction)
{
XMBAttributeList obstructionAttrs = setting.GetAttributes();
ControlGroup = obstructionAttrs.GetNamedItem(at_group).ToInt();
ControlGroup2 = obstructionAttrs.GetNamedItem(at_group2).ToInt();
}
//
else if (element_name == el_garrison)
{
XMBElementList garrison = setting.GetChildNodes();
Garrison.reserve(garrison.size());
for (const XMBElement& garr_ent : garrison)
{
XMBAttributeList garrisonAttrs = garr_ent.GetAttributes();
Garrison.push_back(garrisonAttrs.GetNamedItem(at_uid).ToInt());
}
}
//
else if (element_name == el_turrets)
{
XMBElementList turrets = setting.GetChildNodes();
Turrets.reserve(turrets.size());
for (const XMBElement& turretPoint : turrets)
{
XMBAttributeList turretAttrs = turretPoint.GetAttributes();
Turrets.emplace_back(
turretAttrs.GetNamedItem(at_turret),
turretAttrs.GetNamedItem(at_uid).ToInt()
);
}
}
//
else if (element_name == el_actor)
{
XMBAttributeList attrs = setting.GetAttributes();
CStr seedStr = attrs.GetNamedItem(at_seed);
if (!seedStr.empty())
{
Seed = seedStr.ToLong();
ENSURE(Seed >= 0);
}
}
else
debug_warn(L"Invalid map XML data");
}
entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
entity_id_t player = cmpPlayerManager->GetPlayerByID(PlayerID);
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
{ // Don't add entities with invalid player IDs
LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(TemplateName));
}
else
{
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
- cmpPosition->JumpTo(Position.X, Position.Z);
- cmpPosition->SetYRotation(Orientation.Y);
+ cmpPosition->JumpTo(Position.getX(), Position.getZ());
+ cmpPosition->SetYRotation(Orientation.getY());
// TODO: other parts of the position
}
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
cmpOwnership->SetOwner(PlayerID);
if (!Garrison.empty())
{
CmpPtr cmpGarrisonHolder(sim, ent);
if (cmpGarrisonHolder)
cmpGarrisonHolder->SetInitEntities(std::move(Garrison));
else
LOGERROR("CXMLMapReader::ReadEntities() entity '%d' of player '%d' has no GarrisonHolder component and thus cannot garrison units.", ent, PlayerID);
}
if (!Turrets.empty())
{
CmpPtr cmpTurretHolder(sim, ent);
if (cmpTurretHolder)
cmpTurretHolder->SetInitEntities(std::move(Turrets));
else
LOGERROR("CXMLMapReader::ReadEntities() entity '%d' of player '%d' has no TurretHolder component and thus cannot use turrets.", ent, PlayerID);
}
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
{
if (ControlGroup != INVALID_ENTITY)
cmpObstruction->SetControlGroup(ControlGroup);
if (ControlGroup2 != INVALID_ENTITY)
cmpObstruction->SetControlGroup2(ControlGroup2);
cmpObstruction->ResolveFoundationCollisions();
}
CmpPtr cmpVisual(sim, ent);
if (cmpVisual)
{
if (Seed != -1)
cmpVisual->SetActorSeed((u32)Seed);
// TODO: variation/selection strings
}
if (PlayerID == m_MapReader.m_PlayerID && (boost::algorithm::ends_with(TemplateName, L"civil_centre") || m_MapReader.m_StartingCameraTarget == INVALID_ENTITY))
{
// Focus on civil centre or first entity owned by player
m_MapReader.m_StartingCameraTarget = ent;
}
}
completed_jobs++;
LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
}
return 0;
}
void CXMLReader::ReadXML()
{
for (XMBElement node : nodes)
{
CStr name = xmb_file.GetElementString(node.GetNodeName());
if (name == "Terrain")
{
ReadTerrain(node);
}
else if (name == "Environment")
{
ReadEnvironment(node);
}
else if (name == "Camera")
{
ReadCamera(node);
}
else if (name == "ScriptSettings")
{
// Already loaded - this is to prevent an assertion
}
else if (name == "Entities")
{
// Handled by ProgressiveReadEntities instead
}
else if (name == "Paths")
{
ReadPaths(node);
}
else if (name == "Triggers")
{
ReadTriggers(node);
}
else if (name == "Script")
{
if (m_MapReader.pSimulation2)
m_MapReader.pSimulation2->SetStartupScript(node.GetText());
}
else
{
debug_printf("Invalid XML element in map file: %s\n", name.c_str());
debug_warn(L"Invalid map XML data");
}
}
}
int CXMLReader::ProgressiveReadEntities()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
int ret;
while (node_idx < nodes.size())
{
XMBElement node = nodes[node_idx];
CStr name = xmb_file.GetElementString(node.GetNodeName());
if (name == "Entities")
{
if (!m_MapReader.m_SkipEntities)
{
ret = ReadEntities(node, end_time);
if (ret != 0) // error or timed out
return ret;
}
}
node_idx++;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// load script settings from map
int CMapReader::LoadScriptSettings()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
// parse the script settings
if (pSimulation2)
pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
return 0;
}
// load player settings script
int CMapReader::LoadPlayerSettings()
{
if (pSimulation2)
pSimulation2->LoadPlayerSettings(true);
return 0;
}
// load map settings script
int CMapReader::LoadMapSettings()
{
if (pSimulation2)
pSimulation2->LoadMapSettings();
return 0;
}
int CMapReader::ReadXML()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
xml_reader->ReadXML();
return 0;
}
// progressive
int CMapReader::ReadXMLEntities()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
int ret = xml_reader->ProgressiveReadEntities();
// finished or failed
if (ret <= 0)
{
SAFE_DELETE(xml_reader);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int CMapReader::LoadRMSettings()
{
// copy random map settings over to sim
ENSURE(pSimulation2);
pSimulation2->SetMapSettings(m_ScriptSettings);
return 0;
}
int CMapReader::GenerateMap()
{
ScriptRequest rq(pSimulation2->GetScriptInterface());
if (!m_MapGen)
{
// Initialize map generator
m_MapGen = new CMapGenerator();
VfsPath scriptPath;
if (m_ScriptFile.length())
scriptPath = L"maps/random/"+m_ScriptFile;
// Stringify settings to pass across threads
std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(&m_ScriptSettings);
// Try to generate map
m_MapGen->GenerateMap(scriptPath, scriptSettings);
}
// Check status
int progress = m_MapGen->GetProgress();
if (progress < 0)
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else if (progress == 0)
{
// Finished, get results as StructuredClone object, which must be read to obtain the JS::Value
ScriptInterface::StructuredClone results = m_MapGen->GetResults();
// Parse data into simulation context
JS::RootedValue data(rq.cx);
pSimulation2->GetScriptInterface().ReadStructuredClone(results, &data);
if (data.isUndefined())
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else
{
m_MapData.init(rq.cx, data);
}
}
else
{
// Still working
// Sleep for a while, slowing down the rendering thread
// to allow more CPU for the map generator thread
SDL_Delay(100);
}
// return progress
return progress;
};
int CMapReader::ParseTerrain()
{
TIMER(L"ParseTerrain");
ScriptRequest rq(pSimulation2->GetScriptInterface());
// parse terrain from map data
// an error here should stop the loading process
#define GET_TERRAIN_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
{ LOGERROR("CMapReader::ParseTerrain() failed to get '%s' property", #prop);\
throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
u32 size;
GET_TERRAIN_PROPERTY(m_MapData, size, size)
m_PatchesPerSide = size / PATCH_SIZE;
// flat heightmap of u16 data
GET_TERRAIN_PROPERTY(m_MapData, height, m_Heightmap)
// load textures
std::vector textureNames;
GET_TERRAIN_PROPERTY(m_MapData, textureNames, textureNames)
num_terrain_tex = textureNames.size();
while (cur_terrain_tex < num_terrain_tex)
{
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
}
// build tile data
m_Tiles.resize(SQR(size));
JS::RootedValue tileData(rq.cx);
GET_TERRAIN_PROPERTY(m_MapData, tileData, &tileData)
// parse tile data object into flat arrays
std::vector tileIndex;
std::vector tilePriority;
GET_TERRAIN_PROPERTY(tileData, index, tileIndex);
GET_TERRAIN_PROPERTY(tileData, priority, tilePriority);
ENSURE(SQR(size) == tileIndex.size() && SQR(size) == tilePriority.size());
// reorder by patches and store
for (size_t x = 0; x < size; ++x)
{
size_t patchX = x / PATCH_SIZE;
size_t offX = x % PATCH_SIZE;
for (size_t y = 0; y < size; ++y)
{
size_t patchY = y / PATCH_SIZE;
size_t offY = y % PATCH_SIZE;
STileDesc tile;
tile.m_Tex1Index = tileIndex[y*size + x];
tile.m_Tex2Index = 0xFFFF;
tile.m_Priority = tilePriority[y*size + x];
m_Tiles[(patchY * m_PatchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)] = tile;
}
}
// reset generator state
cur_terrain_tex = 0;
#undef GET_TERRAIN_PROPERTY
return 0;
}
int CMapReader::ParseEntities()
{
TIMER(L"ParseEntities");
ScriptRequest rq(pSimulation2->GetScriptInterface());
// parse entities from map data
std::vector entities;
if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData, "entities", entities))
LOGWARNING("CMapReader::ParseEntities() failed to get 'entities' property");
CSimulation2& sim = *pSimulation2;
CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY);
size_t entity_idx = 0;
size_t num_entities = entities.size();
Entity currEnt;
while (entity_idx < num_entities)
{
// Get current entity struct
currEnt = entities[entity_idx];
entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
{ // Don't add entities with invalid player IDs
LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(currEnt.templateName));
}
else
{
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
- cmpPosition->JumpTo(currEnt.position.X * (int)TERRAIN_TILE_SIZE, currEnt.position.Z * (int)TERRAIN_TILE_SIZE);
- cmpPosition->SetYRotation(currEnt.rotation.Y);
+ cmpPosition->JumpTo(currEnt.position.getX() * (int)TERRAIN_TILE_SIZE, currEnt.position.getZ() * (int)TERRAIN_TILE_SIZE);
+ cmpPosition->SetYRotation(currEnt.rotation.getY());
// TODO: other parts of the position
}
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
cmpOwnership->SetOwner(currEnt.playerID);
// Detect and fix collisions between foundation-blocking entities.
// This presently serves to copy wall tower control groups to wall
// segments, allowing players to expand RMS-generated walls.
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
cmpObstruction->ResolveFoundationCollisions();
if (currEnt.playerID == m_PlayerID && (boost::algorithm::ends_with(currEnt.templateName, L"civil_centre") || m_StartingCameraTarget == INVALID_ENTITY))
{
// Focus on civil centre or first entity owned by player
m_StartingCameraTarget = currEnt.entityID;
}
}
entity_idx++;
}
return 0;
}
int CMapReader::ParseEnvironment()
{
// parse environment settings from map data
ScriptRequest rq(pSimulation2->GetScriptInterface());
#define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
LOGWARNING("CMapReader::ParseEnvironment() failed to get '%s' property", #prop);
JS::RootedValue envObj(rq.cx);
GET_ENVIRONMENT_PROPERTY(m_MapData, Environment, &envObj)
if (envObj.isUndefined())
{
LOGWARNING("CMapReader::ParseEnvironment(): Environment settings not found");
return 0;
}
if (pPostproc)
pPostproc->SetPostEffect(L"default");
std::wstring skySet;
GET_ENVIRONMENT_PROPERTY(envObj, SkySet, skySet)
if (pSkyMan)
pSkyMan->SetSkySet(skySet);
- CColor sunColor;
+ RGBAcolor sunColor;
GET_ENVIRONMENT_PROPERTY(envObj, SunColor, sunColor)
- m_LightEnv.m_SunColor = RGBColor(sunColor.r, sunColor.g, sunColor.b);
+ m_LightEnv.m_SunColor = RGBcolor(sunColor.getR(), sunColor.getG(), sunColor.getB());
GET_ENVIRONMENT_PROPERTY(envObj, SunElevation, m_LightEnv.m_Elevation)
GET_ENVIRONMENT_PROPERTY(envObj, SunRotation, m_LightEnv.m_Rotation)
- CColor ambientColor;
+ RGBAcolor ambientColor;
GET_ENVIRONMENT_PROPERTY(envObj, AmbientColor, ambientColor)
- m_LightEnv.m_AmbientColor = RGBColor(ambientColor.r, ambientColor.g, ambientColor.b);
+ m_LightEnv.m_AmbientColor = RGBcolor(ambientColor.getR(), ambientColor.getG(), ambientColor.getB());
// Water properties
JS::RootedValue waterObj(rq.cx);
GET_ENVIRONMENT_PROPERTY(envObj, Water, &waterObj)
JS::RootedValue waterBodyObj(rq.cx);
GET_ENVIRONMENT_PROPERTY(waterObj, WaterBody, &waterBodyObj)
// Water level - necessary
float waterHeight;
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Height, waterHeight)
CmpPtr cmpWaterManager(*pSimulation2, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
// If we have graphics, get rest of settings
if (pWaterMan)
{
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Type, pWaterMan->m_WaterType)
if (pWaterMan->m_WaterType == L"default")
pWaterMan->m_WaterType = L"ocean";
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Color, pWaterMan->m_WaterColor)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Tint, pWaterMan->m_WaterTint)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Waviness, pWaterMan->m_Waviness)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Murkiness, pWaterMan->m_Murkiness)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, WindAngle, pWaterMan->m_WindAngle)
}
JS::RootedValue fogObject(rq.cx);
GET_ENVIRONMENT_PROPERTY(envObj, Fog, &fogObject);
GET_ENVIRONMENT_PROPERTY(fogObject, FogFactor, m_LightEnv.m_FogFactor);
GET_ENVIRONMENT_PROPERTY(fogObject, FogThickness, m_LightEnv.m_FogMax);
- CColor fogColor;
+ RGBAcolor fogColor;
GET_ENVIRONMENT_PROPERTY(fogObject, FogColor, fogColor);
- m_LightEnv.m_FogColor = RGBColor(fogColor.r, fogColor.g, fogColor.b);
+ m_LightEnv.m_FogColor = RGBcolor(fogColor.getR(), fogColor.getG(), fogColor.getB());
JS::RootedValue postprocObject(rq.cx);
GET_ENVIRONMENT_PROPERTY(envObj, Postproc, &postprocObject);
std::wstring postProcEffect;
GET_ENVIRONMENT_PROPERTY(postprocObject, PostprocEffect, postProcEffect);
if (pPostproc)
pPostproc->SetPostEffect(postProcEffect);
GET_ENVIRONMENT_PROPERTY(postprocObject, Brightness, m_LightEnv.m_Brightness);
GET_ENVIRONMENT_PROPERTY(postprocObject, Contrast, m_LightEnv.m_Contrast);
GET_ENVIRONMENT_PROPERTY(postprocObject, Saturation, m_LightEnv.m_Saturation);
GET_ENVIRONMENT_PROPERTY(postprocObject, Bloom, m_LightEnv.m_Bloom);
m_LightEnv.CalculateSunDirection();
#undef GET_ENVIRONMENT_PROPERTY
return 0;
}
int CMapReader::ParseCamera()
{
ScriptRequest rq(pSimulation2->GetScriptInterface());
// parse camera settings from map data
// defaults if we don't find player starting camera
float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
CVector3D translation = CVector3D(100, 150, -100);
#define GET_CAMERA_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
LOGWARNING("CMapReader::ParseCamera() failed to get '%s' property", #prop);
JS::RootedValue cameraObj(rq.cx);
GET_CAMERA_PROPERTY(m_MapData, Camera, &cameraObj)
if (!cameraObj.isUndefined())
{ // If camera property exists, read values
CFixedVector3D pos;
GET_CAMERA_PROPERTY(cameraObj, Position, pos)
translation = pos;
GET_CAMERA_PROPERTY(cameraObj, Rotation, rotation)
GET_CAMERA_PROPERTY(cameraObj, Declination, declination)
}
#undef GET_CAMERA_PROPERTY
if (pGameView)
{
pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
pGameView->GetCamera()->m_Orientation.RotateY(rotation);
pGameView->GetCamera()->m_Orientation.Translate(translation);
pGameView->GetCamera()->UpdateFrustum();
}
return 0;
}
CMapReader::~CMapReader()
{
// Cleaup objects
delete xml_reader;
delete m_MapGen;
}
Index: source/graphics/MapWriter.cpp
===================================================================
--- source/graphics/MapWriter.cpp (revision 24796)
+++ source/graphics/MapWriter.cpp (working copy)
@@ -1,505 +1,505 @@
/* Copyright (C) 2021 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 "Camera.h"
#include "CinemaManager.h"
#include "GameView.h"
#include "LightEnv.h"
#include "MapReader.h"
#include "MapWriter.h"
#include "Patch.h"
#include "Terrain.h"
#include "TerrainTextureEntry.h"
#include "TerrainTextureManager.h"
#include "maths/MathUtil.h"
#include "maths/NUSpline.h"
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "ps/Filesystem.h"
#include "ps/XML/XMLWriter.h"
#include "renderer/PostprocManager.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpCinemaManager.h"
#include "simulation2/components/ICmpGarrisonHolder.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpTurretHolder.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/components/ICmpWaterManager.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
// CMapWriter constructor: nothing to do at the minute
CMapWriter::CMapWriter()
{
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// SaveMap: try to save the current map to the given file
void CMapWriter::SaveMap(const VfsPath& pathname, CTerrain* pTerrain,
WaterManager* pWaterMan, SkyManager* pSkyMan,
CLightEnv* pLightEnv, CCamera* pCamera, CCinemaManager* UNUSED(pCinema),
CPostprocManager* pPostproc,
CSimulation2* pSimulation2)
{
CFilePacker packer(FILE_VERSION, "PSMP");
// build necessary data
PackMap(packer, pTerrain);
try
{
// write it out
packer.Write(pathname);
}
catch (PSERROR_File_WriteFailed&)
{
LOGERROR("Failed to write map '%s'", pathname.string8());
return;
}
VfsPath pathnameXML = pathname.ChangeExtension(L".xml");
WriteXML(pathnameXML, pWaterMan, pSkyMan, pLightEnv, pCamera, pPostproc, pSimulation2);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// GetHandleIndex: return the index of the given handle in the given list; or 0xFFFF if
// handle isn't in list
static u16 GetEntryIndex(const CTerrainTextureEntry* entry, const std::vector& entries)
{
const size_t limit = std::min(entries.size(), size_t(0xFFFEu)); // paranoia
for (size_t i=0;i& textures,
std::vector& tiles)
{
// the list of all handles in use
std::vector entries;
// resize tile array to required size
tiles.resize(SQR(pTerrain->GetVerticesPerSide()-1));
STileDesc* tileptr=&tiles[0];
// now iterate through all the tiles
const ssize_t patchesPerSide=pTerrain->GetPatchesPerSide();
for (ssize_t j=0;jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
u16 index=u16(GetEntryIndex(mp.GetTextureEntry(),entries));
if (index==0xFFFF) {
index=(u16)entries.size();
entries.push_back(mp.GetTextureEntry());
}
tileptr->m_Tex1Index=index;
tileptr->m_Tex2Index=0xFFFF;
tileptr->m_Priority=mp.GetPriority();
tileptr++;
}
}
}
}
// now find the texture names for each handle
for (size_t i=0;iGetTag();
}
textures.push_back(texturename);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PackMap: pack the current world into a raw data stream
void CMapWriter::PackMap(CFilePacker& packer, CTerrain* pTerrain)
{
// now pack everything up
PackTerrain(packer, pTerrain);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PackTerrain: pack the terrain onto the end of the output data stream
// - data: map size, heightmap, list of textures used by map, texture tile assignments
void CMapWriter::PackTerrain(CFilePacker& packer, CTerrain* pTerrain)
{
// pack map size
const ssize_t mapsize = pTerrain->GetPatchesPerSide();
packer.PackSize(mapsize);
// pack heightmap
packer.PackRaw(pTerrain->GetHeightMap(),sizeof(u16)*SQR(pTerrain->GetVerticesPerSide()));
// the list of textures used by map
std::vector terrainTextures;
// descriptions of each tile
std::vector tiles;
// build lists by scanning through the terrain
EnumTerrainTextures(pTerrain, terrainTextures, tiles);
// pack texture names
const size_t numTextures = terrainTextures.size();
packer.PackSize(numTextures);
for (size_t i=0;i(FILE_VERSION));
ENSURE(pSimulation2);
CSimulation2& sim = *pSimulation2;
if (!sim.GetStartupScript().empty())
{
XMLWriter_Element scriptTag(xmlMapFile, "Script");
scriptTag.Text(sim.GetStartupScript().c_str(), true);
}
{
XMLWriter_Element environmentTag(xmlMapFile, "Environment");
environmentTag.Setting("SkySet", pSkyMan->GetSkySet());
{
XMLWriter_Element sunColorTag(xmlMapFile, "SunColor");
- sunColorTag.Attribute("r", pLightEnv->m_SunColor.X); // yes, it's X/Y/Z...
- sunColorTag.Attribute("g", pLightEnv->m_SunColor.Y);
- sunColorTag.Attribute("b", pLightEnv->m_SunColor.Z);
+ sunColorTag.Attribute("r", pLightEnv->m_SunColor.getR()); // yes, it's X/Y/Z...
+ sunColorTag.Attribute("g", pLightEnv->m_SunColor.getG());
+ sunColorTag.Attribute("b", pLightEnv->m_SunColor.getB());
}
{
XMLWriter_Element SunElevationTag(xmlMapFile, "SunElevation");
SunElevationTag.Attribute("angle", pLightEnv->m_Elevation);
}
{
XMLWriter_Element sunRotationTag(xmlMapFile, "SunRotation");
sunRotationTag.Attribute("angle", pLightEnv->m_Rotation);
}
{
XMLWriter_Element ambientColorTag(xmlMapFile, "AmbientColor");
- ambientColorTag.Attribute("r", pLightEnv->m_AmbientColor.X);
- ambientColorTag.Attribute("g", pLightEnv->m_AmbientColor.Y);
- ambientColorTag.Attribute("b", pLightEnv->m_AmbientColor.Z);
+ ambientColorTag.Attribute("r", pLightEnv->m_AmbientColor.getR());
+ ambientColorTag.Attribute("g", pLightEnv->m_AmbientColor.getG());
+ ambientColorTag.Attribute("b", pLightEnv->m_AmbientColor.getB());
}
{
XMLWriter_Element fogTag(xmlMapFile, "Fog");
fogTag.Setting("FogFactor", pLightEnv->m_FogFactor);
fogTag.Setting("FogThickness", pLightEnv->m_FogMax);
{
XMLWriter_Element fogColorTag(xmlMapFile, "FogColor");
- fogColorTag.Attribute("r", pLightEnv->m_FogColor.X);
- fogColorTag.Attribute("g", pLightEnv->m_FogColor.Y);
- fogColorTag.Attribute("b", pLightEnv->m_FogColor.Z);
+ fogColorTag.Attribute("r", pLightEnv->m_FogColor.getR());
+ fogColorTag.Attribute("g", pLightEnv->m_FogColor.getG());
+ fogColorTag.Attribute("b", pLightEnv->m_FogColor.getB());
}
}
{
XMLWriter_Element waterTag(xmlMapFile, "Water");
{
XMLWriter_Element waterBodyTag(xmlMapFile, "WaterBody");
CmpPtr cmpWaterManager(sim, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
waterBodyTag.Setting("Type", pWaterMan->m_WaterType);
{
XMLWriter_Element colorTag(xmlMapFile, "Color");
- colorTag.Attribute("r", pWaterMan->m_WaterColor.r);
- colorTag.Attribute("g", pWaterMan->m_WaterColor.g);
- colorTag.Attribute("b", pWaterMan->m_WaterColor.b);
+ colorTag.Attribute("r", pWaterMan->m_WaterColor.getR());
+ colorTag.Attribute("g", pWaterMan->m_WaterColor.getG());
+ colorTag.Attribute("b", pWaterMan->m_WaterColor.getB());
}
{
XMLWriter_Element tintTag(xmlMapFile, "Tint");
- tintTag.Attribute("r", pWaterMan->m_WaterTint.r);
- tintTag.Attribute("g", pWaterMan->m_WaterTint.g);
- tintTag.Attribute("b", pWaterMan->m_WaterTint.b);
+ tintTag.Attribute("r", pWaterMan->m_WaterTint.getR());
+ tintTag.Attribute("g", pWaterMan->m_WaterTint.getG());
+ tintTag.Attribute("b", pWaterMan->m_WaterTint.getB());
}
waterBodyTag.Setting("Height", cmpWaterManager->GetExactWaterLevel(0, 0));
waterBodyTag.Setting("Waviness", pWaterMan->m_Waviness);
waterBodyTag.Setting("Murkiness", pWaterMan->m_Murkiness);
waterBodyTag.Setting("WindAngle", pWaterMan->m_WindAngle);
}
}
{
XMLWriter_Element postProcTag(xmlMapFile, "Postproc");
{
postProcTag.Setting("Brightness", pLightEnv->m_Brightness);
postProcTag.Setting("Contrast", pLightEnv->m_Contrast);
postProcTag.Setting("Saturation", pLightEnv->m_Saturation);
postProcTag.Setting("Bloom", pLightEnv->m_Bloom);
postProcTag.Setting("PostEffect", pPostproc->GetPostEffect());
}
}
}
{
XMLWriter_Element cameraTag(xmlMapFile, "Camera");
{
XMLWriter_Element positionTag(xmlMapFile, "Position");
CVector3D pos = pCamera->GetOrientation().GetTranslation();
- positionTag.Attribute("x", pos.X);
- positionTag.Attribute("y", pos.Y);
- positionTag.Attribute("z", pos.Z);
+ positionTag.Attribute("x", pos.getX());
+ positionTag.Attribute("y", pos.getY());
+ positionTag.Attribute("z", pos.getZ());
}
CVector3D in = pCamera->GetOrientation().GetIn();
// Convert to spherical coordinates
- float rotation = atan2(in.X, in.Z);
- float declination = atan2(sqrt(in.X*in.X + in.Z*in.Z), in.Y) - static_cast(M_PI / 2);
+ float rotation = atan2(in.getX(), in.getZ());
+ float declination = atan2(sqrt(in.getX()*in.getX() + in.getZ()*in.getZ()), in.getY()) - static_cast(M_PI / 2);
{
XMLWriter_Element rotationTag(xmlMapFile, "Rotation");
rotationTag.Attribute("angle", rotation);
}
{
XMLWriter_Element declinationTag(xmlMapFile, "Declination");
declinationTag.Attribute("angle", declination);
}
}
{
std::string settings = sim.GetMapSettingsString();
if (!settings.empty())
{
XMLWriter_Element scriptSettingsTag(xmlMapFile, "ScriptSettings");
scriptSettingsTag.Text(("\n" + settings + "\n").c_str(), true);
}
}
{
XMLWriter_Element entitiesTag(xmlMapFile, "Entities");
CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY);
ENSURE(cmpTemplateManager);
// This will probably need to be changed in the future, but for now we'll
// just save all entities that have a position
CSimulation2::InterfaceList ents = sim.GetEntitiesWithInterface(IID_Position);
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
entity_id_t ent = it->first;
// Don't save local entities (placement previews etc)
if (ENTITY_IS_LOCAL(ent))
continue;
XMLWriter_Element entityTag(xmlMapFile, "Entity");
entityTag.Attribute("uid", ent);
entityTag.Setting("Template", cmpTemplateManager->GetCurrentTemplateName(ent));
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
entityTag.Setting("Player", static_cast(cmpOwnership->GetOwner()));
CmpPtr cmpGarrisonHolder(sim, ent);
if (cmpGarrisonHolder)
{
std::vector garrison = cmpGarrisonHolder->GetEntities();
if (!garrison.empty())
{
XMLWriter_Element garrisonTag(xmlMapFile, "Garrison");
for (const entity_id_t garr_ent_id : garrison)
{
XMLWriter_Element garrisonedEntityTag(xmlMapFile, "GarrisonedEntity");
garrisonedEntityTag.Attribute("uid", static_cast(garr_ent_id));
}
}
}
CmpPtr cmpTurretHolder(sim, ent);
if (cmpTurretHolder)
{
std::vector > turrets = cmpTurretHolder->GetTurrets();
if (!turrets.empty())
{
XMLWriter_Element turretTag(xmlMapFile, "Turrets");
for (const std::pair& turret : turrets)
{
XMLWriter_Element turretedEntityTag(xmlMapFile, "Turret");
turretedEntityTag.Attribute("turret", turret.first);
turretedEntityTag.Attribute("uid", static_cast(turret.second));
}
}
}
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
CFixedVector3D pos;
if (cmpPosition->IsInWorld())
pos = cmpPosition->GetPosition();
CFixedVector3D rot = cmpPosition->GetRotation();
{
XMLWriter_Element positionTag(xmlMapFile, "Position");
- positionTag.Attribute("x", pos.X);
- positionTag.Attribute("z", pos.Z);
+ positionTag.Attribute("x", pos.getX());
+ positionTag.Attribute("z", pos.getZ());
// TODO: height offset etc
}
{
XMLWriter_Element orientationTag(xmlMapFile, "Orientation");
- orientationTag.Attribute("y", rot.Y);
+ orientationTag.Attribute("y", rot.getY());
// TODO: X, Z maybe
}
}
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
{
// TODO: Currently only necessary because Atlas
// does not set up control groups for its walls.
cmpObstruction->ResolveFoundationCollisions();
entity_id_t group = cmpObstruction->GetControlGroup();
entity_id_t group2 = cmpObstruction->GetControlGroup2();
// Don't waste space writing the default control groups.
if (group != ent || group2 != INVALID_ENTITY)
{
XMLWriter_Element obstructionTag(xmlMapFile, "Obstruction");
if (group != ent)
obstructionTag.Attribute("group", group);
if (group2 != INVALID_ENTITY)
obstructionTag.Attribute("group2", group2);
}
}
CmpPtr cmpVisual(sim, ent);
if (cmpVisual)
{
entity_id_t seed = static_cast(cmpVisual->GetActorSeed());
if (seed != ent)
{
XMLWriter_Element actorTag(xmlMapFile, "Actor");
actorTag.Attribute("seed",seed);
}
// TODO: variation/selection strings
}
}
}
CmpPtr cmpCinemaManager(sim, SYSTEM_ENTITY);
if (cmpCinemaManager)
{
const std::map& paths = cmpCinemaManager->GetPaths();
std::map::const_iterator it = paths.begin();
XMLWriter_Element pathsTag(xmlMapFile, "Paths");
for ( ; it != paths.end(); ++it )
{
fixed timescale = it->second.GetTimescale();
const std::vector& position_nodes = it->second.GetAllNodes();
const std::vector& target_nodes = it->second.GetTargetSpline().GetAllNodes();
const CCinemaData* data = it->second.GetData();
XMLWriter_Element pathTag(xmlMapFile, "Path");
pathTag.Attribute("name", data->m_Name);
pathTag.Attribute("timescale", timescale);
pathTag.Attribute("orientation", data->m_Orientation);
pathTag.Attribute("mode", data->m_Mode);
pathTag.Attribute("style", data->m_Style);
struct SEvent
{
fixed time;
const char* type;
CFixedVector3D value;
SEvent(fixed time, const char* type, CFixedVector3D value)
: time(time), type(type), value(value)
{}
bool operator<(const SEvent& another) const
{
return time < another.time;
}
};
// All events of a manipulating of camera (position/rotation/target)
std::vector events;
events.reserve(position_nodes.size() + target_nodes.size());
fixed last_position = fixed::Zero();
for (size_t i = 0; i < position_nodes.size(); ++i)
{
fixed distance = i > 0 ? position_nodes[i - 1].Distance : fixed::Zero();
last_position += distance;
events.emplace_back(last_position, "Position", position_nodes[i].Position);
}
fixed last_target = fixed::Zero();
for (size_t i = 0; i < target_nodes.size(); ++i)
{
fixed distance = i > 0 ? target_nodes[i - 1].Distance : fixed::Zero();
last_target += distance;
events.emplace_back(last_target, "Target", target_nodes[i].Position);
}
std::sort(events.begin(), events.end());
for (size_t i = 0; i < events.size();)
{
XMLWriter_Element nodeTag(xmlMapFile, "Node");
fixed deltatime = i > 0 ? (events[i].time - events[i - 1].time) : fixed::Zero();
nodeTag.Attribute("deltatime", deltatime);
size_t j = i;
for (; j < events.size() && events[j].time == events[i].time; ++j)
{
// Types: Position/Rotation/Target
XMLWriter_Element eventTypeTag(xmlMapFile, events[j].type);
- eventTypeTag.Attribute("x", events[j].value.X);
- eventTypeTag.Attribute("y", events[j].value.Y);
- eventTypeTag.Attribute("z", events[j].value.Z);
+ eventTypeTag.Attribute("x", events[j].value.getX());
+ eventTypeTag.Attribute("y", events[j].value.getY());
+ eventTypeTag.Attribute("z", events[j].value.getZ());
}
i = j;
}
}
}
}
if (!xmlMapFile.StoreVFS(g_VFS, filename))
LOGERROR("Failed to write map '%s'", filename.string8());
}
Index: source/graphics/Material.cpp
===================================================================
--- source/graphics/Material.cpp (revision 24796)
+++ source/graphics/Material.cpp (working copy)
@@ -1,95 +1,97 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
+#include "graphics/Color.h"
#include "Material.h"
+#include "ps/CStr.h"
-static CColor BrokenColor(0.3f, 0.3f, 0.3f, 1.0f);
+static RGBAcolor BrokenColor(0.3f, 0.3f, 0.3f, 1.0f);
CMaterial::CMaterial() :
m_AlphaBlending(false)
{
}
void CMaterial::SetShaderEffect(const CStr& effect)
{
m_ShaderEffect = CStrIntern(effect);
}
void CMaterial::AddShaderDefine(CStrIntern key, CStrIntern value)
{
m_ShaderDefines.Add(key, value);
m_CombinedShaderDefines.clear();
}
void CMaterial::AddConditionalDefine(const char* defname, const char* defvalue, int type, std::vector &args)
{
m_ConditionalDefines.Add(defname, defvalue, type, args);
m_CombinedShaderDefines.clear();
}
void CMaterial::AddStaticUniform(const char* key, const CVector4D& value)
{
m_StaticUniforms.Add(key, value);
}
void CMaterial::AddSampler(const TextureSampler& texture)
{
m_Samplers.push_back(texture);
if (texture.Name == str_baseTex)
m_DiffuseTexture = texture.Sampler;
}
void CMaterial::AddRenderQuery(const char* key)
{
m_RenderQueries.Add(key);
}
void CMaterial::AddRequiredSampler(const CStr& samplerName)
{
CStrIntern string(samplerName);
m_RequiredSamplers.push_back(string);
}
// Set up m_CombinedShaderDefines so that index i contains m_ShaderDefines, plus
// the extra defines from m_ConditionalDefines[j] for all j where bit j is set in i.
// This lets GetShaderDefines() cheaply return the defines for any combination of conditions.
//
// (This might scale badly if we had a large number of conditional defines per material,
// but currently we don't expect to have many.)
void CMaterial::RecomputeCombinedShaderDefines()
{
m_CombinedShaderDefines.clear();
int size = m_ConditionalDefines.GetSize();
// Loop over all 2^n combinations of flags
for (int i = 0; i < (1 << size); i++)
{
CShaderDefines defs = m_ShaderDefines;
for (int j = 0; j < size; j++)
{
if (i & (1 << j))
{
const CShaderConditionalDefines::CondDefine& def = m_ConditionalDefines.GetItem(j);
defs.Add(def.m_DefName, def.m_DefValue);
}
}
m_CombinedShaderDefines.push_back(defs);
}
}
Index: source/graphics/Material.h
===================================================================
--- source/graphics/Material.h (revision 24796)
+++ source/graphics/Material.h (working copy)
@@ -1,103 +1,103 @@
/* Copyright (C) 2021 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 .
*/
#ifndef INCLUDED_MATERIAL
#define INCLUDED_MATERIAL
-#include "graphics/Color.h"
#include "graphics/ShaderDefines.h"
-#include "graphics/Texture.h"
-#include "ps/CStr.h"
-#include "ps/CStrIntern.h"
+#include "graphics/Texture.h" // HAS to be here; used structurally
+class CStr;
+class CVector4D;
+#include "ps/CStrIntern.h" // HAS to be here; used structurally
#include "simulation2/helpers/Player.h"
class CMaterial
{
public:
struct TextureSampler
{
TextureSampler(const CStr &n, CTexturePtr t) : Name(n), Sampler(t) {}
TextureSampler(const CStrIntern &n, CTexturePtr t) : Name(n), Sampler(t) {}
CStrIntern Name;
CTexturePtr Sampler;
};
typedef std::vector SamplersVector;
CMaterial();
// Whether this material's shaders use alpha blending, in which case
// models using this material need to be rendered in a special order
// relative to the alpha-blended water plane
void SetUsesAlphaBlending(bool flag) { m_AlphaBlending = flag; }
bool UsesAlphaBlending() const { return m_AlphaBlending; }
const CTexturePtr& GetDiffuseTexture() const { return m_DiffuseTexture; }
void SetShaderEffect(const CStr& effect);
CStrIntern GetShaderEffect() const { return m_ShaderEffect; }
// Must call RecomputeCombinedShaderDefines after this, before rendering with this material
void AddShaderDefine(CStrIntern key, CStrIntern value);
// conditionFlags is a bitmask representing which indexes of the
// GetConditionalDefines() list are currently matching.
// Use 0 if you don't care about conditional defines.
const CShaderDefines& GetShaderDefines(uint32_t conditionFlags) const { return m_CombinedShaderDefines.at(conditionFlags); }
// Must call RecomputeCombinedShaderDefines after this, before rendering with this material
void AddConditionalDefine(const char* defname, const char* defvalue, int type, std::vector &args);
const CShaderConditionalDefines& GetConditionalDefines() const { return m_ConditionalDefines; }
void AddStaticUniform(const char* key, const CVector4D& value);
const CShaderUniforms& GetStaticUniforms() const { return m_StaticUniforms; }
void AddSampler(const TextureSampler& texture);
const SamplersVector& GetSamplers() const { return m_Samplers; }
void AddRenderQuery(const char* key);
const CShaderRenderQueries& GetRenderQueries() const { return m_RenderQueries; }
void AddRequiredSampler(const CStr& samplerName);
const std::vector& GetRequiredSampler() const { return m_RequiredSamplers; }
// Must be called after all AddShaderDefine and AddConditionalDefine
void RecomputeCombinedShaderDefines();
private:
// This pointer is kept to make it easier for the fixed pipeline to
// access the only texture it's interested in.
CTexturePtr m_DiffuseTexture;
SamplersVector m_Samplers;
std::vector m_RequiredSamplers;
CStrIntern m_ShaderEffect;
CShaderDefines m_ShaderDefines;
CShaderConditionalDefines m_ConditionalDefines;
std::vector m_CombinedShaderDefines;
CShaderUniforms m_StaticUniforms;
CShaderRenderQueries m_RenderQueries;
bool m_AlphaBlending;
};
#endif
Index: source/graphics/Model.cpp
===================================================================
--- source/graphics/Model.cpp (revision 24796)
+++ source/graphics/Model.cpp (working copy)
@@ -1,676 +1,676 @@
/* Copyright (C) 2021 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 .
*/
/*
* Mesh object with texture and skinning information
*/
#include "precompiled.h"
#include "Model.h"
#include "Decal.h"
#include "ModelDef.h"
#include "maths/Quaternion.h"
#include "maths/BoundingBoxAligned.h"
#include "SkeletonAnim.h"
#include "SkeletonAnimDef.h"
#include "SkeletonAnimManager.h"
#include "MeshManager.h"
#include "ObjectEntry.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/res/h_mgr.h"
#include "lib/sysdep/rtl.h"
#include "ps/Profile.h"
#include "ps/CLogger.h"
#include "renderer/RenderingOptions.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpWaterManager.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor
CModel::CModel(CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation)
: m_Flags(0), m_Anim(NULL), m_AnimTime(0), m_Simulation(simulation),
m_BoneMatrices(NULL), m_AmmoPropPoint(NULL), m_AmmoLoadedProp(0),
m_SkeletonAnimManager(skeletonAnimManager)
{
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Destructor
CModel::~CModel()
{
ReleaseData();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete anything allocated by the model
void CModel::ReleaseData()
{
rtl_FreeAligned(m_BoneMatrices);
for (size_t i = 0; i < m_Props.size(); ++i)
delete m_Props[i].m_Model;
m_Props.clear();
m_pModelDef = CModelDefPtr();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InitModel: setup model from given geometry
bool CModel::InitModel(const CModelDefPtr& modeldef)
{
// clean up any existing data first
ReleaseData();
m_pModelDef = modeldef;
size_t numBones = modeldef->GetNumBones();
if (numBones != 0)
{
size_t numBlends = modeldef->GetNumBlends();
// allocate matrices for bone transformations
// (one extra matrix is used for the special case of bind-shape relative weighting)
m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + 1 + numBlends), 16);
for (size_t i = 0; i < numBones + 1 + numBlends; ++i)
{
m_BoneMatrices[i].SetIdentity();
}
}
m_PositionValid = true;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcBound: calculate the world space bounds of this model
void CModel::CalcBounds()
{
// Need to calculate the object bounds first, if that hasn't already been done
if (! (m_Anim && m_Anim->m_AnimDef))
{
if (m_ObjectBounds.IsEmpty())
CalcStaticObjectBounds();
}
else
{
if (m_Anim->m_ObjectBounds.IsEmpty())
CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
m_ObjectBounds = m_Anim->m_ObjectBounds;
}
// Ensure the transform is set correctly before we use it
ValidatePosition();
// Now transform the object-space bounds to world-space bounds
m_ObjectBounds.Transform(GetTransform(), m_WorldBounds);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
void CModel::CalcStaticObjectBounds()
{
m_ObjectBounds.SetEmpty();
size_t numverts=m_pModelDef->GetNumVertices();
SModelVertex* verts=m_pModelDef->GetVertices();
for (size_t i=0;im_AnimDef)
{
CSkeletonAnim dummyanim;
dummyanim.m_AnimDef=anim;
if (!SetAnimation(&dummyanim)) return;
}
size_t numverts=m_pModelDef->GetNumVertices();
SModelVertex* verts=m_pModelDef->GetVertices();
// Remove any transformations, so that we calculate the bounding box
// at the origin. The box is later re-transformed onto the object, without
// having to recalculate the size of the box.
CMatrix3D transform, oldtransform = GetTransform();
CModelAbstract* oldparent = m_Parent;
m_Parent = 0;
transform.SetIdentity();
CRenderableObject::SetTransform(transform);
// Following seems to stomp over the current animation time - which, unsurprisingly,
// introduces artefacts in the currently playing animation. Save it here and restore it
// at the end.
float AnimTime = m_AnimTime;
// iterate through every frame of the animation
for (size_t j=0;jGetNumFrames();j++) {
m_PositionValid = false;
ValidatePosition();
// extend bounds by vertex positions at the frame
for (size_t i=0;iGetFrameTime();
}
m_PositionValid = false;
m_Parent = oldparent;
SetTransform(oldtransform);
m_AnimTime = AnimTime;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
const CBoundingBoxAligned CModel::GetWorldBoundsRec()
{
CBoundingBoxAligned bounds = GetWorldBounds();
for (size_t i = 0; i < m_Props.size(); ++i)
bounds += m_Props[i].m_Model->GetWorldBoundsRec();
return bounds;
}
const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec()
{
CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary
// now extend these bounds to include the props' selection bounds (if any)
for (size_t i = 0; i < m_Props.size(); ++i)
{
const Prop& prop = m_Props[i];
if (prop.m_Hidden || !prop.m_Selectable)
continue; // prop is hidden from rendering, so it also shouldn't be used for selection
CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec();
if (propSelectionBounds.IsEmpty())
continue; // submodel does not wish to participate in selection box, exclude it
// We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added
// to the bounds in our object-space. For that, we need the transform of the prop attachment point.
//
// We have the prop point information; however, it's not trivial to compute its exact location in our object-space
// since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of
// an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation
// system and are quite opaque to use from the outside (see @ref ValidatePosition).
//
// However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of
// our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's
// world-space transform as either
//
// T' = T x B x O
// or
// T' = T x O
//
// where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local
// offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to
// (taking into account animation and everything).
//
// From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So,
// all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily,
// this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch.
CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T'
propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T'
// Transform the prop's bounds into our object coordinate space
CBoundingBoxAligned transformedPropSelectionBounds;
propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds);
objBounds += transformedPropSelectionBounds;
}
return objBounds;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildAnimation: load raw animation frame animation from given file, and build a
// animation specific to this model
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos)
{
CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname);
if (!def)
return NULL;
CSkeletonAnim* anim = new CSkeletonAnim();
anim->m_Name = name;
anim->m_ID = ID;
anim->m_Frequency = frequency;
anim->m_AnimDef = def;
anim->m_Speed = speed;
if (actionpos == -1.f)
anim->m_ActionPos = -1.f;
else
anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration();
if (actionpos2 == -1.f)
anim->m_ActionPos2 = -1.f;
else
anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration();
if (soundpos == -1.f)
anim->m_SoundPos = -1.f;
else
anim->m_SoundPos = soundpos * anim->m_AnimDef->GetDuration();
anim->m_ObjectBounds.SetEmpty();
InvalidateBounds();
return anim;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Update: update this model to the given time, in msec
void CModel::UpdateTo(float time)
{
// update animation time, but don't calculate bone matrices - do that (lazily) when
// something requests them; that saves some calculation work for offscreen models,
// and also assures the world space, inverted bone matrices (required for normal
// skinning) are up to date with respect to m_Transform
m_AnimTime = time;
// mark vertices as dirty
SetDirty(RENDERDATA_UPDATE_VERTICES);
// mark matrices as dirty
InvalidatePosition();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InvalidatePosition
void CModel::InvalidatePosition()
{
m_PositionValid = false;
for (size_t i = 0; i < m_Props.size(); ++i)
m_Props[i].m_Model->InvalidatePosition();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ValidatePosition: ensure that current transform and bone matrices are both uptodate
void CModel::ValidatePosition()
{
if (m_PositionValid)
{
ENSURE(!m_Parent || m_Parent->m_PositionValid);
return;
}
if (m_Parent && !m_Parent->m_PositionValid)
{
// Make sure we don't base our calculations on
// a parent animation state that is out of date.
m_Parent->ValidatePosition();
// Parent will recursively call our validation.
ENSURE(m_PositionValid);
return;
}
if (m_Anim && m_BoneMatrices)
{
// PROFILE( "generating bone matrices" );
ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys());
m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
}
else if (m_BoneMatrices)
{
// Bones but no animation - probably a buggy actor forgot to set up the animation,
// so just render it in its bind pose
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
{
m_BoneMatrices[i].SetIdentity();
m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation);
m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation);
}
}
// For CPU skinning, we precompute as much as possible so that the only
// per-vertex work is a single matrix*vec multiplication.
// For GPU skinning, we try to minimise CPU work by doing most computation
// in the vertex shader instead.
// Using g_RenderingOptions to detect CPU vs GPU is a bit hacky,
// and this doesn't allow the setting to change at runtime, but there isn't
// an obvious cleaner way to determine what data needs to be computed,
// and GPU skinning is a rarely-used experimental feature anyway.
bool worldSpaceBoneMatrices = !g_RenderingOptions.GetGPUSkinning();
bool computeBlendMatrices = !g_RenderingOptions.GetGPUSkinning();
if (m_BoneMatrices && worldSpaceBoneMatrices)
{
// add world-space transformation to m_BoneMatrices
const CMatrix3D transform = GetTransform();
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
m_BoneMatrices[i].Concatenate(transform);
}
// our own position is now valid; now we can safely update our props' positions without fearing
// that doing so will cause a revalidation of this model (see recursion above).
m_PositionValid = true;
CMatrix3D translate;
CVector3D objTranslation = m_Transform.GetTranslation();
float objectHeight = 0.0f;
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (cmpTerrain)
- objectHeight = cmpTerrain->GetExactGroundLevel(objTranslation.X, objTranslation.Z);
+ objectHeight = cmpTerrain->GetExactGroundLevel(objTranslation.getX(), objTranslation.getZ());
// Object height is incorrect for floating objects. We use water height instead.
CmpPtr cmpWaterManager(m_Simulation, SYSTEM_ENTITY);
if (cmpWaterManager)
{
- float waterHeight = cmpWaterManager->GetExactWaterLevel(objTranslation.X, objTranslation.Z);
+ float waterHeight = cmpWaterManager->GetExactWaterLevel(objTranslation.getX(), objTranslation.getZ());
if (waterHeight >= objectHeight && m_Flags & MODELFLAG_FLOATONWATER)
objectHeight = waterHeight;
}
// re-position and validate all props
for (const Prop& prop : m_Props)
{
CMatrix3D proptransform = prop.m_Point->m_Transform;
if (prop.m_Point->m_BoneIndex != 0xff)
{
CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex];
if (!worldSpaceBoneMatrices)
boneMatrix.Concatenate(GetTransform());
proptransform.Concatenate(boneMatrix);
}
else
{
// not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin)
proptransform.Concatenate(m_Transform);
}
// Adjust prop height to terrain level when needed
if (cmpTerrain && (prop.m_MaxHeight != 0.f || prop.m_MinHeight != 0.f))
{
const CVector3D& propTranslation = proptransform.GetTranslation();
- const float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.X, propTranslation.Z);
+ const float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.getX(), propTranslation.getZ());
const float translateHeight = std::min(prop.m_MaxHeight, std::max(prop.m_MinHeight, propTerrain - objectHeight));
translate.SetTranslation(0.f, translateHeight, 0.f);
proptransform.Concatenate(translate);
}
prop.m_Model->SetTransform(proptransform);
prop.m_Model->ValidatePosition();
}
if (m_BoneMatrices)
{
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
{
m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i];
}
// Note: there is a special case of joint influence, in which the vertex
// is influenced by the bind-shape transform instead of a particular bone,
// which we indicate with the blending bone ID set to the total number
// of bones. But since we're skinning in world space, we use the model's
// world space transform and store that matrix in this special index.
// (see http://trac.wildfiregames.com/ticket/1012)
m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform;
if (computeBlendMatrices)
m_pModelDef->BlendBoneMatrices(m_BoneMatrices);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetAnimation: set the given animation as the current animation on this model;
// return false on error, else true
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once)
{
m_Anim = nullptr; // in case something fails
if (anim)
{
m_Flags &= ~MODELFLAG_NOLOOPANIMATION;
if (once)
m_Flags |= MODELFLAG_NOLOOPANIMATION;
// Not rigged or animation is not valid.
if (!m_BoneMatrices || !anim->m_AnimDef)
return false;
if (anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones())
{
LOGERROR("Mismatch between model's skeleton and animation's skeleton (%s.dae has %lu model bones while the animation %s has %lu animation keys.)",
m_pModelDef->GetName().string8().c_str() ,
static_cast(m_pModelDef->GetNumBones()),
anim->m_Name.c_str(),
static_cast(anim->m_AnimDef->GetNumKeys()));
return false;
}
// Reset the cached bounds when the animation is changed.
m_ObjectBounds.SetEmpty();
InvalidateBounds();
// Start anim from beginning.
m_AnimTime = 0;
}
m_Anim = anim;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CopyAnimation
void CModel::CopyAnimationFrom(CModel* source)
{
m_Anim = source->m_Anim;
m_AnimTime = source->m_AnimTime;
m_Flags &= ~MODELFLAG_CASTSHADOWS;
if (source->m_Flags & MODELFLAG_CASTSHADOWS)
m_Flags |= MODELFLAG_CASTSHADOWS;
m_ObjectBounds.SetEmpty();
InvalidateBounds();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AddProp: add a prop to the model on the given point
void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight, float maxHeight, bool selectable)
{
// position model according to prop point position
// this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box
model->SetTransform(point->m_Transform);
model->m_Parent = this;
Prop prop;
prop.m_Point = point;
prop.m_Model = model;
prop.m_ObjectEntry = objectentry;
prop.m_MinHeight = minHeight;
prop.m_MaxHeight = maxHeight;
prop.m_Selectable = selectable;
m_Props.push_back(prop);
}
void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry)
{
AddProp(point, model, objectentry);
m_AmmoPropPoint = point;
m_AmmoLoadedProp = m_Props.size() - 1;
m_Props[m_AmmoLoadedProp].m_Hidden = true;
// we only need to invalidate the selection box here if it is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
void CModel::ShowAmmoProp()
{
if (m_AmmoPropPoint == NULL)
return;
// Show the ammo prop, hide all others on the same prop point
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Point == m_AmmoPropPoint)
m_Props[i].m_Hidden = (i != m_AmmoLoadedProp);
// we only need to invalidate the selection box here if it is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
void CModel::HideAmmoProp()
{
if (m_AmmoPropPoint == NULL)
return;
// Hide the ammo prop, show all others on the same prop point
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Point == m_AmmoPropPoint)
m_Props[i].m_Hidden = (i == m_AmmoLoadedProp);
// we only need to invalidate here if the selection box is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
CModelAbstract* CModel::FindFirstAmmoProp()
{
if (m_AmmoPropPoint)
return m_Props[m_AmmoLoadedProp].m_Model;
for (size_t i = 0; i < m_Props.size(); ++i)
{
CModel* propModel = m_Props[i].m_Model->ToCModel();
if (propModel)
{
CModelAbstract* model = propModel->FindFirstAmmoProp();
if (model)
return model;
}
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Clone: return a clone of this model
CModelAbstract* CModel::Clone() const
{
CModel* clone = new CModel(m_SkeletonAnimManager, m_Simulation);
clone->m_ObjectBounds = m_ObjectBounds;
clone->InitModel(m_pModelDef);
clone->SetMaterial(m_Material);
clone->SetAnimation(m_Anim);
clone->SetFlags(m_Flags);
for (size_t i = 0; i < m_Props.size(); i++)
{
// eek! TODO, RC - need to investigate shallow clone here
if (m_AmmoPropPoint && i == m_AmmoLoadedProp)
clone->AddAmmoProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry);
else
clone->AddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry, m_Props[i].m_MinHeight, m_Props[i].m_MaxHeight, m_Props[i].m_Selectable);
}
return clone;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetTransform: set the transform on this object, and reorientate props accordingly
void CModel::SetTransform(const CMatrix3D& transform)
{
// call base class to set transform on this object
CRenderableObject::SetTransform(transform);
InvalidatePosition();
}
//////////////////////////////////////////////////////////////////////////
void CModel::AddFlagsRec(int flags)
{
m_Flags |= flags;
if (flags & MODELFLAG_IGNORE_LOS)
{
m_Material.AddShaderDefine(str_IGNORE_LOS, str_1);
m_Material.RecomputeCombinedShaderDefines();
}
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Model->ToCModel())
m_Props[i].m_Model->ToCModel()->AddFlagsRec(flags);
}
void CModel::RemoveShadowsRec()
{
m_Flags &= ~MODELFLAG_CASTSHADOWS;
m_Material.AddShaderDefine(str_DISABLE_RECEIVE_SHADOWS, str_1);
m_Material.RecomputeCombinedShaderDefines();
for (size_t i = 0; i < m_Props.size(); ++i)
{
if (m_Props[i].m_Model->ToCModel())
m_Props[i].m_Model->ToCModel()->RemoveShadowsRec();
else if (m_Props[i].m_Model->ToCModelDecal())
m_Props[i].m_Model->ToCModelDecal()->RemoveShadows();
}
}
void CModel::SetMaterial(const CMaterial &material)
{
m_Material = material;
}
void CModel::SetPlayerID(player_id_t id)
{
CModelAbstract::SetPlayerID(id);
for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
it->m_Model->SetPlayerID(id);
}
-void CModel::SetShadingColor(const CColor& color)
+void CModel::SetShadingColor(const RGBAcolor& color)
{
CModelAbstract::SetShadingColor(color);
for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
it->m_Model->SetShadingColor(color);
}
Index: source/graphics/Model.h
===================================================================
--- source/graphics/Model.h (revision 24796)
+++ source/graphics/Model.h (working copy)
@@ -1,307 +1,307 @@
/* Copyright (C) 2021 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 .
*/
/*
* Mesh object with texture and skinning information
*/
#ifndef INCLUDED_MODEL
#define INCLUDED_MODEL
#include
#include "graphics/Texture.h"
#include "graphics/Material.h"
#include "graphics/MeshManager.h"
#include "graphics/ModelAbstract.h"
struct SPropPoint;
class CObjectEntry;
class CSkeletonAnim;
class CSkeletonAnimDef;
class CSkeletonAnimManager;
class CSimulation2;
#define MODELFLAG_CASTSHADOWS (1<<0)
#define MODELFLAG_NOLOOPANIMATION (1<<1)
#define MODELFLAG_SILHOUETTE_DISPLAY (1<<2)
#define MODELFLAG_SILHOUETTE_OCCLUDER (1<<3)
#define MODELFLAG_IGNORE_LOS (1<<4)
#define MODELFLAG_FLOATONWATER (1<<5)
///////////////////////////////////////////////////////////////////////////////
// CModel: basically, a mesh object - holds the texturing and skinning
// information for a model in game
class CModel : public CModelAbstract
{
NONCOPYABLE(CModel);
public:
struct Prop
{
Prop() : m_MinHeight(0.f), m_MaxHeight(0.f), m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false), m_Selectable(true) {}
float m_MinHeight;
float m_MaxHeight;
/**
* Location of the prop point within its parent model, relative to either a bone in the parent model or to the
* parent model's origin. See the documentation for @ref SPropPoint for more details.
* @see SPropPoint
*/
const SPropPoint* m_Point;
/**
* Pointer to the model associated with this prop. Note that the transform matrix held by this model is the full object-to-world
* space transform, taking into account all parent model positioning (see @ref CModel::ValidatePosition for positioning logic).
* @see CModel::ValidatePosition
*/
CModelAbstract* m_Model;
CObjectEntry* m_ObjectEntry;
bool m_Hidden; ///< Should this prop be temporarily removed from rendering?
bool m_Selectable; /// < should this prop count in the selection size?
};
public:
// constructor
CModel(CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation);
// destructor
~CModel();
/// Dynamic cast
virtual CModel* ToCModel()
{
return this;
}
// setup model from given geometry
bool InitModel(const CModelDefPtr& modeldef);
// update this model's state; 'time' is the absolute time since the start of the animation, in MS
void UpdateTo(float time);
// get the model's geometry data
const CModelDefPtr& GetModelDef() { return m_pModelDef; }
// set the model's material
void SetMaterial(const CMaterial &material);
// set the model's player ID, recursively through props
void SetPlayerID(player_id_t id);
// set the models mod color
- virtual void SetShadingColor(const CColor& color);
+ virtual void SetShadingColor(const RGBAcolor& color);
// get the model's material
CMaterial& GetMaterial() { return m_Material; }
// set the given animation as the current animation on this model
bool SetAnimation(CSkeletonAnim* anim, bool once = false);
// get the currently playing animation, if any
CSkeletonAnim* GetAnimation() const { return m_Anim; }
// set the animation state to be the same as from another; both models should
// be compatible types (same type of skeleton)
void CopyAnimationFrom(CModel* source);
// set object flags
void SetFlags(int flags) { m_Flags=flags; }
// get object flags
int GetFlags() const { return m_Flags; }
// add object flags, recursively through props
void AddFlagsRec(int flags);
// remove shadow casting and receiving, recursively through props
// TODO: replace with more generic shader define + flags setting
void RemoveShadowsRec();
// recurse down tree setting dirty bits
virtual void SetDirtyRec(int dirtyflags) {
SetDirty(dirtyflags);
for (size_t i=0;iSetDirtyRec(dirtyflags);
}
}
virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
for (size_t i = 0; i < m_Props.size(); ++i)
m_Props[i].m_Model->SetTerrainDirty(i0, j0, i1, j1);
}
virtual void SetEntityVariable(const std::string& name, float value)
{
for (size_t i = 0; i < m_Props.size(); ++i)
m_Props[i].m_Model->SetEntityVariable(name, value);
}
// --- WORLD/OBJECT SPACE BOUNDS -----------------------------------------------------------------
/// Overridden to calculate both the world-space and object-space bounds of this model, and stores the result in
/// m_Bounds and m_ObjectBounds, respectively.
virtual void CalcBounds();
/// Returns the object-space bounds for this model, excluding its children.
const CBoundingBoxAligned& GetObjectBounds()
{
RecalculateBoundsIfNecessary(); // recalculates both object-space and world-space bounds if necessary
return m_ObjectBounds;
}
virtual const CBoundingBoxAligned GetWorldBoundsRec(); // reimplemented here
/// Auxiliary method; calculates object space bounds of this model, based solely on vertex positions, and stores
/// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcAnimatedObjectBounds) if it has been determined
/// that the object-space bounds are static.
void CalcStaticObjectBounds();
/// Auxiliary method; calculate object-space bounds encompassing all vertex positions for given animation, and stores
/// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcStaticBounds) if it has been determined that the
/// object-space bounds need to take animations into account.
void CalcAnimatedObjectBounds(CSkeletonAnimDef* anim,CBoundingBoxAligned& result);
// --- SELECTION BOX/BOUNDS ----------------------------------------------------------------------
/// Reimplemented here since proper models should participate in selection boxes.
virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec();
/**
* Set transform of this object.
*
* @note In order to ensure that all child props are updated properly,
* you must call ValidatePosition().
*/
virtual void SetTransform(const CMatrix3D& transform);
/**
* Return whether this is a skinned/skeletal model. If it is, Get*BoneMatrices()
* will return valid non-NULL arrays.
*/
bool IsSkinned() { return (m_BoneMatrices != NULL); }
// return the models bone matrices; 16-byte aligned for SSE reads
const CMatrix3D* GetAnimatedBoneMatrices() {
ENSURE(m_PositionValid);
return m_BoneMatrices;
}
/**
* Load raw animation frame animation from given file, and build an
* animation specific to this model.
* @param pathname animation file to load
* @param name animation name (e.g. "idle")
* @param ID specific ID of the animation, to sync with props
* @param frequency influences the random choices
* @param speed animation speed as a factor of the default animation speed
* @param actionpos offset of 'action' event, in range [0, 1]
* @param actionpos2 offset of 'action2' event, in range [0, 1]
* @param sound offset of 'sound' event, in range [0, 1]
* @return new animation, or NULL on error
*/
CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos);
/**
* Add a prop to the model on the given point.
*/
void AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight = 0.f, float maxHeight = 0.f, bool selectable = true);
/**
* Add a prop to the model on the given point, and treat it as the ammo prop.
* The prop will be hidden by default.
*/
void AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry);
/**
* Show the ammo prop (if any), and hide any other props on that prop point.
*/
void ShowAmmoProp();
/**
* Hide the ammo prop (if any), and show any other props on that prop point.
*/
void HideAmmoProp();
/**
* Find the first prop used for ammo, by this model or its own props.
*/
CModelAbstract* FindFirstAmmoProp();
// return prop list
std::vector& GetProps() { return m_Props; }
const std::vector& GetProps() const { return m_Props; }
// return a clone of this model
virtual CModelAbstract* Clone() const;
/**
* Ensure that both the transformation and the bone
* matrices are correct for this model and all its props.
*/
virtual void ValidatePosition();
/**
* Mark this model's position and bone matrices,
* and all props' positions as invalid.
*/
virtual void InvalidatePosition();
private:
// delete anything allocated by the model
void ReleaseData();
// Needed for terrain aligned props
CSimulation2& m_Simulation;
// object flags
int m_Flags;
// model's material
CMaterial m_Material;
// pointer to the model's raw 3d data
CModelDefPtr m_pModelDef;
// object space bounds of model - accounts for bounds of all possible animations
// that can play on a model. Not always up-to-date - currently CalcBounds()
// updates it when necessary.
CBoundingBoxAligned m_ObjectBounds;
// animation currently playing on this model, if any
CSkeletonAnim* m_Anim;
// time (in MS) into the current animation
float m_AnimTime;
/**
* Current state of all bones on this model; null if associated modeldef isn't skeletal.
* Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their
* transformation matrix held is relative to the bone transformation (see @ref SPropPoint and
* @ref CModel::ValidatePosition).
*
* @see SPropPoint
*/
CMatrix3D* m_BoneMatrices;
// list of current props on model
std::vector m_Props;
/**
* The prop point to which the ammo prop is attached, or NULL if none
*/
const SPropPoint* m_AmmoPropPoint;
/**
* If m_AmmoPropPoint is not NULL, then the index in m_Props of the ammo prop
*/
size_t m_AmmoLoadedProp;
// manager object which can load animations for us
CSkeletonAnimManager& m_SkeletonAnimManager;
};
#endif
Index: source/graphics/ModelAbstract.cpp
===================================================================
--- source/graphics/ModelAbstract.cpp (revision 24796)
+++ source/graphics/ModelAbstract.cpp (working copy)
@@ -1,92 +1,92 @@
/* Copyright (C) 2011 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 "ModelAbstract.h"
#include "ps/CLogger.h"
const CBoundingBoxOriented& CModelAbstract::GetSelectionBox()
{
if (!m_SelectionBoxValid)
{
CalcSelectionBox();
m_SelectionBoxValid = true;
}
return m_SelectionBox;
}
void CModelAbstract::CalcSelectionBox()
{
if (m_CustomSelectionShape)
{
// custom shape
switch(m_CustomSelectionShape->m_Type)
{
case CustomSelectionShape::BOX:
{
// create object-space bounds according to the information in the descriptor, and transform them to world-space.
// the box is centered on the X and Z axes, but extends from 0 to its height on the Y axis.
const float width = m_CustomSelectionShape->m_Size0;
const float depth = m_CustomSelectionShape->m_Size1;
const float height = m_CustomSelectionShape->m_Height;
CBoundingBoxAligned bounds;
bounds += CVector3D(-width/2.f, 0, -depth/2.f);
bounds += CVector3D( width/2.f, height, depth/2.f);
bounds.Transform(GetTransform(), m_SelectionBox);
}
break;
case CustomSelectionShape::CYLINDER:
{
// TODO: unimplemented
m_SelectionBox.SetEmpty();
LOGWARNING("[ModelAbstract] TODO: Cylinder selection boxes are not yet implemented. Use BOX or BOUNDS selection shapes instead.");
}
break;
default:
{
m_SelectionBox.SetEmpty();
//LOGWARNING("[ModelAbstract] Unrecognized selection shape type: %ld", m_CustomSelectionShape->m_Type);
debug_warn("[ModelAbstract] Unrecognized selection shape type");
}
break;
}
}
else
{
// standard method
// Get the object-space bounds that should be used to construct this model (and its children)'s selection box
CBoundingBoxAligned objBounds = GetObjectSelectionBoundsRec();
if (objBounds.IsEmpty())
{
m_SelectionBox.SetEmpty(); // model does not wish to participate in selection
return;
}
// Prevent the bounding box from extending through the terrain; clip the lower plane at Y=0 in object space.
- if (objBounds[1].Y > 0.f) // should always be the case, unless the models are defined really weirdly
- objBounds[0].Y = std::max(0.f, objBounds[0].Y);
+ if (objBounds[1].getY() > 0.f) // should always be the case, unless the models are defined really weirdly
+ objBounds[0].Yref() = std::max(0.f, objBounds[0].getY());
// transform object-space axis-aligned bounds to world-space arbitrary-aligned box
objBounds.Transform(GetTransform(), m_SelectionBox);
}
}
Index: source/graphics/ModelAbstract.h
===================================================================
--- source/graphics/ModelAbstract.h (revision 24796)
+++ source/graphics/ModelAbstract.h (working copy)
@@ -1,191 +1,191 @@
/* Copyright (C) 2021 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 .
*/
#ifndef INCLUDED_MODELABSTRACT
#define INCLUDED_MODELABSTRACT
-#include "graphics/Color.h"
-#include "graphics/RenderableObject.h"
-#include "maths/BoundingBoxOriented.h"
+#include "graphics/Color.h" // HAS to be here; used structurally
+#include "graphics/RenderableObject.h" // HAS to be here; inherited
+#include "maths/BoundingBoxOriented.h" // HAS to be here; used structurally
#include "simulation2/helpers/Player.h"
class CModel;
class CModelDecal;
class CModelParticleEmitter;
/**
* Abstract base class for graphical objects that are used by units,
* or as props attached to other CModelAbstract objects.
* This includes meshes, terrain decals, and sprites.
* These objects exist in a tree hierarchy.
*/
class CModelAbstract : public CRenderableObject
{
NONCOPYABLE(CModelAbstract);
public:
/**
* Describes a custom selection shape to be used for a model's selection box instead of the default
* recursive bounding boxes.
*/
struct CustomSelectionShape
{
enum EType {
/// The selection shape is determined by an oriented box of custom, user-specified size.
BOX,
/// The selection shape is determined by a cylinder of custom, user-specified size.
CYLINDER
};
EType m_Type; ///< Type of shape.
float m_Size0; ///< Box width if @ref BOX, or radius if @ref CYLINDER
float m_Size1; ///< Box depth if @ref BOX, or radius if @ref CYLINDER
float m_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER
};
public:
CModelAbstract()
: m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER),
m_SelectionBoxValid(false), m_CustomSelectionShape(NULL)
{ }
~CModelAbstract()
{
delete m_CustomSelectionShape; // allocated and set externally by CCmpVisualActor, but our responsibility to clean up
}
virtual CModelAbstract* Clone() const = 0;
/// Dynamic cast
virtual CModel* ToCModel() { return NULL; }
/// Dynamic cast
virtual CModelDecal* ToCModelDecal() { return NULL; }
/// Dynamic cast
virtual CModelParticleEmitter* ToCModelParticleEmitter() { return NULL; }
// (This dynamic casting is a bit ugly, but we won't have many subclasses
// and this seems the easiest way to integrate with other code that wants
// type-specific processing)
/// Calls SetDirty on this model and all child objects.
virtual void SetDirtyRec(int dirtyflags) = 0;
/// Returns world space bounds of this object and all child objects.
virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); } // default implementation
/**
* Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The
* returned selection box may be empty to indicate that it does not wish to participate in the selection process.
*/
virtual const CBoundingBoxOriented& GetSelectionBox();
virtual void InvalidateBounds()
{
m_BoundsValid = false;
// a call to this method usually means that the model's transform has changed, i.e. it has moved or rotated, so we'll also
// want to update the selection box accordingly regardless of the shape it is built from.
m_SelectionBoxValid = false;
}
/// Sets a custom selection shape as described by a @p descriptor. Argument may be NULL
/// if you wish to keep the default behaviour of using the recursively-calculated bounding boxes.
void SetCustomSelectionShape(CustomSelectionShape* descriptor)
{
if (m_CustomSelectionShape != descriptor)
{
m_CustomSelectionShape = descriptor;
m_SelectionBoxValid = false; // update the selection box when it is next requested
}
}
/**
* Returns the (object-space) bounds that should be used to construct a selection box for this model and its children.
* May return an empty bound to indicate that this model and its children should not be selectable themselves, or should
* not be included in its parent model's selection box. This method is used for constructing the default selection boxes,
* as opposed to any boxes of custom shape specified by @ref m_CustomSelectionShape.
*
* If you wish your model type to be included in selection boxes, override this method and have it return the object-space
* bounds of itself, augmented recursively (via this method) with the object-space selection bounds of its children.
*/
virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec() { return CBoundingBoxAligned::EMPTY; }
/**
* Called when terrain has changed in the given inclusive bounds.
* Might call SetDirty if the change affects this model.
*/
virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) = 0;
/**
* Called when the entity tries to set some variable to affect the display of this model
* and/or its child objects.
*/
virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { }
/**
* Ensure that both the transformation and the bone matrices are correct for this model and all its props.
*/
virtual void ValidatePosition() = 0;
/**
* Mark this model's position and bone matrices, and all props' positions as invalid.
*/
virtual void InvalidatePosition() = 0;
virtual void SetPlayerID(player_id_t id) { m_PlayerID = id; }
// get the model's player ID; initial default is INVALID_PLAYER
virtual player_id_t GetPlayerID() const { return m_PlayerID; }
- virtual void SetShadingColor(const CColor& color) { m_ShadingColor = color; }
- virtual CColor GetShadingColor() const { return m_ShadingColor; }
+ virtual void SetShadingColor(const RGBAcolor& color) { m_ShadingColor = color; }
+ virtual RGBAcolor GetShadingColor() const { return m_ShadingColor; }
protected:
void CalcSelectionBox();
public:
/// If non-null, points to the model that we are attached to.
CModelAbstract* m_Parent;
/// True if both transform and and bone matrices are valid.
bool m_PositionValid;
player_id_t m_PlayerID;
/// Modulating color
- CColor m_ShadingColor;
+ RGBAcolor m_ShadingColor;
protected:
/// Selection box for this model.
CBoundingBoxOriented m_SelectionBox;
/// Is the current selection box valid?
bool m_SelectionBoxValid;
/// Pointer to a descriptor for a custom-defined selection box shape. If no custom selection box is required, this is NULL
/// and the standard recursive-bounding-box-based selection box is used. Otherwise, a custom selection box described by this
/// field will be used.
/// @see SetCustomSelectionShape
CustomSelectionShape* m_CustomSelectionShape;
};
#endif // INCLUDED_MODELABSTRACT
Index: source/graphics/ModelDef.cpp
===================================================================
--- source/graphics/ModelDef.cpp (revision 24796)
+++ source/graphics/ModelDef.cpp (working copy)
@@ -1,495 +1,495 @@
/* Copyright (C) 2021 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 .
*/
/*
* Defines a raw 3d model.
*/
#include "precompiled.h"
#include "ModelDef.h"
#include "graphics/SkeletonAnimDef.h"
#include "lib/sse.h"
#include "ps/FileIo.h"
#include "maths/Vector4D.h"
#if COMPILER_HAS_SSE
# include
#endif
CVector3D CModelDef::SkinPoint(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[])
{
CVector3D result (0, 0, 0);
for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i)
{
result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Transform(vtx.m_Coords) * vtx.m_Blend.m_Weight[i];
}
return result;
}
CVector3D CModelDef::SkinNormal(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[])
{
// To be correct, the normal vectors apparently need to be multiplied by the
// inverse of the transpose. Unfortunately inverses are slow.
// If a matrix is orthogonal, M * M^T = I and so the inverse of the transpose
// is the original matrix. But that's not entirely relevant here, because
// the bone matrices include translation components and so they're not
// orthogonal.
// But that's okay because we have
// M = T * R
// and want to find
// n' = (M^T^-1) * n
// = (T * R)^T^-1 * n
// = (R^T * T^T)^-1 * n
// = (T^T^-1 * R^T^-1) * n
// R is indeed orthogonal so R^T^-1 = R. T isn't orthogonal at all.
// But n is only a 3-vector, and from the forms of T and R (which have
// lots of zeroes) I can convince myself that replacing T with T^T^-1 has no
// effect on anything but the fourth component of M^T^-1 - and the fourth
// component is discarded since it has no effect on n', and so we can happily
// use n' = M*n.
//
// (This isn't very good as a proof, but it's better than assuming M is
// orthogonal when it's clearly not.)
CVector3D result (0, 0, 0);
for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i)
{
result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Rotate(vtx.m_Norm) * vtx.m_Blend.m_Weight[i];
}
// If there was more than one influence, the result is probably not going
// to be of unit length (since it's a weighted sum of several independent
// unit vectors), so we need to normalise it.
// (It's fairly common to only have one influence, so it seems sensible to
// optimise that case a bit.)
if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence
result.Normalize();
return result;
}
void(*CModelDef::SkinPointsAndNormals)(
size_t numVertices,
const VertexArrayIterator& Position,
const VertexArrayIterator& Normal,
const SModelVertex* vertices,
const size_t* blendIndices,
const CMatrix3D newPoseMatrices[]) {};
static void SkinPointsAndNormalsFallback(
size_t numVertices,
const VertexArrayIterator& Position,
const VertexArrayIterator& Normal,
const SModelVertex* vertices,
const size_t* blendIndices,
const CMatrix3D newPoseMatrices[])
{
// To avoid some performance overhead, get the raw vertex array pointers
char* PositionData = Position.GetData();
size_t PositionStride = Position.GetStride();
char* NormalData = Normal.GetData();
size_t NormalStride = Normal.GetStride();
for (size_t j = 0; j < numVertices; ++j)
{
const SModelVertex& vtx = vertices[j];
CVector3D pos = newPoseMatrices[blendIndices[j]].Transform(vtx.m_Coords);
CVector3D norm = newPoseMatrices[blendIndices[j]].Rotate(vtx.m_Norm);
// If there was more than one influence, the result is probably not going
// to be of unit length (since it's a weighted sum of several independent
// unit vectors), so we need to normalise it.
// (It's fairly common to only have one influence, so it seems sensible to
// optimise that case a bit.)
if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence
norm.Normalize();
- memcpy(PositionData + PositionStride*j, &pos.X, 3*sizeof(float));
- memcpy(NormalData + NormalStride*j, &norm.X, 3*sizeof(float));
+ memcpy(PositionData + PositionStride*j, &pos, 3*sizeof(float)); //DanW58, was &pos.X
+ memcpy(NormalData + NormalStride*j, &norm, 3*sizeof(float)); //DanW58, was &norm.X
}
}
#if COMPILER_HAS_SSE
static void SkinPointsAndNormalsSSE(
size_t numVertices,
const VertexArrayIterator& Position,
const VertexArrayIterator& Normal,
const SModelVertex* vertices,
const size_t* blendIndices,
const CMatrix3D newPoseMatrices[])
{
// To avoid some performance overhead, get the raw vertex array pointers
char* PositionData = Position.GetData();
size_t PositionStride = Position.GetStride();
char* NormalData = Normal.GetData();
size_t NormalStride = Normal.GetStride();
// Must be aligned correctly for SSE
ASSERT((intptr_t)newPoseMatrices % 16 == 0);
ASSERT((intptr_t)PositionData % 16 == 0);
ASSERT((intptr_t)PositionStride % 16 == 0);
ASSERT((intptr_t)NormalData % 16 == 0);
ASSERT((intptr_t)NormalStride % 16 == 0);
__m128 col0, col1, col2, col3, vec0, vec1, vec2;
for (size_t j = 0; j < numVertices; ++j)
{
const SModelVertex& vtx = vertices[j];
const CMatrix3D& mtx = newPoseMatrices[blendIndices[j]];
// Loads matrix to xmm registers.
col0 = _mm_load_ps(mtx._data);
col1 = _mm_load_ps(mtx._data + 4);
col2 = _mm_load_ps(mtx._data + 8);
col3 = _mm_load_ps(mtx._data + 12);
// Loads and computes vertex coordinates.
- vec0 = _mm_load1_ps(&vtx.m_Coords.X); // v0 = [x, x, x, x]
+ vec0 = _mm_load1_ps(vtx.m_Coords.pX()); // v0 = [x, x, x, x] //DanW58, was &&vtx.m_Coords.X
vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x]
- vec1 = _mm_load1_ps(&vtx.m_Coords.Y); // v1 = [y, y, y, y]
+ vec1 = _mm_load1_ps(vtx.m_Coords.pY()); // v1 = [y, y, y, y] //DanW58, was &&vtx.m_Coords.X
vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...]
- vec1 = _mm_load1_ps(&vtx.m_Coords.Z); // v1 = [z, z, z, z]
+ vec1 = _mm_load1_ps(vtx.m_Coords.pZ()); // v1 = [z, z, z, z] //DanW58, was &&vtx.m_Coords.X
vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z]
vec1 = _mm_add_ps(vec1, col3); // v1 = [_13*z + _14, ...]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z + _14, ...]
_mm_store_ps((float*)(PositionData + PositionStride*j), vec0);
// Loads and computes normal vectors.
- vec0 = _mm_load1_ps(&vtx.m_Norm.X); // v0 = [x, x, x, x]
+ vec0 = _mm_load1_ps(vtx.m_Norm.pX()); // v0 = [x, x, x, x]
vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x]
- vec1 = _mm_load1_ps(&vtx.m_Norm.Y); // v1 = [y, y, y, y]
+ vec1 = _mm_load1_ps(vtx.m_Norm.pY()); // v1 = [y, y, y, y]
vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...]
- vec1 = _mm_load1_ps(&vtx.m_Norm.Z); // v1 = [z, z, z, z]
+ vec1 = _mm_load1_ps(vtx.m_Norm.pZ()); // v1 = [z, z, z, z]
vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z, ...]
// If there was more than one influence, the result is probably not going
// to be of unit length (since it's a weighted sum of several independent
// unit vectors), so we need to normalise it.
// (It's fairly common to only have one influence, so it seems sensible to
// optimise that case a bit.)
if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence
{
// Normalization.
// vec1 = [x*x, y*y, z*z, ?*?]
vec1 = _mm_mul_ps(vec0, vec0);
// vec2 = [y*y, z*z, x*x, ?*?]
vec2 = _mm_shuffle_ps(vec1, vec1, _MM_SHUFFLE(3, 0, 2, 1));
vec1 = _mm_add_ps(vec1, vec2);
// vec2 = [z*z, x*x, y*y, ?*?]
vec2 = _mm_shuffle_ps(vec2, vec2, _MM_SHUFFLE(3, 0, 2, 1));
vec1 = _mm_add_ps(vec1, vec2);
// rsqrt(a) = 1 / sqrt(a)
vec1 = _mm_rsqrt_ps(vec1);
vec0 = _mm_mul_ps(vec0, vec1);
}
_mm_store_ps((float*)(NormalData + NormalStride*j), vec0);
}
}
#endif
void CModelDef::BlendBoneMatrices(
CMatrix3D boneMatrices[])
{
for (size_t i = 0; i < m_NumBlends; ++i)
{
const SVertexBlend& blend = m_pBlends[i];
CMatrix3D& boneMatrix = boneMatrices[m_NumBones + 1 + i];
// Note: there is a special case of joint influence, in which the vertex
// is influenced by the bind-shape matrix instead of a particular bone,
// which we indicate by setting the bone ID to the total number of bones.
// It should be blended with the world space transform and we have already
// set up this matrix in boneMatrices.
// (see http://trac.wildfiregames.com/ticket/1012)
boneMatrix.Blend(boneMatrices[blend.m_Bone[0]], blend.m_Weight[0]);
for (size_t j = 1; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j)
boneMatrix.AddBlend(boneMatrices[blend.m_Bone[j]], blend.m_Weight[j]);
}
}
// CModelDef Constructor
CModelDef::CModelDef() :
m_NumVertices(0), m_NumUVsPerVertex(0), m_pVertices(0), m_NumFaces(0), m_pFaces(0),
m_NumBones(0), m_Bones(0), m_InverseBindBoneMatrices(NULL),
m_NumBlends(0), m_pBlends(0), m_pBlendIndices(0),
m_Name(L"[not loaded]")
{
}
// CModelDef Destructor
CModelDef::~CModelDef()
{
for(RenderDataMap::iterator it = m_RenderData.begin(); it != m_RenderData.end(); ++it)
delete it->second;
delete[] m_pVertices;
delete[] m_pFaces;
delete[] m_Bones;
delete[] m_InverseBindBoneMatrices;
delete[] m_pBlends;
delete[] m_pBlendIndices;
}
// FindPropPoint: find and return pointer to prop point matching given name;
// return null if no match (case insensitive search)
const SPropPoint* CModelDef::FindPropPoint(const char* name) const
{
for (size_t i = 0; i < m_PropPoints.size(); ++i)
if (m_PropPoints[i].m_Name == name)
return &m_PropPoints[i];
return 0;
}
// Load: read and return a new CModelDef initialised with data from given file
CModelDef* CModelDef::Load(const VfsPath& filename, const VfsPath& name)
{
CFileUnpacker unpacker;
// read everything in from file
unpacker.Read(filename,"PSMD");
// check version
if (unpacker.GetVersion() mdef (new CModelDef());
mdef->m_Name = name;
// now unpack everything
mdef->m_NumVertices = unpacker.UnpackSize();
// versions prior to 4 only support 1 UV set, 4 and later store it here
if (unpacker.GetVersion() <= 3)
{
mdef->m_NumUVsPerVertex = 1;
}
else
{
mdef->m_NumUVsPerVertex = unpacker.UnpackSize();
}
mdef->m_pVertices=new SModelVertex[mdef->m_NumVertices];
for (size_t i = 0; i < mdef->m_NumVertices; ++i)
{
unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Coords, 12);
unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Norm, 12);
for (size_t s = 0; s < mdef->m_NumUVsPerVertex; ++s)
{
float uv[2];
unpacker.UnpackRaw(&uv[0], 8);
mdef->m_pVertices[i].m_UVs.push_back(uv[0]);
mdef->m_pVertices[i].m_UVs.push_back(uv[1]);
}
unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Blend, sizeof(SVertexBlend));
}
mdef->m_NumFaces = unpacker.UnpackSize();
mdef->m_pFaces=new SModelFace[mdef->m_NumFaces];
unpacker.UnpackRaw(mdef->m_pFaces,sizeof(SModelFace)*mdef->m_NumFaces);
mdef->m_NumBones = unpacker.UnpackSize();
if (mdef->m_NumBones)
{
mdef->m_Bones=new CBoneState[mdef->m_NumBones];
unpacker.UnpackRaw(mdef->m_Bones,mdef->m_NumBones*sizeof(CBoneState));
mdef->m_pBlendIndices = new size_t[mdef->m_NumVertices];
std::vector blends;
for (size_t i = 0; i < mdef->m_NumVertices; i++)
{
const SVertexBlend &blend = mdef->m_pVertices[i].m_Blend;
if (blend.m_Bone[1] == 0xFF)
{
mdef->m_pBlendIndices[i] = blend.m_Bone[0];
}
else
{
// If there's already a vertex using the same blend as this, then
// reuse its entry from blends; otherwise add the new one to blends
size_t j;
for (j = 0; j < blends.size(); j++)
{
if (blend == blends[j]) break;
}
if (j >= blends.size())
blends.push_back(blend);
// This index is offset by one to allow the special case of a
// weighted influence relative to the bind-shape rather than
// a particular bone. See comment in BlendBoneMatrices.
mdef->m_pBlendIndices[i] = mdef->m_NumBones + 1 + j;
}
}
mdef->m_NumBlends = blends.size();
mdef->m_pBlends = new SVertexBlend[mdef->m_NumBlends];
std::copy(blends.begin(), blends.end(), mdef->m_pBlends);
}
if (unpacker.GetVersion() >= 2)
{
// versions >=2 also have prop point data
size_t numPropPoints = unpacker.UnpackSize();
mdef->m_PropPoints.resize(numPropPoints);
if (numPropPoints)
{
for (size_t i = 0; i < numPropPoints; i++)
{
unpacker.UnpackString(mdef->m_PropPoints[i].m_Name);
- unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position));
- unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation));
+ unpacker.UnpackRaw(mdef->m_PropPoints[i].m_Position.ncpX(), sizeof(mdef->m_PropPoints[i].m_Position));
+ unpacker.UnpackRaw(mdef->m_PropPoints[i].m_Rotation.m_V.ncpX(), sizeof(mdef->m_PropPoints[i].m_Rotation));
unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex));
// build prop point transform
mdef->m_PropPoints[i].m_Transform.SetIdentity();
mdef->m_PropPoints[i].m_Transform.Rotate(mdef->m_PropPoints[i].m_Rotation);
mdef->m_PropPoints[i].m_Transform.Translate(mdef->m_PropPoints[i].m_Position);
}
}
}
if (unpacker.GetVersion() <= 2)
{
// Versions <=2 don't include the default 'root' prop point, so add it here
SPropPoint prop;
prop.m_Name = "root";
prop.m_Transform.SetIdentity();
prop.m_BoneIndex = 0xFF;
mdef->m_PropPoints.push_back(prop);
}
if (unpacker.GetVersion() <= 2)
{
// Versions <=2 store the vertexes relative to the bind pose. That
// isn't useful when you want to do correct skinning, so later versions
// store them in world space. So, fix the old models by skinning each
// vertex:
if (mdef->m_NumBones) // only do skinned models
{
std::vector bindPose (mdef->m_NumBones);
for (size_t i = 0; i < mdef->m_NumBones; ++i)
{
bindPose[i].SetIdentity();
bindPose[i].Rotate(mdef->m_Bones[i].m_Rotation);
bindPose[i].Translate(mdef->m_Bones[i].m_Translation);
}
for (size_t i = 0; i < mdef->m_NumVertices; ++i)
{
mdef->m_pVertices[i].m_Coords = SkinPoint(mdef->m_pVertices[i], &bindPose[0]);
mdef->m_pVertices[i].m_Norm = SkinNormal(mdef->m_pVertices[i], &bindPose[0]);
}
}
}
// Compute the inverse bind-pose matrices, needed by the skinning code
mdef->m_InverseBindBoneMatrices = new CMatrix3D[mdef->m_NumBones];
CBoneState* defpose = mdef->GetBones();
for (size_t i = 0; i < mdef->m_NumBones; ++i)
{
mdef->m_InverseBindBoneMatrices[i].SetIdentity();
mdef->m_InverseBindBoneMatrices[i].Translate(-defpose[i].m_Translation);
mdef->m_InverseBindBoneMatrices[i].Rotate(defpose[i].m_Rotation.GetInverse());
}
return mdef.release();
}
// Save: write the given CModelDef to the given file
void CModelDef::Save(const VfsPath& filename, const CModelDef* mdef)
{
CFilePacker packer(FILE_VERSION, "PSMD");
// pack everything up
const size_t numVertices = mdef->GetNumVertices();
packer.PackSize(numVertices);
packer.PackRaw(mdef->GetVertices(), sizeof(SModelVertex) * numVertices);
const size_t numFaces = mdef->GetNumFaces();
packer.PackSize(numFaces);
packer.PackRaw(mdef->GetFaces(), sizeof(SModelFace) * numFaces);
const size_t numBones = mdef->m_NumBones;
packer.PackSize(numBones);
if (numBones)
packer.PackRaw(mdef->m_Bones, sizeof(CBoneState) * numBones);
const size_t numPropPoints = mdef->m_PropPoints.size();
packer.PackSize(numPropPoints);
for (size_t i = 0; i < numPropPoints; i++)
{
packer.PackString(mdef->m_PropPoints[i].m_Name);
- packer.PackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position));
- packer.PackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation));
+ packer.PackRaw(mdef->m_PropPoints[i].m_Position.pX(), sizeof(mdef->m_PropPoints[i].m_Position));
+ packer.PackRaw(mdef->m_PropPoints[i].m_Rotation.m_V.pX(), sizeof(mdef->m_PropPoints[i].m_Rotation));
packer.PackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex));
}
// flush everything out to file
packer.Write(filename);
}
// SetRenderData: Set the render data object for the given key,
void CModelDef::SetRenderData(const void* key, CModelDefRPrivate* data)
{
delete m_RenderData[key];
m_RenderData[key] = data;
}
// GetRenderData: Get the render data object for the given key,
// or 0 if no such object exists.
// Reference count of the render data object is automatically increased.
CModelDefRPrivate* CModelDef::GetRenderData(const void* key) const
{
RenderDataMap::const_iterator it = m_RenderData.find(key);
if (it != m_RenderData.end())
return it->second;
return 0;
}
void ModelDefActivateFastImpl()
{
#if COMPILER_HAS_SSE
if (HostHasSSE())
{
CModelDef::SkinPointsAndNormals = SkinPointsAndNormalsSSE;
return;
}
#endif
CModelDef::SkinPointsAndNormals = SkinPointsAndNormalsFallback;
}
Index: source/graphics/ObjectEntry.cpp
===================================================================
--- source/graphics/ObjectEntry.cpp (revision 24796)
+++ source/graphics/ObjectEntry.cpp (working copy)
@@ -1,307 +1,309 @@
/* Copyright (C) 2021 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 "ObjectEntry.h"
#include "graphics/Decal.h"
#include "graphics/Material.h"
#include "graphics/MaterialManager.h"
#include "graphics/MeshManager.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/ObjectBase.h"
#include "graphics/ObjectManager.h"
#include "graphics/ParticleManager.h"
#include "graphics/SkeletonAnim.h"
#include "graphics/TextureManager.h"
#include "lib/rand.h"
#include "ps/CLogger.h"
+#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "simulation2/Simulation2.h"
#include
CObjectEntry::CObjectEntry(CObjectBase* base, CSimulation2& simulation) :
m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Model(NULL), m_Outdated(false), m_Simulation(simulation)
{
}
CObjectEntry::~CObjectEntry()
{
for (const std::pair& anim : m_Animations)
delete anim.second;
delete m_Model;
}
bool CObjectEntry::BuildVariation(const std::vector >& selections,
const std::vector& variationKey,
CObjectManager& objectManager)
{
CObjectBase::Variation variation = m_Base->BuildVariation(variationKey);
// Copy the chosen data onto this model:
for (std::multimap::iterator it = variation.samplers.begin(); it != variation.samplers.end(); ++it)
m_Samplers.push_back(it->second);
m_ModelName = variation.model;
if (! variation.color.empty())
{
std::stringstream str;
str << variation.color;
int r, g, b;
if (! (str >> r >> g >> b)) // Any trailing data is ignored
LOGERROR("Actor '%s' has invalid RGB color '%s'", utf8_from_wstring(m_Base->m_ShortName), variation.color);
else
- m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
+ m_Color = RGBAcolor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
}
if (variation.decal.m_SizeX && variation.decal.m_SizeZ)
{
CMaterial material = g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material);
for (const CObjectBase::Samp& samp : m_Samplers)
{
CTextureProperties textureProps(samp.m_SamplerFile);
textureProps.SetWrap(GL_CLAMP_TO_BORDER);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
// TODO: Should check which renderpath is selected and only preload the necessary textures.
texture->Prefetch();
material.AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture));
}
SDecal decal(material,
variation.decal.m_SizeX, variation.decal.m_SizeZ,
variation.decal.m_Angle, variation.decal.m_OffsetX, variation.decal.m_OffsetZ,
m_Base->m_Properties.m_FloatOnWater);
m_Model = new CModelDecal(objectManager.GetTerrain(), decal);
return true;
}
if (!variation.particles.empty())
{
m_Model = new CModelParticleEmitter(g_Renderer.GetParticleManager().LoadEmitterType(variation.particles));
return true;
}
std::vector props;
for (std::multimap::iterator it = variation.props.begin(); it != variation.props.end(); ++it)
props.push_back(it->second);
// Build the model:
// try and create a model
CModelDefPtr modeldef (objectManager.GetMeshManager().GetMesh(m_ModelName));
if (!modeldef)
{
LOGERROR("CObjectEntry::BuildVariation(): Model %s failed to load", m_ModelName.string8());
return false;
}
// delete old model, create new
CModel* model = new CModel(objectManager.GetSkeletonAnimManager(), m_Simulation);
delete m_Model;
m_Model = model;
model->SetMaterial(g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material));
- model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.r, m_Color.g, m_Color.b, m_Color.a));
+// model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.getR(), m_Color.getG(), m_Color.getB(), m_Color.getA()));
+ model->GetMaterial().AddStaticUniform("objectColor", m_Color);
model->InitModel(modeldef);
if (m_Samplers.empty())
LOGERROR("Actor '%s' has no textures.", utf8_from_wstring(m_Base->m_ShortName));
for (const CObjectBase::Samp& samp : m_Samplers)
{
CTextureProperties textureProps(samp.m_SamplerFile);
textureProps.SetWrap(GL_CLAMP_TO_EDGE);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
// if we've loaded this model we're probably going to render it soon, so prefetch its texture.
// All textures are prefetched even in the fixed pipeline, including the normal maps etc.
// TODO: Should check which renderpath is selected and only preload the necessary textures.
texture->Prefetch();
model->GetMaterial().AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture));
}
for (const CStrIntern& requSampName : model->GetMaterial().GetRequiredSampler())
{
if (std::find_if(m_Samplers.begin(), m_Samplers.end(),
[&](const CObjectBase::Samp& sampler) { return sampler.m_SamplerName == requSampName; }) == m_Samplers.end())
LOGERROR("Actor %s: required texture sampler %s not found (material %s)", utf8_from_wstring(m_Base->m_ShortName), requSampName.string().c_str(), m_Base->m_Material.string8().c_str());
}
// calculate initial object space bounds, based on vertex positions
model->CalcStaticObjectBounds();
// load the animations
for (std::multimap::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)
{
CStr name = it->first.LowerCase();
if (it->second.m_FileName.empty())
continue;
CSkeletonAnim* anim = model->BuildAnimation(
it->second.m_FileName,
name,
it->second.m_ID,
it->second.m_Frequency,
it->second.m_Speed,
it->second.m_ActionPos,
it->second.m_ActionPos2,
it->second.m_SoundPos);
if (anim)
m_Animations.insert(std::make_pair(name, anim));
}
// ensure there's always an idle animation
if (m_Animations.find("idle") == m_Animations.end())
{
CSkeletonAnim* anim = new CSkeletonAnim();
anim->m_Name = "idle";
anim->m_ID = "";
anim->m_AnimDef = NULL;
anim->m_Frequency = 0;
anim->m_Speed = 0.f;
anim->m_ActionPos = 0.f;
anim->m_ActionPos2 = 0.f;
anim->m_SoundPos = 0.f;
m_Animations.insert(std::make_pair("idle", anim));
// Ignore errors, since they're probably saying this is a non-animated model
model->SetAnimation(anim);
}
else
{
// start up idling
if (!model->SetAnimation(GetRandomAnimation("idle")))
LOGERROR("Failed to set idle animation in model \"%s\"", m_ModelName.string8());
}
// build props - TODO, RC - need to fix up bounds here
// TODO: Make sure random variations get handled correctly when a prop fails
for (const CObjectBase::Prop& prop : props)
{
// Pluck out the special attachpoint 'projectile'
if (prop.m_PropPointName == "projectile")
{
m_ProjectileModelName = prop.m_ModelName;
continue;
}
CObjectEntry* oe = objectManager.FindObjectVariation(prop.m_ModelName.c_str(), selections);
if (!oe)
{
LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop.m_ModelName), utf8_from_wstring(m_Base->m_ShortName));
continue;
}
// If we don't have a projectile but this prop does (e.g. it's our rider), then
// use that as our projectile too
if (m_ProjectileModelName.empty() && !oe->m_ProjectileModelName.empty())
m_ProjectileModelName = oe->m_ProjectileModelName;
CStr ppn = prop.m_PropPointName;
bool isAmmo = false;
// Handle the special attachpoint 'loaded-'
if (ppn.Find("loaded-") == 0)
{
ppn = prop.m_PropPointName.substr(7);
isAmmo = true;
}
const SPropPoint* proppoint = modeldef->FindPropPoint(ppn.c_str());
if (proppoint)
{
CModelAbstract* propmodel = oe->m_Model->Clone();
if (isAmmo)
model->AddAmmoProp(proppoint, propmodel, oe);
else
model->AddProp(proppoint, propmodel, oe, prop.m_minHeight, prop.m_maxHeight, prop.m_selectable);
if (propmodel->ToCModel())
propmodel->ToCModel()->SetAnimation(oe->GetRandomAnimation("idle"));
}
else
LOGERROR("Failed to find matching prop point called \"%s\" in model \"%s\" for actor \"%s\"", ppn, m_ModelName.string8(), utf8_from_wstring(m_Base->m_ShortName));
}
// Setup flags.
if (m_Base->m_Properties.m_CastShadows)
{
model->SetFlags(model->GetFlags() | MODELFLAG_CASTSHADOWS);
}
if (m_Base->m_Properties.m_FloatOnWater)
{
model->SetFlags(model->GetFlags() | MODELFLAG_FLOATONWATER);
}
return true;
}
CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName, const CStr& ID) const
{
std::vector anims = GetAnimations(animationName, ID);
int totalFreq = 0;
for (CSkeletonAnim* anim : anims)
totalFreq += anim->m_Frequency;
if (totalFreq == 0)
return anims[rand(0, anims.size())];
int r = rand(0, totalFreq);
for (CSkeletonAnim* anim : anims)
{
r -= anim->m_Frequency;
if (r < 0)
return anim;
}
return NULL;
}
std::vector CObjectEntry::GetAnimations(const CStr& animationName, const CStr& ID) const
{
std::vector anims;
SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
{
if (ID.empty() || it->second->m_ID == ID)
anims.push_back(it->second);
}
if (anims.empty())
{
lower = m_Animations.lower_bound("idle");
upper = m_Animations.upper_bound("idle");
for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
anims.push_back(it->second);
}
ENSURE(!anims.empty());
return anims;
}
Index: source/graphics/ObjectEntry.h
===================================================================
--- source/graphics/ObjectEntry.h (revision 24796)
+++ source/graphics/ObjectEntry.h (working copy)
@@ -1,97 +1,97 @@
/* Copyright (C) 2021 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 .
*/
#ifndef INCLUDED_OBJECTENTRY
#define INCLUDED_OBJECTENTRY
class CModelAbstract;
class CSkeletonAnim;
class CObjectBase;
class CObjectManager;
class CSimulation2;
struct SPropPoint;
#include