1 /** @file materials.cpp
2 *
3 * @authors Copyright (c) 2016-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * GPL: http://www.gnu.org/licenses/gpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 * Public License for more details. You should have received a copy of the GNU
15 * General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "doomsday/world/materials.h"
20 #include "doomsday/world/MaterialScheme"
21 #include "doomsday/world/MaterialManifest"
22 #include "doomsday/world/world.h"
23 #include "doomsday/resource/resources.h"
24
25 #include <de/memory.h>
26 #include <unordered_set>
27
28 using namespace de;
29
30 namespace world {
31
32 struct SchemeHashKey
33 {
34 String scheme;
35
SchemeHashKeyworld::SchemeHashKey36 SchemeHashKey(String const &s) : scheme(s) {}
operator ==world::SchemeHashKey37 bool operator == (SchemeHashKey const &other) const {
38 return !scheme.compare(other.scheme, Qt::CaseInsensitive);
39 }
40 };
41
qHash(SchemeHashKey const & key)42 uint qHash(SchemeHashKey const &key)
43 {
44 return key.scheme.at(1).toLower().unicode();
45 }
46
DENG2_PIMPL(Materials)47 DENG2_PIMPL(Materials)
48 , DENG2_OBSERVES(MaterialScheme, ManifestDefined)
49 , DENG2_OBSERVES(MaterialManifest, MaterialDerived)
50 , DENG2_OBSERVES(MaterialManifest, Deletion)
51 , DENG2_OBSERVES(Material, Deletion)
52 {
53 /// System subspace schemes containing the manifests/resources.
54 QHash<SchemeHashKey, MaterialScheme *> materialSchemes;
55 QList<MaterialScheme *> materialSchemeCreationOrder;
56
57 QList<Material *> materials; ///< From all schemes.
58 int materialManifestCount = 0; ///< Total number of material manifests (in all schemes).
59
60 std::unordered_set<Material *> animatedMaterialsSubset; ///< Subset of materials (not owned) that need to animate.
61
62 MaterialManifestGroups materialGroups;
63
64 uint materialManifestIdMapSize = 0;
65 MaterialManifest **materialManifestIdMap = nullptr; ///< Index with materialid_t-1
66
67 Impl(Public *i) : Base(i)
68 {
69 /// @note Order here defines the ambigious-URI search order.
70 createMaterialScheme("Sprites");
71 createMaterialScheme("Textures");
72 createMaterialScheme("Flats");
73 createMaterialScheme("System");
74 }
75
76 ~Impl()
77 {
78 self().clearAllMaterialGroups();
79 self().clearAllMaterialSchemes();
80 clearMaterialManifests();
81 }
82
83 void clearMaterialManifests()
84 {
85 qDeleteAll(materialSchemes);
86 materialSchemes.clear();
87 materialSchemeCreationOrder.clear();
88
89 // Clear the manifest index/map.
90 if (materialManifestIdMap)
91 {
92 M_Free(materialManifestIdMap);
93 materialManifestIdMap = 0;
94 materialManifestIdMapSize = 0;
95 }
96 materialManifestCount = 0;
97 }
98
99 void createMaterialScheme(String name)
100 {
101 DENG2_ASSERT(name.length() >= MaterialScheme::min_name_length);
102
103 // Create a new scheme.
104 MaterialScheme *newScheme = new MaterialScheme(name);
105 materialSchemes.insert(name, newScheme);
106 materialSchemeCreationOrder.append(newScheme);
107
108 // We want notification when a new manifest is defined in this scheme.
109 newScheme->audienceForManifestDefined += this;
110 }
111
112 /// Observes MaterialScheme ManifestDefined.
113 void materialSchemeManifestDefined(MaterialScheme & /*scheme*/, MaterialManifest &manifest)
114 {
115 /// Number of elements to block-allocate in the material index to material manifest map.
116 int const MANIFESTIDMAP_BLOCK_ALLOC = 32;
117
118 // We want notification when the manifest is derived to produce a material.
119 manifest.audienceForMaterialDerived += this;
120
121 // We want notification when the manifest is about to be deleted.
122 manifest.audienceForDeletion += this;
123
124 // Acquire a new unique identifier for the manifest.
125 materialid_t const id = materialid_t(++materialManifestCount); // 1-based.
126 manifest.setId(id);
127
128 // Add the new manifest to the id index/map.
129 if (materialManifestCount > int(materialManifestIdMapSize))
130 {
131 // Allocate more memory.
132 materialManifestIdMapSize += MANIFESTIDMAP_BLOCK_ALLOC;
133 materialManifestIdMap = (MaterialManifest **) M_Realloc(materialManifestIdMap, sizeof(*materialManifestIdMap) * materialManifestIdMapSize);
134 }
135 materialManifestIdMap[materialManifestCount - 1] = &manifest;
136 }
137
138 /// Observes MaterialManifest MaterialDerived.
139 void materialManifestMaterialDerived(MaterialManifest & /*manifest*/, Material &material)
140 {
141 // Include this new material in the scheme-agnostic list of instances.
142 materials.append(&material);
143
144 // We want notification when the material is about to be deleted.
145 material.audienceForDeletion() += this;
146 }
147
148 /// Observes MaterialManifest Deletion.
149 void materialManifestBeingDeleted(MaterialManifest const &manifest)
150 {
151 foreach (MaterialManifestGroup *group, materialGroups)
152 {
153 group->remove(const_cast<MaterialManifest *>(&manifest));
154 }
155 materialManifestIdMap[manifest.id() - 1 /*1-based*/] = 0;
156
157 // There will soon be one fewer manifest in the system.
158 materialManifestCount -= 1;
159 }
160
161 /// Observes Material Deletion.
162 void materialBeingDeleted(Material const &material)
163 {
164 Material *pMat = const_cast<Material *>(&material);
165 materials.removeOne(pMat);
166 animatedMaterialsSubset.erase(pMat);
167 }
168 };
169
Materials()170 Materials::Materials()
171 : d(new Impl(this))
172 {}
173
materialScheme(String name) const174 MaterialScheme &Materials::materialScheme(String name) const
175 {
176 if (!name.isEmpty())
177 {
178 auto found = d->materialSchemes.find(name);
179 if (found != d->materialSchemes.end()) return **found;
180 }
181 /// @throw UnknownSchemeError An unknown scheme was referenced.
182 throw Resources::UnknownSchemeError("Materials::materialScheme",
183 "No scheme found matching '" + name + "'");
184 }
185
isKnownMaterialScheme(String name) const186 bool Materials::isKnownMaterialScheme(String name) const
187 {
188 if (!name.isEmpty())
189 {
190 return d->materialSchemes.contains(name);
191 }
192 return false;
193 }
194
materialSchemeCount() const195 int Materials::materialSchemeCount() const
196 {
197 return d->materialSchemes.count();
198 }
199
forAllMaterialSchemes(std::function<LoopResult (MaterialScheme &)> func) const200 LoopResult Materials::forAllMaterialSchemes(std::function<LoopResult (MaterialScheme &)> func) const
201 {
202 for (MaterialScheme *scheme : d->materialSchemes)
203 {
204 if (auto result = func(*scheme)) return result;
205 }
206 return LoopContinue;
207 }
208
toMaterialManifest(materialid_t id) const209 MaterialManifest &Materials::toMaterialManifest(materialid_t id) const
210 {
211 duint32 idx = id - 1; // 1-based index.
212 if (idx < duint32(d->materialManifestCount))
213 {
214 if (d->materialManifestIdMap[idx])
215 {
216 return *d->materialManifestIdMap[idx];
217 }
218 // Internal bookeeping error.
219 DENG2_ASSERT(false);
220 }
221 /// @throw InvalidMaterialIdError The specified material id is invalid.
222 throw UnknownMaterialIdError("Materials::toMaterialManifest",
223 "Invalid material ID " + String::number(id) +
224 ", valid range " +
225 Rangei(1, d->materialManifestCount + 1).asText());
226 }
227
materialPtr(de::Uri const & path)228 Material *Materials::materialPtr(de::Uri const &path)
229 {
230 if (auto *manifest = materialManifestPtr(path)) return manifest->materialPtr();
231 return nullptr;
232 }
233
hasMaterialManifest(de::Uri const & path) const234 bool Materials::hasMaterialManifest(de::Uri const &path) const
235 {
236 return materialManifestPtr(path) != nullptr;
237 }
238
materialManifest(de::Uri const & uri) const239 MaterialManifest &Materials::materialManifest(de::Uri const &uri) const
240 {
241 if (auto *mm = materialManifestPtr(uri))
242 {
243 return *mm;
244 }
245 /// @throw MissingResourceManifestError Failed to locate a matching manifest.
246 throw Resources::MissingResourceManifestError("Materials::materialManifest",
247 "Failed to locate a manifest matching \"" + uri.asText() + "\"");
248 }
249
materialManifestPtr(de::Uri const & uri) const250 MaterialManifest *Materials::materialManifestPtr(de::Uri const &uri) const
251 {
252 // Does the user want a manifest in a specific scheme?
253 if (!uri.scheme().isEmpty())
254 {
255 MaterialScheme &specifiedScheme = materialScheme(uri.scheme());
256 return specifiedScheme.tryFind(uri.path());
257 }
258 else
259 {
260 // No, check each scheme in priority order.
261 foreach (MaterialScheme *scheme, d->materialSchemeCreationOrder)
262 {
263 if (auto *manifest = scheme->tryFind(uri.path()))
264 {
265 return manifest;
266 }
267 }
268 }
269 return nullptr;
270 }
271
materialCount() const272 dint Materials::materialCount() const
273 {
274 return d->materials.count();
275 }
276
forAllMaterials(const std::function<LoopResult (Material &)> & func) const277 LoopResult Materials::forAllMaterials(const std::function<LoopResult (Material &)> &func) const
278 {
279 for (Material *mat : d.getConst()->materials)
280 {
281 if (auto result = func(*mat))
282 {
283 return result;
284 }
285 }
286 return LoopContinue;
287 }
288
forAnimatedMaterials(const std::function<LoopResult (Material &)> & func) const289 LoopResult Materials::forAnimatedMaterials(const std::function<LoopResult (Material &)> &func) const
290 {
291 for (Material *mat : d.getConst()->animatedMaterialsSubset)
292 {
293 if (auto result = func(*mat))
294 {
295 return result;
296 }
297 }
298 return LoopContinue;
299 }
300
updateLookup()301 void Materials::updateLookup()
302 {
303 d->animatedMaterialsSubset.clear();
304 for (auto *mat : d->materials)
305 {
306 if (mat->isAnimated())
307 {
308 d->animatedMaterialsSubset.insert(mat);
309 }
310 }
311 }
312
newMaterialGroup()313 Materials::MaterialManifestGroup &Materials::newMaterialGroup()
314 {
315 // Allocating one by one is inefficient, but it doesn't really matter.
316 d->materialGroups.append(new MaterialManifestGroup());
317 return *d->materialGroups.back();
318 }
319
materialGroup(dint groupIdx) const320 Materials::MaterialManifestGroup &Materials::materialGroup(dint groupIdx) const
321 {
322 groupIdx -= 1; // 1-based index.
323 if (groupIdx >= 0 && groupIdx < d->materialGroups.count())
324 {
325 return *d->materialGroups[groupIdx];
326 }
327 /// @throw UnknownMaterialGroupError An unknown material group was referenced.
328 throw UnknownMaterialGroupError("Materials::materialGroup",
329 "Invalid group #" + String::number(groupIdx+1) + ", valid range " +
330 Rangeui(1, d->materialGroups.count() + 1).asText());
331 }
332
allMaterialGroups() const333 Materials::MaterialManifestGroups const &Materials::allMaterialGroups() const
334 {
335 return d->materialGroups;
336 }
337
clearAllMaterialGroups()338 void Materials::clearAllMaterialGroups()
339 {
340 qDeleteAll(d->materialGroups);
341 d->materialGroups.clear();
342 }
343
clearAllMaterialSchemes()344 void Materials::clearAllMaterialSchemes()
345 {
346 forAllMaterialSchemes([] (MaterialScheme &scheme) {
347 scheme.clear();
348 return LoopContinue;
349 });
350 DENG2_ASSERT(materialCount() == 0); // sanity check
351 }
352
get()353 Materials &Materials::get() // static
354 {
355 return World::get().materials();
356 }
357
358 } // namespace world
359