Index: source/graphics/Camera.cpp =================================================================== --- source/graphics/Camera.cpp (revision 24796) +++ source/graphics/Camera.cpp (working copy) @@ -1,436 +1,436 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #include "precompiled.h" #include "Camera.h" #include "graphics/HFTracer.h" #include "graphics/Terrain.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" CCamera::CCamera() : m_NearPlane(0.0f), m_FarPlane(0.0f), m_FOV(0.0f), m_ProjType(CUSTOM) { // Set viewport to something anything should handle, but should be initialised // to window size before use. m_ViewPort.m_X = 0; m_ViewPort.m_Y = 0; m_ViewPort.m_Width = 800; m_ViewPort.m_Height = 600; } CCamera::~CCamera() = default; void CCamera::SetProjection(const CMatrix3D& matrix) { m_ProjType = CUSTOM; m_ProjMat = matrix; } void CCamera::SetProjectionFromCamera(const CCamera& camera) { m_ProjType = camera.m_ProjType; m_NearPlane = camera.m_NearPlane; m_FarPlane = camera.m_FarPlane; if (m_ProjType == PERSPECTIVE) { m_FOV = camera.m_FOV; } m_ProjMat = camera.m_ProjMat; } void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov) { m_ProjType = PERSPECTIVE; m_NearPlane = nearp; m_FarPlane = farp; m_FOV = fov; m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane); } // Updates the frustum planes. Should be called // everytime the view or projection matrices are // altered. void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor) { CMatrix3D MatFinal; CMatrix3D MatView; m_Orientation.GetInverse(MatView); MatFinal = m_ProjMat * MatView; m_ViewFrustum.SetNumPlanes(6); // get the RIGHT plane - m_ViewFrustum.m_aPlanes[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11; - m_ViewFrustum.m_aPlanes[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12; - m_ViewFrustum.m_aPlanes[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13; - m_ViewFrustum.m_aPlanes[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14; + m_ViewFrustum.m_aPlanes[0].m_Norm.Xref() = scissor[1].getX()*MatFinal._41 - MatFinal._11; + m_ViewFrustum.m_aPlanes[0].m_Norm.Yref() = scissor[1].getX()*MatFinal._42 - MatFinal._12; + m_ViewFrustum.m_aPlanes[0].m_Norm.Zref() = scissor[1].getX()*MatFinal._43 - MatFinal._13; + m_ViewFrustum.m_aPlanes[0].m_Dist = scissor[1].getX()*MatFinal._44 - MatFinal._14; // get the LEFT plane - m_ViewFrustum.m_aPlanes[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11; - m_ViewFrustum.m_aPlanes[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12; - m_ViewFrustum.m_aPlanes[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13; - m_ViewFrustum.m_aPlanes[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14; + m_ViewFrustum.m_aPlanes[1].m_Norm.Xref() = -scissor[0].getX()*MatFinal._41 + MatFinal._11; + m_ViewFrustum.m_aPlanes[1].m_Norm.Yref() = -scissor[0].getX()*MatFinal._42 + MatFinal._12; + m_ViewFrustum.m_aPlanes[1].m_Norm.Zref() = -scissor[0].getX()*MatFinal._43 + MatFinal._13; + m_ViewFrustum.m_aPlanes[1].m_Dist = -scissor[0].getX()*MatFinal._44 + MatFinal._14; // get the BOTTOM plane - m_ViewFrustum.m_aPlanes[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21; - m_ViewFrustum.m_aPlanes[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22; - m_ViewFrustum.m_aPlanes[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23; - m_ViewFrustum.m_aPlanes[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24; + m_ViewFrustum.m_aPlanes[2].m_Norm.Xref() = -scissor[0].getY()*MatFinal._41 + MatFinal._21; + m_ViewFrustum.m_aPlanes[2].m_Norm.Yref() = -scissor[0].getY()*MatFinal._42 + MatFinal._22; + m_ViewFrustum.m_aPlanes[2].m_Norm.Zref() = -scissor[0].getY()*MatFinal._43 + MatFinal._23; + m_ViewFrustum.m_aPlanes[2].m_Dist = -scissor[0].getY()*MatFinal._44 + MatFinal._24; // get the TOP plane - m_ViewFrustum.m_aPlanes[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21; - m_ViewFrustum.m_aPlanes[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22; - m_ViewFrustum.m_aPlanes[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23; - m_ViewFrustum.m_aPlanes[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24; + m_ViewFrustum.m_aPlanes[3].m_Norm.Xref() = scissor[1].getY()*MatFinal._41 - MatFinal._21; + m_ViewFrustum.m_aPlanes[3].m_Norm.Yref() = scissor[1].getY()*MatFinal._42 - MatFinal._22; + m_ViewFrustum.m_aPlanes[3].m_Norm.Zref() = scissor[1].getY()*MatFinal._43 - MatFinal._23; + m_ViewFrustum.m_aPlanes[3].m_Dist = scissor[1].getY()*MatFinal._44 - MatFinal._24; // get the FAR plane - m_ViewFrustum.m_aPlanes[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31; - m_ViewFrustum.m_aPlanes[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32; - m_ViewFrustum.m_aPlanes[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33; - m_ViewFrustum.m_aPlanes[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34; + m_ViewFrustum.m_aPlanes[4].m_Norm.Xref() = scissor[1].getZ()*MatFinal._41 - MatFinal._31; + m_ViewFrustum.m_aPlanes[4].m_Norm.Yref() = scissor[1].getZ()*MatFinal._42 - MatFinal._32; + m_ViewFrustum.m_aPlanes[4].m_Norm.Zref() = scissor[1].getZ()*MatFinal._43 - MatFinal._33; + m_ViewFrustum.m_aPlanes[4].m_Dist = scissor[1].getZ()*MatFinal._44 - MatFinal._34; // get the NEAR plane - m_ViewFrustum.m_aPlanes[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31; - m_ViewFrustum.m_aPlanes[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32; - m_ViewFrustum.m_aPlanes[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33; - m_ViewFrustum.m_aPlanes[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34; + m_ViewFrustum.m_aPlanes[5].m_Norm.Xref() = -scissor[0].getZ()*MatFinal._41 + MatFinal._31; + m_ViewFrustum.m_aPlanes[5].m_Norm.Yref() = -scissor[0].getZ()*MatFinal._42 + MatFinal._32; + m_ViewFrustum.m_aPlanes[5].m_Norm.Zref() = -scissor[0].getZ()*MatFinal._43 + MatFinal._33; + m_ViewFrustum.m_aPlanes[5].m_Dist = -scissor[0].getZ()*MatFinal._44 + MatFinal._34; for (size_t i = 0; i < 6; ++i) m_ViewFrustum.m_aPlanes[i].Normalize(); } void CCamera::ClipFrustum(const CPlane& clipPlane) { CPlane normClipPlane = clipPlane; normClipPlane.Normalize(); m_ViewFrustum.AddPlane(normClipPlane); } void CCamera::SetViewPort(const SViewPort& viewport) { m_ViewPort.m_X = viewport.m_X; m_ViewPort.m_Y = viewport.m_Y; m_ViewPort.m_Width = viewport.m_Width; m_ViewPort.m_Height = viewport.m_Height; } float CCamera::GetAspectRatio() const { return static_cast(m_ViewPort.m_Width) / static_cast(m_ViewPort.m_Height); } void CCamera::GetViewQuad(float dist, Quad& quad) const { ENSURE(m_ProjType == PERSPECTIVE); const float y = dist * tanf(m_FOV * 0.5f); const float x = y * GetAspectRatio(); - quad[0].X = -x; - quad[0].Y = -y; - quad[0].Z = dist; - quad[1].X = x; - quad[1].Y = -y; - quad[1].Z = dist; - quad[2].X = x; - quad[2].Y = y; - quad[2].Z = dist; - quad[3].X = -x; - quad[3].Y = y; - quad[3].Z = dist; + quad[0].Xref() = -x; + quad[0].Yref() = -y; + quad[0].Zref() = dist; + quad[1].Xref() = x; + quad[1].Yref() = -y; + quad[1].Zref() = dist; + quad[2].Xref() = x; + quad[2].Yref() = y; + quad[2].Zref() = dist; + quad[3].Xref() = -x; + quad[3].Yref() = y; + quad[3].Zref() = dist; } void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const { // Coordinates relative to the camera plane. const float dx = static_cast(px) / g_Renderer.GetWidth(); const float dy = 1.0f - static_cast(py) / g_Renderer.GetHeight(); Quad points; GetViewQuad(m_FarPlane, points); // Transform from camera space to world space. for (CVector3D& point : points) point = m_Orientation.Transform(point); // Get world space position of mouse point at the far clipping plane. CVector3D basisX = points[1] - points[0]; CVector3D basisY = points[3] - points[0]; CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy); origin = m_Orientation.GetTranslation(); // Build direction for the camera origin to the target point. dir = targetPoint - origin; dir.Normalize(); } void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const { CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse(); - CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f)); + CVector4D screenspace = transform.Transform(CVector4D(world.getX(), world.getY(), world.getZ(), 1.0f)); x = screenspace.X / screenspace.W; y = screenspace.Y / screenspace.W; x = (x + 1) * 0.5f * g_Renderer.GetWidth(); y = (1 - y) * 0.5f * g_Renderer.GetHeight(); } CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const { CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; BuildCameraRay(px, py, origin, dir); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); if (!aboveWater) { if (gotTerrain) return terrainPoint; // Off the edge of the world? // Work out where it /would/ hit, if the map were extended out to infinity with average height. return GetWorldCoordinates(px, py, 50.0f); } CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint ); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { - waterPoint.X = Clamp(waterPoint.X, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); - waterPoint.Z = Clamp(waterPoint.Z, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); + waterPoint.Xref() = Clamp(waterPoint.getX(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); + waterPoint.Zref() = Clamp(waterPoint.getZ(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const { CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h CVector3D origin, dir, delta, currentTarget; BuildCameraRay(px, py, origin, dir); if (plane.FindRayIntersection(origin, dir, ¤tTarget)) return currentTarget; // No intersection with the infinite plane - nothing sensible can be returned, // so just choose an arbitrary point on the plane return CVector3D(0.f, h, 0.f); } CVector3D CCamera::GetFocus() const { // Basically the same as GetWorldCoordinates CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; origin = m_Orientation.GetTranslation(); dir = m_Orientation.GetIn(); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint ); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { - waterPoint.X = Clamp(waterPoint.X, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); - waterPoint.Z = Clamp(waterPoint.Z, 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); + waterPoint.Xref() = Clamp(waterPoint.getX(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); + waterPoint.Zref() = Clamp(waterPoint.getZ(), 0.f, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } void CCamera::LookAt(const CVector3D& camera, const CVector3D& target, const CVector3D& up) { CVector3D delta = target - camera; LookAlong(camera, delta, up); } void CCamera::LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up) { orientation.Normalize(); up.Normalize(); CVector3D s = orientation.Cross(up); - m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X; - m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y; - m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z; + m_Orientation._11 = -s.getX(); m_Orientation._12 = up.getX(); m_Orientation._13 = orientation.getX(); m_Orientation._14 = camera.getX(); + m_Orientation._21 = -s.getY(); m_Orientation._22 = up.getY(); m_Orientation._23 = orientation.getY(); m_Orientation._24 = camera.getY(); + m_Orientation._31 = -s.getZ(); m_Orientation._32 = up.getZ(); m_Orientation._33 = orientation.getZ(); m_Orientation._34 = camera.getZ(); m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f; } // Render the camera's frustum void CCamera::Render(int intermediates) const { #if CONFIG2_GLES #warning TODO: implement camera frustum for GLES #else Quad nearPoints; Quad farPoints; GetViewQuad(m_NearPlane, nearPoints); GetViewQuad(m_FarPlane, farPoints); for(int i = 0; i < 4; i++) { nearPoints[i] = m_Orientation.Transform(nearPoints[i]); farPoints[i] = m_Orientation.Transform(farPoints[i]); } // near plane glBegin(GL_POLYGON); - glVertex3fv(&nearPoints[0].X); - glVertex3fv(&nearPoints[1].X); - glVertex3fv(&nearPoints[2].X); - glVertex3fv(&nearPoints[3].X); + glVertex3fv(&nearPoints[0].Xref()); + glVertex3fv(&nearPoints[1].Xref()); + glVertex3fv(&nearPoints[2].Xref()); + glVertex3fv(&nearPoints[3].Xref()); glEnd(); // far plane glBegin(GL_POLYGON); - glVertex3fv(&farPoints[0].X); - glVertex3fv(&farPoints[1].X); - glVertex3fv(&farPoints[2].X); - glVertex3fv(&farPoints[3].X); + glVertex3fv(&farPoints[0].Xref()); + glVertex3fv(&farPoints[1].Xref()); + glVertex3fv(&farPoints[2].Xref()); + glVertex3fv(&farPoints[3].Xref()); glEnd(); // connection lines glBegin(GL_QUAD_STRIP); - glVertex3fv(&nearPoints[0].X); - glVertex3fv(&farPoints[0].X); - glVertex3fv(&nearPoints[1].X); - glVertex3fv(&farPoints[1].X); - glVertex3fv(&nearPoints[2].X); - glVertex3fv(&farPoints[2].X); - glVertex3fv(&nearPoints[3].X); - glVertex3fv(&farPoints[3].X); - glVertex3fv(&nearPoints[0].X); - glVertex3fv(&farPoints[0].X); + glVertex3fv(&nearPoints[0].Xref()); + glVertex3fv(&farPoints[0].Xref()); + glVertex3fv(&nearPoints[1].Xref()); + glVertex3fv(&farPoints[1].Xref()); + glVertex3fv(&nearPoints[2].Xref()); + glVertex3fv(&farPoints[2].Xref()); + glVertex3fv(&nearPoints[3].Xref()); + glVertex3fv(&farPoints[3].Xref()); + glVertex3fv(&nearPoints[0].Xref()); + glVertex3fv(&farPoints[0].Xref()); glEnd(); // intermediate planes CVector3D intermediatePoints[4]; for(int i = 0; i < intermediates; ++i) { float t = (i+1.0)/(intermediates+1.0); for(int j = 0; j < 4; ++j) intermediatePoints[j] = nearPoints[j]*t + farPoints[j]*(1.0-t); glBegin(GL_POLYGON); - glVertex3fv(&intermediatePoints[0].X); - glVertex3fv(&intermediatePoints[1].X); - glVertex3fv(&intermediatePoints[2].X); - glVertex3fv(&intermediatePoints[3].X); + glVertex3fv(&intermediatePoints[0].Xref()); + glVertex3fv(&intermediatePoints[1].Xref()); + glVertex3fv(&intermediatePoints[2].Xref()); + glVertex3fv(&intermediatePoints[3].Xref()); glEnd(); } #endif } Index: source/graphics/CameraController.cpp =================================================================== --- source/graphics/CameraController.cpp (revision 24796) +++ source/graphics/CameraController.cpp (working copy) @@ -1,719 +1,719 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CameraController.h" #include "graphics/HFTracer.h" #include "graphics/Terrain.h" #include "graphics/scripting/JSInterface_GameView.h" #include "lib/input.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Pyrogenesis.h" #include "ps/TouchInput.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" extern int g_xres, g_yres; // Maximum distance outside the edge of the map that the camera's // focus point can be moved static const float CAMERA_EDGE_MARGIN = 2.0f * TERRAIN_TILE_SIZE; CCameraController::CCameraController(CCamera& camera) : ICameraController(camera), m_ConstrainCamera(true), m_FollowEntity(INVALID_ENTITY), m_FollowFirstPerson(false), // Dummy values (these will be filled in by the config file) m_ViewScrollSpeed(0), m_ViewScrollSpeedModifier(1), m_ViewRotateXSpeed(0), m_ViewRotateXMin(0), m_ViewRotateXMax(0), m_ViewRotateXDefault(0), m_ViewRotateYSpeed(0), m_ViewRotateYSpeedWheel(0), m_ViewRotateYDefault(0), m_ViewRotateSpeedModifier(1), m_ViewDragSpeed(0), m_ViewZoomSpeed(0), m_ViewZoomSpeedWheel(0), m_ViewZoomMin(0), m_ViewZoomMax(0), m_ViewZoomDefault(0), m_ViewZoomSpeedModifier(1), m_ViewFOV(DEGTORAD(45.f)), m_ViewNear(2.f), m_ViewFar(4096.f), m_JoystickPanX(-1), m_JoystickPanY(-1), m_JoystickRotateX(-1), m_JoystickRotateY(-1), m_JoystickZoomIn(-1), m_JoystickZoomOut(-1), m_HeightSmoothness(0.5f), m_HeightMin(16.f), m_PosX(0, 0, 0.01f), m_PosY(0, 0, 0.01f), m_PosZ(0, 0, 0.01f), m_Zoom(0, 0, 0.1f), m_RotateX(0, 0, 0.001f), m_RotateY(0, 0, 0.001f) { SViewPort vp; vp.m_X = 0; vp.m_Y = 0; vp.m_Width = g_xres; vp.m_Height = g_yres; m_Camera.SetViewPort(vp); SetCameraProjection(); SetupCameraMatrixSmooth(&m_Camera.m_Orientation); m_Camera.UpdateFrustum(); } CCameraController::~CCameraController() = default; void CCameraController::LoadConfig() { CFG_GET_VAL("view.scroll.speed", m_ViewScrollSpeed); CFG_GET_VAL("view.scroll.speed.modifier", m_ViewScrollSpeedModifier); CFG_GET_VAL("view.rotate.x.speed", m_ViewRotateXSpeed); CFG_GET_VAL("view.rotate.x.min", m_ViewRotateXMin); CFG_GET_VAL("view.rotate.x.max", m_ViewRotateXMax); CFG_GET_VAL("view.rotate.x.default", m_ViewRotateXDefault); CFG_GET_VAL("view.rotate.y.speed", m_ViewRotateYSpeed); CFG_GET_VAL("view.rotate.y.speed.wheel", m_ViewRotateYSpeedWheel); CFG_GET_VAL("view.rotate.y.default", m_ViewRotateYDefault); CFG_GET_VAL("view.rotate.speed.modifier", m_ViewRotateSpeedModifier); CFG_GET_VAL("view.drag.speed", m_ViewDragSpeed); CFG_GET_VAL("view.zoom.speed", m_ViewZoomSpeed); CFG_GET_VAL("view.zoom.speed.wheel", m_ViewZoomSpeedWheel); CFG_GET_VAL("view.zoom.min", m_ViewZoomMin); CFG_GET_VAL("view.zoom.max", m_ViewZoomMax); CFG_GET_VAL("view.zoom.default", m_ViewZoomDefault); CFG_GET_VAL("view.zoom.speed.modifier", m_ViewZoomSpeedModifier); CFG_GET_VAL("joystick.camera.pan.x", m_JoystickPanX); CFG_GET_VAL("joystick.camera.pan.y", m_JoystickPanY); CFG_GET_VAL("joystick.camera.rotate.x", m_JoystickRotateX); CFG_GET_VAL("joystick.camera.rotate.y", m_JoystickRotateY); CFG_GET_VAL("joystick.camera.zoom.in", m_JoystickZoomIn); CFG_GET_VAL("joystick.camera.zoom.out", m_JoystickZoomOut); CFG_GET_VAL("view.height.smoothness", m_HeightSmoothness); CFG_GET_VAL("view.height.min", m_HeightMin); #define SETUP_SMOOTHNESS(CFG_PREFIX, SMOOTHED_VALUE) \ { \ float smoothness = SMOOTHED_VALUE.GetSmoothness(); \ CFG_GET_VAL(CFG_PREFIX ".smoothness", smoothness); \ SMOOTHED_VALUE.SetSmoothness(smoothness); \ } SETUP_SMOOTHNESS("view.pos", m_PosX); SETUP_SMOOTHNESS("view.pos", m_PosY); SETUP_SMOOTHNESS("view.pos", m_PosZ); SETUP_SMOOTHNESS("view.zoom", m_Zoom); SETUP_SMOOTHNESS("view.rotate.x", m_RotateX); SETUP_SMOOTHNESS("view.rotate.y", m_RotateY); #undef SETUP_SMOOTHNESS CFG_GET_VAL("view.near", m_ViewNear); CFG_GET_VAL("view.far", m_ViewFar); CFG_GET_VAL("view.fov", m_ViewFOV); // Convert to radians m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault)); m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault)); m_ViewFOV = DEGTORAD(m_ViewFOV); } void CCameraController::SetViewport(const SViewPort& vp) { m_Camera.SetViewPort(vp); SetCameraProjection(); } void CCameraController::Update(const float deltaRealTime) { // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_mouse_y; if (HotkeyIsPressed("camera.rotate.cw")) m_RotateY.AddSmoothly(m_ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.ccw")) m_RotateY.AddSmoothly(-m_ViewRotateYSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.up")) m_RotateX.AddSmoothly(-m_ViewRotateXSpeed * deltaRealTime); if (HotkeyIsPressed("camera.rotate.down")) m_RotateX.AddSmoothly(m_ViewRotateXSpeed * deltaRealTime); float moveRightward = 0.f; float moveForward = 0.f; if (HotkeyIsPressed("camera.pan")) { moveRightward += m_ViewDragSpeed * mouse_dx; moveForward += m_ViewDragSpeed * -mouse_dy; } if (g_mouse_active) { if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres) moveRightward += m_ViewScrollSpeed * deltaRealTime; else if (g_mouse_x <= 3 && g_mouse_x >= 0) moveRightward -= m_ViewScrollSpeed * deltaRealTime; if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres) moveForward -= m_ViewScrollSpeed * deltaRealTime; else if (g_mouse_y <= 3 && g_mouse_y >= 0) moveForward += m_ViewScrollSpeed * deltaRealTime; } if (HotkeyIsPressed("camera.right")) moveRightward += m_ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.left")) moveRightward -= m_ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.up")) moveForward += m_ViewScrollSpeed * deltaRealTime; if (HotkeyIsPressed("camera.down")) moveForward -= m_ViewScrollSpeed * deltaRealTime; if (g_Joystick.IsEnabled()) { // This could all be improved with extra speed and sensitivity settings // (maybe use pow to allow finer control?), and inversion settings moveRightward += g_Joystick.GetAxisValue(m_JoystickPanX) * m_ViewScrollSpeed * deltaRealTime; moveForward -= g_Joystick.GetAxisValue(m_JoystickPanY) * m_ViewScrollSpeed * deltaRealTime; m_RotateX.AddSmoothly(g_Joystick.GetAxisValue(m_JoystickRotateX) * m_ViewRotateXSpeed * deltaRealTime); m_RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m_JoystickRotateY) * m_ViewRotateYSpeed * deltaRealTime); // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1 m_Zoom.AddSmoothly((g_Joystick.GetAxisValue(m_JoystickZoomIn) + 1.0f) / 2.0f * m_ViewZoomSpeed * deltaRealTime); m_Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m_JoystickZoomOut) + 1.0f) / 2.0f * m_ViewZoomSpeed * deltaRealTime); } if (moveRightward || moveForward) { // Break out of following mode when the user starts scrolling m_FollowEntity = INVALID_ENTITY; float s = sin(m_RotateY.GetSmoothedValue()); float c = cos(m_RotateY.GetSmoothedValue()); m_PosX.AddSmoothly(c * moveRightward); m_PosZ.AddSmoothly(-s * moveRightward); m_PosX.AddSmoothly(s * moveForward); m_PosZ.AddSmoothly(c * moveForward); } if (m_FollowEntity) { CmpPtr cmpPosition(*(g_Game->GetSimulation2()), m_FollowEntity); CmpPtr cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpPosition && cmpPosition->IsInWorld() && cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == LosVisibility::VISIBLE) { // Get the most recent interpolated position float frameOffset = g_Game->GetSimulation2()->GetLastFrameOffset(); CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset); CVector3D pos = transform.GetTranslation(); if (m_FollowFirstPerson) { float x, z, angle; cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle); float height = 4.f; m_Camera.m_Orientation.SetIdentity(); m_Camera.m_Orientation.RotateX(static_cast(M_PI) / 24.f); m_Camera.m_Orientation.RotateY(angle); - m_Camera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z); + m_Camera.m_Orientation.Translate(pos.getX(), pos.getY() + height, pos.getZ()); m_Camera.UpdateFrustum(); return; } else { // Move the camera to match the unit CCamera targetCam = m_Camera; SetupCameraMatrixSmoothRot(&targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pos - pivot; - m_PosX.AddSmoothly(delta.X); - m_PosY.AddSmoothly(delta.Y); - m_PosZ.AddSmoothly(delta.Z); + m_PosX.AddSmoothly(delta.getX()); + m_PosY.AddSmoothly(delta.getY()); + m_PosZ.AddSmoothly(delta.getZ()); } } else { // The unit disappeared (died or garrisoned etc), so stop following it m_FollowEntity = INVALID_ENTITY; } } if (HotkeyIsPressed("camera.zoom.in")) m_Zoom.AddSmoothly(-m_ViewZoomSpeed * deltaRealTime); if (HotkeyIsPressed("camera.zoom.out")) m_Zoom.AddSmoothly(m_ViewZoomSpeed * deltaRealTime); if (m_ConstrainCamera) m_Zoom.ClampSmoothly(m_ViewZoomMin, m_ViewZoomMax); float zoomDelta = -m_Zoom.Update(deltaRealTime); if (zoomDelta) { CVector3D forwards = m_Camera.GetOrientation().GetIn(); - m_PosX.AddSmoothly(forwards.X * zoomDelta); - m_PosY.AddSmoothly(forwards.Y * zoomDelta); - m_PosZ.AddSmoothly(forwards.Z * zoomDelta); + m_PosX.AddSmoothly(forwards.getX() * zoomDelta); + m_PosY.AddSmoothly(forwards.getY() * zoomDelta); + m_PosZ.AddSmoothly(forwards.getZ() * zoomDelta); } if (m_ConstrainCamera) m_RotateX.ClampSmoothly(DEGTORAD(m_ViewRotateXMin), DEGTORAD(m_ViewRotateXMax)); FocusHeight(true); // Ensure the ViewCamera focus is inside the map with the chosen margins // if not so - apply margins to the camera if (m_ConstrainCamera) { CCamera targetCam = m_Camera; SetupCameraMatrixSmoothRot(&targetCam.m_Orientation); CTerrain* pTerrain = g_Game->GetWorld()->GetTerrain(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot; CVector3D desiredPivot = pivot; CmpPtr cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY); if (cmpRangeManager && cmpRangeManager->GetLosCircular()) { // Clamp to a circular region around the center of the map float r = pTerrain->GetMaxX() / 2; - CVector3D center(r, desiredPivot.Y, r); + CVector3D center(r, desiredPivot.getY(), r); float dist = (desiredPivot - center).Length(); if (dist > r - CAMERA_EDGE_MARGIN) desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN); } else { // Clamp to the square edges of the map - desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); - desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); + desiredPivot.Xref() = Clamp(desiredPivot.getX(), pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN); + desiredPivot.Zref() = Clamp(desiredPivot.getZ(), pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN); } // Update the position so that pivot is within the margin - m_PosX.SetValueSmoothly(desiredPivot.X + delta.X); - m_PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z); + m_PosX.SetValueSmoothly(desiredPivot.getX() + delta.getX()); + m_PosZ.SetValueSmoothly(desiredPivot.getZ() + delta.getZ()); } m_PosX.Update(deltaRealTime); m_PosY.Update(deltaRealTime); m_PosZ.Update(deltaRealTime); // Handle rotation around the Y (vertical) axis { CCamera targetCam = m_Camera; SetupCameraMatrixSmooth(&targetCam.m_Orientation); float rotateYDelta = m_RotateY.Update(deltaRealTime); if (rotateYDelta) { // We've updated RotateY, and need to adjust Pos so that it's still // facing towards the original focus point (the terrain in the center // of the screen). CVector3D upwards(0.0f, 1.0f, 0.0f); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(upwards, rotateYDelta); CVector3D d = q.Rotate(delta) - delta; - m_PosX.Add(d.X); - m_PosY.Add(d.Y); - m_PosZ.Add(d.Z); + m_PosX.Add(d.getX()); + m_PosY.Add(d.getY()); + m_PosZ.Add(d.getZ()); } } // Handle rotation around the X (sideways, relative to camera) axis { CCamera targetCam = m_Camera; SetupCameraMatrixSmooth(&targetCam.m_Orientation); float rotateXDelta = m_RotateX.Update(deltaRealTime); if (rotateXDelta) { CVector3D rightwards = targetCam.GetOrientation().GetLeft() * -1.0f; CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot; CQuaternion q; q.FromAxisAngle(rightwards, rotateXDelta); CVector3D d = q.Rotate(delta) - delta; - m_PosX.Add(d.X); - m_PosY.Add(d.Y); - m_PosZ.Add(d.Z); + m_PosX.Add(d.getX()); + m_PosY.Add(d.getY()); + m_PosZ.Add(d.getZ()); } } /* This is disabled since it doesn't seem necessary: // Ensure the camera's near point is never inside the terrain if (m_ConstrainCamera) { CMatrix3D target; target.SetIdentity(); target.RotateX(m_RotateX.GetValue()); target.RotateY(m_RotateY.GetValue()); target.Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue()); CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear; float ground = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z); float limit = ground + 16.f; if (nearPoint.Y < limit) m_PosY.AddSmoothly(limit - nearPoint.Y); } */ m_RotateY.Wrap(-static_cast(M_PI), static_cast(M_PI)); // Update the camera matrix SetCameraProjection(); SetupCameraMatrixSmooth(&m_Camera.m_Orientation); m_Camera.UpdateFrustum(); } CVector3D CCameraController::GetSmoothPivot(CCamera& camera) const { return camera.GetOrientation().GetTranslation() + camera.GetOrientation().GetIn() * m_Zoom.GetSmoothedValue(); } CVector3D CCameraController::GetCameraPivot() const { return GetSmoothPivot(m_Camera); } CVector3D CCameraController::GetCameraPosition() const { return CVector3D(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue()); } CVector3D CCameraController::GetCameraRotation() const { // The angle of rotation around the Z axis is not used. return CVector3D(m_RotateX.GetValue(), m_RotateY.GetValue(), 0.0f); } float CCameraController::GetCameraZoom() const { return m_Zoom.GetValue(); } void CCameraController::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom) { - m_PosX.SetValue(pos.X); - m_PosY.SetValue(pos.Y); - m_PosZ.SetValue(pos.Z); + m_PosX.SetValue(pos.getX()); + m_PosY.SetValue(pos.getY()); + m_PosZ.SetValue(pos.getZ()); m_RotateX.SetValue(rotX); m_RotateY.SetValue(rotY); m_Zoom.SetValue(zoom); FocusHeight(false); SetupCameraMatrixNonSmooth(&m_Camera.m_Orientation); m_Camera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m_FollowEntity = INVALID_ENTITY; } void CCameraController::MoveCameraTarget(const CVector3D& target) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference between that position and the camera point, and restoring // that difference to our new target) CCamera targetCam = m_Camera; SetupCameraMatrixNonSmooth(&targetCam.m_Orientation); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = target - pivot; - m_PosX.SetValueSmoothly(delta.X + m_PosX.GetValue()); - m_PosZ.SetValueSmoothly(delta.Z + m_PosZ.GetValue()); + m_PosX.SetValueSmoothly(delta.getX() + m_PosX.GetValue()); + m_PosZ.SetValueSmoothly(delta.getZ() + m_PosZ.GetValue()); FocusHeight(false); // Break out of following mode so the camera really moves to the target m_FollowEntity = INVALID_ENTITY; } void CCameraController::ResetCameraTarget(const CVector3D& target) { CMatrix3D orientation; orientation.SetIdentity(); orientation.RotateX(DEGTORAD(m_ViewRotateXDefault)); orientation.RotateY(DEGTORAD(m_ViewRotateYDefault)); CVector3D delta = orientation.GetIn() * m_ViewZoomDefault; - m_PosX.SetValue(target.X - delta.X); - m_PosY.SetValue(target.Y - delta.Y); - m_PosZ.SetValue(target.Z - delta.Z); + m_PosX.SetValue(target.getX() - delta.getX()); + m_PosY.SetValue(target.getY() - delta.getY()); + m_PosZ.SetValue(target.getZ() - delta.getZ()); m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault)); m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault)); m_Zoom.SetValue(m_ViewZoomDefault); FocusHeight(false); SetupCameraMatrixSmooth(&m_Camera.m_Orientation); m_Camera.UpdateFrustum(); // Break out of following mode so the camera really moves to the target m_FollowEntity = INVALID_ENTITY; } void CCameraController::FollowEntity(entity_id_t entity, bool firstPerson) { m_FollowEntity = entity; m_FollowFirstPerson = firstPerson; } entity_id_t CCameraController::GetFollowedEntity() { return m_FollowEntity; } void CCameraController::SetCameraProjection() { m_Camera.SetPerspectiveProjection(m_ViewNear, m_ViewFar, m_ViewFOV); } void CCameraController::ResetCameraAngleZoom() { CCamera targetCam = m_Camera; SetupCameraMatrixNonSmooth(&targetCam.m_Orientation); // Compute the zoom adjustment to get us back to the default CVector3D forwards = targetCam.GetOrientation().GetIn(); CVector3D pivot = GetSmoothPivot(targetCam); CVector3D delta = pivot - targetCam.GetOrientation().GetTranslation(); float dist = delta.Dot(forwards); m_Zoom.AddSmoothly(m_ViewZoomDefault - dist); // Reset orientations to default m_RotateX.SetValueSmoothly(DEGTORAD(m_ViewRotateXDefault)); m_RotateY.SetValueSmoothly(DEGTORAD(m_ViewRotateYDefault)); } void CCameraController::SetupCameraMatrixSmooth(CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m_RotateX.GetSmoothedValue()); orientation->RotateY(m_RotateY.GetSmoothedValue()); orientation->Translate(m_PosX.GetSmoothedValue(), m_PosY.GetSmoothedValue(), m_PosZ.GetSmoothedValue()); } void CCameraController::SetupCameraMatrixSmoothRot(CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m_RotateX.GetSmoothedValue()); orientation->RotateY(m_RotateY.GetSmoothedValue()); orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue()); } void CCameraController::SetupCameraMatrixNonSmooth(CMatrix3D* orientation) { orientation->SetIdentity(); orientation->RotateX(m_RotateX.GetValue()); orientation->RotateY(m_RotateY.GetValue()); orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue()); } void CCameraController::FocusHeight(bool smooth) { /* The camera pivot height is moved towards ground level. To prevent excessive zoom when looking over a cliff, the target ground level is the maximum of the ground level at the camera's near and pivot points. The ground levels are filtered to achieve smooth camera movement. The filter radius is proportional to the zoom level. The camera height is clamped to prevent map penetration. */ if (!m_ConstrainCamera) return; CCamera targetCam = m_Camera; SetupCameraMatrixSmoothRot(&targetCam.m_Orientation); const CVector3D position = targetCam.GetOrientation().GetTranslation(); const CVector3D forwards = targetCam.GetOrientation().GetIn(); // horizontal view radius - const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m_Zoom.GetSmoothedValue(); + const float radius = forwards.HorizontalLength() * m_Zoom.GetSmoothedValue(); const float near_radius = radius * m_HeightSmoothness; const float pivot_radius = radius * m_HeightSmoothness; const CVector3D nearPoint = position + forwards * m_ViewNear; const CVector3D pivotPoint = position + forwards * m_Zoom.GetSmoothedValue(); const float ground = std::max( - g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z), + g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.getX(), nearPoint.getZ()), g_Renderer.GetWaterManager()->m_WaterHeight); // filter ground levels for smooth camera movement - const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius); - const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius); + const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.getX(), nearPoint.getZ(), near_radius); + const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.getX(), pivotPoint.getZ(), pivot_radius); // filtered maximum visible ground level in view const float filtered_ground = std::max( std::max(filtered_near_ground, filtered_pivot_ground), g_Renderer.GetWaterManager()->m_WaterHeight); // target camera height above pivot point - const float pivot_height = -forwards.Y * (m_Zoom.GetSmoothedValue() - m_ViewNear); + const float pivot_height = -forwards.getY() * (m_Zoom.GetSmoothedValue() - m_ViewNear); // minimum camera height above filtered ground level const float min_height = (m_HeightMin + ground - filtered_ground); const float target_height = std::max(pivot_height, min_height); - const float height = (nearPoint.Y - filtered_ground); + const float height = (nearPoint.getY() - filtered_ground); const float diff = target_height - height; if (fabsf(diff) < 0.0001f) return; if (smooth) m_PosY.AddSmoothly(diff); else m_PosY.Add(diff); } InReaction CCameraController::HandleEvent(const SDL_Event_* ev) { switch (ev->ev.type) { case SDL_HOTKEYPRESS: { std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "camera.reset") { ResetCameraAngleZoom(); return IN_HANDLED; } return IN_PASS; } case SDL_HOTKEYDOWN: { std::string hotkey = static_cast(ev->ev.user.data1); // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). if (hotkey == "camera.zoom.wheel.in") { m_Zoom.AddSmoothly(-m_ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.zoom.wheel.out") { m_Zoom.AddSmoothly(m_ViewZoomSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.cw") { m_RotateY.AddSmoothly(m_ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.rotate.wheel.ccw") { m_RotateY.AddSmoothly(-m_ViewRotateYSpeedWheel); return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.increase") { m_ViewScrollSpeed *= m_ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.scroll.speed.decrease") { m_ViewScrollSpeed /= m_ViewScrollSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.increase") { m_ViewRotateXSpeed *= m_ViewRotateSpeedModifier; m_ViewRotateYSpeed *= m_ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.rotate.speed.decrease") { m_ViewRotateXSpeed /= m_ViewRotateSpeedModifier; m_ViewRotateYSpeed /= m_ViewRotateSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.increase") { m_ViewZoomSpeed *= m_ViewZoomSpeedModifier; return IN_HANDLED; } else if (hotkey == "camera.zoom.speed.decrease") { m_ViewZoomSpeed /= m_ViewZoomSpeedModifier; return IN_HANDLED; } return IN_PASS; } } return IN_PASS; } Index: source/graphics/CinemaManager.cpp =================================================================== --- source/graphics/CinemaManager.cpp (revision 24796) +++ source/graphics/CinemaManager.cpp (working copy) @@ -1,256 +1,256 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include #include #include "graphics/CinemaManager.h" - +#include "graphics/Color.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStr.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Hotkey.h" #include "ps/World.h" #include "simulation2/components/ICmpCinemaManager.h" #include "simulation2/components/ICmpOverlayRenderer.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/components/ICmpTerritoryManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/ComponentManager.h" #include "simulation2/Simulation2.h" #include "renderer/Renderer.h" CCinemaManager::CCinemaManager() : m_DrawPaths(false) { } void CCinemaManager::Update(const float deltaRealTime) const { CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); if (!cmpCinemaManager) return; if (IsPlaying()) cmpCinemaManager->PlayQueue(deltaRealTime, g_Game->GetView()->GetCamera()); } void CCinemaManager::Render() const { if (IsEnabled()) DrawBars(); else if (m_DrawPaths) DrawPaths(); } void CCinemaManager::DrawPaths() const { CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); if (!cmpCinemaManager) return; for (const std::pair& p : cmpCinemaManager->GetPaths()) { - DrawSpline(p.second, CColor(0.2f, 0.2f, 1.f, 0.9f), 128, true); - DrawNodes(p.second, CColor(0.1f, 1.f, 0.f, 1.f)); + DrawSpline(p.second, RGBAcolor(0.2f, 0.2f, 1.f, 0.9f), 128, true); + DrawNodes(p.second, RGBAcolor(0.1f, 1.f, 0.f, 1.f)); if (p.second.GetTargetSpline().GetAllNodes().empty()) continue; - DrawSpline(p.second.GetTargetSpline(), CColor(1.f, 0.3f, 0.4f, 0.9f), 128, true); - DrawNodes(p.second.GetTargetSpline(), CColor(1.f, 0.1f, 0.f, 1.f)); + DrawSpline(p.second.GetTargetSpline(), RGBAcolor(1.f, 0.3f, 0.4f, 0.9f), 128, true); + DrawNodes(p.second.GetTargetSpline(), RGBAcolor(1.f, 0.1f, 0.f, 1.f)); } } -void CCinemaManager::DrawSpline(const RNSpline& spline, const CColor& splineColor, int smoothness, bool lines) const +void CCinemaManager::DrawSpline(const RNSpline& spline, const RGBAcolor& splineColor, int smoothness, bool lines) const { if (spline.GetAllNodes().size() < 2) return; if (spline.GetAllNodes().size() == 2 && lines) smoothness = 2; float start = spline.MaxDistance.ToFloat() / smoothness; float time = 0; #if CONFIG2_GLES #warning TODO : implement CCinemaPath on GLES #else glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); - glColor4f(splineColor.r, splineColor.g, splineColor.b, splineColor.a); + glColor4f(splineColor.getR(), splineColor.getG(), splineColor.getB(), splineColor.getA()); if (lines) { glLineWidth(1.8f); glEnable(GL_LINE_SMOOTH); glBegin(GL_LINE_STRIP); for (int i = 0; i <= smoothness; ++i) { time = start * i / spline.MaxDistance.ToFloat(); CVector3D tmp = spline.GetPosition(time); - glVertex3f(tmp.X, tmp.Y, tmp.Z); + glVertex3f(tmp.getX(), tmp.getY(), tmp.getZ()); } glEnd(); // Height indicator if (g_Game && g_Game->GetWorld() && g_Game->GetWorld()->GetTerrain()) { glLineWidth(1.1f); glBegin(GL_LINES); for (int i = 0; i <= smoothness; ++i) { time = start * i / spline.MaxDistance.ToFloat(); CVector3D tmp = spline.GetPosition(time); - float groundY = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(tmp.X, tmp.Z); - glVertex3f(tmp.X, tmp.Y, tmp.Z); - glVertex3f(tmp.X, groundY, tmp.Z); + float groundY = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(tmp.getX(), tmp.getZ()); + glVertex3f(tmp.getX(), tmp.getY(), tmp.getZ()); + glVertex3f(tmp.getX(), groundY, tmp.getZ()); } glEnd(); } glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); } else { smoothness /= 2; start = spline.MaxDistance.ToFloat() / smoothness; glEnable(GL_POINT_SMOOTH); glPointSize(3.0f); glBegin(GL_POINTS); for (int i = 0; i <= smoothness; ++i) { time = start * i / spline.MaxDistance.ToFloat(); CVector3D tmp = spline.GetPosition(time); - glVertex3f(tmp.X, tmp.Y, tmp.Z); + glVertex3f(tmp.getX(), tmp.getY(), tmp.getZ()); } glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); } glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); #endif } -void CCinemaManager::DrawNodes(const RNSpline& spline, const CColor& nodeColor) const +void CCinemaManager::DrawNodes(const RNSpline& spline, const RGBAcolor& nodeColor) const { #if CONFIG2_GLES #warning TODO : implement CCinemaPath on GLES #else glDisable(GL_DEPTH_TEST); glEnable(GL_POINT_SMOOTH); glPointSize(7.0f); - glColor4f(nodeColor.r, nodeColor.g, nodeColor.b, nodeColor.a); + glColor4f(nodeColor.getR(), nodeColor.getG(), nodeColor.getB(), nodeColor.getA()); glBegin(GL_POINTS); for (const SplineData& node : spline.GetAllNodes()) - glVertex3f(node.Position.X.ToFloat(), node.Position.Y.ToFloat(), node.Position.Z.ToFloat()); + glVertex3f(node.Position.getX().ToFloat(), node.Position.getY().ToFloat(), node.Position.getZ().ToFloat()); glEnd(); glPointSize(1.0f); glDisable(GL_POINT_SMOOTH); glEnable(GL_DEPTH_TEST); #endif } void CCinemaManager::DrawBars() const { int height = (float)g_xres / 2.39f; int shift = (g_yres - height) / 2; if (shift <= 0) return; #if CONFIG2_GLES #warning TODO : implement bars for GLES #else // Set up transform for GL bars glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); CMatrix3D transform; transform.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glLoadMatrixf(&transform._11); glColor4f(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); glBegin(GL_QUADS); glVertex2i(0, 0); glVertex2i(g_xres, 0); glVertex2i(g_xres, shift); glVertex2i(0, shift); glEnd(); glBegin(GL_QUADS); glVertex2i(0, g_yres - shift); glVertex2i(g_xres, g_yres - shift); glVertex2i(g_xres, g_yres); glVertex2i(0, g_yres); glEnd(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); // Restore transform glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); #endif } bool CCinemaManager::IsEnabled() const { CmpPtr cmpCinemaManager(g_Game->GetSimulation2()->GetSimContext().GetSystemEntity()); return cmpCinemaManager && cmpCinemaManager->IsEnabled(); } bool CCinemaManager::IsPlaying() const { return IsEnabled() && g_Game && !g_Game->m_Paused; } bool CCinemaManager::GetPathsDrawing() const { return m_DrawPaths; } void CCinemaManager::SetPathsDrawing(const bool drawPath) { m_DrawPaths = drawPath; } Index: source/graphics/CinemaManager.h =================================================================== --- source/graphics/CinemaManager.h (revision 24796) +++ source/graphics/CinemaManager.h (working copy) @@ -1,61 +1,61 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_CINEMAMANAGER #define INCLUDED_CINEMAMANAGER #include "lib/input.h" // InReaction - can't forward-declare enum -#include "graphics/Color.h" #include "ps/CStr.h" #include "simulation2/helpers/CinemaPath.h" +class RGBAcolor; /** * Class for in game playing of cinematics. Should only be instantiated in CGameView. */ class CCinemaManager { public: CCinemaManager(); ~CCinemaManager() {} /** * Renders black bars and paths (if enabled) */ void Render() const; void DrawBars() const; void DrawPaths() const; - void DrawSpline(const RNSpline& spline, const CColor& splineColor, int smoothness, bool lines) const; - void DrawNodes(const RNSpline& spline, const CColor& nodesColor) const; + void DrawSpline(const RNSpline& spline, const RGBAcolor& splineColor, int smoothness, bool lines) const; + void DrawNodes(const RNSpline& spline, const RGBAcolor& nodesColor) const; bool IsPlaying() const; bool IsEnabled() const; /** * Updates CCinemManager and current path * @param deltaRealTime Elapsed real time since the last frame. */ void Update(const float deltaRealTime) const; bool GetPathsDrawing() const; void SetPathsDrawing(const bool drawPath); private: bool m_DrawPaths; }; #endif Index: source/graphics/Color.cpp =================================================================== --- source/graphics/Color.cpp (revision 24796) +++ source/graphics/Color.cpp (working copy) @@ -1,145 +1,158 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "graphics/Color.h" #include "graphics/SColor.h" #include "maths/MathUtil.h" +#include "maths/Vector4D.h" #include "lib/sse.h" #include "ps/CLogger.h" #include "ps/CStr.h" #if COMPILER_HAS_SSE #include #endif -static SColor4ub ConvertRGBColorTo4ubFallback(const RGBColor& src) +static SColor4ub ConvertRGBcolorTo4ubFallback(const RGBcolor& src) { SColor4ub result; - result.R = Clamp(static_cast(src.X * 255), 0, 255); - result.G = Clamp(static_cast(src.Y * 255), 0, 255); - result.B = Clamp(static_cast(src.Z * 255), 0, 255); + result.R = static_cast(Clamp(static_cast(src.getR() * 256.0f), 0, 255)); + result.G = static_cast(Clamp(static_cast(src.getG() * 256.0f), 0, 255)); + result.B = static_cast(Clamp(static_cast(src.getB() * 256.0f), 0, 255)); result.A = 255; return result; } // on IA32, this is replaced by an SSE assembly version in ia32.cpp -SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src) = ConvertRGBColorTo4ubFallback; +SColor4ub (*ConvertRGBcolorTo4ub)(const RGBcolor& src) = ConvertRGBcolorTo4ubFallback; // Assembler-optimized function for color conversion #if COMPILER_HAS_SSE -static SColor4ub ConvertRGBColorTo4ubSSE(const RGBColor& src) +static SColor4ub ConvertRGBcolorTo4ubSSE(const RGBcolor& src) { const __m128 zero = _mm_setzero_ps(); const __m128 _255 = _mm_set_ss(255.0f); - __m128 r = _mm_load_ss(&src.X); - __m128 g = _mm_load_ss(&src.Y); - __m128 b = _mm_load_ss(&src.Z); + __m128 r = _mm_load_ss(src.pR()); + __m128 g = _mm_load_ss(src.pG()); + __m128 b = _mm_load_ss(src.pB()); // C = min(255, 255*max(C, 0)) ( == Clamp(255*C, 0, 255) ) r = _mm_max_ss(r, zero); g = _mm_max_ss(g, zero); b = _mm_max_ss(b, zero); r = _mm_mul_ss(r, _255); g = _mm_mul_ss(g, _255); b = _mm_mul_ss(b, _255); r = _mm_min_ss(r, _255); g = _mm_min_ss(g, _255); b = _mm_min_ss(b, _255); // convert to integer and combine channels using bit logic int ri = _mm_cvtss_si32(r); int gi = _mm_cvtss_si32(g); int bi = _mm_cvtss_si32(b); return SColor4ub(ri, gi, bi, 0xFF); } #endif void ColorActivateFastImpl() { #if COMPILER_HAS_SSE if (HostHasSSE()) { - ConvertRGBColorTo4ub = ConvertRGBColorTo4ubSSE; + ConvertRGBcolorTo4ub = ConvertRGBcolorTo4ubSSE; return; } #endif debug_printf("No SSE available. Slow fallback routines will be used.\n"); } /** * Important: This function does not modify the value if parsing fails. */ -bool CColor::ParseString(const CStr8& value, int defaultAlpha) +bool RGBAcolor::ParseString(const CStr8& value, int defaultAlpha) { const size_t NUM_VALS = 4; int values[NUM_VALS] = { 0, 0, 0, defaultAlpha }; std::stringstream stream; stream.str(value); // Parse each value size_t i; for (i = 0; i < NUM_VALS; ++i) { if (stream.eof()) break; stream >> values[i]; if ((stream.rdstate() & std::stringstream::failbit) != 0) { - LOGWARNING("Unable to parse CColor parameters. Your input: '%s'", value.c_str()); + LOGWARNING("Unable to parse RGBAcolor parameters. Your input: '%s'", value.c_str()); return false; } if (values[i] < 0 || values[i] > 255) { - LOGWARNING("Invalid value (<0 or >255) when parsing CColor parameters. Your input: '%s'", value.c_str()); + LOGWARNING("Invalid value (<0 or >255) when parsing RGBAcolor parameters. Your input: '%s'", value.c_str()); return false; } } if (i < 3) { - LOGWARNING("Not enough parameters when parsing as CColor. Your input: '%s'", value.c_str()); + LOGWARNING("Not enough parameters when parsing as RGBAcolor. Your input: '%s'", value.c_str()); return false; } if (!stream.eof()) { - LOGWARNING("Too many parameters when parsing as CColor. Your input: '%s'", value.c_str()); + LOGWARNING("Too many parameters when parsing as RGBAcolor. Your input: '%s'", value.c_str()); return false; } - r = values[0] / 255.f; - g = values[1] / 255.f; - b = values[2] / 255.f; - a = values[3] / 255.f; + R = values[0] / 255.f; + G = values[1] / 255.f; + B = values[2] / 255.f; + A = values[3] / 255.f; return true; } -bool CColor::operator==(const CColor& color) const +bool RGBAcolor::operator==(const RGBAcolor& color) const { return - r == color.r && - g == color.g && - b == color.b && - a == color.a; + R == color.R && + G == color.G && + B == color.B && + A == color.A; } + +RGBAcolor::operator CVector4D() const { return CVector4D(R,G,B,A); } + +#if 0 +#include "lib/code_annotation.h" +PRINT_SIZEOF_AS_ERROR(RGBcolor); +#endif +#if 0 +#include "lib/code_annotation.h" +PRINT_SIZEOF_AS_ERROR(RGBAcolor); +#endif + Index: source/graphics/Color.h =================================================================== --- source/graphics/Color.h (revision 24796) +++ source/graphics/Color.h (working copy) @@ -1,86 +1,189 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ - +// (Note there's also a Color struct for the messaging system, somewhere...) #ifndef INCLUDED_COLOR #define INCLUDED_COLOR +struct Color; #include "graphics/SColor.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" +#include "maths/MathUtil.h" // Simple defines for 3 and 4 component floating point colors - just map to // corresponding vector types. -typedef CVector3D RGBColor; -typedef CVector4D RGBAColor; +class RGBcolor +{ + float R, G, B; +public: + ///constructors, etc + RGBcolor() {} + RGBcolor(float x, float y, float z) : R(x), G(y), B(z) { } + ///get functions + float getR() const { return R; } + float getG() const { return G; } + float getB() const { return B; } + float MaxComponent() { return std::max({R, G, B}); } + float MinComponent() { return std::min({R, G, B}); } + u8 getR8() const { return static_cast( Clamp( int(R*256.0f), 0, 255 ) ); } + u8 getG8() const { return static_cast( Clamp( int(G*256.0f), 0, 255 ) ); } + u8 getB8() const { return static_cast( Clamp( int(B*256.0f), 0, 255 ) ); } + ///Not quite "set functions"; "get non-const ref" functions to minimize changes + float& Rref(){ return R; } + float& Gref(){ return G; } + float& Bref(){ return B; } + ///pointers for the stuuupid SSE library + float const * pR() const { return (((float*)this)+0); } + float const * pG() const { return (((float*)this)+1); } + float const * pB() const { return (((float*)this)+2); } + void clamp0to1() + { + if( R < 0.0f ) R = 0.0f; + if( 0.996f < R ) R = 0.996f; + if( G < 0.0f ) G = 0.0f; + if( 0.996f < G ) G = 0.996f; + if( B < 0.0f ) B = 0.0f; + if( 0.996f < B ) B = 0.996f; + } + RGBcolor clamped0to1() const + { + RGBcolor result(*this); + result.clamp0to1(); + return result; + } + bool operator==(const RGBcolor& p) const + { + return (R == p.R && G == p.G && B == p.B); + } + bool operator!=(const RGBcolor& p) const + { + return (R != p.R || G != p.G || B != p.B); + } + RGBcolor operator+(const RGBcolor& c) const + { + return RGBcolor(R + c.getR(), G + c.getG(), B + c.getB()); + } + RGBcolor operator-(const RGBcolor& c) const + { + return RGBcolor(R - c.getR(), G - c.getG(), B - c.getB()); + } + RGBcolor const & operator+=(const RGBcolor& v) + { + R += v.getR(); G += v.getG(); B += v.getB(); + return *this; + } + RGBcolor const & operator-=(const RGBcolor& v) + { + R -= v.getR(); G -= v.getG(); B -= v.getB(); + return *this; + } + RGBcolor operator*(float value) const + { + return RGBcolor(R * value, G * value, B * value); + } + RGBcolor& operator*=(float value) + { + R *= value; + G *= value; + B *= value; + return *this; + } +}; +//typedef CVector4D RGBAColor; // Convert float RGB(A) colors to unsigned byte. // Exposed as function pointer because it is set at init-time to // one of several implementations depending on CPU caps. -extern SColor4ub (*ConvertRGBColorTo4ub)(const RGBColor& src); +extern SColor4ub (*ConvertRGBcolorTo4ub)(const RGBcolor& src); /** * Detects CPU caps and activates the best possible codepath. */ extern void ColorActivateFastImpl(); class CStr8; -struct CColor +class RGBAcolor { - CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {} - CColor(float cr, float cg, float cb, float ca) : r(cr), g(cg), b(cb), a(ca) {} +protected: //CGUIColor inherits RGBAcolor and wants to manipulate the variables + float R, G, B, A; +public: + RGBAcolor() : R(-1.f), G(-1.f), B(-1.f), A(1.f) {} + RGBAcolor(float cr, float cg, float cb, float ca) : R(cr), G(cg), B(cb), A(ca) {} + ///get functions + float getR() const { return R; } + float getG() const { return G; } + float getB() const { return B; } + float getA() const { return A; } + float MaxComponent() { return std::max({R, G, B}); } //alpha channel's component? + float MinComponent() { return std::min({R, G, B}); } //alpha channel's component? + u8 getR8() const { return static_cast( Clamp( int(R*256.0f), 0, 255 ) ); } + u8 getG8() const { return static_cast( Clamp( int(G*256.0f), 0, 255 ) ); } + u8 getB8() const { return static_cast( Clamp( int(B*256.0f), 0, 255 ) ); } + u8 getA8() const { return static_cast( Clamp( int(A*256.0f), 0, 255 ) ); } + ///Conversion to CVector4D is needed for the shader uniforms + operator CVector4D() const; + ///Not quite "set functions"; "get non-const ref" functions to minimize changes + float& Rref(){ return R; } + float& Gref(){ return G; } + float& Bref(){ return B; } + float& Aref(){ return A; } /** * Returns whether this has been set to a valid color. + * DanW58: I suppose values greater than 1 are valid in the case that + * color is being used for a light source, like the Sun, and not just + * reflective material color --which can never be greater than 1.0f + * It's funny then that the default constructor puts RGB at -1.0f... + * I've no idea why... */ operator bool() const { - return r >= 0 && g >= 0 && b >= 0 && a >= 0; + return R >= 0 && G >= 0 && B >= 0 && A >= 0; } /** * Try to parse @p Value as a color. Returns true on success, false otherwise. * Leaves the color unchanged if it failed. - * @param value Should be "r g b" or "r g b a" where each value is an integer in [0,255]. - * @param defaultAlpha The alpha value that is used if the format of @p Value is "r g b". + * @param value Should be "R G B" or "R G B A" where each value is an integer in [0,255]. + * @param defaultAlpha The alpha value that is used if the format of @p Value is "R G B". */ bool ParseString(const CStr8& value, int defaultAlpha = 255); - bool operator==(const CColor& color) const; - bool operator!=(const CColor& color) const + bool operator==(const RGBAcolor& color) const; + bool operator!=(const RGBAcolor& color) const { return !(*this == color); } // For passing to glColor[34]fv: - const float* FloatArray() const { return &r; } + const float* FloatArray() const { return &R; } // For passing to CRenderer: SColor4ub AsSColor4ub() const { return SColor4ub( - static_cast(r * 255.f), - static_cast(g * 255.f), - static_cast(b * 255.f), - static_cast(a * 255.f) + static_cast(Clamp(R * 256.0f, 0, 255)), + static_cast(Clamp(G * 255.0f, 0, 255)), + static_cast(Clamp(B * 255.0f, 0, 255)), + static_cast(Clamp(A * 255.0f, 0, 255)) ); } - float r, g, b, a; }; #endif // INCLUDED_COLOR Index: source/graphics/Decal.cpp =================================================================== --- source/graphics/Decal.cpp (revision 24796) +++ source/graphics/Decal.cpp (working copy) @@ -1,115 +1,115 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Decal.h" #include "graphics/Terrain.h" #include "maths/MathUtil.h" CModelAbstract* CModelDecal::Clone() const { CModelDecal* clone = new CModelDecal(m_Terrain, m_Decal); return clone; } void CModelDecal::CalcVertexExtents(ssize_t& i0, ssize_t& j0, ssize_t& i1, ssize_t& j1) { CVector3D corner0(m_Decal.m_OffsetX + m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ + m_Decal.m_SizeZ/2); CVector3D corner1(m_Decal.m_OffsetX + m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ - m_Decal.m_SizeZ/2); CVector3D corner2(m_Decal.m_OffsetX - m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ - m_Decal.m_SizeZ/2); CVector3D corner3(m_Decal.m_OffsetX - m_Decal.m_SizeX/2, 0, m_Decal.m_OffsetZ + m_Decal.m_SizeZ/2); corner0 = GetTransform().Transform(corner0); corner1 = GetTransform().Transform(corner1); corner2 = GetTransform().Transform(corner2); corner3 = GetTransform().Transform(corner3); - i0 = floor(std::min(std::min(corner0.X, corner1.X), std::min(corner2.X, corner3.X)) / TERRAIN_TILE_SIZE); - j0 = floor(std::min(std::min(corner0.Z, corner1.Z), std::min(corner2.Z, corner3.Z)) / TERRAIN_TILE_SIZE); - i1 = ceil(std::max(std::max(corner0.X, corner1.X), std::max(corner2.X, corner3.X)) / TERRAIN_TILE_SIZE); - j1 = ceil(std::max(std::max(corner0.Z, corner1.Z), std::max(corner2.Z, corner3.Z)) / TERRAIN_TILE_SIZE); + i0 = floor(std::min(std::min(corner0.getX(), corner1.getX()), std::min(corner2.getX(), corner3.getX())) / TERRAIN_TILE_SIZE); + j0 = floor(std::min(std::min(corner0.getZ(), corner1.getZ()), std::min(corner2.getZ(), corner3.getZ())) / TERRAIN_TILE_SIZE); + i1 = ceil(std::max(std::max(corner0.getX(), corner1.getX()), std::max(corner2.getX(), corner3.getX())) / TERRAIN_TILE_SIZE); + j1 = ceil(std::max(std::max(corner0.getZ(), corner1.getZ()), std::max(corner2.getZ(), corner3.getZ())) / TERRAIN_TILE_SIZE); i0 = Clamp(i0, static_cast(0), m_Terrain->GetVerticesPerSide() - 1); j0 = Clamp(j0, static_cast(0), m_Terrain->GetVerticesPerSide() - 1); i1 = Clamp(i1, static_cast(0), m_Terrain->GetVerticesPerSide() - 1); j1 = Clamp(j1, static_cast(0), m_Terrain->GetVerticesPerSide() - 1); } void CModelDecal::CalcBounds() { ssize_t i0, j0, i1, j1; CalcVertexExtents(i0, j0, i1, j1); m_WorldBounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1); } void CModelDecal::SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) { // Check if there's no intersection between the dirty range and this decal ssize_t bi0, bj0, bi1, bj1; CalcVertexExtents(bi0, bj0, bi1, bj1); if (bi1 < i0 || bi0 > i1 || bj1 < j0 || bj0 > j1) return; SetDirty(RENDERDATA_UPDATE_VERTICES); } void CModelDecal::InvalidatePosition() { m_PositionValid = false; } void CModelDecal::ValidatePosition() { if (m_PositionValid) { ENSURE(!m_Parent || m_Parent->m_PositionValid); return; } if (m_Parent && !m_Parent->m_PositionValid) { // Make sure we don't base our calculations on // a parent animation state that is out of date. m_Parent->ValidatePosition(); // Parent will recursively call our validation. ENSURE(m_PositionValid); return; } m_PositionValid = true; } void CModelDecal::SetTransform(const CMatrix3D& transform) { // Since decals are assumed to be horizontal and projected downwards // onto the terrain, use just the Y-axis rotation and the translation CMatrix3D newTransform; newTransform.SetYRotation(transform.GetYRotation() + m_Decal.m_Angle); newTransform.Translate(transform.GetTranslation()); CRenderableObject::SetTransform(newTransform); InvalidatePosition(); } void CModelDecal::RemoveShadows() { m_Decal.m_Material.AddShaderDefine(str_DISABLE_RECEIVE_SHADOWS, str_1); m_Decal.m_Material.RecomputeCombinedShaderDefines(); } Index: source/graphics/Frustum.cpp =================================================================== --- source/graphics/Frustum.cpp (revision 24796) +++ source/graphics/Frustum.cpp (working copy) @@ -1,160 +1,160 @@ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CFrustum is a collection of planes which define a viewing space. */ /* Usually associated with the camera, there are 6 planes which define the view pyramid. But we allow more planes per frustum which may be used for portal rendering, where a portal may have 3 or more edges. */ #include "precompiled.h" #include "Frustum.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" CFrustum::CFrustum () { m_NumPlanes = 0; } CFrustum::~CFrustum () { } void CFrustum::SetNumPlanes (size_t num) { m_NumPlanes = num; //clip it if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) { debug_warn(L"CFrustum::SetNumPlanes: Too many planes"); m_NumPlanes = MAX_NUM_FRUSTUM_PLANES-1; } } void CFrustum::AddPlane(const CPlane& plane) { if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) { debug_warn(L"CFrustum::AddPlane: Too many planes"); return; } m_aPlanes[m_NumPlanes++] = plane; } void CFrustum::Transform(CMatrix3D& m) { for (size_t i = 0; i < m_NumPlanes; ++i) { CVector3D n = m.Rotate(m_aPlanes[i].m_Norm); CVector3D p = m.Transform(m_aPlanes[i].m_Norm * -m_aPlanes[i].m_Dist); m_aPlanes[i].Set(n, p); m_aPlanes[i].Normalize(); } } bool CFrustum::IsPointVisible(const CVector3D& point) const { for (size_t i=0; i radius) return false; } return true; } bool CFrustum::IsBoxVisible(const CVector3D& position, const CBoundingBoxAligned& bounds) const { //basically for every plane we calculate the furthest point //in the box to that plane. If that point is beyond the plane //then the box is not visible CVector3D FarPoint; CVector3D Min = position+bounds[0]; CVector3D Max = position+bounds[1]; for (size_t i=0; i 0.0f ? Max.X : Min.X; - FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? Max.Y : Min.Y; - FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? Max.Z : Min.Z; + FarPoint.Xref() = m_aPlanes[i].m_Norm.getX() > 0.0f ? Max.getX() : Min.getX(); + FarPoint.Yref() = m_aPlanes[i].m_Norm.getY() > 0.0f ? Max.getY() : Min.getY(); + FarPoint.Zref() = m_aPlanes[i].m_Norm.getZ() > 0.0f ? Max.getZ() : Min.getZ(); if (m_aPlanes[i].IsPointOnBackSide(FarPoint)) return false; } return true; } bool CFrustum::IsBoxVisible(const CBoundingBoxAligned& bounds) const { //Same as the previous one, but with the position = (0, 0, 0) CVector3D FarPoint; for (size_t i=0; i 0.0f ? bounds[1].X : bounds[0].X; - FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? bounds[1].Y : bounds[0].Y; - FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? bounds[1].Z : bounds[0].Z; + FarPoint.Xref() = m_aPlanes[i].m_Norm.getX() > 0.0f ? bounds[1].getX() : bounds[0].getX(); + FarPoint.Yref() = m_aPlanes[i].m_Norm.getY() > 0.0f ? bounds[1].getY() : bounds[0].getY(); + FarPoint.Zref() = m_aPlanes[i].m_Norm.getZ() > 0.0f ? bounds[1].getZ() : bounds[0].getZ(); if (m_aPlanes[i].IsPointOnBackSide(FarPoint)) return false; } return true; } Index: source/graphics/GameView.cpp =================================================================== --- source/graphics/GameView.cpp (revision 24796) +++ source/graphics/GameView.cpp (working copy) @@ -1,415 +1,415 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "GameView.h" #include "graphics/CameraController.h" #include "graphics/CinemaManager.h" #include "graphics/ColladaManager.h" #include "graphics/HFTracer.h" #include "graphics/LOSTexture.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/SmoothedValue.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "graphics/scripting/JSInterface_GameView.h" #include "lib/input.h" #include "lib/timer.h" #include "lobby/IXmppClient.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/TouchInput.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" #include class CGameViewImpl { NONCOPYABLE(CGameViewImpl); public: CGameViewImpl(CGame* game) : Game(game), ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()), LOSTexture(*game->GetSimulation2()), TerritoryTexture(*game->GetSimulation2()), ViewCamera(), CullCamera(), LockCullCamera(false), Culling(true), CameraController(new CCameraController(ViewCamera)) { } CGame* Game; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CLOSTexture LOSTexture; CTerritoryTexture TerritoryTexture; /** * this camera controls the eye position when rendering */ CCamera ViewCamera; /** * this camera controls the frustum that is used for culling * and shadow calculations * * Note that all code that works with camera movements should only change * m_ViewCamera. The render functions automatically sync the cull camera to * the view camera depending on the value of m_LockCullCamera. */ CCamera CullCamera; /** * When @c true, the cull camera is locked in place. * When @c false, the cull camera follows the view camera. * * Exposed to JS as gameView.lockCullCamera */ bool LockCullCamera; /** * When @c true, culling is enabled so that only models that have a chance of * being visible are sent to the renderer. * Otherwise, the entire world is sent to the renderer. * * Exposed to JS as gameView.culling */ bool Culling; /** * Cache global lighting environment. This is used to check whether the * environment has changed during the last frame, so that vertex data can be updated etc. */ CLightEnv CachedLightEnv; CCinemaManager CinemaManager; /** * Controller of the view's camera. We use a std::unique_ptr for an easy * on the fly replacement. It's guaranteed that the pointer is never nulllptr. */ std::unique_ptr CameraController; }; #define IMPLEMENT_BOOLEAN_SETTING(NAME) \ bool CGameView::Get##NAME##Enabled() const \ { \ return m->NAME; \ } \ \ void CGameView::Set##NAME##Enabled(bool Enabled) \ { \ m->NAME = Enabled; \ } IMPLEMENT_BOOLEAN_SETTING(Culling); IMPLEMENT_BOOLEAN_SETTING(LockCullCamera); bool CGameView::GetConstrainCameraEnabled() const { return m->CameraController->GetConstrainCamera(); } void CGameView::SetConstrainCameraEnabled(bool enabled) { m->CameraController->SetConstrainCamera(enabled); } #undef IMPLEMENT_BOOLEAN_SETTING CGameView::CGameView(CGame *pGame): m(new CGameViewImpl(pGame)) { m->CullCamera = m->ViewCamera; g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); } CGameView::~CGameView() { UnloadResources(); delete m; } void CGameView::SetViewport(const SViewPort& vp) { m->CameraController->SetViewport(vp); } CObjectManager& CGameView::GetObjectManager() { return m->ObjectManager; } CCamera* CGameView::GetCamera() { return &m->ViewCamera; } CCinemaManager* CGameView::GetCinema() { return &m->CinemaManager; }; CLOSTexture& CGameView::GetLOSTexture() { return m->LOSTexture; } CTerritoryTexture& CGameView::GetTerritoryTexture() { return m->TerritoryTexture; } int CGameView::Initialize() { m->CameraController->LoadConfig(); return 0; } void CGameView::RegisterInit() { // CGameView init RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1); RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); } void CGameView::BeginFrame() { if (m->LockCullCamera == false) { // Set up cull camera m->CullCamera = m->ViewCamera; } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); CheckLightEnv(); m->Game->CachePlayerColors(); } void CGameView::Render() { g_Renderer.RenderScene(*this); } /////////////////////////////////////////////////////////// // This callback is part of the Scene interface // Submit all objects visible in the given frustum void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { { PROFILE3("submit terrain"); CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f; const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide(); // find out which patches will be drawn for (ssize_t j=0; jGetPatch(i,j); // can't fail // If the patch is underwater, calculate a bounding box that also contains the water plane CBoundingBoxAligned bounds = patch->GetWorldBounds(); - if(bounds[1].Y < waterHeight) - bounds[1].Y = waterHeight; + if(bounds[1].getY() < waterHeight) + bounds[1].Yref() = waterHeight; if (!m->Culling || frustum.IsBoxVisible(bounds)) c->Submit(patch); } } } m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling); } void CGameView::CheckLightEnv() { if (m->CachedLightEnv == g_LightEnv) return; m->CachedLightEnv = g_LightEnv; CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain(); if (!pTerrain) return; PROFILE("update light env"); pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR); const std::vector& units = m->Game->GetWorld()->GetUnitManager().GetUnits(); for (size_t i = 0; i < units.size(); ++i) units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR); } void CGameView::UnloadResources() { g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); g_Renderer.GetWaterManager()->UnloadWaterTextures(); } void CGameView::Update(const float deltaRealTime) { // If camera movement is being handled by the touch-input system, // then we should stop to avoid conflicting with it if (g_TouchInput.IsEnabled()) return; if (!g_app_has_focus) return; m->CinemaManager.Update(deltaRealTime); if (m->CinemaManager.IsEnabled()) return; m->CameraController->Update(deltaRealTime); } CVector3D CGameView::GetCameraPivot() const { return m->CameraController->GetCameraPivot(); } CVector3D CGameView::GetCameraPosition() const { return m->CameraController->GetCameraPosition(); } CVector3D CGameView::GetCameraRotation() const { return m->CameraController->GetCameraRotation(); } float CGameView::GetCameraZoom() const { return m->CameraController->GetCameraZoom(); } void CGameView::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom) { m->CameraController->SetCamera(pos, rotX, rotY, zoom); } void CGameView::MoveCameraTarget(const CVector3D& target) { m->CameraController->MoveCameraTarget(target); } void CGameView::ResetCameraTarget(const CVector3D& target) { m->CameraController->ResetCameraTarget(target); } void CGameView::FollowEntity(entity_id_t entity, bool firstPerson) { m->CameraController->FollowEntity(entity, firstPerson); } entity_id_t CGameView::GetFollowedEntity() { return m->CameraController->GetFollowedEntity(); } InReaction game_view_handler(const SDL_Event_* ev) { // put any events that must be processed even if inactive here if (!g_app_has_focus || !g_Game || !g_Game->IsGameStarted() || g_Game->GetView()->GetCinema()->IsEnabled()) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_HOTKEYPRESS: { std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "wireframe") { if (g_XmppClient && g_rankedGame == true) break; else if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetWaterRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); g_Renderer.SetOverlayRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetWaterRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); g_Renderer.SetOverlayRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetWaterRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); g_Renderer.SetOverlayRenderMode(SOLID); } return IN_HANDLED; } } } return m->CameraController->HandleEvent(ev); } Index: source/graphics/HFTracer.cpp =================================================================== --- source/graphics/HFTracer.cpp (revision 24796) +++ source/graphics/HFTracer.cpp (working copy) @@ -1,360 +1,360 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Determine intersection of rays with a heightfield. */ #include "precompiled.h" #include "HFTracer.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Vector3D.h" #include // To cope well with points that are slightly off the edge of the map, // we act as if there's an N-tile margin around the edges of the heightfield. // (N shouldn't be too huge else it'll hurt performance a little when // RayIntersect loops through it all.) // CTerrain::CalcPosition implements clamp-to-edge behaviour so the tracer // will have that behaviour. static const int MARGIN_SIZE = 64; /////////////////////////////////////////////////////////////////////////////// // CHFTracer constructor CHFTracer::CHFTracer(CTerrain *pTerrain): m_pTerrain(pTerrain), m_Heightfield(m_pTerrain->GetHeightMap()), m_MapSize(m_pTerrain->GetVerticesPerSide()), m_CellSize((float)TERRAIN_TILE_SIZE), m_HeightScale(HEIGHT_SCALE) { } /////////////////////////////////////////////////////////////////////////////// // RayTriIntersect: intersect a ray with triangle defined by vertices // v0,v1,v2; return true if ray hits triangle at distance less than dist, // or false otherwise static bool RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2, const CVector3D& origin, const CVector3D& dir, float& dist) { const float EPSILON=0.00001f; // calculate edge vectors CVector3D edge0=v1-v0; CVector3D edge1=v2-v0; // begin calculating determinant - also used to calculate U parameter CVector3D pvec=dir.Cross(edge1); // if determinant is near zero, ray lies in plane of triangle float det = edge0.Dot(pvec); if (fabs(det)1.01f) return false; // prepare to test V parameter CVector3D qvec=tvec.Cross(edge0); // calculate V parameter and test bounds float v=dir.Dot(qvec)*inv_det; if (v<0.0f || u+v>1.0f) return false; // calculate distance to intersection point from ray origin float d=edge1.Dot(qvec)*inv_det; if (d>=0 && dCalcPosition(cx,cz,vpos[0]); m_pTerrain->CalcPosition(cx+1,cz,vpos[1]); m_pTerrain->CalcPosition(cx+1,cz+1,vpos[2]); m_pTerrain->CalcPosition(cx,cz+1,vpos[3]); dist=1.0e30f; if (RayTriIntersect(vpos[0],vpos[1],vpos[2],origin,dir,dist)) { res=true; } if (RayTriIntersect(vpos[0],vpos[2],vpos[3],origin,dir,dist)) { res=true; } return res; } /////////////////////////////////////////////////////////////////////////////// // RayIntersect: intersect ray with this heightfield; return true if // intersection occurs (and fill in grid coordinates of intersection), or false // otherwise bool CHFTracer::RayIntersect(const CVector3D& origin, const CVector3D& dir, int& x, int& z, CVector3D& ipt) const { // If the map is empty (which should never happen), // return early before we crash when reading zero-sized heightmaps if (!m_MapSize) { debug_warn(L"CHFTracer::RayIntersect called with zero-size map"); return false; } // intersect first against bounding box CBoundingBoxAligned bound; bound[0] = CVector3D(-MARGIN_SIZE * m_CellSize, 0, -MARGIN_SIZE * m_CellSize); bound[1] = CVector3D((m_MapSize + MARGIN_SIZE) * m_CellSize, 65535 * m_HeightScale, (m_MapSize + MARGIN_SIZE) * m_CellSize); float tmin,tmax; if (!bound.RayIntersect(origin,dir,tmin,tmax)) { // ray missed world bounds; no intersection return false; } // project origin onto grid, if necessary, to get starting point for traversal CVector3D traversalPt; if (tmin>0) { traversalPt=origin+dir*tmin; } else { traversalPt=origin; } // setup traversal variables - int sx=dir.X<0 ? -1 : 1; - int sz=dir.Z<0 ? -1 : 1; + int sx=dir.getX()<0 ? -1 : 1; + int sz=dir.getZ()<0 ? -1 : 1; float invCellSize=1.0f/float(m_CellSize); - float fcx=traversalPt.X*invCellSize; + float fcx=traversalPt.getX()*invCellSize; int cx=(int)floor(fcx); - float fcz=traversalPt.Z*invCellSize; + float fcz=traversalPt.getZ()*invCellSize; int cz=(int)floor(fcz); float invdx = 1.0e20f; float invdz = 1.0e20f; - if (fabs(dir.X) > 1.0e-20) - invdx = float(1.0/fabs(dir.X)); - if (fabs(dir.Z) > 1.0e-20) - invdz = float(1.0/fabs(dir.Z)); + if (fabs(dir.getX()) > 1.0e-20) + invdx = float(1.0/fabs(dir.getX())); + if (fabs(dir.getZ()) > 1.0e-20) + invdz = float(1.0/fabs(dir.getZ())); do { // test current cell if (cx >= -MARGIN_SIZE && cx < int(m_MapSize + MARGIN_SIZE - 1) && cz >= -MARGIN_SIZE && cz < int(m_MapSize + MARGIN_SIZE - 1)) { float dist; if (CellIntersect(cx,cz,origin,dir,dist)) { x=cx; z=cz; ipt=origin+dir*dist; return true; } } else { // Degenerate case: y close to zero // catch travelling off the map if ((cx < -MARGIN_SIZE) && (sx < 0)) return false; if ((cx >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sx > 0)) return false; if ((cz < -MARGIN_SIZE) && (sz < 0)) return false; if ((cz >= (int)(m_MapSize + MARGIN_SIZE - 1)) && (sz > 0)) return false; } // get coords of current cell - fcx=traversalPt.X*invCellSize; - fcz=traversalPt.Z*invCellSize; + fcx=traversalPt.getX()*invCellSize; + fcz=traversalPt.getZ()*invCellSize; // get distance to next cell in x,z float dx=(sx==-1) ? fcx-float(cx) : 1-(fcx-float(cx)); dx*=invdx; float dz=(sz==-1) ? fcz-float(cz) : 1-(fcz-float(cz)); dz*=invdz; // advance .. float dist; if (dx=0); + } while (traversalPt.getY()>=0); // fell off end of heightmap with no intersection; return a miss return false; } static bool TestTile(u16* heightmap, int stride, int i, int j, const CVector3D& pos, const CVector3D& dir, CVector3D& isct) { u16 y00 = heightmap[i + j*stride]; u16 y10 = heightmap[i+1 + j*stride]; u16 y01 = heightmap[i + (j+1)*stride]; u16 y11 = heightmap[i+1 + (j+1)*stride]; CVector3D p00( i * TERRAIN_TILE_SIZE, y00 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE); CVector3D p10((i+1) * TERRAIN_TILE_SIZE, y10 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE); CVector3D p01( i * TERRAIN_TILE_SIZE, y01 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE); CVector3D p11((i+1) * TERRAIN_TILE_SIZE, y11 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE); int mid1 = y00+y11; int mid2 = y01+y10; int triDir = (mid1 < mid2); float dist = FLT_MAX; if (triDir) { if (RayTriIntersect(p00, p10, p01, pos, dir, dist) || // lower-left triangle RayTriIntersect(p11, p01, p10, pos, dir, dist)) // upper-right triangle { isct = pos + dir * dist; return true; } } else { if (RayTriIntersect(p00, p11, p01, pos, dir, dist) || // upper-left triangle RayTriIntersect(p00, p10, p11, pos, dir, dist)) // lower-right triangle { isct = pos + dir * dist; return true; } } return false; } bool CHFTracer::PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out) { // (TODO: This largely duplicates RayIntersect - some refactoring might be // nice in the future.) // General approach: // Given the ray defined by origin + dir * t, we increase t until it // enters the patch's bounding box. The x,z coordinates identify which // tile it is currently above/below. Do an intersection test vs the tile's // two triangles. If it doesn't hit, do a 2D line rasterisation to find // the next tiles the ray will pass through, and test each of them. // Start by jumping to the point where the ray enters the bounding box CBoundingBoxAligned bound = patch->GetWorldBounds(); float tmin, tmax; if (!bound.RayIntersect(origin, dir, tmin, tmax)) { // Ray missed patch; no intersection return false; } int heightmapStride = patch->m_Parent->GetVerticesPerSide(); // Get heightmap, offset to start at this patch u16* heightmap = patch->m_Parent->GetHeightMap() + patch->m_X * PATCH_SIZE + patch->m_Z * PATCH_SIZE * heightmapStride; // Get patch-space position of ray origin and bbox entry point CVector3D patchPos( patch->m_X * PATCH_SIZE * TERRAIN_TILE_SIZE, 0.0f, patch->m_Z * PATCH_SIZE * TERRAIN_TILE_SIZE); CVector3D originPatch = origin - patchPos; CVector3D entryPatch = originPatch + dir * tmin; // We want to do a simple 2D line rasterisation (with the 3D ray projected // down onto the Y plane). That will tell us which cells are intersected // in 2D dimensions, then we can do a more precise 3D intersection test. // // WLOG, assume the ray has direction dir.x > 0, dir.z > 0, and starts in // cell (i,j). The next cell intersecting the line must be either (i+1,j) // or (i,j+1). To tell which, just check whether the point (i+1,j+1) is // above or below the ray. Advance into that cell and repeat. // // (If the ray passes precisely through (i+1,j+1), we can pick either. // If the ray is parallel to Y, only the first cell matters, then we can // carry on rasterising in any direction (a bit of a waste of time but // should be extremely rare, and it's safe and simple).) // Work out which tile we're starting in - int i = Clamp(static_cast(entryPatch.X / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1); - int j = Clamp(static_cast(entryPatch.Z / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1); + int i = Clamp(static_cast(entryPatch.getX() / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1); + int j = Clamp(static_cast(entryPatch.getZ() / TERRAIN_TILE_SIZE), 0, static_cast(PATCH_SIZE) - 1); // Work out which direction the ray is going in - int di = (dir.X >= 0 ? 1 : 0); - int dj = (dir.Z >= 0 ? 1 : 0); + int di = (dir.getX() >= 0 ? 1 : 0); + int dj = (dir.getZ() >= 0 ? 1 : 0); do { CVector3D isct; if (TestTile(heightmap, heightmapStride, i, j, originPatch, dir, isct)) { if (out) *out = isct + patchPos; return true; } // Get the vertex between the two possible next cells float nx = (i + di) * (int)TERRAIN_TILE_SIZE; float nz = (j + dj) * (int)TERRAIN_TILE_SIZE; // Test which side of the ray the vertex is on, and advance into the // appropriate cell, using a test that works for all 4 combinations // of di,dj - float dot = dir.Z * (nx - originPatch.X) - dir.X * (nz - originPatch.Z); + float dot = dir.getZ() * (nx - originPatch.getX()) - dir.getX() * (nz - originPatch.getZ()); if ((di == dj) == (dot > 0.0f)) j += dj*2-1; else i += di*2-1; } while (i >= 0 && j >= 0 && i < PATCH_SIZE && j < PATCH_SIZE); // Ran off the edge of the patch, so no intersection return false; } Index: source/graphics/LightEnv.cpp =================================================================== --- source/graphics/LightEnv.cpp (revision 24796) +++ source/graphics/LightEnv.cpp (working copy) @@ -1,57 +1,57 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "graphics/LightEnv.h" #include "maths/MathUtil.h" - +#include "maths/Vector3D.h" CLightEnv::CLightEnv() : m_Elevation(DEGTORAD(45)), m_Rotation(DEGTORAD(315)), m_SunColor(1.5, 1.5, 1.5), m_AmbientColor(0x50/255.f, 0x60/255.f, 0x85/255.f), m_FogColor(0xCC/255.f, 0xCC/255.f, 0xE5/255.f), m_FogFactor(0.000f), m_FogMax(0.5f), m_Brightness(0.0f), m_Contrast(1.0f), m_Saturation(0.99f), m_Bloom(0.1999f) { CalculateSunDirection(); } void CLightEnv::SetElevation(float f) { m_Elevation = f; CalculateSunDirection(); } void CLightEnv::SetRotation(float f) { m_Rotation = f; CalculateSunDirection(); } void CLightEnv::CalculateSunDirection() { - m_SunDir.Y = -sinf(m_Elevation); - float scale = 1 + m_SunDir.Y; - m_SunDir.X = scale * sinf(m_Rotation); - m_SunDir.Z = scale * cosf(m_Rotation); + m_SunDir.Yref() = -sinf(m_Elevation); + float scale = 1 + m_SunDir.getY(); + m_SunDir.Xref() = scale * sinf(m_Rotation); + m_SunDir.Zref() = scale * cosf(m_Rotation); m_SunDir.Normalize(); } Index: source/graphics/LightEnv.h =================================================================== --- source/graphics/LightEnv.h (revision 24796) +++ source/graphics/LightEnv.h (working copy) @@ -1,125 +1,124 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CLightEnv, a class describing the current lights */ #ifndef INCLUDED_LIGHTENV #define INCLUDED_LIGHTENV -#include "graphics/Color.h" -#include "maths/MathUtil.h" -#include "maths/Vector3D.h" +#include "graphics/Color.h" // HAS to be here, used in structs class CMapWriter; class CMapReader; +class CVector3D; /** * Class CLightEnv: description of a lighting environment - contains all the * necessary parameters for representation of the lighting within a scenario */ class CLightEnv { public: - RGBColor m_SunColor; - RGBColor m_AmbientColor; - RGBColor m_FogColor; + RGBcolor m_SunColor; + RGBcolor m_AmbientColor; + RGBcolor m_FogColor; float m_FogFactor; float m_FogMax; float m_Brightness, m_Contrast, m_Saturation, m_Bloom; CLightEnv(); float GetElevation() const { return m_Elevation; } float GetRotation() const { return m_Rotation; } const CVector3D& GetSunDir() const { return m_SunDir; } void SetElevation(float f); void SetRotation(float f); /** * Calculate brightness of a point of a unit with the given normal vector, * for rendering with CPU lighting. * The resulting color contains both ambient and diffuse light. * To cope with sun overbrightness, the color is scaled by 0.5. * * @param normal normal vector (must have length 1) */ - RGBColor EvaluateUnitScaled(const CVector3D& normal) const + RGBcolor EvaluateUnitScaled(const CVector3D& normal) const { float dot = -normal.Dot(m_SunDir); - RGBColor color = m_AmbientColor; + RGBcolor color = m_AmbientColor; if (dot > 0) color += m_SunColor * dot; return color * 0.5f; } // Comparison operators bool operator==(const CLightEnv& o) const { return m_Elevation == o.m_Elevation && m_Rotation == o.m_Rotation && m_SunColor == o.m_SunColor && m_AmbientColor == o.m_AmbientColor && m_FogColor == o.m_FogColor && m_FogFactor == o.m_FogFactor && m_FogMax == o.m_FogMax && m_Brightness == o.m_Brightness && m_Contrast == o.m_Contrast && m_Saturation == o.m_Saturation && m_Bloom == o.m_Bloom; } bool operator!=(const CLightEnv& o) const { return !(*this == o); } private: friend class CMapWriter; friend class CMapReader; friend class CXMLReader; /** * Height of sun above the horizon, in radians. * For example, an elevation of M_PI/2 means the sun is straight up. */ float m_Elevation; /** * Direction of sun on the compass, in radians. * For example, a rotation of zero means the sun is in the direction (0,0,-1) * and a rotation of M_PI/2 means the sun is in the direction (1,0,0) (not taking * elevation into account). */ float m_Rotation; /** * Vector corresponding to m_Elevation and m_Rotation. * Updated by CalculateSunDirection. */ CVector3D m_SunDir; void CalculateSunDirection(); }; #endif // INCLUDED_LIGHTENV Index: source/graphics/MapReader.cpp =================================================================== --- source/graphics/MapReader.cpp (revision 24796) +++ source/graphics/MapReader.cpp (working copy) @@ -1,1603 +1,1603 @@ /* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "MapReader.h" #include "graphics/Camera.h" #include "graphics/CinemaManager.h" #include "graphics/Entity.h" #include "graphics/GameView.h" #include "graphics/MapGenerator.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Loader.h" #include "ps/LoaderThunks.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/PostprocManager.h" #include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptContext.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpCinemaManager.h" #include "simulation2/components/ICmpGarrisonHolder.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpTurretHolder.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" #include #if defined(_MSC_VER) && _MSC_VER > 1900 #pragma warning(disable: 4456) // Declaration hides previous local declaration. #pragma warning(disable: 4458) // Declaration hides class member. #endif CMapReader::CMapReader() : xml_reader(0), m_PatchesPerSide(0), m_MapGen(0) { cur_terrain_tex = 0; // important - resets generator state } // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful void CMapReader::LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities) { pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; pSimulation2 = pSimulation2_; pSimContext = pSimContext_; m_PlayerID = playerID_; m_SkipEntities = skipEntities; m_StartingCameraTarget = INVALID_ENTITY; m_ScriptSettings.init(cx.GetGeneralJSContext(), settings); filename_xml = pathname.ChangeExtension(L".xml"); // In some cases (particularly tests) we don't want to bother storing a large // mostly-empty .pmp file, so we let the XML file specify basic terrain instead. // If there's an .xml file and no .pmp, then we're probably in this XML-only mode only_xml = false; if (!VfsFileExists(pathname) && VfsFileExists(filename_xml)) { only_xml = true; } file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp if (!only_xml) { // [25ms] unpacker.Read(pathname, "PSMP"); file_format_version = unpacker.GetVersion(); } // check oldest supported version if (file_format_version < FILE_READ_VERSION) throw PSERROR_Game_World_MapLoadFailed("Could not load terrain file - too old version!"); // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); // reset post effects if (pPostproc) pPostproc->SetPostEffect(L"default"); // load map or script settings script if (settings.isUndefined()) RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50); else RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // unpack the data if (!only_xml) RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200); // read the corresponding XML file RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // read entities RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); } // LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful void CMapReader::LoadRandomMap(const CStrW& scriptFile, const ScriptContext& cx, JS::HandleValue settings, CTerrain *pTerrain_, WaterManager* pWaterMan_, SkyManager* pSkyMan_, CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_, CSimulation2 *pSimulation2_, int playerID_) { m_ScriptFile = scriptFile; pSimulation2 = pSimulation2_; pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL; m_ScriptSettings.init(cx.GetGeneralJSContext(), settings); pTerrain = pTerrain_; pLightEnv = pLightEnv_; pGameView = pGameView_; pWaterMan = pWaterMan_; pSkyMan = pSkyMan_; pCinema = pCinema_; pTrigMan = pTrigMan_; pPostproc = pPostproc_; m_PlayerID = playerID_; m_SkipEntities = false; m_StartingCameraTarget = INVALID_ENTITY; // delete all existing entities if (pSimulation2) pSimulation2->ResetState(); only_xml = false; // copy random map settings (before entity creation) RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50); // load player settings script (must be done before reading map) RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50); // load map generator with random map script RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 20000); // parse RMS results into terrain structure RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500); // parse RMS results into environment settings RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5); // parse RMS results into camera settings RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5); // apply terrain data to the world RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5); // parse RMS results into entities RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000); // apply misc data to the world RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5); // load map settings script (must be done after reading map) RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5); } // UnpackMap: unpack the given data from the raw data stream into local variables int CMapReader::UnpackMap() { return UnpackTerrain(); } // UnpackTerrain: unpack the terrain from the end of the input data stream // - data: map size, heightmap, list of textures used by map, texture tile assignments int CMapReader::UnpackTerrain() { // yield after this time is reached. balances increased progress bar // smoothness vs. slowing down loading. const double end_time = timer_Time() + 200e-3; // first call to generator (this is skipped after first call, // i.e. when the loop below was interrupted) if (cur_terrain_tex == 0) { m_PatchesPerSide = (ssize_t)unpacker.UnpackSize(); // unpack heightmap [600us] size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1; m_Heightmap.resize(SQR(verticesPerSide)); unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16)); // unpack # textures num_terrain_tex = unpacker.UnpackSize(); m_TerrainTextures.reserve(num_terrain_tex); } // unpack texture names; find handle for each texture. // interruptible. while (cur_terrain_tex < num_terrain_tex) { CStr texturename; unpacker.UnpackString(texturename); ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename); m_TerrainTextures.push_back(texentry); cur_terrain_tex++; LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex); } // unpack tile data [3ms] ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE; m_Tiles.resize(size_t(SQR(tilesPerSide))); unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size()); // reset generator state. cur_terrain_tex = 0; return 0; } int CMapReader::ApplyTerrainData() { if (m_PatchesPerSide == 0) { // we'll probably crash when trying to use this map later throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details."); } if (!only_xml) { // initialise the terrain pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]); // setup the textures on the minipatches STileDesc* tileptr = &m_Tiles[0]; for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index]; mp.Priority = tileptr->m_Priority; tileptr++; } } } } } CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); return 0; } // ApplyData: take all the input data, and rebuild the scene from it int CMapReader::ApplyData() { // copy over the lighting parameters if (pLightEnv) *pLightEnv = m_LightEnv; CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY); if (pGameView && cmpPlayerManager) { // Default to global camera (with constraints) pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus()); // TODO: Starting rotation? CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID)); if (cmpPlayer && cmpPlayer->HasStartingCamera()) { // Use player starting camera CFixedVector3D pos = cmpPlayer->GetStartingCameraPos(); - pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); + pGameView->ResetCameraTarget(CVector3D(pos.getX().ToFloat(), pos.getY().ToFloat(), pos.getZ().ToFloat())); } else if (m_StartingCameraTarget != INVALID_ENTITY) { // Point camera at entity CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget); if (cmpPosition) { CFixedVector3D pos = cmpPosition->GetPosition(); - pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat())); + pGameView->ResetCameraTarget(CVector3D(pos.getX().ToFloat(), pos.getY().ToFloat(), pos.getZ().ToFloat())); } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname) { VfsPath filename_xml = pathname.ChangeExtension(L".xml"); CXeromyces xmb_file; if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK) return PSRETURN_File_ReadFailed; // Define all the relevant elements used in the XML file #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(scenario); EL(scriptsettings); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(root.GetNodeName() == el_scenario); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_scriptsettings) { m_ScriptSettings = child.GetText(); } } return PSRETURN_OK; } void CMapSummaryReader::GetMapSettings(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) { ScriptRequest rq(scriptInterface); ScriptInterface::CreateObject(rq, ret); if (m_ScriptSettings.empty()) return; JS::RootedValue scriptSettingsVal(rq.cx); scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal); scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Holds various state data while reading maps, so that loading can be // interrupted (e.g. to update the progress display) then later resumed. class CXMLReader { NONCOPYABLE(CXMLReader); public: CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader) : m_MapReader(mapReader), nodes(NULL, 0, NULL) { Init(xml_filename); } CStr ReadScriptSettings(); // read everything except for entities void ReadXML(); // return semantics: see Loader.cpp!LoadFunc. int ProgressiveReadEntities(); private: CXeromyces xmb_file; CMapReader& m_MapReader; int el_entity; int el_tracks; int el_template, el_player; int el_position, el_orientation, el_obstruction; int el_garrison; int el_turrets; int el_actor; int at_x; int at_y; int at_z; int at_group, at_group2; int at_angle; int at_uid; int at_seed; int at_turret; XMBElementList nodes; // children of root // loop counters size_t node_idx; size_t entity_idx; // # entities+nonentities processed and total (for progress calc) int completed_jobs, total_jobs; // maximum used entity ID, so we can safely allocate new ones entity_id_t max_uid; void Init(const VfsPath& xml_filename); void ReadTerrain(XMBElement parent); void ReadEnvironment(XMBElement parent); void ReadCamera(XMBElement parent); void ReadPaths(XMBElement parent); void ReadTriggers(XMBElement parent); int ReadEntities(XMBElement parent, double end_time); }; void CXMLReader::Init(const VfsPath& xml_filename) { // must only assign once, so do it here node_idx = entity_idx = 0; if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK) throw PSERROR_Game_World_MapLoadFailed("Could not read map XML file!"); // define the elements and attributes that are frequently used in the XML file, // so we don't need to do lots of string construction and comparison when // reading the data. // (Needs to be synchronised with the list in CXMLReader - ugh) #define EL(x) el_##x = xmb_file.GetElementID(#x) #define AT(x) at_##x = xmb_file.GetAttributeID(#x) EL(entity); EL(tracks); EL(template); EL(player); EL(position); EL(garrison); EL(turrets); EL(orientation); EL(obstruction); EL(actor); AT(x); AT(y); AT(z); AT(group); AT(group2); AT(angle); AT(uid); AT(seed); AT(turret); #undef AT #undef EL XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); // find out total number of entities+nonentities // (used when calculating progress) completed_jobs = 0; total_jobs = 0; for (XMBElement node : nodes) total_jobs += node.GetChildNodes().size(); // Find the maximum entity ID, so we can safely allocate new IDs without conflicts max_uid = SYSTEM_ENTITY; XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities")); XERO_ITER_EL(ents, ent) { CStr uid = ent.GetAttributes().GetNamedItem(at_uid); max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt()); } } CStr CXMLReader::ReadScriptSettings() { XMBElement root = xmb_file.GetRoot(); ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario"); nodes = root.GetChildNodes(); XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings")); return settings.GetText(); } void CXMLReader::ReadTerrain(XMBElement parent) { #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) AT(patches); AT(texture); AT(priority); AT(height); #undef AT ssize_t patches = 9; CStr texture = "grass1_spring"; int priority = 0; u16 height = 16384; XERO_ITER_ATTR(parent, attr) { if (attr.Name == at_patches) patches = attr.Value.ToInt(); else if (attr.Name == at_texture) texture = attr.Value; else if (attr.Name == at_priority) priority = attr.Value.ToInt(); else if (attr.Name == at_height) height = (u16)attr.Value.ToInt(); } m_MapReader.m_PatchesPerSide = patches; // Load the texture ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled) CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture); m_MapReader.pTerrain->Initialize(patches, NULL); // Fill the heightmap u16* heightmap = m_MapReader.pTerrain->GetHeightMap(); ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide(); for (ssize_t i = 0; i < SQR(verticesPerSide); ++i) heightmap[i] = height; // Fill the texture map for (ssize_t pz = 0; pz < patches; ++pz) { for (ssize_t px = 0; px < patches; ++px) { CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail for (ssize_t z = 0; z < PATCH_SIZE; ++z) { for (ssize_t x = 0; x < PATCH_SIZE; ++x) { patch->m_MiniPatches[z][x].Tex = texentry; patch->m_MiniPatches[z][x].Priority = priority; } } } } } void CXMLReader::ReadEnvironment(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(posteffect); EL(skyset); EL(suncolor); EL(sunelevation); EL(sunrotation); EL(ambientcolor); EL(water); EL(waterbody); EL(type); EL(color); EL(tint); EL(height); EL(waviness); EL(murkiness); EL(windangle); EL(fog); EL(fogcolor); EL(fogfactor); EL(fogthickness); EL(postproc); EL(brightness); EL(contrast); EL(saturation); EL(bloom); AT(r); AT(g); AT(b); #undef AT #undef EL XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_skyset) { if (m_MapReader.pSkyMan) m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8()); } else if (element_name == el_suncolor) { - m_MapReader.m_LightEnv.m_SunColor = RGBColor( + m_MapReader.m_LightEnv.m_SunColor = RGBcolor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_sunelevation) { m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_sunrotation) { m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_ambientcolor) { - m_MapReader.m_LightEnv.m_AmbientColor = RGBColor( + m_MapReader.m_LightEnv.m_AmbientColor = RGBcolor( attrs.GetNamedItem(at_r).ToFloat(), attrs.GetNamedItem(at_g).ToFloat(), attrs.GetNamedItem(at_b).ToFloat()); } else if (element_name == el_fog) { XERO_ITER_EL(element, fog) { int fog_element_name = fog.GetNodeName(); if (fog_element_name == el_fogcolor) { XMBAttributeList fogAttributes = fog.GetAttributes(); - m_MapReader.m_LightEnv.m_FogColor = RGBColor( + m_MapReader.m_LightEnv.m_FogColor = RGBcolor( fogAttributes.GetNamedItem(at_r).ToFloat(), fogAttributes.GetNamedItem(at_g).ToFloat(), fogAttributes.GetNamedItem(at_b).ToFloat()); } else if (fog_element_name == el_fogfactor) { m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat(); } else if (fog_element_name == el_fogthickness) { m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat(); } } } else if (element_name == el_postproc) { XERO_ITER_EL(element, postproc) { int post_element_name = postproc.GetNodeName(); if (post_element_name == el_brightness) { m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat(); } else if (post_element_name == el_contrast) { m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat(); } else if (post_element_name == el_saturation) { m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat(); } else if (post_element_name == el_bloom) { m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat(); } else if (post_element_name == el_posteffect) { if (m_MapReader.pPostproc) m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8()); } } } else if (element_name == el_water) { XERO_ITER_EL(element, waterbody) { ENSURE(waterbody.GetNodeName() == el_waterbody); XERO_ITER_EL(waterbody, waterelement) { int water_element_name = waterelement.GetNodeName(); if (water_element_name == el_height) { CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY); ENSURE(cmpWaterManager); cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText())); continue; } // The rest are purely graphical effects, and should be ignored if // graphics are disabled if (!m_MapReader.pWaterMan) continue; if (water_element_name == el_type) { if (waterelement.GetText() == "default") m_MapReader.pWaterMan->m_WaterType = L"ocean"; else m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8(); } #define READ_COLOR(el, out) \ else if (water_element_name == el) \ { \ XMBAttributeList colorAttrs = waterelement.GetAttributes(); \ - out = CColor( \ + out = RGBAcolor( \ colorAttrs.GetNamedItem(at_r).ToFloat(), \ colorAttrs.GetNamedItem(at_g).ToFloat(), \ colorAttrs.GetNamedItem(at_b).ToFloat(), \ 1.f); \ } #define READ_FLOAT(el, out) \ else if (water_element_name == el) \ { \ out = waterelement.GetText().ToFloat(); \ } \ READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor) READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint) READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness) READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness) READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle) #undef READ_FLOAT #undef READ_COLOR else debug_warn(L"Invalid map XML data"); } } } else debug_warn(L"Invalid map XML data"); } m_MapReader.m_LightEnv.CalculateSunDirection(); } void CXMLReader::ReadCamera(XMBElement parent) { // defaults if we don't find player starting camera #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(declination); EL(rotation); EL(position); AT(angle); AT(x); AT(y); AT(z); #undef AT #undef EL float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f); CVector3D translation = CVector3D(100, 150, -100); XERO_ITER_EL(parent, element) { int element_name = element.GetNodeName(); XMBAttributeList attrs = element.GetAttributes(); if (element_name == el_declination) { declination = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_rotation) { rotation = attrs.GetNamedItem(at_angle).ToFloat(); } else if (element_name == el_position) { translation = CVector3D( attrs.GetNamedItem(at_x).ToFloat(), attrs.GetNamedItem(at_y).ToFloat(), attrs.GetNamedItem(at_z).ToFloat()); } else debug_warn(L"Invalid map XML data"); } if (m_MapReader.pGameView) { m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination); m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation); m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation); m_MapReader.pGameView->GetCamera()->UpdateFrustum(); } } void CXMLReader::ReadPaths(XMBElement parent) { #define EL(x) int el_##x = xmb_file.GetElementID(#x) #define AT(x) int at_##x = xmb_file.GetAttributeID(#x) EL(path); EL(rotation); EL(node); EL(position); EL(target); AT(name); AT(timescale); AT(orientation); AT(mode); AT(style); AT(x); AT(y); AT(z); AT(deltatime); #undef EL #undef AT CmpPtr cmpCinemaManager(*m_MapReader.pSimContext, SYSTEM_ENTITY); XERO_ITER_EL(parent, element) { int elementName = element.GetNodeName(); if (elementName == el_path) { CCinemaData pathData; XMBAttributeList attrs = element.GetAttributes(); CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8()); pathData.m_Name = pathName; pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale)); pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8(); pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8(); pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8(); TNSpline positionSpline, targetSpline; fixed lastPositionTime = fixed::Zero(); fixed lastTargetTime = fixed::Zero(); XERO_ITER_EL(element, pathChild) { elementName = pathChild.GetNodeName(); attrs = pathChild.GetAttributes(); // Load node data used for spline if (elementName == el_node) { lastPositionTime += fixed::FromString(attrs.GetNamedItem(at_deltatime)); lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime)); XERO_ITER_EL(pathChild, nodeChild) { elementName = nodeChild.GetNodeName(); attrs = nodeChild.GetAttributes(); if (elementName == el_position) { CFixedVector3D position(fixed::FromString(attrs.GetNamedItem(at_x)), fixed::FromString(attrs.GetNamedItem(at_y)), fixed::FromString(attrs.GetNamedItem(at_z))); positionSpline.AddNode(position, CFixedVector3D(), lastPositionTime); lastPositionTime = fixed::Zero(); } else if (elementName == el_rotation) { // TODO: Implement rotation slerp/spline as another object } else if (elementName == el_target) { CFixedVector3D targetPosition(fixed::FromString(attrs.GetNamedItem(at_x)), fixed::FromString(attrs.GetNamedItem(at_y)), fixed::FromString(attrs.GetNamedItem(at_z))); targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime); lastTargetTime = fixed::Zero(); } else LOGWARNING("Invalid cinematic element for node child"); } } else LOGWARNING("Invalid cinematic element for path child"); } // Construct cinema path with data gathered CCinemaPath path(pathData, positionSpline, targetSpline); if (path.Empty()) { LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8()); return; } if (!cmpCinemaManager) continue; if (!cmpCinemaManager->HasPath(pathName)) cmpCinemaManager->AddPath(path); else LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8()); } else LOGWARNING("Invalid path child with name '%s'", element.GetText()); } } void CXMLReader::ReadTriggers(XMBElement UNUSED(parent)) { } int CXMLReader::ReadEntities(XMBElement parent, double end_time) { XMBElementList entities = parent.GetChildNodes(); ENSURE(m_MapReader.pSimulation2); CSimulation2& sim = *m_MapReader.pSimulation2; CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY); while (entity_idx < entities.size()) { // all new state at this scope and below doesn't need to be // wrapped, since we only yield after a complete iteration. XMBElement entity = entities[entity_idx++]; ENSURE(entity.GetNodeName() == el_entity); XMBAttributeList attrs = entity.GetAttributes(); CStr uid = attrs.GetNamedItem(at_uid); ENSURE(!uid.empty()); int EntityUid = uid.ToInt(); CStrW TemplateName; int PlayerID = 0; std::vector Garrison; std::vector> Turrets; CFixedVector3D Position; CFixedVector3D Orientation; long Seed = -1; // Obstruction control groups. entity_id_t ControlGroup = INVALID_ENTITY; entity_id_t ControlGroup2 = INVALID_ENTITY; XERO_ITER_EL(entity, setting) { int element_name = setting.GetNodeName(); //