FlightGear next
texture_replace.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: texture_replace.cxx
3 * SPDX-FileCopyrightText: Copyright (C) 2012 Thomas Geymayer
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include <osg/Texture2D>
8#include <osg/Geode>
9#include <osg/NodeVisitor>
10#include <osg/StateSet>
11
12#include <simgear/canvas/CanvasObjectPlacement.hxx>
13#include <simgear/scene/material/EffectGeode.hxx>
14
15#include <Main/globals.hxx>
16#include <Scenery/scenery.hxx>
17
18#include "texture_replace.hxx"
19
20namespace canvas {
21
22/*
23 * Used to remember the located groups that require modification
24 */
25typedef struct {
26 osg::ref_ptr<osg::Group> parent;
27 osg::ref_ptr<osg::Geode> node;
28 unsigned int unit;
30
34class ReplaceStaticTextureVisitor : public osg::NodeVisitor
35{
36 public:
37
38 typedef osg::ref_ptr<osg::Group> GroupPtr;
39 typedef osg::ref_ptr<osg::Material> MaterialPtr;
40
42 osg::Texture2D* new_texture ):
43 osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
45 _new_texture(new_texture),
47 {}
48
49 ReplaceStaticTextureVisitor( SGPropertyNode* placement,
50 osg::Texture2D* new_texture,
51 osg::NodeCallback* cull_callback = 0,
52 const simgear::canvas::CanvasWeakPtr& canvas =
53 simgear::canvas::CanvasWeakPtr() ):
54 osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
55 _tex_name( placement->getStringValue("texture") ),
56 _node_name( placement->getStringValue("node") ),
57 _parent_name( placement->getStringValue("parent") ),
58 _node(placement),
59 _new_texture(new_texture),
60 _cull_callback(cull_callback),
62 {
63 if( _tex_name.empty()
64 && _node_name.empty()
65 && _parent_name.empty() )
66 SG_LOG
67 (
68 SG_GL,
69 SG_DEV_ALERT,
70 "No filter criterion for replacing texture. "
71 " Every texture will be replaced!"
72 );
73 }
74
79 simgear::canvas::Placements& getPlacements()
80 {
81 return _placements;
82 }
83
84 virtual void apply(osg::Geode& node)
85 {
86 simgear::EffectGeode* effectGeode = dynamic_cast<simgear::EffectGeode*>(&node);
87 if( !effectGeode )
88 return;
89 simgear::Effect* eff = effectGeode->getEffect();
90 if (!eff)
91 return;
92
93 // Assume that the parent node to the EffectGeode contains the object
94 // name. This is true for AC3D and glTF models.
95 osg::Group *parent = node.getParent(0);
96 if( !_node_name.empty() && getNodeName(*parent) != _node_name )
97 return;
98
99 if( !_parent_name.empty() )
100 {
101 // Traverse nodes upwards starting at the parent node (skip current
102 // node)
103 const osg::NodePath& np = getNodePath();
104 bool found = false;
105 for( int i = static_cast<int>(np.size()) - 2; i >= 0; --i )
106 {
107 const osg::Node* path_segment = np[i];
108 const osg::Node* path_parent = path_segment->getParent(0);
109
110 // A node without a name is always the parent of the root node of
111 // the model just containing the file name
112 if( path_parent && path_parent->getName().empty() )
113 return;
114
115 if( path_segment->getName() == _parent_name )
116 {
117 found = true;
118 break;
119 }
120 }
121
122 if( !found )
123 return;
124 }
125
126 // NOTE: The texture units that correspond to each texture type (e.g.
127 // 0 for base color, 1 for normal map, etc.) must match the ones in:
128 // 1. PBR Effect: $FG_ROOT/Effects/model-pbr.eff
129 // 2. glTF loader: simgear/scene/model/ReaderWriterGLTF.cxx
130 // 3. PBR animations: simgear/scene/model/SGPBRAnimation.cxx
131 // 4. Canvas: flightgear/src/Canvas/texture_replace.cxx
132 unsigned int unit = 0;
133 if (_tex_name.empty() || _tex_name == "base-color") unit = 0;
134 else if (_tex_name == "normalmap") unit = 1;
135 else if (_tex_name == "orm") unit = 2;
136 else if (_tex_name == "emissive") unit = 3;
137 else {
138 SG_LOG(SG_GL, SG_DEV_ALERT, "Unknown texture '" << _tex_name
139 << "'. Using base-color by default");
140 }
141
142 groups_to_modify.push_back({parent, &node, unit});
143 }
144 /*
145 * this section of code used to be in the apply method above, however to work this requires modification of the scenegraph nodes
146 * that are currently iterating, so instead the apply method will locate the groups to be modified and when finished then the
147 * nodes can actually be modified safely. Initially found thanks to the debug RTL in MSVC2015 throwing an exception.
148 * should be called immediately after the visitor to ensure that the groups are still valid and that nothing else has modified these groups.
149 */
151 {
152 for (auto g : groups_to_modify) {
153 // insert a new group between the geode an it's parent which overrides
154 // the texture
155 GroupPtr group = new osg::Group;
156 group->setName("canvas texture group");
157 group->addChild(g.node);
158 g.parent->removeChild(g.node);
159 g.parent->addChild(group);
160
161 if (_cull_callback)
162 group->setCullCallback(_cull_callback);
163
164 group->getOrCreateStateSet()->setTextureAttributeAndModes(
165 g.unit,
167 osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
168
169 _placements.push_back(simgear::canvas::PlacementPtr(
170 new simgear::canvas::ObjectPlacement(_node, group, _canvas)
171 ));
172
173 SG_LOG
174 (
175 SG_GL,
176 SG_INFO,
177 "Replaced texture '" << _tex_name << "'"
178 << " for object '" << g.parent->getName() << "'"
179 << (!_parent_name.empty() ? " with parent '" + _parent_name + "'"
180 : "")
181 );
182 }
183 groups_to_modify.clear();
184 }
185
186 protected:
187
188 std::string _tex_name,
194
195 SGPropertyNode_ptr _node;
196 osg::Texture2D *_new_texture;
197 osg::NodeCallback *_cull_callback;
198 typedef std::vector<GroupListItem> GroupList;
200 simgear::canvas::CanvasWeakPtr _canvas;
201 simgear::canvas::Placements _placements;
202
203 const std::string& getNodeName(const osg::Node& node) const
204 {
205 if( !node.getName().empty() )
206 return node.getName();
207
208 // Special handling for pick animation which clears the name of the object
209 // and instead sets the name of a parent group with one or two groups
210 // attached (one for normal rendering and one for the picking highlight).
211 osg::Group const* parent = node.getParent(0);
212 if( parent->getName() == "pick render group" )
213 return parent->getParent(0)->getName();
214
215 return node.getName();
216 }
217};
218
219//------------------------------------------------------------------------------
220simgear::canvas::Placements
221set_texture( osg::Node* branch,
222 const char * name,
223 osg::Texture2D* new_texture )
224{
225 ReplaceStaticTextureVisitor visitor(name, new_texture);
226 branch->accept(visitor);
227 visitor.modify_groups();
228 return visitor.getPlacements();
229}
230
231//------------------------------------------------------------------------------
232simgear::canvas::Placements
234 osg::Texture2D* new_texture )
235{
236 return set_texture
237 (
238 globals->get_scenery()->get_aircraft_branch(),
239 name,
240 new_texture
241 );
242}
243
244//------------------------------------------------------------------------------
245simgear::canvas::Placements
246set_texture( osg::Node* branch,
247 SGPropertyNode* placement,
248 osg::Texture2D* new_texture,
249 osg::NodeCallback* cull_callback,
250 const simgear::canvas::CanvasWeakPtr& canvas )
251{
252 ReplaceStaticTextureVisitor visitor( placement,
253 new_texture,
254 cull_callback,
255 canvas );
256 branch->accept(visitor);
257 visitor.modify_groups();
258 return visitor.getPlacements();
259}
260
261//------------------------------------------------------------------------------
262simgear::canvas::Placements
263set_aircraft_texture( SGPropertyNode* placement,
264 osg::Texture2D* new_texture,
265 osg::NodeCallback* cull_callback,
266 const simgear::canvas::CanvasWeakPtr& canvas )
267{
268 return set_texture
269 (
270 globals->get_scenery()->get_aircraft_branch(),
271 placement,
272 new_texture,
273 cull_callback,
274 canvas
275 );
276}
277
278} // namespace canvas
#define i(x)
Replace a texture in the airplane model with another.
simgear::canvas::CanvasWeakPtr _canvas
ReplaceStaticTextureVisitor(const char *name, osg::Texture2D *new_texture)
osg::ref_ptr< osg::Material > MaterialPtr
std::string _node_name
! Only replace if node name matches
ReplaceStaticTextureVisitor(SGPropertyNode *placement, osg::Texture2D *new_texture, osg::NodeCallback *cull_callback=0, const simgear::canvas::CanvasWeakPtr &canvas=simgear::canvas::CanvasWeakPtr())
simgear::canvas::Placements _placements
std::vector< GroupListItem > GroupList
simgear::canvas::Placements & getPlacements()
Get a list of groups which have been inserted into the scene graph to replace the given texture.
osg::ref_ptr< osg::Group > GroupPtr
virtual void apply(osg::Geode &node)
const std::string & getNodeName(const osg::Node &node) const
const char * name
FGGlobals * globals
Definition globals.cxx:142
simgear::canvas::Placements set_texture(osg::Node *branch, const char *name, osg::Texture2D *new_texture)
Replace an opengl texture name inside a given branch of the scene graph.
simgear::canvas::Placements set_aircraft_texture(const char *name, osg::Texture2D *new_texture)
Replace an opengl texture name inside the aircraft scene graph.
Definition AIBase.hxx:25
osg::ref_ptr< osg::Group > parent
osg::ref_ptr< osg::Geode > node