Coverage Report

Created: 2026-02-26 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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