/
ScaledModelExporter.cpp
234 lines (179 loc) · 5.81 KB
/
ScaledModelExporter.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include "ScaledModelExporter.h"
#include <fstream>
#include "i18n.h"
#include "itextstream.h"
#include "igame.h"
#include "ientity.h"
#include "iscenegraph.h"
#include "os/fs.h"
#include "os/path.h"
#include <boost/format.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <regex>
namespace map
{
void ScaledModelExporter::initialise()
{
_mapEventConn = GlobalMapModule().signal_mapEvent().connect(
sigc::mem_fun(*this, &ScaledModelExporter::onMapEvent)
);
}
void ScaledModelExporter::shutdown()
{
_mapEventConn.disconnect();
}
void ScaledModelExporter::onMapEvent(IMap::MapEvent ev)
{
if (ev == IMap::MapSaving)
{
saveScaledModels();
}
}
void ScaledModelExporter::saveScaledModels()
{
// Find any models with modified scale
GlobalSceneGraph().foreachNode([&](const scene::INodePtr& node)
{
if (Node_isEntity(node))
{
// Find any model nodes below that one
model::ModelNodePtr childModel;
node->foreachNode([&](const scene::INodePtr& child)
{
model::ModelNodePtr candidate = Node_getModel(child);
if (candidate && candidate->hasModifiedScale())
{
childModel = candidate;
}
return true;
});
// Do we have a model with modified scale?
if (childModel)
{
saveScaledModel(node, childModel);
}
}
return true;
});
}
void ScaledModelExporter::saveScaledModel(const scene::INodePtr& entityNode, const model::ModelNodePtr& modelNode)
{
// Request the default format from the preferences
std::string outputExtension = registry::getValue<std::string>(RKEY_DEFAULT_MODEL_EXPORT_FORMAT);
boost::algorithm::to_lower(outputExtension);
rMessage() << "Model format used for export: " << outputExtension <<
" (this can be changed in the preferences)" << std::endl;
// Save the scaled model as ASE
model::IModelExporterPtr exporter = GlobalModelFormatManager().getExporter(outputExtension);
if (!exporter)
{
rError() << "Cannot save out scaled models, no exporter found." << std::endl;
return;
}
// Push the geometry into the exporter
model::IModel& model = modelNode->getIModel();
for (int s = 0; s < model.getSurfaceCount(); ++s)
{
const model::IModelSurface& surface = model.getSurface(s);
exporter->addSurface(surface);
}
// Get the current model file name
Entity* entity = Node_getEntity(entityNode);
fs::path targetPath = getWritableGamePath();
fs::path modelPath = "models/map_specific/scaled";
// Ensure the output path exists
targetPath /= modelPath;
fs::create_directories(targetPath);
fs::path modelKeyValue = entity->getKeyValue("model");
rMessage() << "Exporting scaled model for entity " << entity->getKeyValue("name") <<
": " << modelKeyValue.string() << std::endl;
// Generate a new model name, à la "haystack_scaled3.ase"
std::string modelFilename = generateUniqueModelFilename(targetPath, modelKeyValue, outputExtension);
// assemble the new model spawnarg
modelPath /= modelFilename;
// Export to temporary file and rename afterwards
exportModel(exporter, targetPath, modelFilename);
std::string newModelKey = os::standardPath(modelPath.string());
entity->setKeyValue("model", newModelKey);
rMessage() << "Done exporting scaled model, new model key is " << newModelKey << std::endl;
}
void ScaledModelExporter::exportModel(const model::IModelExporterPtr& exporter,
const fs::path& modelOutputPath, const std::string& modelFilename)
{
fs::path targetPath = modelOutputPath;
// Open a temporary file (leading underscore)
fs::path tempFile = targetPath / ("_" + modelFilename);
std::ofstream::openmode mode = std::ofstream::out;
if (exporter->getFileFormat() == model::IModelExporter::Format::Binary)
{
mode |= std::ios::binary;
}
std::ofstream tempStream(tempFile.string().c_str(), mode);
if (!tempStream.is_open())
{
throw std::runtime_error(
(boost::format(_("Cannot open file for writing: %s")) % tempFile.string()).str());
}
exporter->exportToStream(tempStream);
tempStream.close();
// The full OS path to the output file
targetPath /= modelFilename;
if (fs::exists(targetPath))
{
try
{
fs::remove(targetPath);
}
catch (fs::filesystem_error& e)
{
rError() << "Could not remove the file " << targetPath.string() << std::endl
<< e.what() << std::endl;
throw std::runtime_error(
(boost::format(_("Could not remove the file: %s")) % tempFile.string()).str());
}
}
try
{
fs::rename(tempFile, targetPath);
}
catch (fs::filesystem_error& e)
{
rError() << "Could not rename the temporary file " << tempFile.string() << std::endl
<< e.what() << std::endl;
throw std::runtime_error(
(boost::format(_("Could not rename the temporary file: %s")) % tempFile.string()).str());
}
}
std::string ScaledModelExporter::generateUniqueModelFilename(
const fs::path& outputPath, const fs::path& modelPath, const std::string& outputExtension)
{
std::string modelFilename = modelPath.filename().string();
// Remove any previously existing "_scaledN" suffix
std::regex expr("_scaled\\d+\\.");
modelFilename = std::regex_replace(modelFilename, expr, ".");
std::string filenameNoExt = os::replaceExtension(modelFilename, "");
int i = 0;
while (++i < INT_MAX)
{
std::string generatedFilename = (boost::format("%s_scaled%d.%s") % filenameNoExt % i % outputExtension).str();
fs::path targetFile = outputPath / generatedFilename;
if (!fs::exists(targetFile))
{
return generatedFilename; // break the loop
}
}
throw new std::runtime_error("Could not generate a unique model filename.");
}
fs::path ScaledModelExporter::getWritableGamePath()
{
fs::path targetPath = GlobalGameManager().getModPath();
if (targetPath.empty())
{
targetPath = GlobalGameManager().getUserEnginePath();
rMessage() << "No mod base path found, falling back to user engine path to save model file: " <<
targetPath.string() << std::endl;
}
return targetPath;
}
}