From 3231d98ea4b9b355704ebcb493ae736c235a6395 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 12:50:26 -0400 Subject: [PATCH 1/9] Rename instancing Model::Render -> RenderInstanced --- src/CityOnPlanet.cpp | 2 +- src/scenegraph/Group.cpp | 4 ++-- src/scenegraph/Group.h | 2 +- src/scenegraph/LOD.cpp | 4 ++-- src/scenegraph/LOD.h | 2 +- src/scenegraph/MatrixTransform.cpp | 2 +- src/scenegraph/MatrixTransform.h | 2 +- src/scenegraph/Model.cpp | 8 ++++---- src/scenegraph/Model.h | 2 +- src/scenegraph/ModelNode.cpp | 4 ++-- src/scenegraph/ModelNode.h | 2 +- src/scenegraph/Node.h | 2 +- src/scenegraph/StaticGeometry.cpp | 2 +- src/scenegraph/StaticGeometry.h | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/CityOnPlanet.cpp b/src/CityOnPlanet.cpp index d512280585e..5592932fda1 100644 --- a/src/CityOnPlanet.cpp +++ b/src/CityOnPlanet.cpp @@ -716,7 +716,7 @@ void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustu // render the building models using instancing for (Uint32 i = 0; i < numBuildings; i++) { if (!transform[i].empty()) - m_cityType->buildingTypes[i].model->Render(transform[i]); + m_cityType->buildingTypes[i].model->RenderInstanced(transform[i]); } // Draw debug extents diff --git a/src/scenegraph/Group.cpp b/src/scenegraph/Group.cpp index 5691ce17bc6..3a86a370839 100644 --- a/src/scenegraph/Group.cpp +++ b/src/scenegraph/Group.cpp @@ -133,7 +133,7 @@ namespace SceneGraph { } } - void Group::Render(const std::vector &trans, const RenderData *rd) + void Group::RenderInstanced(const std::vector &trans, const RenderData *rd) { RenderChildren(trans, rd); } @@ -143,7 +143,7 @@ namespace SceneGraph { PROFILE_SCOPED() for (std::vector::iterator itr = m_children.begin(), itEnd = m_children.end(); itr != itEnd; ++itr) { if ((*itr)->GetNodeMask() & rd->nodemask) - (*itr)->Render(trans, rd); + (*itr)->RenderInstanced(trans, rd); } } diff --git a/src/scenegraph/Group.h b/src/scenegraph/Group.h index 0d64b3bf741..74dae8b3b19 100644 --- a/src/scenegraph/Group.h +++ b/src/scenegraph/Group.h @@ -27,7 +27,7 @@ namespace SceneGraph { virtual void Accept(NodeVisitor &v) override; virtual void Traverse(NodeVisitor &v) override; virtual void Render(const matrix4x4f &trans, const RenderData *rd) override; - virtual void Render(const std::vector &trans, const RenderData *rd) override; + virtual void RenderInstanced(const std::vector &trans, const RenderData *rd) override; virtual Node *FindNode(const std::string &) override; // Walk the node hierarchy to the root of the model and compute the global transform of this node. diff --git a/src/scenegraph/LOD.cpp b/src/scenegraph/LOD.cpp index 7e999633797..478a5c9fbd4 100644 --- a/src/scenegraph/LOD.cpp +++ b/src/scenegraph/LOD.cpp @@ -60,7 +60,7 @@ namespace SceneGraph { m_children[lod]->Render(trans, rd); } - void LOD::Render(const std::vector &trans, const RenderData *rd) + void LOD::RenderInstanced(const std::vector &trans, const RenderData *rd) { // anything to draw? if (m_pixelSizes.empty()) @@ -100,7 +100,7 @@ namespace SceneGraph { // now render each of the buffers for each of the lods for (Uint32 inst = 0; inst < transform.size(); inst++) { if (!transform[inst].empty()) { - m_children[inst]->Render(transform[inst], rd); + m_children[inst]->RenderInstanced(transform[inst], rd); } } } diff --git a/src/scenegraph/LOD.h b/src/scenegraph/LOD.h index 6a27e4fedda..87ac8808d4b 100644 --- a/src/scenegraph/LOD.h +++ b/src/scenegraph/LOD.h @@ -18,7 +18,7 @@ namespace SceneGraph { virtual const char *GetTypeName() const override { return "LOD"; } virtual void Accept(NodeVisitor &v) override; virtual void Render(const matrix4x4f &trans, const RenderData *rd) override; - virtual void Render(const std::vector &trans, const RenderData *rd) override; + virtual void RenderInstanced(const std::vector &trans, const RenderData *rd) override; void AddLevel(float pixelRadius, Node *child); virtual void Save(NodeDatabase &) override; static LOD *Load(NodeDatabase &); diff --git a/src/scenegraph/MatrixTransform.cpp b/src/scenegraph/MatrixTransform.cpp index b7621ac878e..71597ec0811 100644 --- a/src/scenegraph/MatrixTransform.cpp +++ b/src/scenegraph/MatrixTransform.cpp @@ -45,7 +45,7 @@ namespace SceneGraph { } static const matrix4x4f s_ident(matrix4x4f::Identity()); - void MatrixTransform::Render(const std::vector &trans, const RenderData *rd) + void MatrixTransform::RenderInstanced(const std::vector &trans, const RenderData *rd) { if (0 == memcmp(&m_transform, &s_ident, sizeof(matrix4x4f))) { // m_transform is identity so avoid performing all multiplications diff --git a/src/scenegraph/MatrixTransform.h b/src/scenegraph/MatrixTransform.h index 6329813e79f..24035f4cc01 100644 --- a/src/scenegraph/MatrixTransform.h +++ b/src/scenegraph/MatrixTransform.h @@ -34,7 +34,7 @@ namespace SceneGraph { static MatrixTransform *Load(NodeDatabase &); virtual void Render(const matrix4x4f &trans, const RenderData *rd) override; - virtual void Render(const std::vector &trans, const RenderData *rd) override; + virtual void RenderInstanced(const std::vector &trans, const RenderData *rd) override; const matrix4x4f &GetTransform() const { return m_transform; } void SetTransform(const matrix4x4f &m) { m_transform = m; } diff --git a/src/scenegraph/Model.cpp b/src/scenegraph/Model.cpp index b628b3f9aa8..ae77ee5a65b 100644 --- a/src/scenegraph/Model.cpp +++ b/src/scenegraph/Model.cpp @@ -190,7 +190,7 @@ namespace SceneGraph { } } - void Model::Render(const std::vector &trans, const RenderData *rd) + void Model::RenderInstanced(const std::vector &trans, const RenderData *rd) { PROFILE_SCOPED(); @@ -221,12 +221,12 @@ namespace SceneGraph { m_renderer->SetWireFrameMode(true); if (params.nodemask & MASK_IGNORE) { - m_root->Render(trans, ¶ms); + m_root->RenderInstanced(trans, ¶ms); } else { params.nodemask = NODE_SOLID; - m_root->Render(trans, ¶ms); + m_root->RenderInstanced(trans, ¶ms); params.nodemask = NODE_TRANSPARENT; - m_root->Render(trans, ¶ms); + m_root->RenderInstanced(trans, ¶ms); } if (m_debugFlags & DEBUG_WIREFRAME) diff --git a/src/scenegraph/Model.h b/src/scenegraph/Model.h index 2a79fc9f4b6..a8b73dde72e 100644 --- a/src/scenegraph/Model.h +++ b/src/scenegraph/Model.h @@ -114,7 +114,7 @@ namespace SceneGraph { void SetDrawClipRadius(float clipRadius) { m_boundingRadius = clipRadius; } void Render(const matrix4x4f &trans, const RenderData *rd = 0); //ModelNode can override RD - void Render(const std::vector &trans, const RenderData *rd = 0); //ModelNode can override RD + void RenderInstanced(const std::vector &trans, const RenderData *rd = 0); //ModelNode can override RD RefCountedPtr CreateCollisionMesh(); RefCountedPtr GetCollisionMesh() const { return m_collMesh; } diff --git a/src/scenegraph/ModelNode.cpp b/src/scenegraph/ModelNode.cpp index c348371c592..38af27eef8e 100644 --- a/src/scenegraph/ModelNode.cpp +++ b/src/scenegraph/ModelNode.cpp @@ -33,13 +33,13 @@ namespace SceneGraph { m_model->Render(trans, &newrd); } - void ModelNode::Render(const std::vector &trans, const RenderData *rd) + void ModelNode::RenderInstanced(const std::vector &trans, const RenderData *rd) { PROFILE_SCOPED() //slight hack here RenderData newrd = *rd; newrd.nodemask |= MASK_IGNORE; - m_model->Render(trans, &newrd); + m_model->RenderInstanced(trans, &newrd); } } // namespace SceneGraph diff --git a/src/scenegraph/ModelNode.h b/src/scenegraph/ModelNode.h index c610b611436..c6e883d913e 100644 --- a/src/scenegraph/ModelNode.h +++ b/src/scenegraph/ModelNode.h @@ -18,7 +18,7 @@ namespace SceneGraph { virtual Node *Clone(NodeCopyCache *cache = 0); virtual const char *GetTypeName() const { return "ModelNode"; } virtual void Render(const matrix4x4f &trans, const RenderData *rd); - virtual void Render(const std::vector &trans, const RenderData *rd); + virtual void RenderInstanced(const std::vector &trans, const RenderData *rd); protected: virtual ~ModelNode() {} diff --git a/src/scenegraph/Node.h b/src/scenegraph/Node.h index ee895b61a09..651120119d5 100644 --- a/src/scenegraph/Node.h +++ b/src/scenegraph/Node.h @@ -84,7 +84,7 @@ namespace SceneGraph { virtual void Accept(NodeVisitor &v); virtual void Traverse(NodeVisitor &v); virtual void Render(const matrix4x4f &trans, const RenderData *rd) {} - virtual void Render(const std::vector &trans, const RenderData *rd) {} + virtual void RenderInstanced(const std::vector &trans, const RenderData *rd) {} void DrawAxes(); void SetName(const std::string &name) { m_name = name; } const std::string &GetName() const { return m_name; } diff --git a/src/scenegraph/StaticGeometry.cpp b/src/scenegraph/StaticGeometry.cpp index 5caf7cc2a96..6121feb8710 100644 --- a/src/scenegraph/StaticGeometry.cpp +++ b/src/scenegraph/StaticGeometry.cpp @@ -54,7 +54,7 @@ namespace SceneGraph { //DrawBoundingBox(m_boundingBox); } - void StaticGeometry::Render(const std::vector &trans, const RenderData *rd) + void StaticGeometry::RenderInstanced(const std::vector &trans, const RenderData *rd) { PROFILE_SCOPED() Graphics::Renderer *r = GetRenderer(); diff --git a/src/scenegraph/StaticGeometry.h b/src/scenegraph/StaticGeometry.h index 6d28383cefc..2652ee358cd 100644 --- a/src/scenegraph/StaticGeometry.h +++ b/src/scenegraph/StaticGeometry.h @@ -30,7 +30,7 @@ namespace SceneGraph { virtual const char *GetTypeName() const override { return "StaticGeometry"; } virtual void Accept(NodeVisitor &nv) override; virtual void Render(const matrix4x4f &trans, const RenderData *rd) override; - virtual void Render(const std::vector &trans, const RenderData *rd) override; + virtual void RenderInstanced(const std::vector &trans, const RenderData *rd) override; virtual void Save(NodeDatabase &) override; static StaticGeometry *Load(NodeDatabase &); From 310513ad490988aaa32ce2732d0f2b1841c15a8a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 14:25:26 -0400 Subject: [PATCH 2/9] Move Plane/Sphere helper classes to math/ folder --- CMakeLists.txt | 2 +- src/GeoPatch.cpp | 2 +- src/Plane.cpp | 18 ------------------ src/Plane.h | 20 -------------------- src/graphics/Frustum.h | 4 ++-- src/math/Plane.h | 32 ++++++++++++++++++++++++++++++++ src/{ => math}/Sphere.cpp | 2 +- src/{ => math}/Sphere.h | 0 8 files changed, 37 insertions(+), 43 deletions(-) delete mode 100644 src/Plane.cpp delete mode 100644 src/Plane.h create mode 100644 src/math/Plane.h rename src/{ => math}/Sphere.cpp (97%) rename src/{ => math}/Sphere.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36974bcc24a..ec000976a48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,7 +152,7 @@ list(APPEND SRC_FOLDERS src/graphics src/graphics/dummy src/graphics/opengl - src/gui + src/math src/pigui src/scenegraph src/ship diff --git a/src/GeoPatch.cpp b/src/GeoPatch.cpp index 1fc3b0a4964..479afc1a058 100644 --- a/src/GeoPatch.cpp +++ b/src/GeoPatch.cpp @@ -9,7 +9,7 @@ #include "MathUtil.h" #include "Pi.h" #include "RefCounted.h" -#include "Sphere.h" +#include "math/Sphere.h" #include "galaxy/SystemBody.h" #include "graphics/Frustum.h" #include "graphics/Graphics.h" diff --git a/src/Plane.cpp b/src/Plane.cpp deleted file mode 100644 index 3b6187c4a3f..00000000000 --- a/src/Plane.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details -// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt - -#include "Plane.h" - -double SPlane::DistanceToPoint(const vector3d &p) const -{ - return a * p.x + b * p.y + c * p.z + d; -} - -SPlane::SPlane(const vector3d &N, const vector3d &P) -{ - const vector3d NormalizedNormal = N.Normalized(); - a = NormalizedNormal.x; - b = NormalizedNormal.y; - c = NormalizedNormal.z; - d = -(P.Dot(NormalizedNormal)); -} diff --git a/src/Plane.h b/src/Plane.h deleted file mode 100644 index a76f7bee7b9..00000000000 --- a/src/Plane.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details -// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt - -#pragma once - -#ifndef _PLANE_H -#define _PLANE_H - -#include "vector3.h" - -struct SPlane { - double a, b, c, d; - double DistanceToPoint(const vector3d &p) const; - SPlane() - { /*default empty for Frustum*/ - } - SPlane(const vector3d &N, const vector3d &P); -}; - -#endif /* _GEOPATCH_H */ diff --git a/src/graphics/Frustum.h b/src/graphics/Frustum.h index 5f9446bbd3f..33da3b57551 100644 --- a/src/graphics/Frustum.h +++ b/src/graphics/Frustum.h @@ -4,7 +4,7 @@ #ifndef _FRUSTUM_H #define _FRUSTUM_H -#include "Plane.h" +#include "math/Plane.h" #include "matrix4x4.h" #include "vector3.h" @@ -40,7 +40,7 @@ namespace Graphics { matrix4x4d m_projMatrix; matrix4x4d m_modelMatrix; - SPlane m_planes[6]; + Plane m_planes[6]; double m_translateThresholdSqr; }; diff --git a/src/math/Plane.h b/src/math/Plane.h new file mode 100644 index 00000000000..3503e1e0ab8 --- /dev/null +++ b/src/math/Plane.h @@ -0,0 +1,32 @@ +// Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "vector3.h" + +/** + * Simple oriented-plane helper class + */ +template +struct Plane { + Plane() + { + } + + Plane(const vector3 &N, const vector3 &P) + { + const vector3 nn = N.Normalized(); + a = nn.x; + b = nn.y; + c = nn.z; + d = -(P.Dot(nn)); + } + + T DistanceToPoint(const vector3 &p) const + { + return a * p.x + b * p.y + c * p.z + d; + } + + T a, b, c, d; +}; diff --git a/src/Sphere.cpp b/src/math/Sphere.cpp similarity index 97% rename from src/Sphere.cpp rename to src/math/Sphere.cpp index dfcc3513ada..af8ac49267d 100644 --- a/src/Sphere.cpp +++ b/src/math/Sphere.cpp @@ -42,7 +42,7 @@ bool SSphere::HorizonCulling(const vector3d &view, const SSphere &obj) const const double y = R1 * R1 * iD1; const vector3d P = m_centre - y * O1C; const vector3d N = -O1C; - const SPlane plane(N, P); + const Plane plane(N, P); status = status || (plane.DistanceToPoint(obj.m_centre) > obj.m_radius); return status; diff --git a/src/Sphere.h b/src/math/Sphere.h similarity index 100% rename from src/Sphere.h rename to src/math/Sphere.h From 64eaa0ee46dbf172558878f2a5558fc02f64e9b0 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 14:32:26 -0400 Subject: [PATCH 3/9] SceneGraph: properly handle top-level transform when rendering instanced - Instead of setting the global transform to identity inside StaticGeometry, allow the caller to determine what the "base" transform should be --- src/scenegraph/Model.cpp | 10 ++++++---- src/scenegraph/Model.h | 2 +- src/scenegraph/ModelNode.cpp | 3 ++- src/scenegraph/StaticGeometry.cpp | 3 --- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/scenegraph/Model.cpp b/src/scenegraph/Model.cpp index ae77ee5a65b..67d6a09a331 100644 --- a/src/scenegraph/Model.cpp +++ b/src/scenegraph/Model.cpp @@ -190,7 +190,7 @@ namespace SceneGraph { } } - void Model::RenderInstanced(const std::vector &trans, const RenderData *rd) + void Model::RenderInstanced(const matrix4x4f &trans, const std::vector &inst, const RenderData *rd) { PROFILE_SCOPED(); @@ -216,17 +216,19 @@ namespace SceneGraph { //BR could also be a property of Node. params.boundingRadius = GetDrawClipRadius(); + m_renderer->SetTransform(trans); + //render in two passes, if this is the top-level model if (m_debugFlags & DEBUG_WIREFRAME) m_renderer->SetWireFrameMode(true); if (params.nodemask & MASK_IGNORE) { - m_root->RenderInstanced(trans, ¶ms); + m_root->RenderInstanced(inst, ¶ms); } else { params.nodemask = NODE_SOLID; - m_root->RenderInstanced(trans, ¶ms); + m_root->RenderInstanced(inst, ¶ms); params.nodemask = NODE_TRANSPARENT; - m_root->RenderInstanced(trans, ¶ms); + m_root->RenderInstanced(inst, ¶ms); } if (m_debugFlags & DEBUG_WIREFRAME) diff --git a/src/scenegraph/Model.h b/src/scenegraph/Model.h index a8b73dde72e..4bc262adfc4 100644 --- a/src/scenegraph/Model.h +++ b/src/scenegraph/Model.h @@ -114,7 +114,7 @@ namespace SceneGraph { void SetDrawClipRadius(float clipRadius) { m_boundingRadius = clipRadius; } void Render(const matrix4x4f &trans, const RenderData *rd = 0); //ModelNode can override RD - void RenderInstanced(const std::vector &trans, const RenderData *rd = 0); //ModelNode can override RD + void RenderInstanced(const matrix4x4f &trans, const std::vector &instTrans, const RenderData *rd = 0); //ModelNode can override RD RefCountedPtr CreateCollisionMesh(); RefCountedPtr GetCollisionMesh() const { return m_collMesh; } diff --git a/src/scenegraph/ModelNode.cpp b/src/scenegraph/ModelNode.cpp index 38af27eef8e..5a2b6fc143b 100644 --- a/src/scenegraph/ModelNode.cpp +++ b/src/scenegraph/ModelNode.cpp @@ -3,6 +3,7 @@ #include "ModelNode.h" #include "Model.h" +#include "graphics/Renderer.h" #include "profiler/Profiler.h" namespace SceneGraph { @@ -39,7 +40,7 @@ namespace SceneGraph { //slight hack here RenderData newrd = *rd; newrd.nodemask |= MASK_IGNORE; - m_model->RenderInstanced(trans, &newrd); + m_model->RenderInstanced(m_renderer->GetTransform(), trans, &newrd); } } // namespace SceneGraph diff --git a/src/scenegraph/StaticGeometry.cpp b/src/scenegraph/StaticGeometry.cpp index 6121feb8710..4e4b1524b97 100644 --- a/src/scenegraph/StaticGeometry.cpp +++ b/src/scenegraph/StaticGeometry.cpp @@ -78,9 +78,6 @@ namespace SceneGraph { ib->SetInstanceCount(numTrans); } - // we'll set the transformation within the vertex shader so identity the global one - r->SetTransform(matrix4x4f::Identity()); - if (m_instanceMaterials.empty()) { // process each mesh for (auto &it : m_meshes) { From 33fa3da9ca581ed15c49e2fb977b0e2dd228a8b1 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 15:54:50 -0400 Subject: [PATCH 4/9] Remove ViewportExtents param from ProjectToScreen - Return viewport-normalized coordinates in 0..1 instead of pixel coordinates in 0..w, 0..h - Add deprecation notice to ProjectToScreen overloads that read renderer internal state and return direct pixel coordinates --- src/graphics/Graphics.cpp | 18 ++++++++++++++++-- src/graphics/Graphics.h | 15 ++++++--------- src/pigui/ModelSpinner.cpp | 3 +-- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/graphics/Graphics.cpp b/src/graphics/Graphics.cpp index 8dfdc3ae576..6a98b690fb4 100644 --- a/src/graphics/Graphics.cpp +++ b/src/graphics/Graphics.cpp @@ -57,12 +57,26 @@ namespace Graphics { vector3f ProjectToScreen(const Renderer *r, const vector3f &in) { - return ProjectToScreen(r->GetTransform() * in, r->GetProjection(), r->GetViewport()); + const Graphics::ViewportExtents &vp = r->GetViewport(); + vector3f vVP = ProjectToScreen(r->GetTransform() * in, r->GetProjection()); + + // viewport coord * size + position + return vector3f( + vVP.x * vp.w + vp.x, + vVP.y * vp.h + vp.y, + vVP.z); } vector3d ProjectToScreen(const Renderer *r, const vector3d &in) { - return ProjectToScreen(matrix4x4d(r->GetTransform()) * in, matrix4x4d(r->GetProjection()), r->GetViewport()); + const Graphics::ViewportExtents &vp = r->GetViewport(); + vector3d vVP = ProjectToScreen(matrix4x4d(r->GetTransform()) * in, matrix4x4d(r->GetProjection())); + + // viewport coord * size + position + return vector3d( + vVP.x * vp.w + vp.x, + vVP.y * vp.h + vp.y, + vVP.z); } Renderer *Init(Settings vs) diff --git a/src/graphics/Graphics.h b/src/graphics/Graphics.h index 71a346dadf2..9d85e43e362 100644 --- a/src/graphics/Graphics.h +++ b/src/graphics/Graphics.h @@ -89,15 +89,16 @@ namespace Graphics { // screenspace as defined by Renderer::GetViewport. // This function applies the current renderer transform to the input point // TODO: find a better place to hang this off of; this is too useful to be tied to a renderer object + // FIXME: these functions should be deprecated since they read internal renderer state + // Add a projection method to CameraContext or call ProjectToScreen directly vector3d ProjectToScreen(const Renderer *r, const vector3d &in); vector3f ProjectToScreen(const Renderer *r, const vector3f &in); // ProjectToScreen handles projecting a point in view-space to 2d viewport space. - // It returns the { X, Y } window coordinates and the NDC depth value as Z. - // Implements gluProject (see the OpenGL documentation or the Mesa implementation of gluProject) + // It returns normalized { X, Y } window coordinates in 0..1 and the NDC depth value as Z. // This implementation is tailored to understand Reverse-Z and our data structures. template - vector3 ProjectToScreen(const vector3 &vcam, const matrix4x4 &proj, const ViewportExtents &vp) + vector3 ProjectToScreen(const vector3 &vcam, const matrix4x4 &proj) { // compute the effective W component for perspective divide. // This code assumes that it's being passed a 'standard' perspective or ortho matrix. @@ -116,12 +117,8 @@ namespace Graphics { -vNDC.z // undo reverse-Z coordinate flip }; - // viewport coord * size + position - return vector3{ - vVP.x * vp.w + vp.x, - vVP.y * vp.h + vp.y, - vVP.z - }; + // values in { 0..1, 0..1, 0..1 } + return vVP; } // does SDL video init, constructs appropriate Renderer diff --git a/src/pigui/ModelSpinner.cpp b/src/pigui/ModelSpinner.cpp index d1162e858a4..f6c9aeece7e 100644 --- a/src/pigui/ModelSpinner.cpp +++ b/src/pigui/ModelSpinner.cpp @@ -155,9 +155,8 @@ vector3f ModelSpinner::ModelSpaceToScreenSpace(vector3f modelSpaceVec) { matrix4x4f projection = matrix4x4f::PerspectiveMatrix(DEG2RAD(SPINNER_FOV), m_size.x / m_size.y, 1.f, 10000.f, true); matrix4x4f modelView = MakeModelViewMat(); - Graphics::ViewportExtents vp = { 0, 0, int32_t(m_size.x), int32_t(m_size.y) }; - return Graphics::ProjectToScreen(modelView * modelSpaceVec, projection, vp); + return Graphics::ProjectToScreen(modelView * modelSpaceVec, projection) * vector3f(m_size.x, m_size.y, 1.0); } vector2d ModelSpinner::GetTagPos(const char *tagName) From 3dc8cc323dad69fb949848b7e936d9efe1ebc68a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 15:59:59 -0400 Subject: [PATCH 5/9] Camera: cache projection matrix - The projection matrix is quite useful to know in a lot of places - Will eventually replace asking the Renderer for its internal projection matrix state --- src/Camera.cpp | 6 ++++-- src/Camera.h | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Camera.cpp b/src/Camera.cpp index a0940fd1262..955bcfb6ede 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -35,7 +35,8 @@ CameraContext::CameraContext(float width, float height, float fovAng, float zNea m_frame(FrameId::Invalid), m_pos(0.0), m_orient(matrix3x3d::Identity()), - m_camFrame(FrameId::Invalid) + m_camFrame(FrameId::Invalid), + m_projMatrix(matrix4x4f::InfinitePerspectiveMatrix(DEG2RAD(m_fovAng), m_width / m_height, m_zNear)) { } @@ -49,6 +50,7 @@ void CameraContext::SetFovAng(float newAng) { m_fovAng = newAng; m_frustum = Frustum(m_width, m_height, m_fovAng, m_zNear, m_zFar); + m_projMatrix = matrix4x4f::InfinitePerspectiveMatrix(DEG2RAD(m_fovAng), m_width / m_height, m_zNear); } void CameraContext::BeginFrame() @@ -82,7 +84,7 @@ void CameraContext::EndFrame() void CameraContext::ApplyDrawTransforms(Graphics::Renderer *r) { Graphics::SetFov(m_fovAng); - r->SetProjection(matrix4x4f::InfinitePerspectiveMatrix(DEG2RAD(m_fovAng), m_width / m_height, m_zNear)); + r->SetProjection(GetProjectionMatrix()); r->SetTransform(matrix4x4f::Identity()); } diff --git a/src/Camera.h b/src/Camera.h index 39de498c2d9..e6ce7afbc2e 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -57,6 +57,8 @@ class CameraContext : public RefCounted { // get the frustum. use for projection const Graphics::Frustum &GetFrustum() const { return m_frustum; } + const matrix4x4f &GetProjectionMatrix() const { return m_projMatrix; } + // generate and destroy the camere frame, used mostly to transform things to camera space void BeginFrame(); void EndFrame(); @@ -74,6 +76,7 @@ class CameraContext : public RefCounted { float m_zFar; Graphics::Frustum m_frustum; + matrix4x4f m_projMatrix; FrameId m_frame; vector3d m_pos; From b3d524e0ae900ae5801536ce40089ce9303e69c3 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 16:04:29 -0400 Subject: [PATCH 6/9] Frustum: remove duplicate ProjectPoint method - WorldView was only user, and the logic was duplicated in Graphics::ProjectToScreen - If w-component of clip space is 0, all components of returned viewport coordinates will be +-inf - no need to introduce additional flow control - Allows drastically simplifying Frustum class --- src/WorldView.cpp | 38 +++++++++++--------------------------- src/graphics/Frustum.cpp | 35 ----------------------------------- src/graphics/Frustum.h | 3 --- 3 files changed, 11 insertions(+), 65 deletions(-) diff --git a/src/WorldView.cpp b/src/WorldView.cpp index ae4e99af860..d71cab89e93 100644 --- a/src/WorldView.cpp +++ b/src/WorldView.cpp @@ -11,21 +11,17 @@ #include "HyperspaceCloud.h" #include "Input.h" #include "Json.h" -#include "Lang.h" #include "Pi.h" #include "Player.h" #include "SDL_keycode.h" #include "SectorView.h" #include "Sensors.h" #include "SpeedLines.h" -#include "StringF.h" -#include "graphics/Frustum.h" #include "graphics/Graphics.h" #include "graphics/Material.h" #include "graphics/Renderer.h" #include "graphics/RenderState.h" #include "matrix4x4.h" -#include "ship/PlayerShipController.h" #include "ship/ShipViewController.h" #include "sound/Sound.h" @@ -350,14 +346,12 @@ void WorldView::UpdateProjectedObjects() void WorldView::UpdateIndicator(Indicator &indicator, const vector3d &cameraSpacePos) { - const Graphics::Frustum frustum = m_cameraContext->GetFrustum(); - const float BORDER = 10.0; const float BORDER_BOTTOM = 90.0; // XXX BORDER_BOTTOM is 10+the control panel height and shouldn't be needed at all - const float w = m_renderer->GetWindowWidth(); - const float h = m_renderer->GetWindowHeight(); + const float w = m_cameraContext->GetWidth(); + const float h = m_cameraContext->GetHeight(); if (cameraSpacePos.LengthSqr() < 1e-6) { // length < 1e-3 indicator.pos.x = w / 2.0f; @@ -366,13 +360,8 @@ void WorldView::UpdateIndicator(Indicator &indicator, const vector3d &cameraSpac return; } - vector3d proj; - if (frustum.ProjectPoint(cameraSpacePos, proj)) { - proj.x *= w; - proj.y = (1.0f - proj.y) * h; - } else { - proj = vector3d(w / 2.0, h / 2.0, 0.0); - } + vector3f screenPos = Graphics::ProjectToScreen(vector3f(cameraSpacePos), m_cameraContext->GetProjectionMatrix()); + vector3f proj = vector3f(screenPos.x * w, h - screenPos.y * h, screenPos.z); indicator.realpos.x = int(proj.x); indicator.realpos.y = int(proj.y); @@ -579,29 +568,24 @@ std::tuple WorldView::CalculateHeadingPitchRoll(PlaneTyp std::isnan(roll) ? 0.0 : roll); } +// Project a point in camera space to the screen static vector3d projectToScreenSpace(const vector3d &pos, RefCountedPtr cameraContext, const bool adjustZ = true) { - const Graphics::Frustum &frustum = cameraContext->GetFrustum(); const float h = cameraContext->GetHeight(); const float w = cameraContext->GetWidth(); - vector3d proj; - if (!frustum.ProjectPoint(pos, proj)) { - return vector3d(w / 2, h / 2, 0); - } - // convert NDC to top-left screen coordinates - proj.x *= w; - proj.y = h - proj.y * h; + + vector3f screenPos = Graphics::ProjectToScreen(vector3f(pos), cameraContext->GetProjectionMatrix()); + vector3d proj = vector3d(screenPos.x * w, h - screenPos.y * h, screenPos.z); // linearize depth coordinate // see https://thxforthefish.com/posts/reverse_z/ - float znear; - float zfar; - Pi::renderer->GetNearFarRange(znear, zfar); - proj.z = -znear / proj.z; + // Note this is normally -znear / proj.z, but proj.z is already negated in ProjectToScreen + proj.z = cameraContext->GetZNear() / proj.z; // set z to -1 if in front of camera, 1 else if (adjustZ) proj.z = pos.z < 0 ? -1 : 1; + return proj; } diff --git a/src/graphics/Frustum.cpp b/src/graphics/Frustum.cpp index ae6402404eb..5df1a1d28d4 100644 --- a/src/graphics/Frustum.cpp +++ b/src/graphics/Frustum.cpp @@ -91,41 +91,6 @@ namespace Graphics { return true; } - // Returns a vector3d in the range { 0..1, 0..1, 1..0 } - bool Frustum::ProjectPoint(const vector3d &in, vector3d &out) const - { - // see the OpenGL documentation - // or http://www.songho.ca/opengl/gl_transform.html - // or http://cgit.freedesktop.org/mesa/glu/tree/src/libutil/project.c (gluProject implementation from Mesa) - - const double *const M = m_modelMatrix.Data(); - const double *const P = m_projMatrix.Data(); - - const double vcam[4] = { // camera space - in.x * M[0] + in.y * M[4] + in.z * M[8] + M[12], - in.x * M[1] + in.y * M[5] + in.z * M[9] + M[13], - in.x * M[2] + in.y * M[6] + in.z * M[10] + M[14], - in.x * M[3] + in.y * M[7] + in.z * M[11] + M[15] - }; - const double vclip[4] = { // clip space - vcam[0] * P[0] + vcam[1] * P[4] + vcam[2] * P[8] + vcam[3] * P[12], - vcam[0] * P[1] + vcam[1] * P[5] + vcam[2] * P[9] + vcam[3] * P[13], - vcam[0] * P[2] + vcam[1] * P[6] + vcam[2] * P[10] + vcam[3] * P[14], - vcam[0] * P[3] + vcam[1] * P[7] + vcam[2] * P[11] + vcam[3] * P[15] - }; - - if (is_zero_exact(vclip[3])) { - return false; - } - - const double w = vclip[3]; - out.x = (vclip[0] / w) * 0.5 + 0.5; - out.y = (vclip[1] / w) * 0.5 + 0.5; - out.z = (vclip[2] / w); - - return true; - } - void Frustum::TranslatePoint(const vector3d &in, vector3d &out) const { out = in; diff --git a/src/graphics/Frustum.h b/src/graphics/Frustum.h index 33da3b57551..88df4ab37c7 100644 --- a/src/graphics/Frustum.h +++ b/src/graphics/Frustum.h @@ -25,9 +25,6 @@ namespace Graphics { // test if point (sphere) is in the frustum, ignoring the far plane bool TestPointInfinite(const vector3d &p, double radius) const; - // project a point onto the near plane (typically the screen) - bool ProjectPoint(const vector3d &in, vector3d &out) const; - // translate the given point outside the frustum to a point inside // returns scale factor to make object at that point appear correctly void TranslatePoint(const vector3d &in, vector3d &out) const; From fab84c9b1933fcf09aadc54a0831c32a4f787b5e Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 16:30:27 -0400 Subject: [PATCH 7/9] Frustum: remove model/projection matrix, TranslatePoint() method - modelview / projection matrices are completely unnecessary now, only the planes are required for frustum testing. - TranslatePoint was a complete kludge and its purpose is better served by an analytic solution. --- src/Camera.cpp | 10 ++++++---- src/graphics/Frustum.cpp | 21 +++++---------------- src/graphics/Frustum.h | 8 -------- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/Camera.cpp b/src/Camera.cpp index 955bcfb6ede..dcbe5e1c4ff 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -189,10 +189,12 @@ void Camera::Update() if (pixSize < BILLBOARD_PIXEL_THRESHOLD) { attrs.billboard = true; - // project the position - vector3d pos; - m_context->GetFrustum().TranslatePoint(attrs.viewCoords, pos); - attrs.billboardPos = vector3f(pos); + // scale the position of the terrain body until it fits within the far plane for its billboard to be rendered + // Note that with an infinite projection matrix there is no far plane and this is somewhat unnecessary + double zFar = m_context->GetZFar(); + double scaleFactor = zFar / attrs.viewCoords.Length() - 0.000001; // tiny nudge closer from the far plane + + attrs.billboardPos = vector3f(attrs.viewCoords * std::min(scaleFactor, 1.0)); // limit the minimum billboard size for planets so they're always a little visible attrs.billboardSize = std::max(1.0f, pixSize); diff --git a/src/graphics/Frustum.cpp b/src/graphics/Frustum.cpp index 5df1a1d28d4..1613e6e8a1b 100644 --- a/src/graphics/Frustum.cpp +++ b/src/graphics/Frustum.cpp @@ -14,20 +14,17 @@ namespace Graphics { // step for translating to frustum space static const double TRANSLATE_STEP = 0.25; - Frustum::Frustum(float width, float height, float fovAng, float znear, float zfar) : - m_projMatrix(matrix4x4d::PerspectiveMatrix(DEG2RAD(Clamp(fovAng, FOV_MIN, FOV_MAX)), width / height, znear, zfar)), - m_modelMatrix(matrix4x4d::Identity()) + Frustum::Frustum(float width, float height, float fovAng, float znear, float zfar) { - InitFromMatrix(m_projMatrix); + matrix4x4d projMatrix = matrix4x4d::PerspectiveMatrix(DEG2RAD(Clamp(fovAng, FOV_MIN, FOV_MAX)), width / height, znear, zfar); + InitFromMatrix(projMatrix); m_translateThresholdSqr = zfar * zfar * TRANSLATE_STEP; } - Frustum::Frustum(const matrix4x4d &modelview, const matrix4x4d &projection) : - m_projMatrix(projection), - m_modelMatrix(modelview) + Frustum::Frustum(const matrix4x4d &modelview, const matrix4x4d &projection) { - const matrix4x4d m = m_projMatrix * m_modelMatrix; + const matrix4x4d m = projection * modelview; InitFromMatrix(m); } @@ -91,12 +88,4 @@ namespace Graphics { return true; } - void Frustum::TranslatePoint(const vector3d &in, vector3d &out) const - { - out = in; - while (out.LengthSqr() > m_translateThresholdSqr) { - out *= TRANSLATE_STEP; - } - } - } // namespace Graphics diff --git a/src/graphics/Frustum.h b/src/graphics/Frustum.h index 88df4ab37c7..81c261ddf5f 100644 --- a/src/graphics/Frustum.h +++ b/src/graphics/Frustum.h @@ -25,20 +25,12 @@ namespace Graphics { // test if point (sphere) is in the frustum, ignoring the far plane bool TestPointInfinite(const vector3d &p, double radius) const; - // translate the given point outside the frustum to a point inside - // returns scale factor to make object at that point appear correctly - void TranslatePoint(const vector3d &in, vector3d &out) const; - private: - // create from current gl state Frustum(){}; void InitFromMatrix(const matrix4x4d &m); - matrix4x4d m_projMatrix; - matrix4x4d m_modelMatrix; Plane m_planes[6]; - double m_translateThresholdSqr; }; } // namespace Graphics From c6faa67c4f170d45312c78838a49e5a32c29514d Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 16:50:55 -0400 Subject: [PATCH 8/9] Frustum: template on floating point number type - Allow constructing independent float/double versions of Graphics::Frustum where needed for speed or memory bandwidth concerns --- src/CityOnPlanet.h | 7 +++-- src/GeoPatch.h | 2 +- src/graphics/Frustum.cpp | 45 +++++--------------------------- src/graphics/Frustum.h | 55 ++++++++++++++++++++++++++++++---------- 4 files changed, 52 insertions(+), 57 deletions(-) diff --git a/src/CityOnPlanet.h b/src/CityOnPlanet.h index a9b527d6c96..10752935f59 100644 --- a/src/CityOnPlanet.h +++ b/src/CityOnPlanet.h @@ -4,13 +4,11 @@ #ifndef _CITYONPLANET_H #define _CITYONPLANET_H -#include "CollMesh.h" #include "FrameId.h" #include "Random.h" #include "JsonFwd.h" #include "matrix4x4.h" - -#include +#include "graphics/Frustum.h" class Geom; class Planet; @@ -19,9 +17,10 @@ class Frame; class SystemPath; class SystemBody; +struct Aabb; + namespace Graphics { class Renderer; - class Frustum; class Material; } // namespace Graphics diff --git a/src/GeoPatch.h b/src/GeoPatch.h index 3dfdaa39ec8..98f444891fe 100644 --- a/src/GeoPatch.h +++ b/src/GeoPatch.h @@ -12,6 +12,7 @@ #include "RefCounted.h" #include "matrix4x4.h" #include "vector3.h" +#include "graphics/Frustum.h" #include #include @@ -26,7 +27,6 @@ namespace Graphics { namespace Graphics { class Renderer; - class Frustum; class MeshObject; } // namespace Graphics diff --git a/src/graphics/Frustum.cpp b/src/graphics/Frustum.cpp index 1613e6e8a1b..aca71015432 100644 --- a/src/graphics/Frustum.cpp +++ b/src/graphics/Frustum.cpp @@ -7,28 +7,8 @@ namespace Graphics { - // min/max FOV in degrees - static const float FOV_MAX = 170.0f; - static const float FOV_MIN = 20.0f; - - // step for translating to frustum space - static const double TRANSLATE_STEP = 0.25; - - Frustum::Frustum(float width, float height, float fovAng, float znear, float zfar) - { - matrix4x4d projMatrix = matrix4x4d::PerspectiveMatrix(DEG2RAD(Clamp(fovAng, FOV_MIN, FOV_MAX)), width / height, znear, zfar); - InitFromMatrix(projMatrix); - - m_translateThresholdSqr = zfar * zfar * TRANSLATE_STEP; - } - - Frustum::Frustum(const matrix4x4d &modelview, const matrix4x4d &projection) - { - const matrix4x4d m = projection * modelview; - InitFromMatrix(m); - } - - void Frustum::InitFromMatrix(const matrix4x4d &m) + template + void FrustumT::InitFromMatrix(const matrix4x4 &m) { // Left clipping plane m_planes[0].a = m[3] + m[0]; @@ -61,7 +41,7 @@ namespace Graphics { m_planes[5].c = m[11] + m[10]; m_planes[5].d = m[15] + m[14]; - // Normalize the fuckers + // Normalize the clipping planes for (int i = 0; i < 6; i++) { double invlen = 1.0 / sqrt(m_planes[i].a * m_planes[i].a + m_planes[i].b * m_planes[i].b + m_planes[i].c * m_planes[i].c); m_planes[i].a *= invlen; @@ -71,21 +51,8 @@ namespace Graphics { } } - bool Frustum::TestPoint(const vector3d &p, double radius) const - { - for (int i = 0; i < 6; i++) - if (m_planes[i].DistanceToPoint(p) + radius < 0) - return false; - return true; - } - - bool Frustum::TestPointInfinite(const vector3d &p, double radius) const - { - // check all planes except far plane - for (int i = 0; i < 5; i++) - if (m_planes[i].DistanceToPoint(p) + radius < 0) - return false; - return true; - } + // explicitly instantiate the function templates + template void FrustumT::InitFromMatrix(const matrix4x4 &); + template void FrustumT::InitFromMatrix(const matrix4x4 &); } // namespace Graphics diff --git a/src/graphics/Frustum.h b/src/graphics/Frustum.h index 81c261ddf5f..efa7bff8b31 100644 --- a/src/graphics/Frustum.h +++ b/src/graphics/Frustum.h @@ -1,6 +1,8 @@ // Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt +#pragma once + #ifndef _FRUSTUM_H #define _FRUSTUM_H @@ -10,29 +12,56 @@ namespace Graphics { - // Frustum can be used for projecting points (3D to 2D) and testing - // if a point lies inside the visible area - // Its' internal projection matrix should, but does not have to, match - // the one used for rendering - class Frustum { + // Frustum can be used for testing if a point lies inside the visible area + template + class FrustumT { public: + FrustumT() = delete; + // create for specified values - Frustum(float width, float height, float fovAng, float nearClip, float farClip); - Frustum(const matrix4x4d &modelview, const matrix4x4d &projection); + FrustumT(float width, float height, float fovAng, float nearClip, float farClip) + { + matrix4x4 projMatrix = matrix4x4::PerspectiveMatrix(fovAng, width / height, nearClip, farClip); + InitFromMatrix(projMatrix); + } + + FrustumT(const matrix4x4 &modelview, const matrix4x4 &projection) + { + const matrix4x4 m = projection * modelview; + InitFromMatrix(m); + } // test if point (sphere) is in the frustum - bool TestPoint(const vector3d &p, double radius) const; + bool TestPoint(const vector3 &p, T radius) const + { + for (int i = 0; i < 6; i++) + if (m_planes[i].DistanceToPoint(p) + radius < 0) + return false; + return true; + } + // test if point (sphere) is in the frustum, ignoring the far plane - bool TestPointInfinite(const vector3d &p, double radius) const; + bool TestPointInfinite(const vector3 &p, T radius) const + { + // check all planes except far plane + for (int i = 0; i < 5; i++) + if (m_planes[i].DistanceToPoint(p) + radius < 0) + return false; + return true; + } private: - Frustum(){}; + void InitFromMatrix(const matrix4x4 &m); - void InitFromMatrix(const matrix4x4d &m); - - Plane m_planes[6]; + Plane m_planes[6]; }; + // Backwards-compatibility typedef + using Frustum = FrustumT; + + using FrustumF = FrustumT; + using FrustumD = FrustumT; + } // namespace Graphics #endif From 18fd318f901373048d7128c1e30780817141f2ac Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Oct 2024 18:21:55 -0400 Subject: [PATCH 9/9] CityOnPlanet: optimize instanced rendering by up to 25% - Precalculate instance transforms for each potentially-visible building at startup rather than every single frame. - Move all matrix multiplication out of the inner loop and do frustum culling with an optimized single-precision float frustum structure. - Pass a CameraContext rather than a Frustum to CityOnPlanet::Render for top-level frustum culling. - Add comments indicating potential future performance improvements. --- src/CityOnPlanet.cpp | 98 +++++++++++++++++++++++++++++--------------- src/CityOnPlanet.h | 16 ++++++-- src/SpaceStation.cpp | 2 +- 3 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/CityOnPlanet.cpp b/src/CityOnPlanet.cpp index 5592932fda1..f22d0eafa97 100644 --- a/src/CityOnPlanet.cpp +++ b/src/CityOnPlanet.cpp @@ -3,6 +3,7 @@ #include "CityOnPlanet.h" +#include "Camera.h" #include "FileSystem.h" #include "Frame.h" #include "Game.h" @@ -11,6 +12,7 @@ #include "Pi.h" #include "Planet.h" #include "SpaceStation.h" + #include "collider/Geom.h" #include "core/Log.h" @@ -18,12 +20,14 @@ #include "graphics/Graphics.h" #include "graphics/Material.h" #include "graphics/RenderState.h" +#include "graphics/Renderer.h" #include "graphics/Types.h" - #include "scenegraph/Animation.h" +#include "scenegraph/Model.h" #include "scenegraph/ModelSkin.h" -#include "scenegraph/SceneGraph.h" -#include "utils.h" + +#include "matrix3x3.h" +#include "matrix4x4.h" #include "utils.h" @@ -87,6 +91,26 @@ void CityOnPlanet::RemoveStaticGeomsFromCollisionSpace() } } +void CityOnPlanet::PrecalcInstanceTransforms(const matrix4x4d &stationOrient) +{ + PROFILE_SCOPED(); + + m_instanceTransforms.clear(); + + // Precalculate building orientation matrices + matrix3x3f rot[4] = { matrix3x3f(stationOrient.GetOrient()) }; + for (int i = 1; i < 4; i++) { + rot[i] = rot[0] * matrix3x3f::RotateY(M_PI * 0.5 * double(i)); + } + + // Cache building instance transforms + // TODO: this could easily be represented as a 4x3 matrix or dual quaternion for significantly less bandwidth + // That requires an "instance buffer" that isn't a 4x4 matrix per instance though... :( + for (const auto &building : m_enabledBuildings) { + m_instanceTransforms.emplace_back(rot[building.rotation], building.pos); + } +} + void CityOnPlanet::GetModelSize(const Aabb &aabb, uint8_t size[2]) { vector3d aabbSize = aabb.max - aabb.min; @@ -326,6 +350,8 @@ CityOnPlanet::CityOnPlanet(Planet *planet, SpaceStation *station, const Uint32 s Generate(station); AddStaticGeomsToCollisionSpace(); + + PrecalcInstanceTransforms(station->GetOrient()); } void CityOnPlanet::Generate(SpaceStation *station) @@ -374,7 +400,8 @@ void CityOnPlanet::Generate(SpaceStation *station) // top-left corner of the grid is -X, -Z relative to station position at center // offset origin by half-grid to ensure grid centers are aligned with the station model - m_gridOrigin = station->GetPosition() - incX * (cityExtents + 0.5) - incZ * (cityExtents + 0.5); + const vector3d stationOrigin = station->GetPosition(); + m_gridOrigin = stationOrigin - incX * (cityExtents + 0.5) - incZ * (cityExtents + 0.5); // Setup the station somewhere in the city (defaults to center for now) const uint32_t stationPos[2] = { @@ -536,7 +563,7 @@ void CityOnPlanet::Generate(SpaceStation *station) Geom *geom = new Geom(cmesh->GetGeomTree(), orientcalc[orient], pos, GetPlanet()); // add it to the list of buildings to render - m_buildings.push_back({ typeIndex, float(cmesh->GetRadius()), orient, pos, geom }); + m_buildings.push_back({ vector3f(pos - stationOrigin), float(cmesh->GetRadius()), typeIndex, orient, geom }); } } @@ -550,10 +577,9 @@ void CityOnPlanet::Generate(SpaceStation *station) // Compute the total AABB of this city Aabb totalAABB; - const vector3d &stationOrigin = station->GetPosition(); for (const auto &buildingInst : m_buildings) { - totalAABB.Update(buildingInst.pos - stationOrigin); + totalAABB.Update(vector3d(buildingInst.pos)); } m_realCentre = totalAABB.min + ((totalAABB.max - totalAABB.min) * 0.5); @@ -640,7 +666,7 @@ bool CityOnPlanet::TestGridOccupancy(uint32_t x, uint32_t y, const uint8_t size[ return test != 0; } -void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustum, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform) +void CityOnPlanet::Render(Graphics::Renderer *r, const CameraContext *camera, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform) { PROFILE_SCOPED() @@ -649,31 +675,25 @@ void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustu return; // Early frustum test of whole city. + const vector3d stationOrigin = station->GetPosition(); const vector3d stationPos = viewTransform * (station->GetPosition() + m_realCentre); - //modelview seems to be always identity - if (!frustum.TestPoint(stationPos, m_clipRadius)) + + if (!camera->GetFrustum().TestPoint(stationPos, m_clipRadius)) return; - // Precalculate building orientation matrices - matrix4x4d rot[4]; - matrix4x4f rotf[4]; - rot[0] = station->GetOrient(); + // Use the station-centered frame-oriented coordinate system we generated building positions in + matrix4x4f modelView = matrix4x4f(viewTransform * matrix4x4d::Translation(station->GetInterpPosition())); + + // Combine all transformations to do as few computations as possible inside of the frustum-test loop + // with as much precision as possible + Graphics::FrustumF frustum = Graphics::FrustumF(modelView, camera->GetProjectionMatrix()); // change detail level if necessary const bool bDetailChanged = m_detailLevel != Pi::detail.cities; if (bDetailChanged) { RemoveStaticGeomsFromCollisionSpace(); AddStaticGeomsToCollisionSpace(); - } - - rot[0] = viewTransform * rot[0]; - for (int i = 1; i < 4; i++) { - rot[i] = rot[0] * matrix4x4d::RotateYMatrix(M_PI * 0.5 * double(i)); - } - for (int i = 0; i < 4; i++) { - for (int e = 0; e < 16; e++) { - rotf[i][e] = float(rot[i][e]); - } + PrecalcInstanceTransforms(station->GetOrient()); } // update any idle animations @@ -696,27 +716,39 @@ void CityOnPlanet::Render(Graphics::Renderer *r, const Graphics::Frustum &frustu transform.resize(numBuildings); + // TODO: we could store all instance transforms in a single array by + // sorting the list of buildings according to instance index... + // Of course, we can't do that because Model::RenderInstanced wants a full- + // owning std::vector of instance data for (uint32_t i = 0; i < numBuildings; i++) { transform[i].reserve(m_buildingCounts[i]); } - for (const auto &building : m_enabledBuildings) { - const vector3d pos = viewTransform * building.pos; + { + // Because we cull each individual instance for performance reasons, we + // have to repack instance transform data into an instance buffer to be + // uploaded to the GPU + // TODO: this could potentially be improved by clustering buildings + // according to a spatial distance metric and then frustum culling those + // clusters instead. It would result in more instanced draws however. - if (!frustum.TestPoint(pos, building.clipRadius)) - continue; + PROFILE_SCOPED_DESC("CityOnPlanet::Render() Frustum Culling") - matrix4x4f instanceRot = matrix4x4f(rotf[building.rotation]); - instanceRot.SetTranslate(vector3f(pos)); + for (size_t i = 0; i < m_enabledBuildings.size(); i++) { + const auto &building = m_enabledBuildings[i]; - transform[building.instIndex].push_back(instanceRot); - ++uCount; + if (!frustum.TestPoint(building.pos, building.clipRadius)) + continue; + + transform[building.instIndex].emplace_back(m_instanceTransforms[i]); + ++uCount; + } } // render the building models using instancing for (Uint32 i = 0; i < numBuildings; i++) { if (!transform[i].empty()) - m_cityType->buildingTypes[i].model->RenderInstanced(transform[i]); + m_cityType->buildingTypes[i].model->RenderInstanced(modelView, transform[i]); } // Draw debug extents diff --git a/src/CityOnPlanet.h b/src/CityOnPlanet.h index 10752935f59..46c3dba449d 100644 --- a/src/CityOnPlanet.h +++ b/src/CityOnPlanet.h @@ -8,8 +8,8 @@ #include "Random.h" #include "JsonFwd.h" #include "matrix4x4.h" -#include "graphics/Frustum.h" +class CameraContext; class Geom; class Planet; class SpaceStation; @@ -39,7 +39,7 @@ class CityOnPlanet { CityOnPlanet(Planet *planet, SpaceStation *station, const Uint32 seed); virtual ~CityOnPlanet(); - void Render(Graphics::Renderer *r, const Graphics::Frustum &camera, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform); + void Render(Graphics::Renderer *r, const CameraContext *camera, const SpaceStation *station, const vector3d &viewCoords, const matrix4x4d &viewTransform); inline Planet *GetPlanet() const { return m_planet; } float GetClipRadius() const { return m_clipRadius; } @@ -114,11 +114,15 @@ class CityOnPlanet { void AddStaticGeomsToCollisionSpace(); void RemoveStaticGeomsFromCollisionSpace(); + void PrecalcInstanceTransforms(const matrix4x4d &stationOrient); + + // NOTE: position here is stored as a 32-bit float offset from the station + // This should retain decent precision within the size of our cities (5-10km radius). struct BuildingInstance { - Uint32 instIndex; + vector3f pos; float clipRadius; + Uint32 instIndex; int rotation; // 0-3 - vector3d pos; Geom *geom; }; @@ -132,10 +136,14 @@ class CityOnPlanet { FrameId m_frame; Random m_rand; + // Both of these vectors are sorted by building instance index std::vector m_buildings; std::vector m_enabledBuildings; + std::vector m_buildingCounts; + std::vector m_instanceTransforms; + // bitmask occupancy grid for quick population of the city std::unique_ptr m_gridBitset; // width of a single grid row in bytes diff --git a/src/SpaceStation.cpp b/src/SpaceStation.cpp index 37dacd66dfc..01bcc8c1c11 100644 --- a/src/SpaceStation.cpp +++ b/src/SpaceStation.cpp @@ -800,7 +800,7 @@ void SpaceStation::Render(Graphics::Renderer *r, const Camera *camera, const vec SetClipRadius(m_adjacentCity->GetClipRadius()); } - m_adjacentCity->Render(r, camera->GetContext()->GetFrustum(), this, viewCoords, viewTransform); + m_adjacentCity->Render(r, camera->GetContext(), this, viewCoords, viewTransform); RenderModel(r, camera, viewCoords, viewTransform); m_navLights->Render(r);