Coverage Report

Created: 2026-08-31 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ogre/OgreMain/src/OgreSceneNode.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
30
namespace Ogre {
31
    //-----------------------------------------------------------------------
32
    SceneNode::SceneNode(SceneManager* creator, const String& name)
33
0
        : Node(name)
34
0
        , mCreator(creator)
35
0
        , mAutoTrackTarget(0)
36
0
        , mGlobalIndex(-1)
37
0
        , mYawFixed(false)
38
0
        , mIsInSceneGraph(false)
39
0
        , mDisplaySceneNode(false)
40
0
        , mShowBoundingBox(false)
41
0
    {
42
0
        needUpdate();
43
0
    }
44
    //-----------------------------------------------------------------------
45
    SceneNode::~SceneNode()
46
0
    {
47
        // Detach all objects, do this manually to avoid needUpdate() call 
48
        // which can fail because of deleted items
49
0
        for (auto & itr : mObjectsByName)
50
0
        {
51
0
            itr->_notifyAttached((SceneNode*)0);
52
0
        }
53
0
        mObjectsByName.clear();
54
0
    }
55
    //-----------------------------------------------------------------------
56
    void SceneNode::_update(bool updateChildren, bool parentHasChanged)
57
0
    {
58
0
        Node::_update(updateChildren, parentHasChanged);
59
0
        _updateBounds();
60
0
    }
61
    //-----------------------------------------------------------------------
62
    void SceneNode::setParent(Node* parent)
63
0
    {
64
0
        Node::setParent(parent);
65
66
0
        if (parent)
67
0
        {
68
0
            SceneNode* sceneParent = static_cast<SceneNode*>(parent);
69
0
            setInSceneGraph(sceneParent->isInSceneGraph());
70
0
        }
71
0
        else
72
0
        {
73
0
            setInSceneGraph(false);
74
0
        }
75
0
    }
76
    //-----------------------------------------------------------------------
77
    void SceneNode::setInSceneGraph(bool inGraph)
78
0
    {
79
0
        if (inGraph != mIsInSceneGraph)
80
0
        {
81
0
            mIsInSceneGraph = inGraph;
82
            // Tell children
83
0
            for (auto child : getChildren())
84
0
            {
85
0
                SceneNode* sceneChild = static_cast<SceneNode*>(child);
86
0
                sceneChild->setInSceneGraph(inGraph);
87
0
            }
88
0
        }
89
0
    }
90
    //-----------------------------------------------------------------------
91
    struct MovableObjectNameExists {
92
        const String& name;
93
0
        bool operator()(const MovableObject* mo) {
94
0
            return mo->getName() == name;
95
0
        }
96
    };
97
    void SceneNode::attachObject(MovableObject* obj)
98
0
    {
99
0
        OgreAssert(!obj->isAttached(), "Object already attached to a SceneNode or a Bone");
100
101
0
        obj->_notifyAttached(this);
102
103
        // Also add to name index
104
0
        MovableObjectNameExists pred = {obj->getName()};
105
0
        ObjectMap::iterator it = std::find_if(mObjectsByName.begin(), mObjectsByName.end(), pred);
106
0
        if (it != mObjectsByName.end())
107
0
            OGRE_EXCEPT(Exception::ERR_DUPLICATE_ITEM,
108
0
                        "An object named '" + obj->getName() + "' already attached to this SceneNode");
109
0
        mObjectsByName.push_back(obj);
110
111
        // Make sure bounds get updated (must go right to the top)
112
0
        needUpdate();
113
0
    }
114
    //-----------------------------------------------------------------------
115
    MovableObject* SceneNode::getAttachedObject(const String& name) const
116
0
    {
117
        // Look up 
118
0
        MovableObjectNameExists pred = {name};
119
0
        auto i = std::find_if(mObjectsByName.begin(), mObjectsByName.end(), pred);
120
121
0
        if (i == mObjectsByName.end())
122
0
        {
123
0
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Attached object " + 
124
0
                name + " not found.", "SceneNode::getAttachedObject");
125
0
        }
126
127
0
        return *i;
128
0
    }
129
    //-----------------------------------------------------------------------
130
    MovableObject* SceneNode::detachObject(unsigned short index)
131
0
    {
132
0
        OgreAssert(index < mObjectsByName.size(), "out of bounds");
133
0
        ObjectMap::iterator i = mObjectsByName.begin();
134
0
        i += index;
135
136
0
        MovableObject* ret = *i;
137
0
        std::swap(*i, mObjectsByName.back());
138
0
        mObjectsByName.pop_back();
139
140
0
        ret->_notifyAttached((SceneNode*)0);
141
142
        // Make sure bounds get updated (must go right to the top)
143
0
        needUpdate();
144
145
0
        return ret;
146
0
    }
147
    //-----------------------------------------------------------------------
148
    MovableObject* SceneNode::detachObject(const String& name)
149
0
    {
150
0
        MovableObjectNameExists pred = {name};
151
0
        ObjectMap::iterator it = std::find_if(mObjectsByName.begin(), mObjectsByName.end(), pred);
152
153
0
        if (it == mObjectsByName.end())
154
0
        {
155
0
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Object " + name + " is not attached "
156
0
                "to this node.", "SceneNode::detachObject");
157
0
        }
158
159
0
        MovableObject* ret = *it;
160
0
        std::swap(*it, mObjectsByName.back());
161
0
        mObjectsByName.pop_back();
162
163
0
        ret->_notifyAttached((SceneNode*)0);
164
        // Make sure bounds get updated (must go right to the top)
165
0
        needUpdate();
166
        
167
0
        return ret;
168
169
0
    }
170
    //-----------------------------------------------------------------------
171
    void SceneNode::detachObject(MovableObject* obj)
172
0
    {
173
0
        auto it = std::find(mObjectsByName.begin(), mObjectsByName.end(), obj);
174
0
        OgreAssert(it != mObjectsByName.end(), "Object is not attached to this node");
175
0
        std::swap(*it, mObjectsByName.back());
176
0
        mObjectsByName.pop_back();
177
0
        obj->_notifyAttached((SceneNode*)0);
178
179
        // Make sure bounds get updated (must go right to the top)
180
0
        needUpdate();
181
0
    }
182
    //-----------------------------------------------------------------------
183
    void SceneNode::detachAllObjects(void)
184
0
    {
185
0
        for (auto & itr : mObjectsByName)
186
0
        {
187
0
            itr->_notifyAttached((SceneNode*)0);
188
0
        }
189
0
        mObjectsByName.clear();
190
        // Make sure bounds get updated (must go right to the top)
191
0
        needUpdate();
192
0
    }
193
    //-----------------------------------------------------------------------
194
    void SceneNode::destroyAllObjects(void)
195
0
    {
196
0
        while (!getAttachedObjects().empty()) {
197
0
            auto obj = getAttachedObjects().front();
198
0
            getCreator()->destroyMovableObject(obj);
199
0
        }
200
0
        needUpdate();
201
0
    }
202
    //-----------------------------------------------------------------------
203
    void SceneNode::_updateBounds(void)
204
0
    {
205
        // Reset bounds first
206
0
        mWorldAABB.setNull();
207
208
        // Update bounds from own attached objects
209
0
        for (auto *o : mObjectsByName)
210
0
        {
211
            // Merge world bounds of each object
212
0
            mWorldAABB.merge(o->getWorldBoundingBox(true));
213
0
        }
214
215
        // Merge with children
216
0
        for (auto child : getChildren())
217
0
        {
218
0
            SceneNode* sceneChild = static_cast<SceneNode*>(child);
219
0
            mWorldAABB.merge(sceneChild->mWorldAABB);
220
0
        }
221
222
0
    }
223
    //-----------------------------------------------------------------------
224
    void SceneNode::_findVisibleObjects(Camera* cam, RenderQueue* queue, 
225
        VisibleObjectsBoundsInfo* visibleBounds, bool includeChildren, 
226
        bool displayNodes, bool onlyShadowCasters)
227
0
    {
228
        // Check self visible
229
0
        if (!cam->isVisible(mWorldAABB))
230
0
            return;
231
232
        // Add all entities
233
0
        for (auto *o : mObjectsByName)
234
0
        {
235
0
            queue->processVisibleObject(o, cam, onlyShadowCasters, visibleBounds);
236
0
        }
237
238
0
        if (includeChildren)
239
0
        {
240
0
            for (auto child : getChildren())
241
0
            {
242
0
                SceneNode* sceneChild = static_cast<SceneNode*>(child);
243
0
                sceneChild->_findVisibleObjects(cam, queue, visibleBounds, includeChildren, 
244
0
                    displayNodes, onlyShadowCasters);
245
0
            }
246
0
        }
247
248
0
        if (mCreator && mCreator->getDebugDrawer())
249
0
        {
250
0
            mCreator->getDebugDrawer()->drawSceneNode(this);
251
0
        }
252
0
    }
253
254
0
    SceneNode::ObjectIterator SceneNode::getAttachedObjectIterator(void) {
255
0
        return ObjectIterator(mObjectsByName.begin(), mObjectsByName.end());
256
0
    }
257
0
    SceneNode::ConstObjectIterator SceneNode::getAttachedObjectIterator(void) const {
258
0
        return ConstObjectIterator(mObjectsByName.begin(), mObjectsByName.end());
259
0
    }
260
261
    //-----------------------------------------------------------------------
262
    void SceneNode::updateFromParentImpl(void) const
263
0
    {
264
0
        Node::updateFromParentImpl();
265
266
        // Notify objects that it has been moved
267
0
        for (auto o : mObjectsByName)
268
0
        {
269
0
            o->_notifyMoved();
270
0
        }
271
0
    }
272
    //-----------------------------------------------------------------------
273
    Node* SceneNode::createChildImpl(void)
274
0
    {
275
0
        assert(mCreator);
276
0
        return mCreator->createSceneNode();
277
0
    }
278
    //-----------------------------------------------------------------------
279
    Node* SceneNode::createChildImpl(const String& name)
280
0
    {
281
0
        assert(mCreator);
282
0
        return mCreator->createSceneNode(name);
283
0
    }
284
    //-----------------------------------------------------------------------
285
    void SceneNode::removeAndDestroyChild(const String& name)
286
0
    {
287
0
        SceneNode* pChild = static_cast<SceneNode*>(removeChild(name));
288
0
        pChild->removeAndDestroyAllChildren();
289
290
0
        pChild->getCreator()->destroySceneNode(name);
291
292
0
    }
293
    //-----------------------------------------------------------------------
294
    void SceneNode::removeAndDestroyChild(unsigned short index)
295
0
    {
296
0
        SceneNode* pChild = static_cast<SceneNode*>(removeChild(index));
297
0
        pChild->removeAndDestroyAllChildren();
298
299
0
        pChild->getCreator()->destroySceneNode(pChild);
300
0
    }
301
    //-----------------------------------------------------------------------
302
    void SceneNode::removeAndDestroyChild(SceneNode* child)
303
0
    {
304
0
        auto it = std::find(getChildren().begin(), getChildren().end(), child);
305
0
        OgreAssert(it != getChildren().end(), "Not a child of this SceneNode");
306
0
        removeAndDestroyChild(it - getChildren().begin());
307
0
    }
308
    //-----------------------------------------------------------------------
309
    void SceneNode::removeAndDestroyAllChildren(void)
310
0
    {
311
        // do not store iterators (invalidated by
312
        // SceneManager::destroySceneNode because it causes removal from parent)
313
0
        while(!getChildren().empty()) {
314
0
            SceneNode* sn = static_cast<SceneNode*>(getChildren().front());
315
0
            sn->removeAndDestroyAllChildren();
316
0
            sn->getCreator()->destroySceneNode(sn);
317
0
        }
318
319
0
        mChildren.clear();
320
0
        needUpdate();
321
0
    }
322
    //-----------------------------------------------------------------------
323
0
    void SceneNode::destroyChildAndObjects(const String& name) {
324
0
        SceneNode* pChild = static_cast<SceneNode*>(getChild(name));
325
0
        pChild->destroyAllChildrenAndObjects();
326
327
0
        removeChild(name);
328
0
        pChild->getCreator()->destroySceneNode(name);
329
330
0
    }
331
332
0
    void SceneNode::destroyChildAndObjects(unsigned short index) {
333
0
        SceneNode* pChild = static_cast<SceneNode*>(removeChild(index));
334
0
        pChild->destroyAllChildrenAndObjects();
335
336
0
        pChild->getCreator()->destroySceneNode(pChild);
337
0
    }
338
339
    void SceneNode::destroyChildAndObjects(SceneNode * child)
340
0
    {
341
0
        auto it = std::find(getChildren().begin(), getChildren().end(), child);
342
0
        OgreAssert(it != getChildren().end(), "Not a child of this SceneNode");
343
0
        destroyChildAndObjects(it - getChildren().begin());
344
0
    }
345
346
    void SceneNode::destroyAllChildrenAndObjects()
347
0
    {
348
        //remove objects directly attached to this node
349
0
        destroyAllObjects();
350
351
        //go over children
352
0
        while(!getChildren().empty()) {
353
0
            SceneNode* child = static_cast<SceneNode*>(getChildren().front());
354
            //recurse
355
0
            child->destroyAllChildrenAndObjects();
356
357
            //destroy child
358
0
            child->getCreator()->destroySceneNode(child);
359
0
        }
360
0
        mChildren.clear();
361
0
        needUpdate();
362
0
    }
363
    //-----------------------------------------------------------------------
364
    void SceneNode::loadChildren(const String& filename)
365
0
    {
366
0
        String baseName, strExt;
367
0
        StringUtil::splitBaseFilename(filename, baseName, strExt);
368
0
        auto codec = Codec::getCodec(strExt);
369
0
        if (!codec)
370
0
            OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "No codec found to load " + filename);
371
372
0
        auto stream = Root::openFileStream(
373
0
            filename, ResourceGroupManager::getSingleton().getWorldResourceGroupName());
374
0
        codec->decode(stream, this);
375
0
    }
376
    void SceneNode::saveChildren(const String& filename)
377
0
    {
378
0
        String baseName, strExt;
379
0
        StringUtil::splitBaseFilename(filename, baseName, strExt);
380
0
        auto codec = Codec::getCodec(strExt);
381
0
        codec->encodeToFile(this, filename);
382
0
    }
383
    //-----------------------------------------------------------------------
384
    SceneNode* SceneNode::createChildSceneNode(const Vector3& inTranslate, 
385
        const Quaternion& inRotate)
386
0
    {
387
0
        return static_cast<SceneNode*>(this->createChild(inTranslate, inRotate));
388
0
    }
389
    //-----------------------------------------------------------------------
390
    SceneNode* SceneNode::createChildSceneNode(const String& name, const Vector3& inTranslate, 
391
        const Quaternion& inRotate)
392
0
    {
393
0
        return static_cast<SceneNode*>(this->createChild(name, inTranslate, inRotate));
394
0
    }
395
    //-----------------------------------------------------------------------
396
    void SceneNode::findLights(LightList& destList, Real radius, uint32 lightMask) const
397
0
    {
398
        // No any optimisation here, hope inherits more smart for that.
399
        //
400
        // If a scene node is static and lights have moved, light list won't change
401
        // can't use a simple global boolean flag since this is only called for
402
        // visible nodes, so temporarily visible nodes will not be updated
403
        // Since this is only called for visible nodes, skip the check for now
404
        //
405
0
        if (mCreator)
406
0
        {
407
            // Use SceneManager to calculate
408
0
            mCreator->_populateLightList(this, radius, destList, lightMask);
409
0
        }
410
0
        else
411
0
        {
412
0
            destList.clear();
413
0
        }
414
0
    }
415
    //-----------------------------------------------------------------------
416
    void SceneNode::setAutoTracking(bool enabled, SceneNode* const target, 
417
        const Vector3& localDirectionVector,
418
        const Vector3& offset)
419
0
    {
420
0
        if (enabled)
421
0
        {
422
0
            mAutoTrackTarget = target;
423
0
            mAutoTrackOffset = offset;
424
0
            mAutoTrackLocalDirection = localDirectionVector;
425
0
        }
426
0
        else
427
0
        {
428
0
            mAutoTrackTarget = 0;
429
0
        }
430
0
        if (mCreator)
431
0
            mCreator->_notifyAutotrackingSceneNode(this, enabled);
432
0
    }
433
    //-----------------------------------------------------------------------
434
    void SceneNode::setFixedYawAxis(bool useFixed, const Vector3& fixedAxis)
435
0
    {
436
0
        mYawFixed = useFixed;
437
0
        mYawFixedAxis = fixedAxis;
438
0
    }
439
440
    //-----------------------------------------------------------------------
441
    void SceneNode::yaw(const Radian& angle, TransformSpace relativeTo)
442
0
    {
443
0
        if (mYawFixed)
444
0
        {
445
0
            rotate(mYawFixedAxis, angle, relativeTo);
446
0
        }
447
0
        else
448
0
        {
449
0
            rotate(Vector3::UNIT_Y, angle, relativeTo);
450
0
        }
451
452
0
    }
453
    //-----------------------------------------------------------------------
454
    void SceneNode::setDirection(Real x, Real y, Real z, TransformSpace relativeTo, 
455
        const Vector3& localDirectionVector)
456
0
    {
457
0
        setDirection(Vector3(x,y,z), relativeTo, localDirectionVector);
458
0
    }
459
460
    //-----------------------------------------------------------------------
461
    void SceneNode::setDirection(const Vector3& vec, TransformSpace relativeTo, 
462
        const Vector3& localDirectionVector)
463
0
    {
464
        // Do nothing if given a zero vector
465
0
        if (vec == Vector3::ZERO) return;
466
467
        // The direction we want the local direction point to
468
0
        Vector3 targetDir = vec.normalisedCopy();
469
470
        // Transform target direction to parent space
471
0
        switch (relativeTo)
472
0
        {
473
0
        case TS_PARENT:
474
0
            break;
475
0
        case TS_LOCAL:
476
0
            targetDir = getOrientation() * targetDir;
477
0
            break;
478
0
        case TS_WORLD:
479
0
            if (getInheritOrientation() && getParent())
480
0
            {
481
0
                targetDir = getParent()->_getDerivedOrientation().UnitInverse() * targetDir;
482
0
            }
483
0
            break;
484
0
        }
485
486
        // Calculate target orientation relative to parent space
487
0
        Quaternion targetOrientation;
488
0
        if( mYawFixed )
489
0
        {
490
            // Calculate the quaternion for rotate local Z to target direction
491
0
            Quaternion unitZToTarget = Math::lookRotation(targetDir, mYawFixedAxis);
492
493
0
            if (localDirectionVector == Vector3::NEGATIVE_UNIT_Z)
494
0
            {
495
                // Special case for avoid calculate 180 degree turn
496
0
                targetOrientation =
497
0
                    Quaternion(-unitZToTarget.y, -unitZToTarget.z, unitZToTarget.w, unitZToTarget.x);
498
0
            }
499
0
            else
500
0
            {
501
                // Calculate the quaternion for rotate local direction to target direction
502
0
                Quaternion localToUnitZ = localDirectionVector.getRotationTo(Vector3::UNIT_Z);
503
0
                targetOrientation = unitZToTarget * localToUnitZ;
504
0
            }
505
0
        }
506
0
        else
507
0
        {
508
0
            const Quaternion& currentOrient = getOrientation();
509
510
            // Get current local direction relative to parent space
511
0
            Vector3 currentDir = currentOrient * localDirectionVector;
512
513
0
            if ((currentDir+targetDir).squaredLength() < 0.00005f)
514
0
            {
515
                // Oops, a 180 degree turn (infinite possible rotation axes)
516
                // Default to yaw i.e. use current UP
517
0
                targetOrientation =
518
0
                    Quaternion(-currentOrient.y, -currentOrient.z, currentOrient.w, currentOrient.x);
519
0
            }
520
0
            else
521
0
            {
522
                // Derive shortest arc to new direction
523
0
                Quaternion rotQuat = currentDir.getRotationTo(targetDir);
524
0
                targetOrientation = rotQuat * currentOrient;
525
0
            }
526
0
        }
527
528
        // Set target orientation
529
0
        setOrientation(targetOrientation);
530
0
    }
531
    //-----------------------------------------------------------------------
532
    void SceneNode::lookAt( const Vector3& targetPoint, TransformSpace relativeTo, 
533
        const Vector3& localDirectionVector)
534
0
    {
535
        // Calculate ourself origin relative to the given transform space
536
0
        Vector3 origin;
537
0
        switch (relativeTo)
538
0
        {
539
0
        default:    // Just in case
540
0
        case TS_WORLD:
541
0
            origin = _getDerivedPosition();
542
0
            break;
543
0
        case TS_PARENT:
544
0
            origin = getPosition();
545
0
            break;
546
0
        case TS_LOCAL:
547
0
            origin = Vector3::ZERO;
548
0
            break;
549
0
        }
550
551
0
        setDirection(targetPoint - origin, relativeTo, localDirectionVector);
552
0
    }
553
    //-----------------------------------------------------------------------
554
    void SceneNode::_autoTrack(void)
555
0
    {
556
        // NB assumes that all scene nodes have been updated
557
0
        if (mAutoTrackTarget)
558
0
        {
559
0
            lookAt(mAutoTrackTarget->_getDerivedPosition() + mAutoTrackOffset, 
560
0
                TS_WORLD, mAutoTrackLocalDirection);
561
            // update self & children
562
0
            _update(true, true);
563
0
        }
564
0
    }
565
    //-----------------------------------------------------------------------
566
    SceneNode* SceneNode::getParentSceneNode(void) const
567
0
    {
568
0
        return static_cast<SceneNode*>(getParent());
569
0
    }
570
    //-----------------------------------------------------------------------
571
    void SceneNode::setVisible(bool visible, bool cascade) const
572
0
    {
573
0
        for (auto o : mObjectsByName)
574
0
        {
575
0
            o->setVisible(visible);
576
0
        }
577
578
0
        if (cascade)
579
0
        {
580
0
            for (auto c : getChildren())
581
0
            {
582
0
                static_cast<SceneNode*>(c)->setVisible(visible, cascade);
583
0
            }
584
0
        }
585
0
    }
586
    //-----------------------------------------------------------------------
587
    void SceneNode::setDebugDisplayEnabled(bool enabled, bool cascade) const
588
0
    {
589
0
        for (auto o : mObjectsByName)
590
0
        {
591
0
            o->setDebugDisplayEnabled(enabled);
592
0
        }
593
594
0
        if (cascade)
595
0
        {
596
0
            for (auto c : getChildren())
597
0
            {
598
0
                static_cast<SceneNode*>(c)->setDebugDisplayEnabled(enabled, cascade);
599
0
            }
600
0
        }
601
0
    }
602
    //-----------------------------------------------------------------------
603
    void SceneNode::flipVisibility(bool cascade) const
604
0
    {
605
0
        for (auto o : mObjectsByName)
606
0
        {
607
0
            o->setVisible(!o->getVisible());
608
0
        }
609
610
0
        if (cascade)
611
0
        {
612
0
            for (auto c : getChildren())
613
0
            {
614
0
                static_cast<SceneNode*>(c)->flipVisibility(cascade);
615
0
            }
616
0
        }
617
0
    }
618
}