Index: ps/trunk/source/graphics/Camera.cpp
===================================================================
--- ps/trunk/source/graphics/Camera.cpp (revision 26120)
+++ ps/trunk/source/graphics/Camera.cpp (revision 26121)
@@ -1,460 +1,459 @@
/* 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/Vector2D.h"
#include "maths/Vector4D.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
CCamera::CCamera()
{
// 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 = ProjectionType::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 == ProjectionType::PERSPECTIVE)
{
m_FOV = camera.m_FOV;
}
else if (m_ProjType == ProjectionType::ORTHO)
{
m_OrthoScale = camera.m_OrthoScale;
}
m_ProjMat = camera.m_ProjMat;
}
void CCamera::SetOrthoProjection(float nearp, float farp, float scale)
{
m_ProjType = ProjectionType::ORTHO;
m_NearPlane = nearp;
m_FarPlane = farp;
m_OrthoScale = scale;
const float halfHeight = 0.5f * m_OrthoScale;
const float halfWidth = halfHeight * GetAspectRatio();
m_ProjMat.SetOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, m_NearPlane, m_FarPlane);
}
void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov)
{
m_ProjType = ProjectionType::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[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11;
m_ViewFrustum[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12;
m_ViewFrustum[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13;
m_ViewFrustum[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14;
// get the LEFT plane
m_ViewFrustum[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11;
m_ViewFrustum[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12;
m_ViewFrustum[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13;
m_ViewFrustum[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14;
// get the BOTTOM plane
m_ViewFrustum[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21;
m_ViewFrustum[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22;
m_ViewFrustum[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23;
m_ViewFrustum[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24;
// get the TOP plane
m_ViewFrustum[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21;
m_ViewFrustum[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22;
m_ViewFrustum[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23;
m_ViewFrustum[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24;
// get the FAR plane
m_ViewFrustum[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31;
m_ViewFrustum[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32;
m_ViewFrustum[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33;
m_ViewFrustum[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34;
// get the NEAR plane
m_ViewFrustum[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31;
m_ViewFrustum[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32;
m_ViewFrustum[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33;
m_ViewFrustum[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34;
for (size_t i = 0; i < 6; ++i)
m_ViewFrustum[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
{
if (m_ProjType == ProjectionType::CUSTOM)
{
const CMatrix3D invProjection = m_ProjMat.GetInverse();
const std::array ndcCorners = {
CVector2D{-1.0f, -1.0f}, CVector2D{1.0f, -1.0f},
CVector2D{1.0f, 1.0f}, CVector2D{-1.0f, 1.0f}};
for (size_t idx = 0; idx < 4; ++idx)
{
const CVector2D& corner = ndcCorners[idx];
CVector4D nearCorner =
invProjection.Transform(CVector4D(corner.X, corner.Y, -1.0f, 1.0f));
nearCorner /= nearCorner.W;
CVector4D farCorner =
invProjection.Transform(CVector4D(corner.X, corner.Y, 1.0f, 1.0f));
farCorner /= farCorner.W;
const float t = (dist - nearCorner.Z) / (farCorner.Z - nearCorner.Z);
const CVector4D quadCorner = nearCorner * (1.0 - t) + farCorner * t;
quad[idx].X = quadCorner.X;
quad[idx].Y = quadCorner.Y;
quad[idx].Z = quadCorner.Z;
}
return;
}
const float y = m_ProjType == ProjectionType::PERSPECTIVE ? dist * tanf(m_FOV * 0.5f) : m_OrthoScale * 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;
}
void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const
{
ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO);
// Coordinates relative to the camera plane.
const float dx = static_cast(px) / m_ViewPort.m_Width;
const float dy = 1.0f - static_cast(py) / m_ViewPort.m_Height;
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.
const CVector3D basisX = points[1] - points[0];
const CVector3D basisY = points[3] - points[0];
if (m_ProjType == ProjectionType::PERSPECTIVE)
{
// Build direction for the camera origin to the target point.
origin = m_Orientation.GetTranslation();
CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy);
dir = targetPoint - origin;
}
else if (m_ProjType == ProjectionType::ORTHO)
{
origin = m_Orientation.GetTranslation() + (basisX * (dx - 0.5f)) + (basisY * (dy - 0.5f));
dir = m_Orientation.GetIn();
}
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));
x = screenspace.X / screenspace.W;
y = screenspace.Y / screenspace.W;
x = (x + 1) * 0.5f * m_ViewPort.m_Width;
y = (1 - y) * 0.5f * m_ViewPort.m_Height;
}
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, (mapSize - 1) * TERRAIN_TILE_SIZE);
waterPoint.Z = Clamp(waterPoint.Z, 0.f, (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, (mapSize - 1) * TERRAIN_TILE_SIZE);
waterPoint.Z = Clamp(waterPoint.Z, 0.f, (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);
}
}
}
CBoundingBoxAligned CCamera::GetBoundsInViewPort(const CBoundingBoxAligned& boundigBox) const
{
const CVector3D cameraPosition = GetOrientation().GetTranslation();
if (boundigBox.IsPointInside(cameraPosition))
return CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, 0.0f), CVector3D(1.0f, 1.0f, 0.0f));
const CMatrix3D viewProjection = GetViewProjection();
CBoundingBoxAligned viewPortBounds;
#define ADD_VISIBLE_POINT_TO_VIEWBOUNDS(POSITION) STMT( \
CVector4D v = viewProjection.Transform(CVector4D((POSITION).X, (POSITION).Y, (POSITION).Z, 1.0f)); \
if (v.W != 0.0f) \
viewPortBounds += CVector3D(v.X, v.Y, v.Z) * (1.0f / v.W); )
std::array worldPositions;
std::array isBehindNearPlane;
const CVector3D lookDirection = GetOrientation().GetIn();
// Check corners.
for (size_t idx = 0; idx < 8; ++idx)
{
worldPositions[idx] = CVector3D(boundigBox[(idx >> 0) & 0x1].X, boundigBox[(idx >> 1) & 0x1].Y, boundigBox[(idx >> 2) & 0x1].Z);
isBehindNearPlane[idx] = lookDirection.Dot(worldPositions[idx]) < lookDirection.Dot(cameraPosition) + GetNearPlane();
if (!isBehindNearPlane[idx])
ADD_VISIBLE_POINT_TO_VIEWBOUNDS(worldPositions[idx]);
}
// Check edges for intersections with the near plane.
for (size_t idxBegin = 0; idxBegin < 8; ++idxBegin)
for (size_t nextComponent = 0; nextComponent < 3; ++nextComponent)
{
const size_t idxEnd = idxBegin | (1u << nextComponent);
if (idxBegin == idxEnd || isBehindNearPlane[idxBegin] == isBehindNearPlane[idxEnd])
continue;
CVector3D intersection;
// Intersect the segment with the near plane.
if (!m_ViewFrustum[5].FindLineSegIntersection(worldPositions[idxBegin], worldPositions[idxEnd], &intersection))
continue;
ADD_VISIBLE_POINT_TO_VIEWBOUNDS(intersection);
}
#undef ADD_VISIBLE_POINT_TO_VIEWBOUNDS
if (viewPortBounds[0].X >= 1.0f || viewPortBounds[1].X <= -1.0f || viewPortBounds[0].Y >= 1.0f || viewPortBounds[1].Y <= -1.0f)
return CBoundingBoxAligned{};
return viewPortBounds;
}
void CCamera::LookAt(const CVector3D& camera, const CVector3D& focus, const CVector3D& up)
{
CVector3D delta = focus - 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._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f;
}
Index: ps/trunk/source/graphics/MaterialManager.cpp
===================================================================
--- ps/trunk/source/graphics/MaterialManager.cpp (revision 26120)
+++ ps/trunk/source/graphics/MaterialManager.cpp (revision 26121)
@@ -1,198 +1,197 @@
/* 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 "MaterialManager.h"
#include "graphics/PreprocessorWrapper.h"
-#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "maths/Vector4D.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/RenderingOptions.h"
#include
CMaterialManager::CMaterialManager()
{
qualityLevel = 5.0;
CFG_GET_VAL("materialmgr.quality", qualityLevel);
qualityLevel = Clamp(qualityLevel, 0.0f, 10.0f);
if (VfsDirectoryExists(L"art/materials/") && !CXeromyces::AddValidator(g_VFS, "material", "art/materials/material.rng"))
LOGERROR("CMaterialManager: failed to load grammar file 'art/materials/material.rng'");
}
CMaterial CMaterialManager::LoadMaterial(const VfsPath& pathname)
{
if (pathname.empty())
return CMaterial();
std::map::iterator iter = m_Materials.find(pathname);
if (iter != m_Materials.end())
return iter->second;
CXeromyces xeroFile;
if (xeroFile.Load(g_VFS, pathname, "material") != PSRETURN_OK)
return CMaterial();
#define EL(x) int el_##x = xeroFile.GetElementID(#x)
#define AT(x) int at_##x = xeroFile.GetAttributeID(#x)
EL(alpha_blending);
EL(alternative);
EL(define);
EL(shader);
EL(uniform);
EL(renderquery);
EL(required_texture);
EL(conditional_define);
AT(effect);
AT(if);
AT(define);
AT(quality);
AT(material);
AT(name);
AT(value);
AT(type);
AT(min);
AT(max);
AT(conf);
#undef AT
#undef EL
CPreprocessorWrapper preprocessor;
preprocessor.AddDefine("CFG_FORCE_ALPHATEST", g_RenderingOptions.GetForceAlphaTest() ? "1" : "0");
CMaterial material;
material.AddStaticUniform("qualityLevel", CVector4D(qualityLevel, 0, 0, 0));
XMBElement root = xeroFile.GetRoot();
XERO_ITER_EL(root, node)
{
int token = node.GetNodeName();
XMBAttributeList attrs = node.GetAttributes();
if (token == el_alternative)
{
CStr cond = attrs.GetNamedItem(at_if);
if (cond.empty() || !preprocessor.TestConditional(cond))
{
cond = attrs.GetNamedItem(at_quality);
if (cond.empty())
continue;
else
{
if (cond.ToFloat() <= qualityLevel)
continue;
}
}
material = LoadMaterial(VfsPath("art/materials") / attrs.GetNamedItem(at_material).FromUTF8());
break;
}
else if (token == el_alpha_blending)
{
material.SetUsesAlphaBlending(true);
}
else if (token == el_shader)
{
material.SetShaderEffect(attrs.GetNamedItem(at_effect));
}
else if (token == el_define)
{
material.AddShaderDefine(CStrIntern(attrs.GetNamedItem(at_name)), CStrIntern(attrs.GetNamedItem(at_value)));
}
else if (token == el_conditional_define)
{
std::vector args;
CStr type = attrs.GetNamedItem(at_type).c_str();
int typeID = -1;
if (type == CStr("draw_range"))
{
typeID = DCOND_DISTANCE;
float valmin = -1.0f;
float valmax = -1.0f;
CStr conf = attrs.GetNamedItem(at_conf);
if (!conf.empty())
{
CFG_GET_VAL("materialmgr." + conf + ".min", valmin);
CFG_GET_VAL("materialmgr." + conf + ".max", valmax);
}
else
{
CStr dmin = attrs.GetNamedItem(at_min);
if (!dmin.empty())
valmin = attrs.GetNamedItem(at_min).ToFloat();
CStr dmax = attrs.GetNamedItem(at_max);
if (!dmax.empty())
valmax = attrs.GetNamedItem(at_max).ToFloat();
}
args.push_back(valmin);
args.push_back(valmax);
if (valmin >= 0.0f)
{
std::stringstream sstr;
sstr << valmin;
material.AddShaderDefine(CStrIntern(conf + "_MIN"), CStrIntern(sstr.str()));
}
if (valmax >= 0.0f)
{
std::stringstream sstr;
sstr << valmax;
material.AddShaderDefine(CStrIntern(conf + "_MAX"), CStrIntern(sstr.str()));
}
}
material.AddConditionalDefine(attrs.GetNamedItem(at_name).c_str(),
attrs.GetNamedItem(at_value).c_str(),
typeID, args);
}
else if (token == el_uniform)
{
std::stringstream str(attrs.GetNamedItem(at_value));
CVector4D vec;
str >> vec.X >> vec.Y >> vec.Z >> vec.W;
material.AddStaticUniform(attrs.GetNamedItem(at_name).c_str(), vec);
}
else if (token == el_renderquery)
{
material.AddRenderQuery(attrs.GetNamedItem(at_name).c_str());
}
else if (token == el_required_texture)
{
material.AddRequiredSampler(attrs.GetNamedItem(at_name));
if (!attrs.GetNamedItem(at_define).empty())
material.AddShaderDefine(CStrIntern(attrs.GetNamedItem(at_define)), str_1);
}
}
material.RecomputeCombinedShaderDefines();
m_Materials[pathname] = material;
return material;
}
Index: ps/trunk/source/graphics/Model.cpp
===================================================================
--- ps/trunk/source/graphics/Model.cpp (revision 26120)
+++ ps/trunk/source/graphics/Model.cpp (revision 26121)
@@ -1,623 +1,618 @@
/* 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 "graphics/Decal.h"
+#include "graphics/MeshManager.h"
+#include "graphics/ModelDef.h"
+#include "graphics/ObjectEntry.h"
+#include "graphics/SkeletonAnim.h"
+#include "graphics/SkeletonAnimDef.h"
+#include "graphics/SkeletonAnimManager.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 "maths/Quaternion.h"
#include "lib/res/h_mgr.h"
#include "lib/sysdep/rtl.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Profile.h"
#include "renderer/RenderingOptions.h"
-#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/Simulation2.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()
{
PROFILE2("CalcStaticObjectBounds");
m_pModelDef->GetMaxBounds(nullptr, !(m_Flags & MODELFLAG_NOLOOPANIMATION), m_ObjectBounds);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation
void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& result)
{
PROFILE2("CalcAnimatedObjectBounds");
m_pModelDef->GetMaxBounds(anim, !(m_Flags & MODELFLAG_NOLOOPANIMATION), result);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
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);
// 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);
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 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_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)
{
CModelAbstract::SetShadingColor(color);
for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
it->m_Model->SetShadingColor(color);
}
Index: ps/trunk/source/graphics/Terrain.cpp
===================================================================
--- ps/trunk/source/graphics/Terrain.cpp (revision 26120)
+++ ps/trunk/source/graphics/Terrain.cpp (revision 26121)
@@ -1,848 +1,842 @@
/* 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 .
*/
-/*
- * Describes ground via heightmap and array of CPatch.
- */
-
#include "precompiled.h"
-#include "lib/res/graphics/ogl_tex.h"
-#include "lib/sysdep/cpu.h"
-
-#include "renderer/Renderer.h"
-
-#include "TerrainProperties.h"
-#include "TerrainTextureEntry.h"
-#include "TerrainTextureManager.h"
+#include "graphics/Terrain.h"
-#include
-#include "Terrain.h"
-#include "Patch.h"
+#include "graphics/Patch.h"
+#include "graphics/TerrainProperties.h"
+#include "graphics/TerrainTextureEntry.h"
+#include "graphics/TerrainTextureManager.h"
+#include "lib/sysdep/cpu.h"
#include "maths/FixedVector3D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
+#include "renderer/Renderer.h"
#include "simulation2/helpers/Pathfinding.h"
+#include
+
///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::CTerrain()
: m_Heightmap(0), m_Patches(0), m_MapSize(0), m_MapSizePatches(0),
m_BaseColor(255, 255, 255, 255)
{
}
///////////////////////////////////////////////////////////////////////////////
// CTerrain constructor
CTerrain::~CTerrain()
{
ReleaseData();
}
///////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete any data allocated by this terrain
void CTerrain::ReleaseData()
{
m_HeightMipmap.ReleaseData();
delete[] m_Heightmap;
delete[] m_Patches;
}
///////////////////////////////////////////////////////////////////////////////
// Initialise: initialise this terrain to the given size
// using given heightmap to setup elevation data
bool CTerrain::Initialize(ssize_t patchesPerSide, const u16* data)
{
// clean up any previous terrain
ReleaseData();
// store terrain size
m_MapSize = patchesPerSide * PATCH_SIZE + 1;
m_MapSizePatches = patchesPerSide;
// allocate data for new terrain
m_Heightmap = new u16[m_MapSize * m_MapSize];
m_Patches = new CPatch[m_MapSizePatches * m_MapSizePatches];
// given a heightmap?
if (data)
{
// yes; keep a copy of it
memcpy(m_Heightmap, data, m_MapSize*m_MapSize*sizeof(u16));
}
else
{
// build a flat terrain
memset(m_Heightmap, 0, m_MapSize*m_MapSize*sizeof(u16));
}
// setup patch parents, indices etc
InitialisePatches();
// initialise mipmap
m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
return true;
}
///////////////////////////////////////////////////////////////////////////////
CStr8 CTerrain::GetMovementClass(ssize_t i, ssize_t j) const
{
CMiniPatch* tile = GetTile(i, j);
if (tile && tile->GetTextureEntry())
return tile->GetTextureEntry()->GetProperties().GetMovementClass();
return "default";
}
///////////////////////////////////////////////////////////////////////////////
// CalcPosition: calculate the world space position of the vertex at (i,j)
// If i,j is off the map, it acts as if the edges of the terrain are extended
// outwards to infinity
void CTerrain::CalcPosition(ssize_t i, ssize_t j, CVector3D& pos) const
{
ssize_t hi = Clamp(i, 0, m_MapSize - 1);
ssize_t hj = Clamp(j, 0, m_MapSize - 1);
u16 height = m_Heightmap[hj*m_MapSize + hi];
pos.X = float(i*TERRAIN_TILE_SIZE);
pos.Y = float(height*HEIGHT_SCALE);
pos.Z = float(j*TERRAIN_TILE_SIZE);
}
///////////////////////////////////////////////////////////////////////////////
// CalcPositionFixed: calculate the world space position of the vertex at (i,j)
void CTerrain::CalcPositionFixed(ssize_t i, ssize_t j, CFixedVector3D& pos) const
{
ssize_t hi = Clamp(i, 0, m_MapSize - 1);
ssize_t hj = Clamp(j, 0, m_MapSize - 1);
u16 height = m_Heightmap[hj*m_MapSize + hi];
pos.X = fixed::FromInt(i) * (int)TERRAIN_TILE_SIZE;
// fixed max value is 32767, but height is a u16, so divide by two to avoid overflow
pos.Y = fixed::FromInt(height/ 2 ) / ((int)HEIGHT_UNITS_PER_METRE / 2);
pos.Z = fixed::FromInt(j) * (int)TERRAIN_TILE_SIZE;
}
///////////////////////////////////////////////////////////////////////////////
// CalcNormal: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormal(ssize_t i, ssize_t j, CVector3D& normal) const
{
CVector3D left, right, up, down;
// Calculate normals of the four half-tile triangles surrounding this vertex:
// get position of vertex where normal is being evaluated
CVector3D basepos;
CalcPosition(i, j, basepos);
if (i > 0) {
CalcPosition(i-1, j, left);
left -= basepos;
left.Normalize();
}
if (i < m_MapSize-1) {
CalcPosition(i+1, j, right);
right -= basepos;
right.Normalize();
}
if (j > 0) {
CalcPosition(i, j-1, up);
up -= basepos;
up.Normalize();
}
if (j < m_MapSize-1) {
CalcPosition(i, j+1, down);
down -= basepos;
down.Normalize();
}
CVector3D n0 = up.Cross(left);
CVector3D n1 = left.Cross(down);
CVector3D n2 = down.Cross(right);
CVector3D n3 = right.Cross(up);
// Compute the mean of the normals
normal = n0 + n1 + n2 + n3;
float nlen=normal.Length();
if (nlen>0.00001f) normal*=1.0f/nlen;
}
///////////////////////////////////////////////////////////////////////////////
// CalcNormalFixed: calculate the world space normal of the vertex at (i,j)
void CTerrain::CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) const
{
CFixedVector3D left, right, up, down;
// Calculate normals of the four half-tile triangles surrounding this vertex:
// get position of vertex where normal is being evaluated
CFixedVector3D basepos;
CalcPositionFixed(i, j, basepos);
if (i > 0) {
CalcPositionFixed(i-1, j, left);
left -= basepos;
left.Normalize();
}
if (i < m_MapSize-1) {
CalcPositionFixed(i+1, j, right);
right -= basepos;
right.Normalize();
}
if (j > 0) {
CalcPositionFixed(i, j-1, up);
up -= basepos;
up.Normalize();
}
if (j < m_MapSize-1) {
CalcPositionFixed(i, j+1, down);
down -= basepos;
down.Normalize();
}
CFixedVector3D n0 = up.Cross(left);
CFixedVector3D n1 = left.Cross(down);
CFixedVector3D n2 = down.Cross(right);
CFixedVector3D n3 = right.Cross(up);
// Compute the mean of the normals
normal = n0 + n1 + n2 + n3;
normal.Normalize();
}
CVector3D CTerrain::CalcExactNormal(float x, float z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = Clamp(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
const ssize_t zi = Clamp(floor(z / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
const float xf = Clamp(x / TERRAIN_TILE_SIZE-xi, 0.0f, 1.0f);
const float zf = Clamp(z / TERRAIN_TILE_SIZE-zi, 0.0f, 1.0f);
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
// Determine which terrain triangle this point is on,
// then compute the normal of that triangle's plane
if (GetTriangulationDir(xi, zi))
{
if (xf + zf <= 1.f)
{
// Lower-left triangle (don't use h11)
return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
else
{
// Upper-right triangle (don't use h00)
return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
}
else
{
if (xf <= zf)
{
// Upper-left triangle (don't use h10)
return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
else
{
// Lower-right triangle (don't use h01)
return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
}
}
}
///////////////////////////////////////////////////////////////////////////////
// GetPatch: return the patch at (i,j) in patch space, or null if the patch is
// out of bounds
CPatch* CTerrain::GetPatch(ssize_t i, ssize_t j) const
{
// range check (invalid indices are passed in by the culling and
// patch blend code because they iterate from 0..#patches and examine
// neighbors without checking if they're already on the edge)
if( (size_t)i >= (size_t)m_MapSizePatches || (size_t)j >= (size_t)m_MapSizePatches )
return 0;
return &m_Patches[(j*m_MapSizePatches)+i];
}
///////////////////////////////////////////////////////////////////////////////
// GetTile: return the tile at (i,j) in tile space, or null if the tile is out
// of bounds
CMiniPatch* CTerrain::GetTile(ssize_t i, ssize_t j) const
{
// see comment above
if( (size_t)i >= (size_t)(m_MapSize-1) || (size_t)j >= (size_t)(m_MapSize-1) )
return 0;
CPatch* patch=GetPatch(i/PATCH_SIZE, j/PATCH_SIZE); // can't fail (due to above check)
return &patch->m_MiniPatches[j%PATCH_SIZE][i%PATCH_SIZE];
}
float CTerrain::GetVertexGroundLevel(ssize_t i, ssize_t j) const
{
i = Clamp(i, 0, m_MapSize - 1);
j = Clamp(j, 0, m_MapSize - 1);
return HEIGHT_SCALE * m_Heightmap[j*m_MapSize + i];
}
fixed CTerrain::GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const
{
i = Clamp(i, 0, m_MapSize - 1);
j = Clamp(j, 0, m_MapSize - 1);
// Convert to fixed metres (being careful to avoid intermediate overflows)
return fixed::FromInt(m_Heightmap[j*m_MapSize + i] / 2) / (int)(HEIGHT_UNITS_PER_METRE / 2);
}
fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const
{
// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
i = Clamp(i, 0, m_MapSize - 2);
j = Clamp(j, 0, m_MapSize - 2);
u16 h00 = m_Heightmap[j*m_MapSize + i];
u16 h01 = m_Heightmap[(j+1)*m_MapSize + i];
u16 h10 = m_Heightmap[j*m_MapSize + (i+1)];
u16 h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
// Difference of highest point from lowest point
u16 delta = std::max(std::max(h00, h01), std::max(h10, h11)) -
std::min(std::min(h00, h01), std::min(h10, h11));
// Compute fractional slope (being careful to avoid intermediate overflows)
return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}
fixed CTerrain::GetExactSlopeFixed(fixed x, fixed z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = Clamp((x / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
const ssize_t zi = Clamp((z / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
const fixed one = fixed::FromInt(1);
const fixed xf = Clamp((x / static_cast(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one);
const fixed zf = Clamp((z / static_cast(TERRAIN_TILE_SIZE)) - fixed::FromInt(zi), fixed::Zero(), one);
u16 h00 = m_Heightmap[zi*m_MapSize + xi];
u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
u16 delta;
if (GetTriangulationDir(xi, zi))
{
if (xf + zf <= one)
{
// Lower-left triangle (don't use h11)
delta = std::max(std::max(h00, h01), h10) -
std::min(std::min(h00, h01), h10);
}
else
{
// Upper-right triangle (don't use h00)
delta = std::max(std::max(h01, h10), h11) -
std::min(std::min(h01, h10), h11);
}
}
else
{
if (xf <= zf)
{
// Upper-left triangle (don't use h10)
delta = std::max(std::max(h00, h01), h11) -
std::min(std::min(h00, h01), h11);
}
else
{
// Lower-right triangle (don't use h01)
delta = std::max(std::max(h00, h10), h11) -
std::min(std::min(h00, h10), h11);
}
}
// Compute fractional slope (being careful to avoid intermediate overflows)
return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
}
float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const
{
// convert to [0,1] interval
float nx = x / (TERRAIN_TILE_SIZE*m_MapSize);
float nz = z / (TERRAIN_TILE_SIZE*m_MapSize);
float nr = radius / (TERRAIN_TILE_SIZE*m_MapSize);
// get trilinear filtered mipmap height
return HEIGHT_SCALE * m_HeightMipmap.GetTrilinearGroundLevel(nx, nz, nr);
}
float CTerrain::GetExactGroundLevel(float x, float z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = Clamp(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
const ssize_t zi = Clamp(floor(z / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
const float xf = Clamp(x / TERRAIN_TILE_SIZE - xi, 0.0f, 1.0f);
const float zf = Clamp(z / TERRAIN_TILE_SIZE - zi, 0.0f, 1.0f);
float h00 = m_Heightmap[zi*m_MapSize + xi];
float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
// Determine which terrain triangle this point is on,
// then compute the linearly-interpolated height on that triangle's plane
if (GetTriangulationDir(xi, zi))
{
if (xf + zf <= 1.f)
{
// Lower-left triangle (don't use h11)
return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h01-h00)*zf);
}
else
{
// Upper-right triangle (don't use h00)
return HEIGHT_SCALE * (h11 + (h01-h11)*(1-xf) + (h10-h11)*(1-zf));
}
}
else
{
if (xf <= zf)
{
// Upper-left triangle (don't use h10)
return HEIGHT_SCALE * (h00 + (h11-h01)*xf + (h01-h00)*zf);
}
else
{
// Lower-right triangle (don't use h01)
return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h11-h10)*zf);
}
}
}
fixed CTerrain::GetExactGroundLevelFixed(fixed x, fixed z) const
{
// Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
const ssize_t xi = Clamp((x / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
const ssize_t zi = Clamp((z / static_cast(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
const fixed one = fixed::FromInt(1);
const fixed xf = Clamp((x / static_cast(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one);
const fixed zf = Clamp((z / static_cast(TERRAIN_TILE_SIZE)) - fixed::FromInt(zi), fixed::Zero(), one);
u16 h00 = m_Heightmap[zi*m_MapSize + xi];
u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
// Intermediate scaling of xf, so we don't overflow in the multiplications below
// (h00 <= 65535, xf <= 1, max fixed is < 32768; divide by 2 here so xf1*h00 <= 32767.5)
const fixed xf0 = xf / 2;
const fixed xf1 = (one - xf) / 2;
// Linearly interpolate
return ((one - zf).Multiply(xf1 * h00 + xf0 * h10)
+ zf.Multiply(xf1 * h01 + xf0 * h11)) / (int)(HEIGHT_UNITS_PER_METRE / 2);
// TODO: This should probably be more like GetExactGroundLevel()
// in handling triangulation properly
}
bool CTerrain::GetTriangulationDir(ssize_t i, ssize_t j) const
{
// Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
i = Clamp(i, 0, m_MapSize - 2);
j = Clamp(j, 0, m_MapSize - 2);
int h00 = m_Heightmap[j*m_MapSize + i];
int h01 = m_Heightmap[(j+1)*m_MapSize + i];
int h10 = m_Heightmap[j*m_MapSize + (i+1)];
int h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
// Prefer triangulating in whichever direction means the midpoint of the diagonal
// will be the highest. (In particular this means a diagonal edge will be straight
// along the top, and jagged along the bottom, which makes sense for terrain.)
int mid1 = h00+h11;
int mid2 = h01+h10;
return (mid1 < mid2);
}
void CTerrain::ResizeAndOffset(ssize_t size, ssize_t horizontalOffset, ssize_t verticalOffset)
{
if (size == m_MapSizePatches && horizontalOffset == 0 && verticalOffset == 0)
{
// Inexplicable request to resize terrain to the same size, ignore it.
return;
}
if (!m_Heightmap ||
std::abs(horizontalOffset) >= size / 2 + m_MapSizePatches / 2 ||
std::abs(verticalOffset) >= size / 2 + m_MapSizePatches / 2)
{
// We have not yet created a terrain, or we are offsetting outside the current source.
// Let's build a default terrain of the given size now.
Initialize(size, 0);
return;
}
// Allocate data for new terrain.
const ssize_t newMapSize = size * PATCH_SIZE + 1;
u16* newHeightmap = new u16[newMapSize * newMapSize];
memset(newHeightmap, 0, newMapSize * newMapSize * sizeof(u16));
CPatch* newPatches = new CPatch[size * size];
// O--------------------+
// | Source |
// | |
// | Source Center (SC) |
// | X |
// | A------+----------------+
// | | | Destination |
// | | | |
// +-------------+------B |
// | Dest. Center (DC) |
// | X |
// | |
// | |
// | |
// | |
// +-----------------------+
//
// Calculations below should also account cases like:
//
// +----------+ +----------+ +----------+ +---+--+---+ +------+
// |S | |D | |S | |S | | D| |D |
// | +---+ | | +---+ | +-+-+ | | | | | | +---+--+
// | | D | | | | S | | |D| | | +---+--+---+ +--+---+ |
// | +---+ | | +---+ | +-+-+ | | S|
// +----------+ +----------+ +----------+ +------+
//
// O = (0, 0)
// SC = (m_MapSizePatches / 2, m_MapSizePatches / 2)
// DC - SC = (horizontalOffset, verticalOffset)
//
// Source upper left:
// A = (max(0, (m_MapSizePatches - size) / 2 + horizontalOffset),
// max(0, (m_MapSizePatches - size) / 2 + verticalOffset))
// Source bottom right:
// B = (min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + horizontalOffset),
// min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + verticalOffset))
//
// A-B is the area that we have to copy from the source to the destination.
// Restate center offset as a window over destination.
// This has the effect of always considering the source to be the same size or smaller.
const ssize_t sourceUpperLeftX = std::max(
static_cast(0), m_MapSizePatches / 2 - size / 2 + horizontalOffset);
const ssize_t sourceUpperLeftZ = std::max(
static_cast(0), m_MapSizePatches / 2 - size / 2 + verticalOffset);
const ssize_t destUpperLeftX = std::max(
static_cast(0), (size / 2 - m_MapSizePatches / 2 - horizontalOffset));
const ssize_t destUpperLeftZ = std::max(
static_cast(0), (size / 2 - m_MapSizePatches / 2 - verticalOffset));
const ssize_t width =
std::min(m_MapSizePatches, m_MapSizePatches / 2 + horizontalOffset + size / 2) - sourceUpperLeftX;
const ssize_t depth =
std::min(m_MapSizePatches, m_MapSizePatches / 2 + verticalOffset + size / 2) - sourceUpperLeftZ;
for (ssize_t j = 0; j < depth * PATCH_SIZE; ++j)
{
// Copy the main part from the source. Destination heightmap:
// +----------+
// | |
// | 1234 | < current j-th row for example.
// | 5678 |
// | |
// +----------+
u16* dst = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize + destUpperLeftX * PATCH_SIZE;
u16* src = m_Heightmap + (j + sourceUpperLeftZ * PATCH_SIZE) * m_MapSize + sourceUpperLeftX * PATCH_SIZE;
std::copy_n(src, width * PATCH_SIZE, dst);
if (destUpperLeftX > 0)
{
// Fill the preceding part by copying the first elements of the
// main part. Destination heightmap:
// +----------+
// | |
// |1111234 | < current j-th row for example.
// | 5678 |
// | |
// +----------+
u16* dst_prefix = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize;
std::fill_n(dst_prefix, destUpperLeftX * PATCH_SIZE, dst[0]);
}
if ((destUpperLeftX + width) * PATCH_SIZE < newMapSize)
{
// Fill the succeeding part by copying the last elements of the
// main part. Destination heightmap:
// +----------+
// | |
// |1111234444| < current j-th row for example.
// | 5678 |
// | |
// +----------+
u16* dst_suffix = dst + width * PATCH_SIZE;
std::fill_n(
dst_suffix,
newMapSize - (width + destUpperLeftX) * PATCH_SIZE,
dst[width * PATCH_SIZE - 1]);
}
}
// Copy over heights from the preceding row. Destination heightmap:
// +----------+
// |1111234444| < copied from the row below
// |1111234444|
// |5555678888|
// | |
// +----------+
for (ssize_t j = 0; j < destUpperLeftZ * PATCH_SIZE; ++j)
{
u16* dst = newHeightmap + j * newMapSize;
u16* src = newHeightmap + destUpperLeftZ * PATCH_SIZE * newMapSize;
std::copy_n(src, newMapSize, dst);
}
// Copy over heights from the succeeding row. Destination heightmap:
// +----------+
// |1111234444|
// |1111234444|
// |5555678888|
// |5555678888| < copied from the row above
// +----------+
for (ssize_t j = (destUpperLeftZ + depth) * PATCH_SIZE; j < newMapSize; ++j)
{
u16* dst = newHeightmap + j * newMapSize;
u16* src = newHeightmap + ((destUpperLeftZ + depth) * PATCH_SIZE - 1) * newMapSize;
std::copy_n(src, newMapSize, dst);
}
// Now build new patches. The same process as for the heightmap.
for (ssize_t j = 0; j < depth; ++j)
{
for (ssize_t i = 0; i < width; ++i)
{
const CPatch& src =
m_Patches[(sourceUpperLeftZ + j) * m_MapSizePatches + sourceUpperLeftX + i];
CPatch& dst =
newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + i];
std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]);
}
for (ssize_t i = 0; i < destUpperLeftX; ++i)
for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
{
const CMiniPatch& src =
newPatches[(destUpperLeftZ + j) * size + destUpperLeftX]
.m_MiniPatches[jPatch][0];
for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
{
CMiniPatch& dst =
newPatches[(destUpperLeftZ + j) * size + i]
.m_MiniPatches[jPatch][iPatch];
dst = src;
}
}
for (ssize_t i = destUpperLeftX + width; i < size; ++i)
{
for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
{
const CMiniPatch& src =
newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + width - 1]
.m_MiniPatches[jPatch][PATCH_SIZE - 1];
for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
{
CMiniPatch& dst =
newPatches[(destUpperLeftZ + j) * size + i].m_MiniPatches[jPatch][iPatch];
dst = src;
}
}
}
}
for (ssize_t j = 0; j < destUpperLeftZ; ++j)
for (ssize_t i = 0; i < size; ++i)
for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
{
const CMiniPatch& src =
newPatches[destUpperLeftZ * size + i].m_MiniPatches[0][iPatch];
for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
{
CMiniPatch& dst =
newPatches[j * size + i].m_MiniPatches[jPatch][iPatch];
dst = src;
}
}
for (ssize_t j = destUpperLeftZ + depth; j < size; ++j)
for (ssize_t i = 0; i < size; ++i)
for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
{
const CMiniPatch& src =
newPatches[(destUpperLeftZ + depth - 1) * size + i].m_MiniPatches[0][iPatch];
for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
{
CMiniPatch& dst =
newPatches[j * size + i].m_MiniPatches[jPatch][iPatch];
dst = src;
}
}
// Release all the original data.
ReleaseData();
// Store new data.
m_Heightmap = newHeightmap;
m_Patches = newPatches;
m_MapSize = newMapSize;
m_MapSizePatches = size;
// Initialise all the new patches.
InitialisePatches();
// Initialise mipmap.
m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
}
///////////////////////////////////////////////////////////////////////////////
// InitialisePatches: initialise patch data
void CTerrain::InitialisePatches()
{
for (ssize_t j = 0; j < m_MapSizePatches; j++)
{
for (ssize_t i = 0; i < m_MapSizePatches; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail
patch->Initialize(this, i, j);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// SetHeightMap: set up a new heightmap from 16-bit source data;
// assumes heightmap matches current terrain size
void CTerrain::SetHeightMap(u16* heightmap)
{
// keep a copy of the given heightmap
memcpy(m_Heightmap, heightmap, m_MapSize*m_MapSize*sizeof(u16));
// recalculate patch bounds, invalidate vertices
for (ssize_t j = 0; j < m_MapSizePatches; j++)
{
for (ssize_t i = 0; i < m_MapSizePatches; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail
patch->InvalidateBounds();
patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
}
}
// update mipmap
m_HeightMipmap.Update(m_Heightmap);
}
///////////////////////////////////////////////////////////////////////////////
void CTerrain::MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dirtyFlags)
{
// Finds the inclusive limits of the patches that include the specified range of tiles
ssize_t pi0 = Clamp( i0 /PATCH_SIZE, 0, m_MapSizePatches-1);
ssize_t pi1 = Clamp((i1-1)/PATCH_SIZE, 0, m_MapSizePatches-1);
ssize_t pj0 = Clamp( j0 /PATCH_SIZE, 0, m_MapSizePatches-1);
ssize_t pj1 = Clamp((j1-1)/PATCH_SIZE, 0, m_MapSizePatches-1);
for (ssize_t j = pj0; j <= pj1; j++)
{
for (ssize_t i = pi0; i <= pi1; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail (i,j were clamped)
if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
patch->CalcBounds();
patch->SetDirty(dirtyFlags);
}
}
if (m_Heightmap)
{
m_HeightMipmap.Update(m_Heightmap,
Clamp(i0, 0, m_MapSize - 1),
Clamp(j0, 0, m_MapSize - 1),
Clamp(i1, 1, m_MapSize),
Clamp(j1, 1, m_MapSize)
);
}
}
void CTerrain::MakeDirty(int dirtyFlags)
{
for (ssize_t j = 0; j < m_MapSizePatches; j++)
{
for (ssize_t i = 0; i < m_MapSizePatches; i++)
{
CPatch* patch = GetPatch(i, j); // can't fail
if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
patch->CalcBounds();
patch->SetDirty(dirtyFlags);
}
}
if (m_Heightmap)
m_HeightMipmap.Update(m_Heightmap);
}
CBoundingBoxAligned CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
i0 = Clamp(i0, 0, m_MapSize - 1);
j0 = Clamp(j0, 0, m_MapSize - 1);
i1 = Clamp(i1, 0, m_MapSize - 1);
j1 = Clamp(j1, 0, m_MapSize - 1);
u16 minH = 65535;
u16 maxH = 0;
for (ssize_t j = j0; j <= j1; ++j)
{
for (ssize_t i = i0; i <= i1; ++i)
{
minH = std::min(minH, m_Heightmap[j*m_MapSize + i]);
maxH = std::max(maxH, m_Heightmap[j*m_MapSize + i]);
}
}
CBoundingBoxAligned bound;
bound[0].X = (float)(i0*TERRAIN_TILE_SIZE);
bound[0].Y = (float)(minH*HEIGHT_SCALE);
bound[0].Z = (float)(j0*TERRAIN_TILE_SIZE);
bound[1].X = (float)(i1*TERRAIN_TILE_SIZE);
bound[1].Y = (float)(maxH*HEIGHT_SCALE);
bound[1].Z = (float)(j1*TERRAIN_TILE_SIZE);
return bound;
}
Index: ps/trunk/source/graphics/TerrainTextureManager.cpp
===================================================================
--- ps/trunk/source/graphics/TerrainTextureManager.cpp (revision 26120)
+++ ps/trunk/source/graphics/TerrainTextureManager.cpp (revision 26121)
@@ -1,148 +1,146 @@
/* 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 "TerrainTextureManager.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainProperties.h"
-#include "lib/ogl.h"
#include "lib/timer.h"
-#include "lib/res/graphics/ogl_tex.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/XML/Xeromyces.h"
#include
#include
#include
CTerrainTextureManager::CTerrainTextureManager()
: m_LastGroupIndex(0)
{
if (!VfsDirectoryExists(L"art/terrains/"))
return;
if (!CXeromyces::AddValidator(g_VFS, "terrain", "art/terrains/terrain.rng"))
LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain.rng'");
if (!CXeromyces::AddValidator(g_VFS, "terrain_texture", "art/terrains/terrain_texture.rng"))
LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain_texture.rng'");
}
CTerrainTextureManager::~CTerrainTextureManager()
{
UnloadTerrainTextures();
for (std::pair& ta : m_TerrainAlphas)
ta.second.m_CompositeAlphaMap.reset();
}
void CTerrainTextureManager::UnloadTerrainTextures()
{
for (CTerrainTextureEntry* const& te : m_TextureEntries)
delete te;
m_TextureEntries.clear();
for (const std::pair& tg : m_TerrainGroups)
delete tg.second;
m_TerrainGroups.clear();
m_LastGroupIndex = 0;
}
CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) const
{
CStr tag = tag_.BeforeLast("."); // Strip extension
for (CTerrainTextureEntry* const& te : m_TextureEntries)
if (te->GetTag() == tag)
return te;
LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag.c_str());
return 0;
}
CTerrainTextureEntry* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr& props, const VfsPath& path)
{
CTerrainTextureEntry* entry = new CTerrainTextureEntry(props, path);
m_TextureEntries.push_back(entry);
return entry;
}
void CTerrainTextureManager::DeleteTexture(CTerrainTextureEntry* entry)
{
std::vector::iterator it = std::find(m_TextureEntries.begin(), m_TextureEntries.end(), entry);
if (it != m_TextureEntries.end())
m_TextureEntries.erase(it);
delete entry;
}
struct AddTextureCallbackData
{
CTerrainTextureManager* self;
CTerrainPropertiesPtr props;
};
static Status AddTextureDirCallback(const VfsPath& pathname, const uintptr_t cbData)
{
AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
VfsPath path = pathname / L"terrains.xml";
if (!VfsFileExists(path))
LOGMESSAGE("'%s' does not exist. Using previous properties.", path.string8());
else
data.props = CTerrainProperties::FromXML(data.props, path);
return INFO::OK;
}
static Status AddTextureCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
if (pathname.Basename() != L"terrains")
data.self->AddTexture(data.props, pathname);
return INFO::OK;
}
int CTerrainTextureManager::LoadTerrainTextures()
{
AddTextureCallbackData data = {this, CTerrainPropertiesPtr(new CTerrainProperties(CTerrainPropertiesPtr()))};
vfs::ForEachFile(g_VFS, L"art/terrains/", AddTextureCallback, (uintptr_t)&data, L"*.xml", vfs::DIR_RECURSIVE, AddTextureDirCallback, (uintptr_t)&data);
return 0;
}
CTerrainGroup* CTerrainTextureManager::FindGroup(const CStr& name)
{
TerrainGroupMap::const_iterator it = m_TerrainGroups.find(name);
if (it != m_TerrainGroups.end())
return it->second;
else
return m_TerrainGroups[name] = new CTerrainGroup(name, ++m_LastGroupIndex);
}
void CTerrainGroup::AddTerrain(CTerrainTextureEntry* pTerrain)
{
m_Terrains.push_back(pTerrain);
}
void CTerrainGroup::RemoveTerrain(CTerrainTextureEntry* pTerrain)
{
std::vector::iterator it = find(m_Terrains.begin(), m_Terrains.end(), pTerrain);
if (it != m_Terrains.end())
m_Terrains.erase(it);
}
Index: ps/trunk/source/graphics/TerrainTextureManager.h
===================================================================
--- ps/trunk/source/graphics/TerrainTextureManager.h (revision 26120)
+++ ps/trunk/source/graphics/TerrainTextureManager.h (revision 26121)
@@ -1,135 +1,134 @@
/* 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_TERRAINTEXTUREMANAGER
#define INCLUDED_TERRAINTEXTUREMANAGER
-#include