Index: ps/trunk/source/maths/BoundingBoxAligned.cpp
===================================================================
--- ps/trunk/source/maths/BoundingBoxAligned.cpp (revision 22371)
+++ ps/trunk/source/maths/BoundingBoxAligned.cpp (revision 22372)
@@ -1,322 +1,335 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
* Axis-aligned bounding box
*/
#include "precompiled.h"
#include "BoundingBoxAligned.h"
-#include "lib/ogl.h"
-
-#include
-
#include "graphics/Frustum.h"
#include "graphics/ShaderProgram.h"
+#include "lib/ogl.h"
#include "maths/BoundingBoxOriented.h"
#include "maths/Brush.h"
#include "maths/Matrix3D.h"
+#include
+
const CBoundingBoxAligned CBoundingBoxAligned::EMPTY = CBoundingBoxAligned(); // initializes to an empty bound
///////////////////////////////////////////////////////////////////////////////
// RayIntersect: intersect ray with this bound; return true
// if ray hits (and store entry and exit times), or false
// otherwise
// note: incoming ray direction must be normalised
-bool CBoundingBoxAligned::RayIntersect(const CVector3D& origin,const CVector3D& dir,
- float& tmin,float& tmax) const
+bool CBoundingBoxAligned::RayIntersect(
+ const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const
{
- float t1,t2;
- float tnear,tfar;
+ float t1, t2;
+ float tnear, tfar;
- if (dir[0]==0) {
- if (origin[0]m_Data[1][0])
+ if (dir[0] == 0)
+ {
+ if (origin[0] < m_Data[0][0] || origin[0] > m_Data[1][0])
return false;
- else {
- tnear=(float) -FLT_MAX;
- tfar=(float) FLT_MAX;
+ else
+ {
+ tnear = -std::numeric_limits::max();
+ tfar = std::numeric_limits::max();
}
- } else {
- t1=(m_Data[0][0]-origin[0])/dir[0];
- t2=(m_Data[1][0]-origin[0])/dir[0];
+ }
+ else
+ {
+ t1 = (m_Data[0][0] - origin[0]) / dir[0];
+ t2 = (m_Data[1][0] - origin[0]) / dir[0];
- if (dir[0]<0) {
+ if (dir[0] < 0)
+ {
tnear = t2;
tfar = t1;
- } else {
+ }
+ else
+ {
tnear = t1;
tfar = t2;
}
- if (tfar<0)
+ if (tfar < 0)
return false;
}
- if (dir[1]==0 && (origin[1]m_Data[1][1]))
+ if (dir[1] == 0 && (origin[1] < m_Data[0][1] || origin[1] > m_Data[1][1]))
return false;
- else {
- t1=(m_Data[0][1]-origin[1])/dir[1];
- t2=(m_Data[1][1]-origin[1])/dir[1];
-
- if (dir[1]<0) {
- if (t2>tnear)
+ else
+ {
+ t1 = (m_Data[0][1] - origin[1]) / dir[1];
+ t2 = (m_Data[1][1] - origin[1]) / dir[1];
+
+ if (dir[1] < 0)
+ {
+ if (t2 > tnear)
tnear = t2;
- if (t1tnear)
+ }
+ else
+ {
+ if (t1 > tnear)
tnear = t1;
- if (t2tfar || tfar<0)
+ if (tnear > tfar || tfar < 0)
return false;
}
- if (dir[2]==0 && (origin[2]m_Data[1][2]))
+ if (dir[2] == 0 && (origin[2] < m_Data[0][2] || origin[2] > m_Data[1][2]))
return false;
- else {
- t1=(m_Data[0][2]-origin[2])/dir[2];
- t2=(m_Data[1][2]-origin[2])/dir[2];
-
- if (dir[2]<0) {
- if (t2>tnear)
+ else
+ {
+ t1 = (m_Data[0][2] - origin[2]) / dir[2];
+ t2 = (m_Data[1][2] - origin[2]) / dir[2];
+
+ if (dir[2] < 0)
+ {
+ if (t2 > tnear)
tnear = t2;
- if (t1tnear)
+ }
+ else
+ {
+ if (t1 > tnear)
tnear = t1;
- if (t2tfar || tfar<0)
- return false;
+ if (tnear > tfar || tfar < 0)
+ return false;
}
- tmin=tnear;
- tmax=tfar;
+ tmin = tnear;
+ tmax = tfar;
return true;
}
///////////////////////////////////////////////////////////////////////////////
// SetEmpty: initialise this bound as empty
void CBoundingBoxAligned::SetEmpty()
{
- m_Data[0]=CVector3D( FLT_MAX, FLT_MAX, FLT_MAX);
- m_Data[1]=CVector3D(-FLT_MAX,-FLT_MAX,-FLT_MAX);
+ m_Data[0] = CVector3D::Max();
+ m_Data[1] = CVector3D::Min();
}
///////////////////////////////////////////////////////////////////////////////
// IsEmpty: tests whether this bound is empty
bool CBoundingBoxAligned::IsEmpty() const
{
- return (m_Data[0].X == FLT_MAX && m_Data[0].Y == FLT_MAX && m_Data[0].Z == FLT_MAX
- && m_Data[1].X == -FLT_MAX && m_Data[1].Y == -FLT_MAX && m_Data[1].Z == -FLT_MAX);
+ return m_Data[0] == CVector3D::Max() && m_Data[1] == CVector3D::Min();
}
///////////////////////////////////////////////////////////////////////////////
// Transform: transform this bound by given matrix; return transformed bound
// in 'result' parameter - slightly modified version of code in Graphic Gems
// (can't remember which one it was, though)
void CBoundingBoxAligned::Transform(const CMatrix3D& m, CBoundingBoxAligned& result) const
{
- ENSURE(this!=&result);
+ ENSURE(this != &result);
- for (int i=0;i<3;++i) {
+ for (int i = 0; i < 3; ++i)
+ {
// handle translation
- result[0][i]=result[1][i]=m(i,3);
+ result[0][i] = result[1][i] = m(i, 3);
// Now find the extreme points by considering the product of the
// min and max with each component of matrix
- for(int j=0;j<3;j++) {
- float a=m(i,j)*m_Data[0][j];
- float b=m(i,j)*m_Data[1][j];
-
- if (a= b)
+ std::swap(a, b);
+
+ result[0][i] += a;
+ result[1][i] += b;
}
}
}
void CBoundingBoxAligned::Transform(const CMatrix3D& transform, CBoundingBoxOriented& result) const
{
const CVector3D& pMin = m_Data[0];
const CVector3D& pMax = m_Data[1];
// the basis vectors of the OBB are the normalized versions of the transformed AABB basis vectors, which
// are the columns of the identity matrix, so the unnormalized OBB basis vectors are the transformation
// matrix columns:
CVector3D u(transform._11, transform._21, transform._31);
CVector3D v(transform._12, transform._22, transform._32);
CVector3D w(transform._13, transform._23, transform._33);
// the half-sizes are scaled by whatever factor the AABB unit vectors end up scaled by
result.m_HalfSizes = CVector3D(
(pMax.X - pMin.X) / 2.f * u.Length(),
(pMax.Y - pMin.Y) / 2.f * v.Length(),
(pMax.Z - pMin.Z) / 2.f * w.Length()
);
u.Normalize();
v.Normalize();
w.Normalize();
result.m_Basis[0] = u;
result.m_Basis[1] = v;
result.m_Basis[2] = w;
result.m_Center = transform.Transform((pMax + pMin) * 0.5f);
}
///////////////////////////////////////////////////////////////////////////////
// Intersect with the given frustum in a conservative manner
void CBoundingBoxAligned::IntersectFrustumConservative(const CFrustum& frustum)
{
// if this bound is empty, then the result must be empty (we should not attempt to intersect with
// a brush, may cause crashes due to the numeric representation of empty bounds -- see
// http://trac.wildfiregames.com/ticket/1027)
if (IsEmpty())
return;
CBrush brush(*this);
CBrush buf;
brush.Intersect(frustum, buf);
buf.Bounds(*this);
}
///////////////////////////////////////////////////////////////////////////////
CFrustum CBoundingBoxAligned::ToFrustum() const
{
CFrustum frustum;
frustum.SetNumPlanes(6);
// get the LEFT plane
frustum.m_aPlanes[0].m_Norm = CVector3D(1, 0, 0);
frustum.m_aPlanes[0].m_Dist = -m_Data[0].X;
// get the RIGHT plane
frustum.m_aPlanes[1].m_Norm = CVector3D(-1, 0, 0);
frustum.m_aPlanes[1].m_Dist = m_Data[1].X;
// get the BOTTOM plane
frustum.m_aPlanes[2].m_Norm = CVector3D(0, 1, 0);
frustum.m_aPlanes[2].m_Dist = -m_Data[0].Y;
// get the TOP plane
frustum.m_aPlanes[3].m_Norm = CVector3D(0, -1, 0);
frustum.m_aPlanes[3].m_Dist = m_Data[1].Y;
// get the NEAR plane
frustum.m_aPlanes[4].m_Norm = CVector3D(0, 0, 1);
frustum.m_aPlanes[4].m_Dist = -m_Data[0].Z;
// get the FAR plane
frustum.m_aPlanes[5].m_Norm = CVector3D(0, 0, -1);
frustum.m_aPlanes[5].m_Dist = m_Data[1].Z;
return frustum;
}
///////////////////////////////////////////////////////////////////////////////
void CBoundingBoxAligned::Expand(float amount)
{
m_Data[0] -= CVector3D(amount, amount, amount);
m_Data[1] += CVector3D(amount, amount, amount);
}
///////////////////////////////////////////////////////////////////////////////
// Render the bounding box
void CBoundingBoxAligned::Render(CShaderProgramPtr& shader) const
{
std::vector data;
#define ADD_FACE(x, y, z) \
ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \
ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z);
#define ADD_PT(u_, v_, x, y, z) \
STMT(int u = u_; int v = v_; \
data.push_back(u); \
data.push_back(v); \
data.push_back(m_Data[x].X); \
data.push_back(m_Data[y].Y); \
data.push_back(m_Data[z].Z); \
)
ADD_FACE(u, v, 0);
ADD_FACE(0, u, v);
ADD_FACE(u, 0, 1-v);
ADD_FACE(u, 1-v, 1);
ADD_FACE(1, u, 1-v);
ADD_FACE(u, 1, v);
#undef ADD_FACE
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6*6);
}
void CBoundingBoxAligned::RenderOutline(CShaderProgramPtr& shader) const
{
std::vector data;
#define ADD_FACE(x, y, z) \
ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); \
ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \
ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); \
ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z);
#define ADD_PT(u_, v_, x, y, z) \
STMT(int u = u_; int v = v_; \
data.push_back(u); \
data.push_back(v); \
data.push_back(m_Data[x].X); \
data.push_back(m_Data[y].Y); \
data.push_back(m_Data[z].Z); \
)
ADD_FACE(u, v, 0);
ADD_FACE(0, u, v);
ADD_FACE(u, 0, 1-v);
ADD_FACE(u, 1-v, 1);
ADD_FACE(1, u, 1-v);
ADD_FACE(u, 1, v);
#undef ADD_FACE
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_LINES, 0, 6*8);
}
Index: ps/trunk/source/maths/BoundingBoxAligned.h
===================================================================
--- ps/trunk/source/maths/BoundingBoxAligned.h (revision 22371)
+++ ps/trunk/source/maths/BoundingBoxAligned.h (revision 22372)
@@ -1,171 +1,165 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
* Axis-aligned bounding box
*/
#ifndef INCLUDED_BOUND
#define INCLUDED_BOUND
-// necessary includes
-#include "Vector3D.h"
+#include "maths/Vector3D.h"
#include "graphics/ShaderProgramPtr.h"
class CFrustum;
class CMatrix3D;
class CBoundingBoxOriented;
-///////////////////////////////////////////////////////////////////////////////
-// basic axis aligned bounding box (AABB) class
+// Basic axis aligned bounding box (AABB) class
class CBoundingBoxAligned
{
public:
+ static const CBoundingBoxAligned EMPTY;
CBoundingBoxAligned() { SetEmpty(); }
- CBoundingBoxAligned(const CVector3D& min, const CVector3D& max) {
+ CBoundingBoxAligned(const CVector3D& min, const CVector3D& max)
+ {
m_Data[0] = min;
m_Data[1] = max;
}
/**
* Transforms these bounds according to the specified transformation matrix @p m, and writes the axis-aligned bounds
* of that result to @p result.
*/
void Transform(const CMatrix3D& m, CBoundingBoxAligned& result) const;
/**
* Transform these bounds using the matrix @p transform, and write out the result as an oriented (i.e. non-axis-aligned) box.
* The difference with @ref Transform(const CMatrix3D&, CBoundingBoxAligned&) is that that method is equivalent to first
* computing this result, and then taking the axis-aligned bounding boxes from the result again.
*/
void Transform(const CMatrix3D& m, CBoundingBoxOriented& result) const;
/**
* Translates these bounds by @p v, and writes the result to @p result.
*/
void Translate(const CVector3D& v, CBoundingBoxAligned& result) const
{
result.m_Data[0] = m_Data[0] + v;
result.m_Data[1] = m_Data[1] + v;
}
CVector3D& operator[](int index) { return m_Data[index]; }
const CVector3D& operator[](int index) const { return m_Data[index]; }
void SetEmpty();
bool IsEmpty() const;
void Extend(const CVector3D& min, const CVector3D& max)
{
if (min.X < m_Data[0].X) m_Data[0].X = min.X;
if (min.Y < m_Data[0].Y) m_Data[0].Y = min.Y;
if (min.Z < m_Data[0].Z) m_Data[0].Z = min.Z;
if (max.X > m_Data[1].X) m_Data[1].X = max.X;
if (max.Y > m_Data[1].Y) m_Data[1].Y = max.Y;
if (max.Z > m_Data[1].Z) m_Data[1].Z = max.Z;
}
// operator+=: extend this bound to include given bound
CBoundingBoxAligned& operator+=(const CBoundingBoxAligned& b)
{
Extend(b.m_Data[0], b.m_Data[1]);
return *this;
}
// operator+=: extend this bound to include given point
CBoundingBoxAligned& operator+=(const CVector3D& pt)
{
Extend(pt, pt);
return *this;
}
/**
* Check if a given ray intersects this AABB.
* See also Real-Time Rendering, Third Edition by T. Akenine-Moller, p. 741--742.
*
* @param[in] origin Origin of the ray.
* @param[in] dir Direction vector of the ray, defining the positive direction of the ray. Must be of unit length.
* @param[out] tmin,tmax distance in the positive direction from the origin of the ray to the entry and exit points in
* the bounding box. If the origin is inside the box, then this is counted as an intersection and one of @p tMin and @p tMax may be negative.
*
* @return true if the ray originating in @p origin and with unit direction vector @p dir intersects this AABB, false otherwise.
*/
bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const;
// return the volume of this bounding box
float GetVolume() const
{
CVector3D v = m_Data[1] - m_Data[0];
return (std::max(v.X, 0.0f) * std::max(v.Y, 0.0f) * std::max(v.Z, 0.0f));
}
- // return the centre of this bounding box
- void GetCentre(CVector3D& centre) const
+ // return the center of this bounding box
+ void GetCenter(CVector3D& center) const
{
- centre = (m_Data[0] + m_Data[1]) * 0.5f;
+ center = (m_Data[0] + m_Data[1]) * 0.5f;
}
/**
* Expand the bounding box by the given amount in every direction.
*/
void Expand(float amount);
/**
* IntersectFrustumConservative: Approximate the intersection of this bounds object
* with the given frustum. The bounds object is overwritten with the results.
*
* The approximation is conservative in the sense that the result will always contain
* the actual intersection, but it may be larger than the intersection itself.
* The result will always be fully contained within the original bounds.
*
* @note While not in the spirit of this function's purpose, a no-op would be a correct
* implementation of this function.
* @note If this bound is empty, the result is the empty bound.
*
* @param frustum the frustum to intersect with
*/
void IntersectFrustumConservative(const CFrustum& frustum);
/**
* Construct a CFrustum that describes the same volume as this bounding box.
* Only valid for non-empty bounding boxes - check IsEmpty() first.
*/
CFrustum ToFrustum() const;
/**
* Render the surfaces of the bound object as triangles.
*/
void Render(CShaderProgramPtr& shader) const;
/**
* Render the outline of the bound object as lines.
*/
void RenderOutline(CShaderProgramPtr& shader) const;
private:
// Holds the minimal and maximal coordinate points in m_Data[0] and m_Data[1], respectively.
CVector3D m_Data[2];
-
-public:
- static const CBoundingBoxAligned EMPTY;
-
};
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-#endif
+#endif // INCLUDED_BOUND
Index: ps/trunk/source/maths/BoundingBoxOriented.cpp
===================================================================
--- ps/trunk/source/maths/BoundingBoxOriented.cpp (revision 22371)
+++ ps/trunk/source/maths/BoundingBoxOriented.cpp (revision 22372)
@@ -1,97 +1,97 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2019 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 "BoundingBoxOriented.h"
#include "maths/BoundingBoxAligned.h"
#include
const CBoundingBoxOriented CBoundingBoxOriented::EMPTY = CBoundingBoxOriented();
CBoundingBoxOriented::CBoundingBoxOriented(const CBoundingBoxAligned& bound)
{
if (bound.IsEmpty())
{
SetEmpty();
}
else
{
- bound.GetCentre(m_Center);
+ bound.GetCenter(m_Center);
// the axes of an AABB are the world-space axes
m_Basis[0].X = 1.f; m_Basis[0].Y = 0.f; m_Basis[0].Z = 0.f;
m_Basis[1].X = 0.f; m_Basis[1].Y = 1.f; m_Basis[1].Z = 0.f;
m_Basis[2].X = 0.f; m_Basis[2].Y = 0.f; m_Basis[2].Z = 1.f;
// element-wise division by two to get half sizes (remember, [1] and [0] are the max and min coord points)
m_HalfSizes = (bound[1] - bound[0]) * 0.5f;
}
}
bool CBoundingBoxOriented::RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin_out, float& tMax_out) const
{
// See Real-Time Rendering, Third Edition, p. 743
float tMin = -FLT_MAX;
float tMax = FLT_MAX;
CVector3D p = m_Center - origin;
for (int i = 0; i < 3; ++i)
{
// test the ray for intersections with the slab whose normal vector is m_Basis[i]
float e = m_Basis[i].Dot(p); // distance between the ray origin and the box center projected onto the slab normal
float f = m_Basis[i].Dot(dir); // cosine of the angle between the slab normal and the ray direction
if(fabsf(f) > 1e-10f)
{
// Determine the distances t1 and t2 from the origin of the ray to the points where it intersects
// the slab. See docs/ray_intersect.pdf for why/how this works.
float invF = 1.f/f;
float t1 = (e + m_HalfSizes[i]) * invF;
float t2 = (e - m_HalfSizes[i]) * invF;
// make sure t1 <= t2, swap if necessary
if (t1 > t2)
{
float tmp = t1;
t1 = t2;
t2 = tmp;
}
// update the overall tMin and tMax if necessary
if (t1 > tMin) tMin = t1;
if (t2 < tMax) tMax = t2;
// try to break out of the loop as fast as possible by checking for some conditions
if (tMin > tMax) return false; // ray misses the box
if (tMax < 0) return false; // box is behind the ray origin
}
else
{
// the ray is parallel to the slab currently being tested, or is as close to parallel
// as makes no difference; return false if the ray is outside of the slab.
if (e > m_HalfSizes[i] || -e > m_HalfSizes[i])
return false;
}
}
tMin_out = tMin;
tMax_out = tMax;
return true;
}
Index: ps/trunk/source/maths/Vector3D.cpp
===================================================================
--- ps/trunk/source/maths/Vector3D.cpp (revision 22371)
+++ ps/trunk/source/maths/Vector3D.cpp (revision 22372)
@@ -1,83 +1,95 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
* Provides an interface for a vector in R3 and allows vector and
* scalar operations on it
*/
#include "precompiled.h"
#include "Vector3D.h"
-#include
-#include
#include "MathUtil.h"
#include "FixedVector3D.h"
+#include
+#include
+
CVector3D::CVector3D(const CFixedVector3D& v) :
X(v.X.ToFloat()), Y(v.Y.ToFloat()), Z(v.Z.ToFloat())
{
}
+// static
+CVector3D CVector3D::Max()
+{
+ const float max_float = std::numeric_limits::max();
+ return CVector3D(max_float, max_float, max_float);
+}
+
+// static
+CVector3D CVector3D::Min()
+{
+ const float max_float = std::numeric_limits::max();
+ return CVector3D(-max_float, -max_float, -max_float);
+}
+
int CVector3D::operator ! () const
{
if (X != 0.0f ||
Y != 0.0f ||
Z != 0.0f)
return 0;
return 1;
}
float CVector3D::LengthSquared () const
{
return ( SQR(X) + SQR(Y) + SQR(Z) );
}
float CVector3D::Length () const
{
return sqrtf ( LengthSquared() );
}
void CVector3D::Normalize ()
{
float scale = 1.0f/Length ();
X *= scale;
Y *= scale;
Z *= scale;
}
CVector3D CVector3D::Normalized () const
{
float scale = 1.0f/Length ();
return CVector3D(X * scale, Y * scale, Z * scale);
}
//-----------------------------------------------------------------------------
float MaxComponent(const CVector3D& v)
{
- float max = -FLT_MAX;
- for(int i = 0; i < 3; i++)
- max = std::max(max, v[i]);
- return max;
+ return std::max({v.X, v.Y, v.Z});
}
Index: ps/trunk/source/maths/Vector3D.h
===================================================================
--- ps/trunk/source/maths/Vector3D.h (revision 22371)
+++ ps/trunk/source/maths/Vector3D.h (revision 22372)
@@ -1,126 +1,130 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
* Provides an interface for a vector in R3 and allows vector and
* scalar operations on it
*/
#ifndef INCLUDED_VECTOR3D
#define INCLUDED_VECTOR3D
class CFixedVector3D;
class CVector3D
{
public:
float X, Y, Z;
public:
+ // Returns maximum/minimum possible position stored in the CVector3D.
+ static CVector3D Max();
+ static CVector3D Min();
+
CVector3D() : X(0.0f), Y(0.0f), Z(0.0f) {}
CVector3D(float x, float y, float z) : X(x), Y(y), Z(z) {}
CVector3D(const CFixedVector3D& v);
int operator!() const;
float& operator[](int index) { return *((&X)+index); }
const float& operator[](int index) const { return *((&X)+index); }
// vector equality (testing float equality, so please be careful if necessary)
bool operator==(const CVector3D &vector) const
{
return (X == vector.X && Y == vector.Y && Z == vector.Z);
}
bool operator!=(const CVector3D& vector) const
{
return !operator==(vector);
}
CVector3D operator+(const CVector3D& vector) const
{
return CVector3D(X + vector.X, Y + vector.Y, Z + vector.Z);
}
CVector3D& operator+=(const CVector3D& vector)
{
X += vector.X;
Y += vector.Y;
Z += vector.Z;
return *this;
}
CVector3D operator-(const CVector3D& vector) const
{
return CVector3D(X - vector.X, Y - vector.Y, Z - vector.Z);
}
CVector3D& operator-=(const CVector3D& vector)
{
X -= vector.X;
Y -= vector.Y;
Z -= vector.Z;
return *this;
}
CVector3D operator*(float value) const
{
return CVector3D(X * value, Y * value, Z * value);
}
CVector3D& operator*=(float value)
{
X *= value;
Y *= value;
Z *= value;
return *this;
}
CVector3D operator-() const
{
return CVector3D(-X, -Y, -Z);
}
public:
float Dot (const CVector3D &vector) const
{
return ( X * vector.X +
Y * vector.Y +
Z * vector.Z );
}
CVector3D Cross (const CVector3D &vector) const
{
CVector3D Temp;
Temp.X = (Y * vector.Z) - (Z * vector.Y);
Temp.Y = (Z * vector.X) - (X * vector.Z);
Temp.Z = (X * vector.Y) - (Y * vector.X);
return Temp;
}
float Length () const;
float LengthSquared () const;
void Normalize ();
CVector3D Normalized () const;
// Returns 3 element array of floats, e.g. for glVertex3fv
const float* GetFloatArray() const { return &X; }
};
extern float MaxComponent(const CVector3D& v);
#endif
Index: ps/trunk/source/maths/tests/test_Bound.h
===================================================================
--- ps/trunk/source/maths/tests/test_Bound.h (revision 22371)
+++ ps/trunk/source/maths/tests/test_Bound.h (revision 22372)
@@ -1,211 +1,211 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "lib/self_test.h"
#include "lib/posix/posix.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/BoundingBoxOriented.h"
#include "maths/Matrix3D.h"
#define TS_ASSERT_VEC_DELTA(v, x, y, z, delta) \
TS_ASSERT_DELTA(v.X, x, delta); \
TS_ASSERT_DELTA(v.Y, y, delta); \
TS_ASSERT_DELTA(v.Z, z, delta);
class TestBound : public CxxTest::TestSuite
{
public:
void setUp()
{
CxxTest::setAbortTestOnFail(true);
}
void test_empty_aabb()
{
CBoundingBoxAligned bound;
TS_ASSERT(bound.IsEmpty());
bound += CVector3D(1, 2, 3);
TS_ASSERT(! bound.IsEmpty());
bound.SetEmpty();
TS_ASSERT(bound.IsEmpty());
}
void test_empty_obb()
{
CBoundingBoxOriented bound;
TS_ASSERT(bound.IsEmpty());
bound.m_Basis[0] = CVector3D(1,0,0);
bound.m_Basis[1] = CVector3D(0,1,0);
bound.m_Basis[2] = CVector3D(0,0,1);
bound.m_HalfSizes = CVector3D(1,2,3);
TS_ASSERT(!bound.IsEmpty());
bound.SetEmpty();
TS_ASSERT(bound.IsEmpty());
}
void test_extend_vector()
{
CBoundingBoxAligned bound;
CVector3D v (1, 2, 3);
bound += v;
- CVector3D centre;
- bound.GetCentre(centre);
- TS_ASSERT_EQUALS(centre, v);
+ CVector3D center;
+ bound.GetCenter(center);
+ TS_ASSERT_EQUALS(center, v);
}
void test_extend_bound()
{
CBoundingBoxAligned bound;
CVector3D v (1, 2, 3);
CBoundingBoxAligned b (v, v);
bound += b;
- CVector3D centre;
- bound.GetCentre(centre);
- TS_ASSERT_EQUALS(centre, v);
+ CVector3D center;
+ bound.GetCenter(center);
+ TS_ASSERT_EQUALS(center, v);
}
void test_aabb_to_obb_translation()
{
CBoundingBoxAligned aabb(CVector3D(-1,-2,-1), CVector3D(1,2,1));
CMatrix3D translation;
translation.SetTranslation(CVector3D(1,3,7));
CBoundingBoxOriented result;
aabb.Transform(translation, result);
TS_ASSERT_VEC_DELTA(result.m_Center, 1.f, 3.f, 7.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[0], 1.f, 0.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[1], 0.f, 1.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[2], 0.f, 0.f, 1.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_HalfSizes, 1.f, 2.f, 1.f, 1e-7f);
}
void test_aabb_to_obb_rotation_around_origin()
{
// rotate a 4x3x3 AABB centered at (5,0,0) 90 degrees CCW around the Z axis, and verify that the
// resulting OBB is correct
CBoundingBoxAligned aabb(CVector3D(3, -1.5f, -1.5f), CVector3D(7, 1.5f, 1.5f));
CMatrix3D rotation;
rotation.SetZRotation(float(M_PI)/2.f);
CBoundingBoxOriented result;
aabb.Transform(rotation, result);
TS_ASSERT_VEC_DELTA(result.m_Center, 0.f, 5.f, 0.f, 1e-6f); // involves some trigonometry, lower precision
TS_ASSERT_VEC_DELTA(result.m_Basis[0], 0.f, 1.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[1], -1.f, 0.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[2], 0.f, 0.f, 1.f, 1e-7f);
}
void test_aabb_to_obb_rotation_around_point()
{
// rotate a 4x3x3 AABB centered at (5,0,0) 45 degrees CW around the Z axis through (2,0,0)
CBoundingBoxAligned aabb(CVector3D(3, -1.5f, -1.5f), CVector3D(7, 1.5f, 1.5f));
// move everything so (2,0,0) becomes the origin, do the rotation, then move everything back
CMatrix3D translate;
CMatrix3D rotate;
CMatrix3D translateBack;
translate.SetTranslation(-2.f, 0, 0);
rotate.SetZRotation(-float(M_PI)/4.f);
translateBack.SetTranslation(2.f, 0, 0);
CMatrix3D transform;
transform.SetIdentity();
transform.Concatenate(translate);
transform.Concatenate(rotate);
transform.Concatenate(translateBack);
CBoundingBoxOriented result;
aabb.Transform(transform, result);
const float invSqrt2 = 1.f/sqrtf(2.f);
TS_ASSERT_VEC_DELTA(result.m_Center, 3*invSqrt2 + 2, -3*invSqrt2, 0.f, 1e-6f); // involves some trigonometry, lower precision
TS_ASSERT_VEC_DELTA(result.m_Basis[0], invSqrt2, -invSqrt2, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[1], invSqrt2, invSqrt2, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[2], 0.f, 0.f, 1.f, 1e-7f);
}
void test_aabb_to_obb_scale()
{
CBoundingBoxAligned aabb(CVector3D(3, -1.5f, -1.5f), CVector3D(7, 1.5f, 1.5f));
CMatrix3D scale;
scale.SetScaling(1.f, 3.f, 7.f);
CBoundingBoxOriented result;
aabb.Transform(scale, result);
TS_ASSERT_VEC_DELTA(result.m_Center, 5.f, 0.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_HalfSizes, 2.f, 4.5f, 10.5f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[0], 1.f, 0.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[1], 0.f, 1.f, 0.f, 1e-7f);
TS_ASSERT_VEC_DELTA(result.m_Basis[2], 0.f, 0.f, 1.f, 1e-7f);
}
// Verify that ray/OBB intersection is correctly determined in degenerate case where the
// box has zero size in one of its dimensions.
void test_degenerate_obb_ray_intersect()
{
// create OBB of a flat 1x1 square in the X/Z plane, with 0 size in the Y dimension
CBoundingBoxOriented bound;
bound.m_Basis[0] = CVector3D(1,0,0); // X
bound.m_Basis[1] = CVector3D(0,1,0); // Y
bound.m_Basis[2] = CVector3D(0,0,1); // Z
bound.m_HalfSizes[0] = 1.f;
bound.m_HalfSizes[1] = 0.f; // no height, i.e. a "flat" OBB
bound.m_HalfSizes[2] = 1.f;
bound.m_Center = CVector3D(0,0,0);
// create two rays; one that should hit the OBB, and one that should miss it
CVector3D ray1origin(-3.5f, 3.f, 0.f);
CVector3D ray1direction(1.f, -1.f, 0.f);
CVector3D ray2origin(-4.5f, 3.f, 0.f);
CVector3D ray2direction(1.f, -1.f, 0.f);
float tMin, tMax;
TSM_ASSERT("Ray 1 should intersect the OBB", bound.RayIntersect(ray1origin, ray1direction, tMin, tMax));
TSM_ASSERT("Ray 2 should not intersect the OBB", !bound.RayIntersect(ray2origin, ray2direction, tMin, tMax));
}
// Verify that transforming a flat AABB to an OBB does not produce NaN basis vectors in the
// resulting OBB (see http://trac.wildfiregames.com/ticket/1121)
void test_degenerate_aabb_to_obb_transform()
{
// create a flat AABB, transform it with some matrix (can even be the identity matrix),
// and verify that the result does not contain any NaN values in its basis vectors
// and/or half-sizes
CBoundingBoxAligned flatAabb(CVector3D(-1,0,-1), CVector3D(1,0,1));
CMatrix3D transform;
transform.SetIdentity();
CBoundingBoxOriented result;
flatAabb.Transform(transform, result);
TS_ASSERT(!isnan(result.m_Basis[0].X) && !isnan(result.m_Basis[0].Y) && !isnan(result.m_Basis[0].Z));
TS_ASSERT(!isnan(result.m_Basis[1].X) && !isnan(result.m_Basis[1].Y) && !isnan(result.m_Basis[1].Z));
TS_ASSERT(!isnan(result.m_Basis[2].X) && !isnan(result.m_Basis[2].Y) && !isnan(result.m_Basis[2].Z));
}
};
Index: ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp (revision 22371)
+++ ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp (revision 22372)
@@ -1,472 +1,472 @@
-/* Copyright (C) 2018 Wildfire Games.
+/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpUnitRenderer.h"
#include "simulation2/MessageTypes.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpSelectable.h"
#include "ICmpVisibility.h"
#include "ICmpVisual.h"
#include "graphics/Frustum.h"
#include "graphics/ModelAbstract.h"
#include "graphics/ObjectEntry.h"
#include "graphics/Overlay.h"
#include "graphics/Unit.h"
#include "maths/BoundingSphere.h"
#include "maths/Matrix3D.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
#include "tools/atlas/GameInterface/GameLoop.h"
/**
* Efficiently(ish) renders all the units in the world.
*
* The class maintains a list of all units that currently exist, and the data
* needed for frustum-culling them. To minimise the amount of work done per
* frame (despite a unit's interpolated position changing every frame), the
* culling data is only updated once per turn: we store the position at the
* start of the turn, and the position at the end of the turn, and assume the
* unit might be anywhere between those two points (linearly).
*
* (Note this is a slightly invalid assumption: units don't always move linearly,
* since their interpolated position depends on terrain and water. But over a
* single turn it's probably going to be a good enough approximation, and will
* only break for units that both start and end the turn off-screen.)
*
* We want to ignore rotation entirely, since it's a complex function of
* interpolated position and terrain. So we store a bounding sphere, which
* is rotation-independent, instead of a bounding box.
*/
class CCmpUnitRenderer : public ICmpUnitRenderer
{
public:
struct SUnit
{
CEntityHandle entity;
CUnit* actor;
int flags;
/**
* m_FrameNumber from when the model's transform was last updated.
* This is used to avoid recomputing it multiple times per frame
* if a model is visible in multiple cull groups.
*/
int lastTransformFrame;
/**
* Worst-case bounding shape, relative to position. Needs to account
* for all possible animations, orientations, etc.
*/
CBoundingSphere boundsApprox;
/**
* Cached LOS visibility status.
*/
ICmpRangeManager::ELosVisibility visibility;
bool visibilityDirty;
/**
* Whether the unit has a valid position. If false, pos0 and pos1
* are meaningless.
*/
bool inWorld;
/**
* World-space positions to interpolate between.
*/
CVector3D pos0;
CVector3D pos1;
/**
* Bounds encompassing the unit's bounds when it is anywhere between
* pos0 and pos1.
*/
CBoundingSphere sweptBounds;
/**
* For debug overlay.
*/
bool culled;
};
std::vector m_Units;
std::vector m_UnitTagsFree;
int m_FrameNumber;
float m_FrameOffset;
bool m_EnableDebugOverlays;
std::vector m_DebugSpheres;
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(UnitRenderer)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_FrameNumber = 0;
m_FrameOffset = 0.0f;
m_EnableDebugOverlays = false;
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_TurnStart:
{
TurnStart();
break;
}
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
break;
}
}
}
SUnit* LookupUnit(tag_t tag)
{
if (tag.n < 1 || tag.n - 1 >= m_Units.size())
return NULL;
return &m_Units[tag.n - 1];
}
virtual tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags)
{
ENSURE(actor != NULL);
tag_t tag;
if (!m_UnitTagsFree.empty())
{
tag = m_UnitTagsFree.back();
m_UnitTagsFree.pop_back();
}
else
{
m_Units.push_back(SUnit());
tag.n = m_Units.size();
}
SUnit* unit = LookupUnit(tag);
unit->entity = entity;
unit->actor = actor;
unit->lastTransformFrame = -1;
unit->flags = flags;
unit->boundsApprox = boundsApprox;
unit->inWorld = false;
unit->visibilityDirty = true;
unit->pos0 = unit->pos1 = CVector3D();
return tag;
}
virtual void RemoveUnit(tag_t tag)
{
SUnit* unit = LookupUnit(tag);
unit->actor = NULL;
unit->inWorld = false;
m_UnitTagsFree.push_back(tag);
}
void RecomputeSweptBounds(SUnit* unit)
{
// Compute the bounding sphere of the capsule formed by
// sweeping boundsApprox from pos0 to pos1
CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter();
float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius();
unit->sweptBounds = CBoundingSphere(mid, radius);
}
virtual void UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox)
{
SUnit* unit = LookupUnit(tag);
unit->actor = actor;
unit->boundsApprox = boundsApprox;
RecomputeSweptBounds(unit);
}
virtual void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1)
{
SUnit* unit = LookupUnit(tag);
unit->inWorld = inWorld;
unit->pos0 = pos0;
unit->pos1 = pos1;
unit->visibilityDirty = true;
RecomputeSweptBounds(unit);
}
void TurnStart();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
void UpdateVisibility(SUnit& unit) const;
virtual float GetFrameOffset() const
{
return m_FrameOffset;
}
virtual void SetDebugOverlay(bool enabled)
{
m_EnableDebugOverlays = enabled;
}
virtual void PickAllEntitiesAtPoint(std::vector >& outEntities, const CVector3D& origin, const CVector3D& dir, bool allowEditorSelectables) const
{
// First, make a rough test with the worst-case bounding boxes to pick all
// entities/models that could possibly be hit by the ray.
std::vector candidates;
for (const SUnit& unit : m_Units)
{
if (!unit.actor || !unit.inWorld)
continue;
if (unit.sweptBounds.RayIntersect(origin, dir))
candidates.push_back(&unit);
}
// Now make a more precise test to get rid of the remaining false positives
float tmin, tmax;
CVector3D center;
for (size_t i = 0; i< candidates.size(); ++i)
{
const SUnit& unit = *candidates[i];
CmpPtr cmpVisual(unit.entity);
if (!cmpVisual)
continue;
CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox();
if (selectionBox.IsEmpty())
{
if (!allowEditorSelectables)
continue;
// Fall back to using old AABB selection method for decals
// see: http://trac.wildfiregames.com/ticket/1032
// Decals are flat objects without a selectionShape defined,
// but they should still be selectable in the editor to move them
// around or delete them after they are placed.
// Check campaigns/labels/ in the Actors tab of atlas for examples.
CBoundingBoxAligned aABBox = cmpVisual->GetBounds();
if (aABBox.IsEmpty())
continue;
if (!aABBox.RayIntersect(origin, dir, tmin, tmax))
continue;
- aABBox.GetCentre(center);
+ aABBox.GetCenter(center);
}
else
{
if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
continue;
center = selectionBox.m_Center;
}
outEntities.emplace_back(unit.entity, center);
}
}
};
void CCmpUnitRenderer::TurnStart()
{
PROFILE3("UnitRenderer::TurnStart");
// Assume units have stopped moving after the previous turn. If that assumption is not
// correct, we will get a UpdateUnitPos to tell us about its movement in the new turn.
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
unit.pos0 = unit.pos1;
unit.sweptBounds = CBoundingSphere(unit.pos1, unit.boundsApprox.GetRadius());
// Visibility must be recomputed on the first frame during this turn
unit.visibilityDirty = true;
}
}
void CCmpUnitRenderer::Interpolate(float frameTime, float frameOffset)
{
PROFILE3("UnitRenderer::Interpolate");
++m_FrameNumber;
m_FrameOffset = frameOffset;
// TODO: we shouldn't update all the animations etc for units that are off-screen
// (but need to be careful about e.g. sounds triggered by animations of off-screen
// units)
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (unit.actor)
unit.actor->UpdateModel(frameTime);
}
m_DebugSpheres.clear();
if (m_EnableDebugOverlays)
{
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (!(unit.actor && unit.inWorld))
continue;
SOverlaySphere sphere;
sphere.m_Center = unit.sweptBounds.GetCenter();
sphere.m_Radius = unit.sweptBounds.GetRadius();
if (unit.culled)
sphere.m_Color = CColor(1.0f, 0.5f, 0.5f, 0.5f);
else
sphere.m_Color = CColor(0.5f, 0.5f, 1.0f, 0.5f);
m_DebugSpheres.push_back(sphere);
}
}
}
void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
// TODO: need a coarse culling pass based on some kind of spatial data
// structure - that's the main point of this design. Once we've got a
// rough list of possibly-visible units, then we can do the more precise
// culling. (And once it's cheap enough, we can do multiple culling passes
// per frame - one for shadow generation, one for water reflections, etc.)
PROFILE3("UnitRenderer::RenderSubmit");
for (size_t i = 0; i < m_Units.size(); ++i)
{
SUnit& unit = m_Units[i];
unit.culled = true;
if (!unit.actor)
continue;
if (unit.visibilityDirty)
UpdateVisibility(unit);
if (unit.visibility == ICmpRangeManager::VIS_HIDDEN)
continue;
if (!g_AtlasGameLoop->running && !g_RenderActors && (unit.flags & ACTOR_ONLY))
continue;
if (!g_AtlasGameLoop->running && (unit.flags & VISIBLE_IN_ATLAS_ONLY))
continue;
if (culling && !frustum.IsSphereVisible(unit.sweptBounds.GetCenter(), unit.sweptBounds.GetRadius()))
continue;
unit.culled = false;
CModelAbstract& unitModel = unit.actor->GetModel();
if (unit.lastTransformFrame != m_FrameNumber)
{
CmpPtr cmpPosition(unit.entity);
if (!cmpPosition)
continue;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset));
unitModel.SetTransform(transform);
unit.lastTransformFrame = m_FrameNumber;
}
if (culling && !frustum.IsBoxVisible(unitModel.GetWorldBoundsRec()))
continue;
collector.SubmitRecursive(&unitModel);
}
for (size_t i = 0; i < m_DebugSpheres.size(); ++i)
collector.Submit(&m_DebugSpheres[i]);
}
void CCmpUnitRenderer::UpdateVisibility(SUnit& unit) const
{
if (unit.inWorld)
{
// The 'always visible' flag means we should always render the unit
// (regardless of whether the LOS system thinks it's visible)
CmpPtr cmpVisibility(unit.entity);
if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
unit.visibility = ICmpRangeManager::VIS_VISIBLE;
else
{
CmpPtr cmpRangeManager(GetSystemEntity());
unit.visibility = cmpRangeManager->GetLosVisibility(unit.entity,
GetSimContext().GetCurrentDisplayedPlayer());
}
}
else
unit.visibility = ICmpRangeManager::VIS_HIDDEN;
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr cmpSelectable(unit.entity);
if (cmpSelectable)
cmpSelectable->SetVisibility(unit.visibility != ICmpRangeManager::VIS_HIDDEN);
unit.visibilityDirty = false;
}
REGISTER_COMPONENT_TYPE(UnitRenderer)
Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 22371)
+++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 22372)
@@ -1,557 +1,557 @@
/* Copyright (C) 2019 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 "ActorViewer.h"
#include "View.h"
#include "graphics/ColladaManager.h"
#include "graphics/LOSTexture.h"
#include "graphics/Unit.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/ObjectManager.h"
#include "graphics/ParticleManager.h"
#include "graphics/Patch.h"
#include "graphics/SkeletonAnimManager.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/UnitManager.h"
#include "graphics/Overlay.h"
#include "maths/MathUtil.h"
#include "ps/Filesystem.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "ps/ProfileViewer.h"
#include "renderer/Renderer.h"
#include "renderer/Scene.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/helpers/Render.h"
struct ActorViewerImpl : public Scene
{
NONCOPYABLE(ActorViewerImpl);
public:
ActorViewerImpl() :
Entity(INVALID_ENTITY),
Terrain(),
ColladaManager(g_VFS),
MeshManager(ColladaManager),
SkeletonAnimManager(ColladaManager),
UnitManager(),
Simulation2(&UnitManager, g_ScriptRuntime, &Terrain),
ObjectManager(MeshManager, SkeletonAnimManager, Simulation2),
LOSTexture(Simulation2),
TerritoryTexture(Simulation2)
{
UnitManager.SetObjectManager(ObjectManager);
}
entity_id_t Entity;
CStrW CurrentUnitID;
CStr CurrentUnitAnim;
float CurrentSpeed;
bool WalkEnabled;
bool GroundEnabled;
bool WaterEnabled;
bool ShadowsEnabled;
// Whether shadows, sky and water are enabled outside of the actor viewer.
bool OldShadows;
bool OldSky;
bool OldWater;
bool SelectionBoxEnabled;
bool AxesMarkerEnabled;
int PropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes
SColor4ub Background;
CTerrain Terrain;
CColladaManager ColladaManager;
CMeshManager MeshManager;
CSkeletonAnimManager SkeletonAnimManager;
CObjectManager ObjectManager;
CUnitManager UnitManager;
CSimulation2 Simulation2;
CLOSTexture LOSTexture;
CTerritoryTexture TerritoryTexture;
SOverlayLine SelectionBoxOverlay;
SOverlayLine AxesMarkerOverlays[3];
std::vector Props;
std::vector PropPointOverlays;
// Simplistic implementation of the Scene interface
virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
{
if (GroundEnabled)
{
for (ssize_t pj = 0; pj < Terrain.GetPatchesPerSide(); ++pj)
for (ssize_t pi = 0; pi < Terrain.GetPatchesPerSide(); ++pi)
c->Submit(Terrain.GetPatch(pi, pj));
}
CmpPtr cmpVisual(Simulation2, Entity);
if (cmpVisual)
{
// add selection box outlines manually
if (SelectionBoxEnabled)
{
SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue
SelectionBoxOverlay.m_Thickness = 2;
SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay);
c->Submit(&SelectionBoxOverlay);
}
// add origin axis thingy
if (AxesMarkerEnabled)
{
CMatrix3D worldSpaceAxes;
// offset from the ground a little bit to prevent fighting with the floor texture (also note: SetTranslation
// sets the identity 3x3 transformation matrix, which are the world axes)
worldSpaceAxes.SetTranslation(cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0));
SimRender::ConstructAxesMarker(worldSpaceAxes, AxesMarkerOverlays[0], AxesMarkerOverlays[1], AxesMarkerOverlays[2]);
c->Submit(&AxesMarkerOverlays[0]);
c->Submit(&AxesMarkerOverlays[1]);
c->Submit(&AxesMarkerOverlays[2]);
}
// add prop point overlays
if (PropPointsMode > 0 && Props.size() > 0)
{
PropPointOverlays.clear(); // doesn't clear capacity, but should be ok since the number of prop points is usually pretty limited
for (size_t i = 0; i < Props.size(); ++i)
{
CModel::Prop& prop = Props[i];
if (prop.m_Model) // should always be the case
{
// prop point positions are automatically updated during animations etc. by CModel::ValidatePosition
const CMatrix3D& propCoordSystem = prop.m_Model->GetTransform();
SOverlayLine pointGimbal;
pointGimbal.m_Color = CColor(1.f, 0.f, 1.f, 1.f);
SimRender::ConstructGimbal(propCoordSystem.GetTranslation(), 0.05f, pointGimbal);
PropPointOverlays.push_back(pointGimbal);
if (PropPointsMode > 1)
{
// scale the prop axes coord system down a bit to distinguish them from the main world-space axes markers
CMatrix3D displayCoordSystem = propCoordSystem;
displayCoordSystem.Scale(0.5f, 0.5f, 0.5f);
// revert translation scaling
displayCoordSystem._14 = propCoordSystem._14;
displayCoordSystem._24 = propCoordSystem._24;
displayCoordSystem._34 = propCoordSystem._34;
// construct an XYZ axes marker for the prop's coordinate system
SOverlayLine xAxis, yAxis, zAxis;
SimRender::ConstructAxesMarker(displayCoordSystem, xAxis, yAxis, zAxis);
PropPointOverlays.push_back(xAxis);
PropPointOverlays.push_back(yAxis);
PropPointOverlays.push_back(zAxis);
}
}
}
for (size_t i = 0; i < PropPointOverlays.size(); ++i)
{
c->Submit(&PropPointOverlays[i]);
}
}
}
// send a RenderSubmit message so the components can submit their visuals to the renderer
Simulation2.RenderSubmit(*c, frustum, false);
}
virtual CLOSTexture& GetLOSTexture()
{
return LOSTexture;
}
virtual CTerritoryTexture& GetTerritoryTexture()
{
return TerritoryTexture;
}
/**
* Recursively fetches the props of the currently displayed entity model and its submodels, and stores them for rendering.
*/
void UpdatePropList();
void UpdatePropListRecursive(CModelAbstract* model);
};
void ActorViewerImpl::UpdatePropList()
{
Props.clear();
CmpPtr cmpVisual(Simulation2, Entity);
if (cmpVisual)
{
CUnit* unit = cmpVisual->GetUnit();
if (unit)
{
CModelAbstract& modelAbstract = unit->GetModel();
UpdatePropListRecursive(&modelAbstract);
}
}
}
void ActorViewerImpl::UpdatePropListRecursive(CModelAbstract* modelAbstract)
{
ENSURE(modelAbstract);
CModel* model = modelAbstract->ToCModel();
if (model)
{
std::vector& modelProps = model->GetProps();
for (CModel::Prop& modelProp : modelProps)
{
Props.push_back(modelProp);
if (modelProp.m_Model)
UpdatePropListRecursive(modelProp.m_Model);
}
}
}
ActorViewer::ActorViewer()
: m(*new ActorViewerImpl())
{
m.WalkEnabled = false;
m.GroundEnabled = true;
m.WaterEnabled = false;
m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
m.SelectionBoxEnabled = false;
m.AxesMarkerEnabled = false;
m.PropPointsMode = 0;
m.Background = SColor4ub(0, 0, 0, 255);
// Create a tiny empty piece of terrain, just so we can put shadows
// on it without having to think too hard
m.Terrain.Initialize(2, NULL);
CTerrainTextureEntry* tex = g_TexMan.FindTexture("whiteness");
if (tex)
{
for (ssize_t pi = 0; pi < m.Terrain.GetPatchesPerSide(); ++pi)
{
for (ssize_t pj = 0; pj < m.Terrain.GetPatchesPerSide(); ++pj)
{
CPatch* patch = m.Terrain.GetPatch(pi, pj);
for (ssize_t i = 0; i < PATCH_SIZE; ++i)
{
for (ssize_t j = 0; j < PATCH_SIZE; ++j)
{
CMiniPatch& mp = patch->m_MiniPatches[i][j];
mp.Tex = tex;
mp.Priority = 0;
}
}
}
}
}
else
{
debug_warn(L"Failed to load whiteness texture");
}
// Prepare the simulation
m.Simulation2.LoadDefaultScripts();
m.Simulation2.ResetState();
// Set player data
m.Simulation2.SetMapSettings(m.Simulation2.GetPlayerDefaults());
m.Simulation2.LoadPlayerSettings(true);
// Tell the simulation we've already loaded the terrain
CmpPtr cmpTerrain(m.Simulation2, SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->ReloadTerrain(false);
// Remove FOW since we're in Atlas
CmpPtr cmpRangeManager(m.Simulation2, SYSTEM_ENTITY);
if (cmpRangeManager)
cmpRangeManager->SetLosRevealAll(-1, true);
m.Simulation2.InitGame();
}
ActorViewer::~ActorViewer()
{
delete &m;
}
CSimulation2* ActorViewer::GetSimulation2()
{
return &m.Simulation2;
}
entity_id_t ActorViewer::GetEntity()
{
return m.Entity;
}
void ActorViewer::UnloadObjects()
{
m.ObjectManager.UnloadObjects();
}
void ActorViewer::SetActor(const CStrW& name, const CStr& animation, player_id_t playerID)
{
bool needsAnimReload = false;
CStrW id = name;
// Recreate the entity, if we don't have one or if the new one is different
if (m.Entity == INVALID_ENTITY || id != m.CurrentUnitID)
{
// Delete the old entity (if any)
if (m.Entity != INVALID_ENTITY)
{
m.Simulation2.DestroyEntity(m.Entity);
m.Simulation2.FlushDestroyedEntities();
m.Entity = INVALID_ENTITY;
}
// Clear particles associated with deleted entity
g_Renderer.GetParticleManager().ClearUnattachedEmitters();
// If there's no actor to display, return with nothing loaded
if (id.empty())
return;
m.Entity = m.Simulation2.AddEntity(L"preview|" + id);
if (m.Entity == INVALID_ENTITY)
return;
CmpPtr cmpPosition(m.Simulation2, m.Entity);
if (cmpPosition)
{
ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
cmpPosition->JumpTo(entity_pos_t::FromInt(c), entity_pos_t::FromInt(c));
cmpPosition->SetYRotation(entity_angle_t::Pi());
}
CmpPtr cmpOwnership(m.Simulation2, m.Entity);
if (cmpOwnership)
cmpOwnership->SetOwner(playerID);
needsAnimReload = true;
}
if (animation != m.CurrentUnitAnim)
needsAnimReload = true;
if (needsAnimReload)
{
CStr anim = animation.LowerCase();
// Emulate the typical simulation animation behaviour
float speed;
float repeattime = 0.f;
if (anim == "walk")
{
CmpPtr cmpUnitMotion(m.Simulation2, m.Entity);
if (cmpUnitMotion)
speed = cmpUnitMotion->GetWalkSpeed().ToFloat();
else
speed = 7.f; // typical unit speed
m.CurrentSpeed = speed;
}
else if (anim == "run")
{
CmpPtr cmpUnitMotion(m.Simulation2, m.Entity);
if (cmpUnitMotion)
speed = cmpUnitMotion->GetWalkSpeed().ToFloat() * cmpUnitMotion->GetRunMultiplier().ToFloat();
else
speed = 12.f; // typical unit speed
m.CurrentSpeed = speed;
}
else if (anim == "melee")
{
speed = 1.f; // speed will be ignored if we have a repeattime
m.CurrentSpeed = 0.f;
CStr code = "var cmp = Engine.QueryInterface("+CStr::FromUInt(m.Entity)+", IID_Attack); " +
"if (cmp) cmp.GetTimers(cmp.GetBestAttack()).repeat; else 0;";
m.Simulation2.GetScriptInterface().Eval(code.c_str(), repeattime);
}
else
{
// Play the animation at normal speed, but movement speed is zero
speed = 1.f;
m.CurrentSpeed = 0.f;
}
CStr sound;
if (anim == "melee")
sound = "attack";
else if (anim == "build")
sound = "build";
else if (anim.Find("gather_") == 0)
sound = anim;
CmpPtr cmpVisual(m.Simulation2, m.Entity);
if (cmpVisual)
{
// TODO: SetEntitySelection(anim)
cmpVisual->SelectAnimation(anim, false, fixed::FromFloat(speed));
if (repeattime)
cmpVisual->SetAnimationSyncRepeat(fixed::FromFloat(repeattime));
}
// update prop list for new entity/animation (relies on needsAnimReload also getting called for entire entity changes)
m.UpdatePropList();
}
m.CurrentUnitID = id;
m.CurrentUnitAnim = animation;
}
void ActorViewer::SetEnabled(bool enabled)
{
if (enabled)
{
// Set shadows, sky and water.
m.OldShadows = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, m.ShadowsEnabled);
m.OldSky = g_Renderer.GetSkyManager()->GetRenderSky();
g_Renderer.GetSkyManager()->SetRenderSky(false);
m.OldWater = g_Renderer.GetWaterManager()->m_RenderWater;
g_Renderer.GetWaterManager()->m_RenderWater = m.WaterEnabled;
}
else
{
// Restore the old renderer state
g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, m.OldShadows);
g_Renderer.GetSkyManager()->SetRenderSky(m.OldSky);
g_Renderer.GetWaterManager()->m_RenderWater = m.OldWater;
}
}
void ActorViewer::SetBackgroundColor(const SColor4ub& color)
{
m.Background = color;
m.Terrain.SetBaseColor(color);
}
void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; }
void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; }
void ActorViewer::SetWaterEnabled(bool enabled)
{
m.WaterEnabled = enabled;
// Adjust water level
entity_pos_t waterLevel = entity_pos_t::FromFloat(enabled ? 10.f : 0.f);
CmpPtr cmpWaterManager(m.Simulation2, SYSTEM_ENTITY);
if (cmpWaterManager)
cmpWaterManager->SetWaterLevel(waterLevel);
}
void ActorViewer::SetShadowsEnabled(bool enabled) { m.ShadowsEnabled = enabled; }
void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; }
void ActorViewer::SetAxesMarkerEnabled(bool enabled) { m.AxesMarkerEnabled = enabled; }
void ActorViewer::SetPropPointsMode(int mode) { m.PropPointsMode = mode; }
void ActorViewer::SetStatsEnabled(bool enabled)
{
if (enabled)
g_ProfileViewer.ShowTable("renderer");
else
g_ProfileViewer.ShowTable("");
}
void ActorViewer::Render()
{
m.Terrain.MakeDirty(RENDERDATA_UPDATE_COLOR);
g_Renderer.SetClearColor(m.Background);
// Set simulation context for rendering purposes
g_Renderer.SetSimulation(&m.Simulation2);
g_Renderer.BeginFrame();
// Find the centre of the interesting region, in the middle of the patch
// and half way up the model (assuming there is one)
CVector3D centre;
CmpPtr cmpVisual(m.Simulation2, m.Entity);
if (cmpVisual)
- cmpVisual->GetBounds().GetCentre(centre);
+ cmpVisual->GetBounds().GetCenter(centre);
else
centre.Y = 0.f;
centre.X = centre.Z = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
CCamera camera = AtlasView::GetView_Actor()->GetCamera();
camera.m_Orientation.Translate(centre.X, centre.Y, centre.Z);
camera.UpdateFrustum();
g_Renderer.SetSceneCamera(camera, camera);
g_Renderer.RenderScene(m);
glDisable(GL_DEPTH_TEST);
g_Logger->Render();
g_ProfileViewer.RenderProfile();
glEnable(GL_DEPTH_TEST);
g_Renderer.EndFrame();
ogl_WarnIfError();
}
void ActorViewer::Update(float simFrameLength, float realFrameLength)
{
m.Simulation2.Update((int)(simFrameLength*1000));
m.Simulation2.Interpolate(simFrameLength, 0, realFrameLength);
if (m.WalkEnabled && m.CurrentSpeed)
{
CmpPtr cmpPosition(m.Simulation2, m.Entity);
if (cmpPosition)
{
// Move the model by speed*simFrameLength forwards
float z = cmpPosition->GetPosition().Z.ToFloat();
z -= m.CurrentSpeed*simFrameLength;
// Wrap at the edges, so it doesn't run off into the horizon
ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
if (z < c - TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f)
z = c + TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f;
cmpPosition->JumpTo(cmpPosition->GetPosition().X, entity_pos_t::FromFloat(z));
}
}
}