Skip to content

Commit 958e680

Browse files
committed
read and render water planes from game files
1 parent 09df7fb commit 958e680

8 files changed

Lines changed: 215 additions & 1 deletion

File tree

libsave/include/SatisfactorySave/GameTypes/UE/CoreUObject/Serialization/AsyncLoading2.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,18 @@ namespace SatisfactorySave {
4747
Null,
4848
};
4949

50+
inline explicit FPackageObjectIndex(EType InType, uint64_t InId)
51+
: TypeAndId((static_cast<uint64_t>(InType) << TypeShift) | InId) {}
52+
5053
public:
54+
FPackageObjectIndex() = default;
55+
56+
inline static FPackageObjectIndex FromPackageImportRef(const FPackageImportReference& PackageImportRef) {
57+
uint64_t Id = static_cast<uint64_t>(PackageImportRef.GetImportedPackageIndex()) << 32 |
58+
PackageImportRef.GetImportedPublicExportHashIndex();
59+
return FPackageObjectIndex(PackageImport, Id);
60+
}
61+
5162
[[nodiscard]] inline bool IsNull() const {
5263
return TypeAndId == Invalid;
5364
}

libsave/include/SatisfactorySave/Pak/AssetFile.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <algorithm>
34
#include <memory>
45
#include <optional>
56
#include <string>
@@ -77,6 +78,26 @@ namespace SatisfactorySave {
7778
std::shared_ptr<AssetExport> getExportObjectByName(const std::string& name);
7879
std::shared_ptr<AssetExport> getExportObjectByHash(uint64_t publicExportHash);
7980

81+
[[nodiscard]] std::optional<FPackageObjectIndex> getPackageObjectIndexByImportedPackageNameAndHash(
82+
const FName& importedPackageName, uint64_t importedPublicExportHash) const {
83+
const auto& names = importedPackageNames();
84+
const auto& hashes = importedPublicExportHashes();
85+
86+
const auto it_name = std::ranges::find(names, importedPackageName);
87+
if (it_name == names.end()) {
88+
return std::nullopt;
89+
}
90+
const auto nameIdx = std::ranges::distance(names.begin(), it_name);
91+
92+
const auto it_hash = std::ranges::find(hashes, importedPublicExportHash);
93+
if (it_hash == hashes.end()) {
94+
return std::nullopt;
95+
}
96+
const auto hashIdx = std::ranges::distance(hashes.begin(), it_hash);
97+
98+
return FPackageObjectIndex::FromPackageImportRef(FPackageImportReference(nameIdx, hashIdx));
99+
}
100+
80101
IStreamArchive& ubulkAr() {
81102
if (ubulk_ar_ == nullptr) {
82103
throw std::runtime_error("No ubulk data!");
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#version 450
2+
3+
in vec3 position;
4+
in vec4 tangent_x;
5+
in vec4 tangent_z;
6+
in vec2 texCoord;
7+
8+
layout(location = 0) out vec4 fragOutAlbedo;
9+
layout(location = 1) out vec4 fragOutNormal;
10+
layout(location = 2) out int fragOutId;
11+
12+
void main() {
13+
vec4 color = vec4(0.0f, 0.5f, 1.0f, 1.0f);
14+
fragOutAlbedo = color;
15+
fragOutNormal = vec4(normalize(tangent_z.xyz), 0.0f);
16+
fragOutId = -2;
17+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#version 450
2+
3+
layout(std430, binding = 0) readonly buffer Transformations { mat4 transformations[]; };
4+
5+
uniform mat4 projMx;
6+
uniform mat4 viewMx;
7+
uniform mat4 modelMx;
8+
uniform mat3 normalMx;
9+
10+
layout(location = 0) in vec3 in_position;
11+
layout(location = 1) in vec4 in_tangent_x;
12+
layout(location = 2) in vec4 in_tangent_z;
13+
layout(location = 3) in vec2 in_texcoord0;
14+
layout(location = 4) in vec2 in_texcoord1;
15+
16+
out vec3 position;
17+
out vec4 tangent_x;
18+
out vec4 tangent_z;
19+
out vec2 texCoord;
20+
21+
void main() {
22+
vec4 world_pos = transformations[gl_InstanceID] * modelMx * vec4(in_position, 1.0f);
23+
gl_Position = projMx * viewMx * world_pos;
24+
position = world_pos.xyz / world_pos.w;
25+
tangent_x = vec4(mat3(modelMx) * in_tangent_x.xyz, in_tangent_x.w);
26+
tangent_z = vec4(normalMx * in_tangent_z.xyz, in_tangent_z.w);
27+
texCoord = in_texcoord0; // Seems to fit best for color and normals.
28+
}

map/src/MapWindow/World/MapLODReader.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "SatisfactorySave/GameTypes/Properties/ObjectProperty.h"
1010
#include "SatisfactorySave/GameTypes/Properties/StructProperty.h"
1111
#include "SatisfactorySave/GameTypes/Structs/PropertyStruct.h"
12+
#include "SatisfactorySave/GameTypes/Structs/RotatorStruct.h"
13+
#include "SatisfactorySave/GameTypes/Structs/VectorStruct.h"
1214
#include "SatisfactorySave/GameTypes/UE/Engine/GameFramework/Actor.h"
1315

1416
Satisfactory3DMap::MapLODReader::MapLODReader(const std::shared_ptr<s::PakManager>& pakManager) {
@@ -24,8 +26,56 @@ Satisfactory3DMap::MapLODReader::MapLODReader(const std::shared_ptr<s::PakManage
2426
throw std::runtime_error("ClassIndex missing for /Script/Engine/WorldPartitionHLOD!");
2527
}
2628

29+
// Water Mesh
30+
if (!pakManager->containsAssetFilename("Game/FactoryGame/World/Environment/Water/Mesh/WaterPlane.uasset")) {
31+
throw std::runtime_error("Missing WaterPlane asset!");
32+
}
33+
auto waterPlaneAsset =
34+
pakManager->readAsset("Game/FactoryGame/World/Environment/Water/Mesh/WaterPlane.uasset");
35+
auto waterPlaneMeshExp = waterPlaneAsset.getExportObjectByName("WaterPlane");
36+
if (waterPlaneMeshExp == nullptr) {
37+
throw std::runtime_error("Invalid WaterPlane asset!");
38+
}
39+
waterPlaneMesh_ = waterPlaneMeshExp->cast_object<s::UStaticMesh>();
40+
if (waterPlaneMesh_ == nullptr) {
41+
throw std::runtime_error("Invalid WaterPlane mesh!");
42+
}
43+
44+
// BP_Water instances
45+
if (!pakManager->containsAssetFilename("Game/FactoryGame/World/Environment/Water/BP/BP_Water.uasset")) {
46+
throw std::runtime_error("Missing BP_Water asset!");
47+
}
48+
auto waterAsset = pakManager->readAsset("Game/FactoryGame/World/Environment/Water/BP/BP_Water.uasset");
49+
const auto bpWaterCIndices = waterAsset.getExportMapIndicesByName("BP_Water_C");
50+
if (bpWaterCIndices.size() != 1) {
51+
throw std::runtime_error("Missing BP_Water_C object!");
52+
}
53+
const auto bpWaterHash = waterAsset.exportMap()[bpWaterCIndices[0]].PublicExportHash;
54+
const auto waterClassIdx = mapAsset.getPackageObjectIndexByImportedPackageNameAndHash(
55+
s::FName("/Game/FactoryGame/World/Environment/Water/BP/BP_Water"), bpWaterHash);
56+
57+
// BP_TranslucentWater instances
58+
if (!pakManager->containsAssetFilename(
59+
"Game/FactoryGame/World/Environment/Water/Translucent/BP_TranslucentWater.uasset")) {
60+
throw std::runtime_error("Missing BP_TranslucentWater asset!");
61+
}
62+
auto tWaterAsset = pakManager->readAsset(
63+
"Game/FactoryGame/World/Environment/Water/Translucent/BP_TranslucentWater.uasset");
64+
const auto bpTWaterCIndices = tWaterAsset.getExportMapIndicesByName("BP_TranslucentWater_C");
65+
if (bpTWaterCIndices.size() != 1) {
66+
throw std::runtime_error("Missing BP_TranslucentWater_C object!");
67+
}
68+
const auto bpTWaterHash = tWaterAsset.exportMap()[bpTWaterCIndices[0]].PublicExportHash;
69+
const auto tWaterClassIdx = mapAsset.getPackageObjectIndexByImportedPackageNameAndHash(
70+
s::FName("/Game/FactoryGame/World/Environment/Water/Translucent/BP_TranslucentWater"), bpTWaterHash);
71+
2772
for (std::size_t i = 0; i < mapAsset.exportMap().size(); i++) {
2873
const auto& exp = mapAsset.exportMap()[i];
74+
75+
if (exp.ClassIndex == waterClassIdx || exp.ClassIndex == tWaterClassIdx) {
76+
readWaterInstance(mapAsset, i);
77+
}
78+
2979
if (exp.ClassIndex != WPHLODClassIndex.value()) {
3080
continue;
3181
}
@@ -118,3 +168,25 @@ Satisfactory3DMap::MapLODReader::MapLODReader(const std::shared_ptr<s::PakManage
118168
}
119169
}
120170
}
171+
172+
void Satisfactory3DMap::MapLODReader::readWaterInstance(s::AssetFile& mapAsset, std::size_t i) {
173+
const auto& waterExp = mapAsset.getExportObjectByIdx(i);
174+
const auto& surfaceRef = waterExp->Object->Properties.get<s::ObjectProperty>("WaterSurface").Value;
175+
if (surfaceRef.pakValue() < 0) {
176+
throw std::runtime_error("Water surface reference < 0 not implemented!");
177+
}
178+
const auto& surfaceExp = mapAsset.getExportObjectByIdx(surfaceRef.pakValue() - 1);
179+
const auto properties = surfaceExp->Object->Properties;
180+
WaterInstance inst;
181+
try {
182+
inst.location = properties.get<s::StructProperty>("RelativeLocation").get<s::VectorStruct>().Data;
183+
} catch (const std::exception& ex) {}
184+
try {
185+
inst.rotation = properties.get<s::StructProperty>("RelativeRotation").get<s::RotatorStruct>().Data;
186+
} catch (const std::exception& ex) {}
187+
try {
188+
inst.scale = properties.get<s::StructProperty>("RelativeScale3D").get<s::VectorStruct>().Data;
189+
} catch (const std::exception& ex) {}
190+
191+
waterInstances_.push_back(inst);
192+
}

map/src/MapWindow/World/MapLODReader.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <memory>
44

5+
#include "SatisfactorySave/GameTypes/UE/Core/Math/Rotator.h"
6+
#include "SatisfactorySave/GameTypes/UE/Core/Math/Vector.h"
57
#include "SatisfactorySave/GameTypes/UE/Engine/Engine/StaticMesh.h"
68
#include "SatisfactorySave/GameTypes/UE/Engine/Engine/Texture2D.h"
79
#include "SatisfactorySave/Pak/PakManager.h"
@@ -19,14 +21,32 @@ namespace Satisfactory3DMap {
1921
std::size_t instanceComponentId;
2022
};
2123

24+
struct WaterInstance {
25+
s::FVector location;
26+
s::FRotator rotation;
27+
s::FVector scale;
28+
};
29+
2230
explicit MapLODReader(const std::shared_ptr<s::PakManager>& pakManager);
2331
~MapLODReader() = default;
2432

25-
[[nodiscard]] const std::vector<MapLODMesh>& meshes() const {
33+
[[nodiscard]] inline const std::vector<MapLODMesh>& meshes() const {
2634
return meshes_;
2735
}
2836

37+
[[nodiscard]] inline const auto& waterPlaneMesh() const {
38+
return waterPlaneMesh_;
39+
}
40+
41+
[[nodiscard]] inline const auto& waterInstances() const {
42+
return waterInstances_;
43+
}
44+
2945
private:
46+
void readWaterInstance(s::AssetFile& mapAsset, std::size_t i);
47+
3048
std::vector<MapLODMesh> meshes_;
49+
std::shared_ptr<s::UStaticMesh> waterPlaneMesh_;
50+
std::vector<WaterInstance> waterInstances_;
3151
};
3252
} // namespace Satisfactory3DMap

map/src/MapWindow/World/MapTileRenderer.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "../OpenGL/GlowlFactory.h"
77
#include "MapLODReader.h"
8+
#include "Utils/GLMUtil.h"
89
#include "Utils/ResourceUtils.h"
910

1011
Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Configuration>& config,
@@ -16,6 +17,9 @@ Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Config
1617
faceNormalsSetting_ = BoolSetting::create("Use face normals", false);
1718
config->registerSetting(faceNormalsSetting_);
1819

20+
showWaterSetting_ = BoolSetting::create("Show Water", true);
21+
config->registerSetting(showWaterSetting_);
22+
1923
MapLODReader LODReader(pakManager);
2024
for (const auto& mesh : LODReader.meshes()) {
2125
MapTileData mapTile;
@@ -30,6 +34,24 @@ Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Config
3034
mapTiles_.push_back(std::move(mapTile));
3135
}
3236

37+
waterMesh_ = makeGlowlMesh(*LODReader.waterPlaneMesh());
38+
waterModelMx_ = glm::scale(glm::mat4(1.0f), glm::vec3(0.01f));
39+
waterNormalMx_ = glm::inverseTranspose(glm::mat3(waterModelMx_));
40+
41+
std::vector<glm::mat4> waterTransformations;
42+
waterTransformations.reserve(LODReader.waterInstances().size());
43+
for (const auto& inst : LODReader.waterInstances()) {
44+
const glm::mat4 translationMx = glm::translate(glm::mat4(1.0f), glmCast(inst.location) * glm::vec3(0.01f));
45+
const glm::mat4 rotationMx = glm::mat4_cast(glmCast(inst.rotation.Quaternion()));
46+
const glm::mat4 scaleMx = glm::scale(glm::mat4(1.0f), glmCast(inst.scale));
47+
glm::mat4 modelMx = translationMx * rotationMx * scaleMx;
48+
waterTransformations.push_back(modelMx);
49+
}
50+
51+
waterTransformationBuffer_ = std::make_unique<glowl::BufferObject>(GL_SHADER_STORAGE_BUFFER,
52+
glm::value_ptr(waterTransformations.front()), waterTransformations.size() * sizeof(glm::mat4), GL_DYNAMIC_DRAW);
53+
waterNumInstances_ = waterTransformations.size();
54+
3355
try {
3456
meshShader_ = std::make_unique<glowl::GLSLProgram>(glowl::GLSLProgram::ShaderSourceList{
3557
{glowl::GLSLProgram::ShaderType::Vertex, getStringResource("shaders/maptile_mesh.vert")},
@@ -39,6 +61,10 @@ Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Config
3961
{glowl::GLSLProgram::ShaderType::Vertex, getStringResource("shaders/maptile_flat.vert")},
4062
{glowl::GLSLProgram::ShaderType::Geometry, getStringResource("shaders/maptile_flat.geom")},
4163
{glowl::GLSLProgram::ShaderType::Fragment, getStringResource("shaders/maptile_flat.frag")}});
64+
65+
waterShader_ = std::make_unique<glowl::GLSLProgram>(glowl::GLSLProgram::ShaderSourceList{
66+
{glowl::GLSLProgram::ShaderType::Vertex, getStringResource("shaders/maptile_water.vert")},
67+
{glowl::GLSLProgram::ShaderType::Fragment, getStringResource("shaders/maptile_water.frag")}});
4268
} catch (glowl::GLSLProgramException& e) {
4369
spdlog::error(e.what());
4470
}
@@ -72,6 +98,18 @@ void Satisfactory3DMap::MapTileRenderer::render(const glm::mat4& projMx, const g
7298
tile.mesh->draw();
7399
}
74100

101+
if (showWaterSetting_->getVal()) {
102+
waterTransformationBuffer_->bind(0);
103+
104+
waterShader_->use();
105+
waterShader_->setUniform("projMx", projMx);
106+
waterShader_->setUniform("viewMx", viewMx);
107+
waterShader_->setUniform("modelMx", waterModelMx_);
108+
waterShader_->setUniform("normalMx", waterNormalMx_);
109+
110+
waterMesh_->draw(waterNumInstances_);
111+
}
112+
75113
glUseProgram(0);
76114

77115
if (wireframeSetting_->getVal()) {

map/src/MapWindow/World/MapTileRenderer.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,16 @@ namespace Satisfactory3DMap {
3333

3434
std::unique_ptr<glowl::GLSLProgram> meshShader_;
3535
std::unique_ptr<glowl::GLSLProgram> flatShader_;
36+
std::unique_ptr<glowl::GLSLProgram> waterShader_;
3637
std::vector<MapTileData> mapTiles_;
38+
std::shared_ptr<glowl::Mesh> waterMesh_;
39+
glm::mat4 waterModelMx_;
40+
glm::mat3 waterNormalMx_;
41+
std::unique_ptr<glowl::BufferObject> waterTransformationBuffer_;
42+
int waterNumInstances_ = 0;
3743

3844
std::shared_ptr<BoolSetting> wireframeSetting_;
3945
std::shared_ptr<BoolSetting> faceNormalsSetting_;
46+
std::shared_ptr<BoolSetting> showWaterSetting_;
4047
};
4148
} // namespace Satisfactory3DMap

0 commit comments

Comments
 (0)