1 /** @file clientmaterial.cpp  Client-side material.
2  *
3  * @authors Copyright © 2009-2015 Daniel Swanson <danij@dengine.net>
4  * @authors Copyright © 2016-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "resource/clientmaterial.h"
22 
23 #include <QFlag>
24 #include <QtAlgorithms>
25 #include <de/Log>
26 #include <doomsday/console/cmd.h>
27 #include <doomsday/res/Textures>
28 #include <doomsday/world/Materials>
29 #include <doomsday/world/MaterialManifest>
30 
31 #include "dd_main.h"
32 #include "MaterialAnimator"
33 
34 using namespace de;
35 
DENG2_PIMPL_NOREF(ClientMaterial::Decoration)36 DENG2_PIMPL_NOREF(ClientMaterial::Decoration)
37 {
38     ClientMaterial *material = nullptr;  ///< Owning Material.
39     Vector2i patternSkip;          ///< Pattern skip intervals.
40     Vector2i patternOffset;        ///< Pattern skip interval offsets.
41 };
42 
Decoration(Vector2i const & patternSkip,Vector2i const & patternOffset)43 ClientMaterial::Decoration::Decoration(Vector2i const &patternSkip, Vector2i const &patternOffset)
44     : d(new Impl)
45 {
46     d->patternSkip   = patternSkip;
47     d->patternOffset = patternOffset;
48 }
49 
~Decoration()50 ClientMaterial::Decoration::~Decoration()
51 {
52     qDeleteAll(_stages);
53 }
54 
material()55 ClientMaterial &ClientMaterial::Decoration::material()
56 {
57     DENG2_ASSERT(d->material);
58     return *d->material;
59 }
60 
material() const61 ClientMaterial const &ClientMaterial::Decoration::material() const
62 {
63     DENG2_ASSERT(d->material);
64     return *d->material;
65 }
66 
setMaterial(ClientMaterial * newOwner)67 void ClientMaterial::Decoration::setMaterial(ClientMaterial *newOwner)
68 {
69     d->material = newOwner;
70 }
71 
patternSkip() const72 Vector2i const &ClientMaterial::Decoration::patternSkip() const
73 {
74     return d->patternSkip;
75 }
76 
patternOffset() const77 Vector2i const &ClientMaterial::Decoration::patternOffset() const
78 {
79     return d->patternOffset;
80 }
81 
stageCount() const82 int ClientMaterial::Decoration::stageCount() const
83 {
84     return _stages.count();
85 }
86 
stage(int index) const87 ClientMaterial::Decoration::Stage &ClientMaterial::Decoration::stage(int index) const
88 {
89     if (!stageCount())
90     {
91         /// @throw MissingStageError  No stages are defined.
92         throw MissingStageError("ClientMaterial::Decoration::stage", "Decoration has no stages");
93     }
94     return *_stages[de::wrap(index, 0, _stages.count())];
95 }
96 
describe() const97 String ClientMaterial::Decoration::describe() const
98 {
99     return "abstract Decoration";
100 }
101 
description() const102 String ClientMaterial::Decoration::description() const
103 {
104     int const numStages = stageCount();
105     String str = _E(b) + describe() + _E(.) + " (" + String::number(numStages) + " stage" + DENG2_PLURAL_S(numStages) + "):";
106     for (int i = 0; i < numStages; ++i)
107     {
108         str += String("\n  [%1] ").arg(i, 2) + _E(>) + stage(i).description() + _E(<);
109     }
110     return str;
111 }
112 
DENG2_PIMPL(ClientMaterial)113 DENG2_PIMPL(ClientMaterial)
114 {
115     AudioEnvironmentId audioEnvironment { AE_NONE };
116 
117     /// Decorations (owned), to be projected into the world (relative to a Surface).
118     QVector<Decoration *> decorations;
119 
120     /// Set of draw-context animators (owned).
121     QVector<MaterialAnimator *> animators;
122 
123     Impl(Public *i) : Base(i) {}
124 
125     ~Impl()
126     {
127         self().clearAllAnimators();
128         self().clearAllDecorations();
129     }
130 
131     MaterialAnimator *findAnimator(MaterialVariantSpec const &spec, bool canCreate = false)
132     {
133         for (auto iter = animators.constBegin(); iter != animators.constEnd(); ++iter)
134         {
135             if ((*iter)->variantSpec().compare(spec))
136             {
137                 return const_cast<MaterialAnimator *>(*iter);  // This will do fine.
138             }
139         }
140 
141         if (!canCreate) return nullptr;
142 
143         animators.append(new MaterialAnimator(self(), spec));
144         return animators.back();
145     }
146 };
147 
ClientMaterial(world::MaterialManifest & manifest)148 ClientMaterial::ClientMaterial(world::MaterialManifest &manifest)
149     : world::Material(manifest)
150     , d(new Impl(this))
151 {}
152 
~ClientMaterial()153 ClientMaterial::~ClientMaterial()
154 {}
155 
isAnimated() const156 bool ClientMaterial::isAnimated() const
157 {
158     if (Material::isAnimated())
159     {
160         return true;
161     }
162     for (const auto &decor : d->decorations)
163     {
164         if (decor->isAnimated())
165         {
166             return true;
167         }
168     }
169     return false;
170 }
171 
audioEnvironment() const172 AudioEnvironmentId ClientMaterial::audioEnvironment() const
173 {
174     return (isDrawable()? d->audioEnvironment : AE_NONE);
175 }
176 
setAudioEnvironment(AudioEnvironmentId newEnvironment)177 void ClientMaterial::setAudioEnvironment(AudioEnvironmentId newEnvironment)
178 {
179     d->audioEnvironment = newEnvironment;
180 }
181 
decorationCount() const182 int ClientMaterial::decorationCount() const
183 {
184     return d->decorations.count();
185 }
186 
forAllDecorations(const std::function<LoopResult (Decoration &)> & func) const187 LoopResult ClientMaterial::forAllDecorations(const std::function<LoopResult (Decoration &)> &func) const
188 {
189     for (Decoration *decor : d.getConst()->decorations)
190     {
191         if (auto result = func(*decor)) return result;
192     }
193     return LoopContinue;
194 }
195 
196 /// @todo Update client side MaterialAnimators?
addDecoration(Decoration * decor)197 void ClientMaterial::addDecoration(Decoration *decor)
198 {
199     if (!decor || d->decorations.contains(decor)) return;
200 
201     decor->setMaterial(this);
202     d->decorations.append(decor);
203 }
204 
clearAllDecorations()205 void ClientMaterial::clearAllDecorations()
206 {
207     qDeleteAll(d->decorations); d->decorations.clear();
208 
209     // Animators refer to decorations.
210     clearAllAnimators();
211 }
212 
animatorCount() const213 int ClientMaterial::animatorCount() const
214 {
215     return d.getConst()->animators.count();
216 }
217 
animator(int index) const218 MaterialAnimator &ClientMaterial::animator(int index) const
219 {
220     return *d.getConst()->animators[index];
221 }
222 
hasAnimator(MaterialVariantSpec const & spec)223 bool ClientMaterial::hasAnimator(MaterialVariantSpec const &spec)
224 {
225     return d->findAnimator(spec) != nullptr;
226 }
227 
getAnimator(MaterialVariantSpec const & spec)228 MaterialAnimator &ClientMaterial::getAnimator(MaterialVariantSpec const &spec)
229 {
230     return *d->findAnimator(spec, true/*create*/);
231 }
232 
forAllAnimators(const std::function<LoopResult (MaterialAnimator &)> & func) const233 LoopResult ClientMaterial::forAllAnimators(const std::function<LoopResult (MaterialAnimator &)> &func) const
234 {
235     for (MaterialAnimator *animator : d.getConst()->animators)
236     {
237         if (auto result = func(*animator)) return result;
238     }
239     return LoopContinue;
240 }
241 
clearAllAnimators()242 void ClientMaterial::clearAllAnimators()
243 {
244     qDeleteAll(d->animators);
245     d->animators.clear();
246 }
247 
find(de::Uri const & uri)248 ClientMaterial &ClientMaterial::find(de::Uri const &uri) // static
249 {
250     return world::Materials::get().material(uri).as<ClientMaterial>();
251 }
252 
description() const253 String ClientMaterial::description() const
254 {
255     String str = world::Material::description();
256 
257     str += _E(b) " x" + String::number(animatorCount()) + _E(.)
258          + _E(l) " EnvClass: \"" _E(.) + (audioEnvironment() == AE_NONE? "N/A" : S_AudioEnvironmentName(audioEnvironment())) + "\"";
259 
260     // Add the decoration config:
261     for (Decoration const *decor : d->decorations)
262     {
263         str += "\n" + decor->description();
264     }
265 
266     return str;
267 }
268