/src/ogre/OgreMain/src/OgreRibbonTrail.cpp
Line | Count | Source |
1 | | /* |
2 | | ----------------------------------------------------------------------------- |
3 | | This source file is part of OGRE |
4 | | (Object-oriented Graphics Rendering Engine) |
5 | | For the latest info, see http://www.ogre3d.org/ |
6 | | |
7 | | Copyright (c) 2000-2014 Torus Knot Software Ltd |
8 | | |
9 | | Permission is hereby granted, free of charge, to any person obtaining a copy |
10 | | of this software and associated documentation files (the "Software"), to deal |
11 | | in the Software without restriction, including without limitation the rights |
12 | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
13 | | copies of the Software, and to permit persons to whom the Software is |
14 | | furnished to do so, subject to the following conditions: |
15 | | |
16 | | The above copyright notice and this permission notice shall be included in |
17 | | all copies or substantial portions of the Software. |
18 | | |
19 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
22 | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
24 | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
25 | | THE SOFTWARE. |
26 | | ----------------------------------------------------------------------------- |
27 | | */ |
28 | | #include "OgreStableHeaders.h" |
29 | | #include "OgreRibbonTrail.h" |
30 | | #include "OgreController.h" |
31 | | |
32 | | namespace Ogre |
33 | | { |
34 | | namespace |
35 | | { |
36 | | /** Controller value for pass frame time to RibbonTrail |
37 | | */ |
38 | | class _OgrePrivate TimeControllerValue : public ControllerValue<float> |
39 | | { |
40 | | protected: |
41 | | RibbonTrail* mTrail; |
42 | | public: |
43 | 0 | TimeControllerValue(RibbonTrail* r) { mTrail = r; } |
44 | | |
45 | 0 | float getValue(void) const override { return 0; }// not a source |
46 | 0 | void setValue(float value) override { mTrail->_timeUpdate(value); } |
47 | | }; |
48 | | } |
49 | | //----------------------------------------------------------------------- |
50 | | //----------------------------------------------------------------------- |
51 | | RibbonTrail::RibbonTrail(const String& name, size_t maxElements, |
52 | | size_t numberOfChains, bool useTextureCoords, bool useColours) |
53 | 0 | :BillboardChain(name, maxElements, 0, useTextureCoords, useColours, true), |
54 | 0 | mFadeController(0) |
55 | 0 | { |
56 | 0 | setTrailLength(100); |
57 | 0 | setNumberOfChains(numberOfChains); |
58 | 0 | mTimeControllerValue = ControllerValueRealPtr(OGRE_NEW TimeControllerValue(this)); |
59 | | |
60 | | // use V as varying texture coord, so we can use 1D textures to 'smear' |
61 | 0 | setTextureCoordDirection(TCD_V); |
62 | | |
63 | |
|
64 | 0 | } |
65 | | //----------------------------------------------------------------------- |
66 | | RibbonTrail::~RibbonTrail() |
67 | 0 | { |
68 | | // Detach listeners |
69 | 0 | for (auto & i : mNodeList) |
70 | 0 | { |
71 | 0 | i->setListener(0); |
72 | 0 | } |
73 | |
|
74 | 0 | if (mFadeController) |
75 | 0 | { |
76 | | // destroy controller |
77 | 0 | ControllerManager::getSingleton().destroyController(mFadeController); |
78 | 0 | } |
79 | |
|
80 | 0 | } |
81 | | //----------------------------------------------------------------------- |
82 | | void RibbonTrail::addNode(Node* n) |
83 | 0 | { |
84 | 0 | if (mNodeList.size() == mChainCount) |
85 | 0 | { |
86 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, |
87 | 0 | mName + " cannot monitor any more nodes, chain count exceeded", |
88 | 0 | "RibbonTrail::addNode"); |
89 | 0 | } |
90 | 0 | if (n->getListener()) |
91 | 0 | { |
92 | 0 | OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, |
93 | 0 | mName + " cannot monitor node " + n->getName() + " since it already has a listener.", |
94 | 0 | "RibbonTrail::addNode"); |
95 | 0 | } |
96 | | |
97 | | // get chain index |
98 | 0 | size_t chainIndex = mFreeChains.back(); |
99 | 0 | mFreeChains.pop_back(); |
100 | 0 | mNodeToChainSegment.push_back(chainIndex); |
101 | 0 | mNodeToSegMap[n] = chainIndex; |
102 | | |
103 | | // initialise the chain |
104 | 0 | resetTrail(chainIndex, n); |
105 | |
|
106 | 0 | mNodeList.push_back(n); |
107 | 0 | n->setListener(this); |
108 | |
|
109 | 0 | } |
110 | | //----------------------------------------------------------------------- |
111 | | size_t RibbonTrail::getChainIndexForNode(const Node* n) |
112 | 0 | { |
113 | 0 | NodeToChainSegmentMap::const_iterator i = mNodeToSegMap.find(n); |
114 | 0 | if (i == mNodeToSegMap.end()) |
115 | 0 | { |
116 | 0 | OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, |
117 | 0 | "This node is not being tracked", "RibbonTrail::getChainIndexForNode"); |
118 | 0 | } |
119 | 0 | return i->second; |
120 | 0 | } |
121 | | //----------------------------------------------------------------------- |
122 | | void RibbonTrail::removeNode(const Node* n) |
123 | 0 | { |
124 | 0 | NodeList::iterator i = std::find(mNodeList.begin(), mNodeList.end(), n); |
125 | 0 | if (i != mNodeList.end()) |
126 | 0 | { |
127 | | // also get matching chain segment |
128 | 0 | size_t index = std::distance(mNodeList.begin(), i); |
129 | 0 | IndexVector::iterator mi = mNodeToChainSegment.begin(); |
130 | 0 | std::advance(mi, index); |
131 | 0 | size_t chainIndex = *mi; |
132 | 0 | BillboardChain::clearChain(chainIndex); |
133 | | // mark as free now |
134 | 0 | mFreeChains.push_back(chainIndex); |
135 | 0 | (*i)->setListener(0); |
136 | 0 | mNodeList.erase(i); |
137 | 0 | mNodeToChainSegment.erase(mi); |
138 | 0 | mNodeToSegMap.erase(mNodeToSegMap.find(n)); |
139 | |
|
140 | 0 | } |
141 | 0 | } |
142 | | //----------------------------------------------------------------------- |
143 | | RibbonTrail::NodeIterator |
144 | | RibbonTrail::getNodeIterator(void) const |
145 | 0 | { |
146 | 0 | return NodeIterator(mNodeList.begin(), mNodeList.end()); |
147 | 0 | } |
148 | | //----------------------------------------------------------------------- |
149 | | void RibbonTrail::setTrailLength(Real len) |
150 | 0 | { |
151 | 0 | OgreAssert(len > 0, "invalid value"); |
152 | 0 | mTrailLength = len; |
153 | 0 | mElemLength = mTrailLength / mMaxElementsPerChain; |
154 | 0 | mSquaredElemLength = mElemLength * mElemLength; |
155 | 0 | } |
156 | | //----------------------------------------------------------------------- |
157 | | void RibbonTrail::setMaxChainElements(size_t maxElements) |
158 | 0 | { |
159 | 0 | BillboardChain::setMaxChainElements(maxElements); |
160 | 0 | mElemLength = mTrailLength / mMaxElementsPerChain; |
161 | 0 | mSquaredElemLength = mElemLength * mElemLength; |
162 | |
|
163 | 0 | resetAllTrails(); |
164 | 0 | } |
165 | | //----------------------------------------------------------------------- |
166 | | void RibbonTrail::setNumberOfChains(size_t numChains) |
167 | 0 | { |
168 | 0 | OgreAssert(numChains >= mNodeList.size(), |
169 | 0 | "Can't shrink the number of chains less than number of tracking nodes"); |
170 | 0 | size_t oldChains = getNumberOfChains(); |
171 | |
|
172 | 0 | BillboardChain::setNumberOfChains(numChains); |
173 | |
|
174 | 0 | mInitialColour.resize(numChains, ColourValue::White); |
175 | 0 | mDeltaColour.resize(numChains, ColourValue::ZERO); |
176 | 0 | mInitialWidth.resize(numChains, 10); |
177 | 0 | mDeltaWidth.resize(numChains, 0); |
178 | |
|
179 | 0 | if (oldChains > numChains) |
180 | 0 | { |
181 | | // remove free chains |
182 | 0 | for (IndexVector::iterator i = mFreeChains.begin(); i != mFreeChains.end();) |
183 | 0 | { |
184 | 0 | if (*i >= numChains) |
185 | 0 | i = mFreeChains.erase(i); |
186 | 0 | else |
187 | 0 | ++i; |
188 | 0 | } |
189 | 0 | } |
190 | 0 | else if (oldChains < numChains) |
191 | 0 | { |
192 | | // add new chains, at front to preserve previous ordering (pop_back) |
193 | 0 | for (size_t i = oldChains; i < numChains; ++i) |
194 | 0 | mFreeChains.insert(mFreeChains.begin(), i); |
195 | 0 | } |
196 | 0 | resetAllTrails(); |
197 | 0 | } |
198 | | //----------------------------------------------------------------------- |
199 | | void RibbonTrail::clearChain(size_t chainIndex) |
200 | 0 | { |
201 | 0 | BillboardChain::clearChain(chainIndex); |
202 | | |
203 | | // Reset if we are tracking for this chain |
204 | 0 | IndexVector::iterator i = std::find(mNodeToChainSegment.begin(), mNodeToChainSegment.end(), chainIndex); |
205 | 0 | if (i != mNodeToChainSegment.end()) |
206 | 0 | { |
207 | 0 | size_t nodeIndex = std::distance(mNodeToChainSegment.begin(), i); |
208 | 0 | resetTrail(*i, mNodeList[nodeIndex]); |
209 | 0 | } |
210 | 0 | } |
211 | | //----------------------------------------------------------------------- |
212 | | void RibbonTrail::setInitialColour(size_t chainIndex, const ColourValue& col) |
213 | 0 | { |
214 | 0 | setInitialColour(chainIndex, col.r, col.g, col.b, col.a); |
215 | 0 | } |
216 | | //----------------------------------------------------------------------- |
217 | | void RibbonTrail::setInitialColour(size_t chainIndex, float r, float g, float b, float a) |
218 | 0 | { |
219 | 0 | mInitialColour.at(chainIndex) = ColourValue(r, g, b, a); |
220 | 0 | } |
221 | | //----------------------------------------------------------------------- |
222 | | void RibbonTrail::setInitialWidth(size_t chainIndex, Real width) |
223 | 0 | { |
224 | 0 | mInitialWidth.at(chainIndex) = width; |
225 | 0 | } |
226 | | //----------------------------------------------------------------------- |
227 | | void RibbonTrail::setColourChange(size_t chainIndex, const ColourValue& valuePerSecond) |
228 | 0 | { |
229 | 0 | setColourChange(chainIndex, |
230 | 0 | valuePerSecond.r, valuePerSecond.g, valuePerSecond.b, valuePerSecond.a); |
231 | 0 | } |
232 | | //----------------------------------------------------------------------- |
233 | | void RibbonTrail::setColourChange(size_t chainIndex, float r, float g, float b, float a) |
234 | 0 | { |
235 | 0 | mDeltaColour.at(chainIndex) = ColourValue(r, g, b, a); |
236 | 0 | manageController(); |
237 | 0 | } |
238 | | //----------------------------------------------------------------------- |
239 | | void RibbonTrail::setWidthChange(size_t chainIndex, Real widthDeltaPerSecond) |
240 | 0 | { |
241 | 0 | mDeltaWidth.at(chainIndex) = widthDeltaPerSecond; |
242 | 0 | manageController(); |
243 | 0 | } |
244 | | //----------------------------------------------------------------------- |
245 | | void RibbonTrail::manageController(void) |
246 | 0 | { |
247 | 0 | bool needController = false; |
248 | 0 | for (size_t i = 0; i < mChainCount; ++i) |
249 | 0 | { |
250 | 0 | if (mDeltaWidth[i] != 0 || mDeltaColour[i] != ColourValue::ZERO) |
251 | 0 | { |
252 | 0 | needController = true; |
253 | 0 | break; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | if (!mFadeController && needController) |
257 | 0 | { |
258 | | // Set up fading via frame time controller |
259 | 0 | ControllerManager& mgr = ControllerManager::getSingleton(); |
260 | 0 | mFadeController = mgr.createFrameTimePassthroughController(mTimeControllerValue); |
261 | 0 | } |
262 | 0 | else if (mFadeController && !needController) |
263 | 0 | { |
264 | | // destroy controller |
265 | 0 | ControllerManager::getSingleton().destroyController(mFadeController); |
266 | 0 | mFadeController = 0; |
267 | 0 | } |
268 | |
|
269 | 0 | } |
270 | | //----------------------------------------------------------------------- |
271 | | void RibbonTrail::nodeUpdated(const Node* node) |
272 | 0 | { |
273 | 0 | size_t chainIndex = getChainIndexForNode(node); |
274 | 0 | updateTrail(chainIndex, node); |
275 | 0 | } |
276 | | //----------------------------------------------------------------------- |
277 | | void RibbonTrail::nodeDestroyed(const Node* node) |
278 | 0 | { |
279 | 0 | removeNode(node); |
280 | |
|
281 | 0 | } |
282 | | //----------------------------------------------------------------------- |
283 | | void RibbonTrail::updateTrail(size_t index, const Node* node) |
284 | 0 | { |
285 | | // Repeat this entire process if chain is stretched beyond its natural length |
286 | 0 | bool done = false; |
287 | 0 | while (!done) |
288 | 0 | { |
289 | | // Node has changed somehow, we're only interested in the derived position |
290 | 0 | ChainSegment& seg = mChainSegmentList[index]; |
291 | 0 | Element& headElem = mChainElementList[seg.start + seg.head]; |
292 | 0 | size_t nextElemIdx = seg.head + 1; |
293 | | // wrap |
294 | 0 | if (nextElemIdx == mMaxElementsPerChain) |
295 | 0 | nextElemIdx = 0; |
296 | 0 | Element& nextElem = mChainElementList[seg.start + nextElemIdx]; |
297 | | |
298 | | // Vary the head elem, but bake new version if that exceeds element len |
299 | 0 | Vector3 newPos = node->_getDerivedPosition(); |
300 | 0 | if (mParentNode) |
301 | 0 | { |
302 | | // Transform position to ourself space |
303 | 0 | newPos = mParentNode->convertWorldToLocalPosition(newPos); |
304 | 0 | } |
305 | 0 | Vector3 diff = newPos - nextElem.position; |
306 | 0 | Real sqlen = diff.squaredLength(); |
307 | 0 | if (sqlen >= mSquaredElemLength) |
308 | 0 | { |
309 | | // Move existing head to mElemLength |
310 | 0 | Vector3 scaledDiff = diff * (mElemLength / Math::Sqrt(sqlen)); |
311 | 0 | headElem.position = nextElem.position + scaledDiff; |
312 | | // Add a new element to be the new head |
313 | 0 | Element newElem( newPos, mInitialWidth[index], 0.0f, |
314 | 0 | mInitialColour[index], node->_getDerivedOrientation() ); |
315 | 0 | addChainElement(index, newElem); |
316 | | // alter diff to represent new head size |
317 | 0 | diff = newPos - headElem.position; |
318 | | // check whether another step is needed or not |
319 | 0 | if (diff.squaredLength() <= mSquaredElemLength) |
320 | 0 | done = true; |
321 | |
|
322 | 0 | } |
323 | 0 | else |
324 | 0 | { |
325 | | // Extend existing head |
326 | 0 | headElem.position = newPos; |
327 | 0 | done = true; |
328 | 0 | } |
329 | | |
330 | | // Is this segment full? |
331 | 0 | if ((seg.tail + 1) % mMaxElementsPerChain == seg.head) |
332 | 0 | { |
333 | | // If so, shrink tail gradually to match head extension |
334 | 0 | Element& tailElem = mChainElementList[seg.start + seg.tail]; |
335 | 0 | size_t preTailIdx; |
336 | 0 | if (seg.tail == 0) |
337 | 0 | preTailIdx = mMaxElementsPerChain - 1; |
338 | 0 | else |
339 | 0 | preTailIdx = seg.tail - 1; |
340 | 0 | Element& preTailElem = mChainElementList[seg.start + preTailIdx]; |
341 | | |
342 | | // Measure tail diff from pretail to tail |
343 | 0 | Vector3 taildiff = tailElem.position - preTailElem.position; |
344 | 0 | Real taillen = taildiff.length(); |
345 | 0 | if (taillen > 1e-06) |
346 | 0 | { |
347 | 0 | Real tailsize = mElemLength - diff.length(); |
348 | 0 | taildiff *= tailsize / taillen; |
349 | 0 | tailElem.position = preTailElem.position + taildiff; |
350 | 0 | } |
351 | |
|
352 | 0 | } |
353 | 0 | } // end while |
354 | | |
355 | |
|
356 | 0 | mBoundsDirty = true; |
357 | | // Need to dirty the parent node, but can't do it using needUpdate() here |
358 | | // since we're in the middle of the scene graph update (node listener), |
359 | | // so re-entrant calls don't work. Queue. |
360 | 0 | if (mParentNode) |
361 | 0 | { |
362 | 0 | Node::queueNeedUpdate(getParentSceneNode()); |
363 | 0 | } |
364 | |
|
365 | 0 | } |
366 | | //----------------------------------------------------------------------- |
367 | | void RibbonTrail::_timeUpdate(Real time) |
368 | 0 | { |
369 | | // Apply all segment effects |
370 | 0 | for (size_t s = 0; s < mChainSegmentList.size(); ++s) |
371 | 0 | { |
372 | 0 | ChainSegment& seg = mChainSegmentList[s]; |
373 | 0 | if (seg.head != SEGMENT_EMPTY && seg.head != seg.tail) |
374 | 0 | { |
375 | | |
376 | 0 | for(size_t e = seg.head + 1;; ++e) // until break |
377 | 0 | { |
378 | 0 | e = e % mMaxElementsPerChain; |
379 | |
|
380 | 0 | Element& elem = mChainElementList[seg.start + e]; |
381 | 0 | elem.width = elem.width - (time * mDeltaWidth[s]); |
382 | 0 | elem.width = std::max(0.0f, elem.width); |
383 | 0 | elem.colour = elem.colour - (mDeltaColour[s] * time); |
384 | 0 | elem.colour.saturate(); |
385 | |
|
386 | 0 | if (e == seg.tail) |
387 | 0 | break; |
388 | 0 | } |
389 | 0 | } |
390 | 0 | } |
391 | 0 | mVertexContentDirty = true; |
392 | 0 | } |
393 | | //----------------------------------------------------------------------- |
394 | | void RibbonTrail::resetTrail(size_t index, const Node* node) |
395 | 0 | { |
396 | 0 | assert(index < mChainCount); |
397 | |
|
398 | 0 | ChainSegment& seg = mChainSegmentList[index]; |
399 | | // set up this segment |
400 | 0 | seg.head = seg.tail = SEGMENT_EMPTY; |
401 | | // Create new element, v coord is always 0.0f |
402 | | // need to convert to take parent node's position into account |
403 | 0 | Vector3 position = node->_getDerivedPosition(); |
404 | 0 | if (mParentNode) |
405 | 0 | { |
406 | 0 | position = mParentNode->convertWorldToLocalPosition(position); |
407 | 0 | } |
408 | 0 | Element e(position, |
409 | 0 | mInitialWidth[index], 0.0f, mInitialColour[index], node->_getDerivedOrientation()); |
410 | | // Add the start position |
411 | 0 | addChainElement(index, e); |
412 | | // Add another on the same spot, this will extend |
413 | 0 | addChainElement(index, e); |
414 | 0 | } |
415 | | //----------------------------------------------------------------------- |
416 | | void RibbonTrail::resetAllTrails(void) |
417 | 0 | { |
418 | 0 | for (size_t i = 0; i < mNodeList.size(); ++i) |
419 | 0 | { |
420 | 0 | resetTrail(i, mNodeList[i]); |
421 | 0 | } |
422 | 0 | } |
423 | | //----------------------------------------------------------------------- |
424 | | const String& RibbonTrail::getMovableType(void) const |
425 | 0 | { |
426 | 0 | return MOT_RIBBON_TRAIL; |
427 | 0 | } |
428 | | //----------------------------------------------------------------------- |
429 | | //----------------------------------------------------------------------- |
430 | | const String MOT_RIBBON_TRAIL = "RibbonTrail"; |
431 | | //----------------------------------------------------------------------- |
432 | | const String& RibbonTrailFactory::getType(void) const |
433 | 3 | { |
434 | 3 | return MOT_RIBBON_TRAIL; |
435 | 3 | } |
436 | | //----------------------------------------------------------------------- |
437 | | MovableObject* RibbonTrailFactory::createInstanceImpl( const String& name, |
438 | | const NameValuePairList* params) |
439 | 0 | { |
440 | 0 | size_t maxElements = 20; |
441 | 0 | size_t numberOfChains = 1; |
442 | 0 | bool useTex = true; |
443 | 0 | bool useCol = true; |
444 | | // optional params |
445 | 0 | if (params != 0) |
446 | 0 | { |
447 | 0 | NameValuePairList::const_iterator ni = params->find("maxElements"); |
448 | 0 | if (ni != params->end()) |
449 | 0 | { |
450 | 0 | maxElements = StringConverter::parseSizeT(ni->second); |
451 | 0 | } |
452 | 0 | ni = params->find("numberOfChains"); |
453 | 0 | if (ni != params->end()) |
454 | 0 | { |
455 | 0 | numberOfChains = StringConverter::parseSizeT(ni->second); |
456 | 0 | } |
457 | 0 | ni = params->find("useTextureCoords"); |
458 | 0 | if (ni != params->end()) |
459 | 0 | { |
460 | 0 | useTex = StringConverter::parseBool(ni->second); |
461 | 0 | } |
462 | 0 | ni = params->find("useVertexColours"); |
463 | 0 | if (ni != params->end()) |
464 | 0 | { |
465 | 0 | useCol = StringConverter::parseBool(ni->second); |
466 | 0 | } |
467 | |
|
468 | 0 | } |
469 | |
|
470 | 0 | return OGRE_NEW RibbonTrail(name, maxElements, numberOfChains, useTex, useCol); |
471 | |
|
472 | 0 | } |
473 | | |
474 | | } |
475 | | |