Skip to content

Commit b41320e

Browse files
committed
update map tile renderer
1 parent ad739de commit b41320e

File tree

8 files changed

+127
-147
lines changed

8 files changed

+127
-147
lines changed

libsave/include/SatisfactorySave/Pak/AssetExport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ namespace SatisfactorySave {
1414
public:
1515
std::shared_ptr<UObject> Object;
1616
std::vector<char> BinaryClassData;
17+
18+
template<typename T>
19+
std::shared_ptr<T> cast_object() {
20+
return std::dynamic_pointer_cast<T>(Object);
21+
}
1722
};
1823
} // namespace SatisfactorySave

libsave/include/SatisfactorySave/Pak/PakManager.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ namespace SatisfactorySave {
9696
return empty;
9797
}
9898

99+
std::optional<FPackageObjectIndex> tryGetScriptObjectIndex(const std::string& fullName) const {
100+
const auto it = globalIdByFullNameMap_.find(fullName);
101+
if (it != globalIdByFullNameMap_.end()) {
102+
return it->second;
103+
}
104+
return std::nullopt;
105+
}
106+
99107
protected:
100108
void init(const std::filesystem::path& gameDir);
101109

@@ -111,5 +119,6 @@ namespace SatisfactorySave {
111119
std::vector<FScriptObjectEntry> ScriptObjectEntries;
112120

113121
std::unordered_map<FPackageObjectIndex, FScriptObjectDesc> ScriptObjectByGlobalIdMap;
122+
std::unordered_map<std::string, FPackageObjectIndex> globalIdByFullNameMap_;
114123
};
115124
} // namespace SatisfactorySave

libsave/src/Pak/PakManager.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ void SatisfactorySave::PakManager::buildScriptObjectMap() {
116116
outer = entry.OuterIndex;
117117
}
118118
}
119+
120+
// Reverse lookup
121+
for (auto& [key, value] : ScriptObjectByGlobalIdMap) {
122+
if (globalIdByFullNameMap_.contains(value.FullName)) {
123+
throw std::runtime_error("ScriptObject FullName is not unique!");
124+
}
125+
globalIdByFullNameMap_[value.FullName] = key;
126+
}
119127
}
120128

121129
void SatisfactorySave::PakManager::cacheLatestPakNames(const std::optional<std::string>& modPrefix) {

map/resources/shaders/maptile_flat.frag

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#version 450
22

3-
uniform sampler2D texD;
3+
uniform sampler2D texBaseColor;
44

55
in vec3 fNormal;
66
in vec2 fTexCoord;
@@ -10,7 +10,7 @@ layout(location = 1) out vec4 fragOutNormal;
1010
layout(location = 2) out int fragOutId;
1111

1212
void main() {
13-
vec4 color = texture(texD, fTexCoord);
13+
vec4 color = texture(texBaseColor, fTexCoord);
1414

1515
fragOutAlbedo = vec4(color.rgb, 1.0f);
1616
fragOutNormal = vec4(fNormal, 0.0f);

map/resources/shaders/maptile_flat.geom

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ void main() {
1414

1515
for (int i = 0; i < 3; i++) {
1616
gl_Position = gl_in[i].gl_Position;
17-
fNormal = normal;
17+
fNormal = -normal;
1818
fTexCoord = vTexCoord[i];
1919
EmitVertex();
2020
}

map/resources/shaders/maptile_mesh.frag

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#version 450
22

3-
uniform sampler2D texD;
4-
uniform sampler2D texN;
3+
uniform sampler2D texBaseColor;
4+
uniform sampler2D texNormal;
55

66
in vec3 position;
77
in vec4 tangent_x;
@@ -13,7 +13,7 @@ layout(location = 1) out vec4 fragOutNormal;
1313
layout(location = 2) out int fragOutId;
1414

1515
void main() {
16-
vec4 color = texture(texD, texCoord);
16+
vec4 color = texture(texBaseColor, texCoord);
1717

1818
// Tangent space matrix
1919
vec3 T = normalize(tangent_x.xyz);
@@ -22,7 +22,7 @@ void main() {
2222
mat3 TBN = mat3(T, B, N);
2323

2424
// Normal map in tangent space
25-
vec2 normalXY = texture(texN, texCoord).rg * 2.0f - 1.0f;
25+
vec2 normalXY = texture(texNormal, texCoord).rg * 2.0f - 1.0f;
2626
float normalZ = sqrt(1.0f - normalXY.x * normalXY.x - normalXY.y * normalXY.y);
2727
vec3 normal = normalize(vec3(normalXY, normalZ));
2828

map/src/MapWindow/World/MapTileRenderer.cpp

Lines changed: 95 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,22 @@
11
#include "MapTileRenderer.h"
22

3-
#include <iostream>
4-
#include <regex>
5-
63
#include <glm/gtc/matrix_inverse.hpp>
74
#include <spdlog/spdlog.h>
85

6+
#include "SatisfactorySave/GameTypes/Arrays/ObjectArray.h"
7+
#include "SatisfactorySave/GameTypes/Arrays/StructArray.h"
8+
#include "SatisfactorySave/GameTypes/Properties/ArrayProperty.h"
9+
#include "SatisfactorySave/GameTypes/Properties/NameProperty.h"
10+
#include "SatisfactorySave/GameTypes/Properties/ObjectProperty.h"
11+
#include "SatisfactorySave/GameTypes/Properties/StructProperty.h"
12+
#include "SatisfactorySave/GameTypes/Structs/PropertyStruct.h"
913
#include "SatisfactorySave/GameTypes/UE/Engine/Engine/StaticMesh.h"
1014
#include "SatisfactorySave/GameTypes/UE/Engine/Engine/Texture2D.h"
15+
#include "SatisfactorySave/GameTypes/UE/Engine/GameFramework/Actor.h"
1116

12-
#include "../OpenGL/Texture.h"
17+
#include "../OpenGL/GlowlFactory.h"
1318
#include "Utils/ResourceUtils.h"
1419

15-
namespace {
16-
#if 0 // TODO update for new game version
17-
const SatisfactorySave::ObjectExport& getExportByClass(const SatisfactorySave::AssetFile& asset,
18-
const std::string& class_name) {
19-
// Validate asset has exactly one "class_name" export
20-
int exportIdx = -1;
21-
for (int i = 0; i < asset.exportMap().size(); i++) {
22-
const auto& exportEntry = asset.exportMap()[i];
23-
if (exportEntry.ClassIndex >= 0) {
24-
throw std::runtime_error("exportEntry.ClassIndex >= 0 not implemented!");
25-
}
26-
const auto& importEntry = asset.importMap().at(-exportEntry.ClassIndex - 1);
27-
if (importEntry.ObjectName == class_name) {
28-
if (exportIdx >= 0) {
29-
throw std::runtime_error("Found more than one " + class_name + " in asset!");
30-
}
31-
exportIdx = i;
32-
}
33-
}
34-
if (exportIdx < 0) {
35-
throw std::runtime_error("No " + class_name + " found in asset!");
36-
}
37-
return asset.exportMap()[exportIdx];
38-
}
39-
#endif
40-
} // namespace
41-
4220
Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Configuration>& config,
4321
const std::shared_ptr<SatisfactorySave::PakManager>& pakManager) {
4422

@@ -48,112 +26,102 @@ Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Config
4826
faceNormalsSetting_ = BoolSetting::create("Use face normals", false);
4927
config->registerSetting(faceNormalsSetting_);
5028

51-
const std::vector<float> pos_x = {
52-
-254000.0f,
53-
-152400.0f,
54-
-50800.0f,
55-
50800.0f,
56-
152400.0f,
57-
254000.0f,
58-
355600.0f,
59-
};
60-
const std::vector<float> pos_y = {
61-
254000.0f,
62-
152400.0f,
63-
50800.0f,
64-
-50800.0f,
65-
-152400.0f,
66-
-254000.0f,
67-
};
68-
69-
#if 0 // TODO update for new game version
7029
if (pakManager != nullptr) {
7130
try {
31+
if (!pakManager->containsAssetFilename("Game/FactoryGame/Map/GameLevel01/Persistent_Level.umap")) {
32+
throw std::runtime_error("Missing Persistent_Level asset!");
33+
}
34+
auto mapAsset = pakManager->readAsset("Game/FactoryGame/Map/GameLevel01/Persistent_Level.umap");
7235

73-
const std::regex regex(
74-
"Game/FactoryGame/Map/GameLevel01/Tile_X([0-9]+)_Y([0-9]+)LOD/SM_(Landscape|PROXY_Tile).*\\.uasset");
75-
std::smatch match;
36+
auto WPHLODClassIndex = pakManager->tryGetScriptObjectIndex("/Script/Engine/WorldPartitionHLOD");
37+
if (!WPHLODClassIndex.has_value()) {
38+
throw std::runtime_error("ClassIndex missing for /Script/Engine/WorldPartitionHLOD!");
39+
}
7640

77-
for (const auto& filename : pakManager->getAllAssetFilenames()) {
78-
if (std::regex_match(filename, match, regex)) {
79-
if (match.size() != 4) {
80-
throw std::runtime_error("Filename regex error!");
81-
}
82-
int tileX = std::stoi(match[1].str());
83-
int tileY = std::stoi(match[2].str());
84-
bool isLandscape = match[3].str() == "Landscape";
85-
bool isNewPosFormat = tileX > 4 || (tileX > 1 && tileY > 3); // Changed tiles with Update 6.
86-
87-
SatisfactorySave::AssetFile asset = pakManager->readAsset(filename);
88-
89-
const auto& staticMeshExportEntry = getExportByClass(asset, "StaticMesh");
90-
91-
// Serialize StaticMesh
92-
asset.seek(staticMeshExportEntry.SerialOffset);
93-
SatisfactorySave::UStaticMesh staticMesh;
94-
asset << staticMesh;
95-
96-
// Textures
97-
std::string texname = std::regex_replace(filename, std::regex("SM_"), "T_");
98-
std::string texname_d;
99-
std::string texname_n;
100-
if (isLandscape) {
101-
texname_d = std::regex_replace(texname, std::regex(".uasset"), "_D.uasset");
102-
texname_n = std::regex_replace(texname, std::regex(".uasset"), "_N.uasset");
103-
} else {
104-
texname_d = std::regex_replace(texname, std::regex(".uasset"), "_Diffuse.uasset");
105-
texname_n = std::regex_replace(texname, std::regex(".uasset"), "_Normal.uasset");
106-
}
107-
if (!pakManager->containsAssetFilename(texname_d) ||
108-
!pakManager->containsAssetFilename(texname_n)) {
109-
throw std::runtime_error("Texture not found!");
110-
}
41+
for (std::size_t i = 0; i < mapAsset.exportMap().size(); i++) {
42+
const auto& exp = mapAsset.exportMap()[i];
43+
if (exp.ClassIndex != WPHLODClassIndex.value()) {
44+
continue;
45+
}
11146

112-
// Diffuse texture
113-
SatisfactorySave::AssetFile assetTexD = pakManager->readAsset(texname_d);
114-
const auto& texDExportEntry = getExportByClass(assetTexD, "Texture2D");
47+
const auto WPHLOD = mapAsset.getExportObjectByIdx(i);
48+
const auto WPHLOD_obj = WPHLOD->cast_object<s::AActor>();
11549

116-
assetTexD.seek(texDExportEntry.SerialOffset);
117-
SatisfactorySave::UTexture2D texD;
118-
assetTexD << texD;
50+
// TODO WPHLOD_obj->ActorLabel;
11951

120-
// Normal texture
121-
SatisfactorySave::AssetFile assetTexN = pakManager->readAsset(texname_n);
122-
const auto& texNExportEntry = getExportByClass(assetTexN, "Texture2D");
52+
const auto& instCompProp = WPHLOD_obj->Properties.get<s::ArrayProperty>("InstanceComponents");
53+
const auto instCompArray = std::dynamic_pointer_cast<s::ObjectArray>(instCompProp.Value);
54+
if (instCompArray == nullptr) {
55+
throw std::runtime_error("InstanceComponents array missing!");
56+
}
12357

124-
assetTexN.seek(texNExportEntry.SerialOffset);
125-
SatisfactorySave::UTexture2D texN;
126-
assetTexN << texN;
58+
for (const auto& instCompRef : instCompArray->Values) {
59+
if (instCompRef.pakValue() <= 0) {
60+
continue;
61+
}
12762

128-
// Render data
129-
MapTileData mapTile;
130-
mapTile.mesh = makeGlowlMesh(staticMesh);
131-
mapTile.texD = makeOpenGLTexture(texD);
132-
mapTile.texN = makeOpenGLTexture(texN);
133-
mapTile.tileX = tileX;
134-
mapTile.tileY = tileY;
135-
136-
// Precalculate matrices
137-
float x = 0.0f;
138-
float y = 0.0f;
139-
if (isLandscape || !isNewPosFormat) {
140-
x = pos_x[tileX];
141-
y = pos_y[tileY];
63+
const auto instCompExp = mapAsset.getExportObjectByIdx(instCompRef.pakValue() - 1);
64+
const auto& meshRef = instCompExp->Object->Properties.get<s::ObjectProperty>("StaticMesh").Value;
65+
if (meshRef.pakValue() <= 0) {
66+
continue;
67+
}
68+
const auto meshExp = mapAsset.getExportObjectByIdx(meshRef.pakValue() - 1);
69+
const auto meshObj = meshExp->cast_object<s::UStaticMesh>();
70+
if (meshObj == nullptr) {
71+
throw std::runtime_error("Invalid UStaticMesh!");
14272
}
14373

144-
const float offset = isLandscape ? -50800.0f : 0.0f;
74+
MapTileData mapTile;
75+
mapTile.mesh = makeGlowlMesh(*meshObj);
76+
77+
mapTile.modelMx = glm::scale(glm::mat4(1.0f), glm::vec3(0.01f));
78+
mapTile.normalMx = glm::inverseTranspose(glm::mat3(mapTile.modelMx));
14579

146-
glm::vec3 position_((x + offset) * 0.01f, -(y + offset) * 0.01f, 0.0f);
147-
glm::vec4 rotation_(0.0f, 0.0f, 0.0f, 1.0f);
148-
glm::vec3 scale_(0.01f);
80+
const auto& matProp = meshObj->Properties.get<s::ArrayProperty>("StaticMaterials");
81+
const auto matArray = std::dynamic_pointer_cast<s::StructArray>(matProp.Value);
82+
if (matArray == nullptr || matArray->Values.size() != 1) {
83+
throw std::runtime_error("Invalid/Unexpected StaticMaterials array!");
84+
}
85+
const auto matStruct = std::dynamic_pointer_cast<s::PropertyStruct>(matArray->Values[0]);
86+
if (matStruct == nullptr) {
87+
throw std::runtime_error("Invalid StaticMaterials PropertyStruct!");
88+
}
89+
const auto& matInterfaceRef = matStruct->Data.get<s::ObjectProperty>("MaterialInterface").Value;
90+
if (matInterfaceRef.pakValue() <= 0) {
91+
throw std::runtime_error("Invalid MaterialInterface reference!");
92+
}
14993

150-
const auto translation = glm::translate(glm::mat4(1.0f), position_);
151-
const auto rotation =
152-
glm::mat4_cast(glm::quat(-rotation_.w, rotation_.x, -rotation_.y, rotation_.z));
153-
const auto scale = glm::scale(glm::mat4(1.0f), scale_);
94+
const auto matExp = mapAsset.getExportObjectByIdx(matInterfaceRef.pakValue() - 1);
95+
const auto& texParamProp =
96+
matExp->Object->Properties.get<s::ArrayProperty>("TextureParameterValues");
97+
const auto texParamArray = std::dynamic_pointer_cast<s::StructArray>(texParamProp.Value);
98+
if (texParamArray == nullptr || texParamArray->Values.size() != 3) {
99+
throw std::runtime_error("Invalid TextureParameterValues array!");
100+
}
154101

155-
mapTile.modelMx = translation * rotation * scale;
156-
mapTile.normalMx = glm::inverseTranspose(glm::mat3(mapTile.modelMx));
102+
const auto texParam0Struct = std::dynamic_pointer_cast<s::PropertyStruct>(texParamArray->Values[0]);
103+
const auto& texParam0Info = texParam0Struct->Data.get<s::StructProperty>("ParameterInfo");
104+
const auto texParam0InfoStruct = std::dynamic_pointer_cast<s::PropertyStruct>(texParam0Info.Value);
105+
const auto name0 = texParam0InfoStruct->Data.get<s::NameProperty>("Name").Value.toString();
106+
if (name0 != "BaseColorTexture") {
107+
throw std::runtime_error("Expected BaseColorTexture!");
108+
}
109+
const auto& texParam0ValueRef =
110+
texParam0Struct->Data.get<s::ObjectProperty>("ParameterValue").Value;
111+
const auto tex0Exp = mapAsset.getExportObjectByIdx(texParam0ValueRef.pakValue() - 1);
112+
mapTile.texBaseColor = std::make_unique<OGLTexture2D>(*tex0Exp->cast_object<s::UTexture2D>());
113+
114+
const auto texParam1Struct = std::dynamic_pointer_cast<s::PropertyStruct>(texParamArray->Values[1]);
115+
const auto& texParam1Info = texParam1Struct->Data.get<s::StructProperty>("ParameterInfo");
116+
const auto texParam1InfoStruct = std::dynamic_pointer_cast<s::PropertyStruct>(texParam1Info.Value);
117+
const auto name1 = texParam1InfoStruct->Data.get<s::NameProperty>("Name").Value.toString();
118+
if (name1 != "NormalTexture") {
119+
throw std::runtime_error("Expected NormalTexture!");
120+
}
121+
const auto& texParam1ValueRef =
122+
texParam1Struct->Data.get<s::ObjectProperty>("ParameterValue").Value;
123+
const auto tex1Exp = mapAsset.getExportObjectByIdx(texParam1ValueRef.pakValue() - 1);
124+
mapTile.texNormal = std::make_unique<OGLTexture2D>(*tex1Exp->cast_object<s::UTexture2D>());
157125

158126
mapTiles_.push_back(std::move(mapTile));
159127
}
@@ -162,7 +130,6 @@ Satisfactory3DMap::MapTileRenderer::MapTileRenderer(const std::shared_ptr<Config
162130
spdlog::warn("Error loading Pak file: {}", ex.what());
163131
}
164132
}
165-
#endif
166133

167134
try {
168135
meshShader_ = std::make_unique<glowl::GLSLProgram>(glowl::GLSLProgram::ShaderSourceList{
@@ -196,12 +163,12 @@ void Satisfactory3DMap::MapTileRenderer::render(const glm::mat4& projMx, const g
196163
shader->setUniform("normalMx", tile.normalMx);
197164

198165
glActiveTexture(GL_TEXTURE0);
199-
glBindTexture(GL_TEXTURE_2D, tile.texD);
200-
shader->setUniform("texD", 0);
166+
tile.texBaseColor->bindTexture();
167+
shader->setUniform("texBaseColor", 0);
201168

202169
glActiveTexture(GL_TEXTURE1);
203-
glBindTexture(GL_TEXTURE_2D, tile.texN);
204-
shader->setUniform("texN", 1);
170+
tile.texNormal->bindTexture();
171+
shader->setUniform("texNormal", 1);
205172

206173
tile.mesh->draw();
207174
}

0 commit comments

Comments
 (0)