1 /* OpenSceneGraph example, osggeometry.
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
16 * THE SOFTWARE.
17 */
18
19 #include <osg/Geode>
20 #include <osg/Geometry>
21 #include <osg/ImageUtils>
22 #include <osg/MatrixTransform>
23 #include <osg/PositionAttitudeTransform>
24 #include <osg/ComputeBoundsVisitor>
25 #include <osg/UserDataContainer>
26 #include <osg/ValueObject>
27 #include <osg/io_utils>
28
29 #include <osgDB/ReadFile>
30 #include <osgDB/WriteFile>
31 #include <osgDB/FileUtils>
32
33 #include <osgVolume/Volume>
34 #include <osgVolume/VolumeTile>
35 #include <osgVolume/RayTracedTechnique>
36 #include <osgVolume/FixedFunctionTechnique>
37
38 #include <osgViewer/Viewer>
39 #include <osgViewer/ViewerEventHandlers>
40
41
42 #include "TransferFunctionWidget.h"
43
44 class Histogram
45 {
46 public:
Histogram()47 Histogram() {}
48
49 void analyse(const osg::Image* image, double interval=0.0);
50
51 void insertZeroBoundaryValues(float xMin=FLT_MAX, float xMax=-FLT_MAX);
52
53 osg::Node* createGraphicalRepresentation();
54
55 typedef std::map<float, float> ValueMap;
56
getValueMap()57 ValueMap& getValueMap() { return _valueMap; }
58
59 protected:
60
61 ValueMap _valueMap;
62 };
63
64 struct PopulateHistogram
65 {
66
PopulateHistogramPopulateHistogram67 PopulateHistogram(Histogram::ValueMap& valueMap):
68 _histogram(valueMap) {}
69
castPopulateHistogram70 float cast(char v) { return v; }
castPopulateHistogram71 float cast(unsigned char v) { return v; }
castPopulateHistogram72 float cast(short v) { return v; }
castPopulateHistogram73 float cast(unsigned short v) { return v; }
castPopulateHistogram74 float cast(int v) { return v; }
castPopulateHistogram75 float cast(unsigned int v) { return v; }
castPopulateHistogram76 float cast(float v) { return v; }
castPopulateHistogram77 float cast(double v) { return v; }
78
79 Histogram::ValueMap& _histogram;
80
updatePopulateHistogram81 void update(int v)
82 {
83 _histogram[v]+=1.0;
84 }
85
normalizePopulateHistogram86 void normalize()
87 {
88 double maxValue = 0;
89 for(Histogram::ValueMap::iterator itr = _histogram.begin();
90 itr != _histogram.end();
91 ++itr)
92 {
93 if (itr->second>maxValue) maxValue = itr->second;
94 }
95
96 for(Histogram::ValueMap::iterator itr = _histogram.begin();
97 itr != _histogram.end();
98 ++itr)
99 {
100 itr->second /= maxValue;
101 }
102 }
103
luminancePopulateHistogram104 void luminance(float l) { update(l); }
alphaPopulateHistogram105 void alpha(float a) { update(a); }
luminance_alphaPopulateHistogram106 void luminance_alpha(float l, float a) { update(l); }
rgbPopulateHistogram107 void rgb(float r, float g, float b) { update(r); }
rgbaPopulateHistogram108 void rgba(float r, float g, float b, float a) { update(a); }
109 };
110
analyse(const osg::Image * image,double interval)111 void Histogram::analyse(const osg::Image* image, double interval)
112 {
113 PopulateHistogram populateHistogram(_valueMap);
114 readImage(image, populateHistogram);
115 populateHistogram.normalize();
116
117 for(Histogram::ValueMap::iterator itr = populateHistogram._histogram.begin();
118 itr != populateHistogram._histogram.end();
119 ++itr)
120 {
121 OSG_NOTICE<<" "<<itr->first<<", "<<itr->second<<std::endl;
122 }
123 }
124
insertZeroBoundaryValues(float xMin,float xMax)125 void Histogram::insertZeroBoundaryValues(float xMin, float xMax)
126 {
127 if (_valueMap.empty())
128 {
129 if (xMin<xMax)
130 {
131 _valueMap[xMin] = 0.0;
132 _valueMap[xMax] = 0.0;
133 }
134 return;
135 }
136
137 float interval = 1.0f;
138 float min_gap_for_single_insertion = interval*1.5;
139 float min_gap_for_double_insertion = interval*2.5;
140
141 if (xMin<_valueMap.begin()->first)
142 {
143 _valueMap[xMin] = 0.0;
144 }
145
146 if (xMax>_valueMap.rbegin()->first)
147 {
148 _valueMap[xMax] = 0.0;
149 }
150
151 ValueMap::iterator itr = _valueMap.begin();
152 float previous_x = itr->first;
153 for(;
154 itr != _valueMap.end();
155 ++itr)
156 {
157 float current_x = itr->first;
158 float gap = current_x-previous_x;
159 if (gap>min_gap_for_double_insertion)
160 {
161 _valueMap[previous_x+interval] = 0.0f;
162 _valueMap[current_x-interval] = 0.0f;
163 }
164 else if (gap>min_gap_for_single_insertion)
165 {
166 _valueMap[(previous_x+current_x)*0.5]=0.0f;
167 }
168 previous_x = current_x;
169 }
170 }
171
createGraphicalRepresentation()172 osg::Node* Histogram::createGraphicalRepresentation()
173 {
174 if (_valueMap.empty()) return 0;
175
176 osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform;
177
178 float xMin = _valueMap.begin()->first;
179 float xMax = _valueMap.rbegin()->first;
180
181 float depth = 0.0f;
182 float yMax = 0.0f;
183
184 // find yMax
185 for(ValueMap::iterator itr = _valueMap.begin();
186 itr != _valueMap.end();
187 ++itr)
188 {
189 float y = itr->second;
190 if (y>yMax) yMax = y;
191 }
192
193 float xScale = 1.0f/(xMax-xMin);
194 float yScale = 1.0f/yMax;
195
196 {
197 osg::ref_ptr<osg::Geode> geode = new osg::Geode;
198 transform->addChild(geode.get());
199
200 osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;
201 geode->addDrawable(geometry.get());
202 geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
203 geode->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON);
204
205 osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
206 geometry->setVertexArray(vertices.get());
207
208 osg::ref_ptr<osg::Vec4Array> colours = new osg::Vec4Array;
209 geometry->setColorArray(colours.get(), osg::Array::BIND_PER_PRIMITIVE_SET);
210 colours->push_back(osg::Vec4(1.0,1.0,1.0,1.0));
211 colours->push_back(osg::Vec4(1.0,1.0,1.0,1.0));
212 colours->push_back(osg::Vec4(1.0,1.0,1.0,0.1));
213
214
215 unsigned numColumnsRequired = _valueMap.size();
216 vertices->reserve(numColumnsRequired*3);
217 for(ValueMap::iterator itr = _valueMap.begin();
218 itr != _valueMap.end();
219 ++itr)
220 {
221 float x = itr->first;
222 float y = itr->second;
223
224 vertices->push_back(osg::Vec3(x*xScale, 0.0f, depth));
225 vertices->push_back(osg::Vec3(x*xScale, y*yScale, depth));
226 vertices->push_back(osg::Vec3(x*xScale, yMax*yScale, depth));
227 }
228
229 osg::ref_ptr<osg::DrawElementsUShort> background_primitives = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
230 osg::ref_ptr<osg::DrawElementsUShort> historgram_primitives = new osg::DrawElementsUShort(GL_TRIANGLE_STRIP);
231 osg::ref_ptr<osg::DrawElementsUShort> outline_primitives = new osg::DrawElementsUShort(GL_LINE_STRIP);
232 for(unsigned int i=0; i<numColumnsRequired; ++i)
233 {
234 int iv = i*3;
235
236 background_primitives->push_back(iv+2);
237 background_primitives->push_back(iv+1);
238
239 historgram_primitives->push_back(iv+1);
240 historgram_primitives->push_back(iv+0);
241
242 outline_primitives->push_back(iv+1);
243
244 }
245
246 geometry->addPrimitiveSet(outline_primitives.get());
247 geometry->addPrimitiveSet(historgram_primitives.get());
248 geometry->addPrimitiveSet(background_primitives.get());
249 }
250
251 //transform->setMatrix(osg::Matrix::scale(xScale/(maxX-minY), yScale/(yMax), 1.0f));
252
253 transform->setMatrix(osg::Matrix::scale(2.0,1.0,1.0)*osg::Matrix::rotate(osg::DegreesToRadians(90.0), osg::Vec3d(1.0,0.0,0.0)));
254
255 return transform.release();
256 }
257
readTransferFunctionFile(const std::string & filename,float colorScale=1.0f)258 osg::TransferFunction1D* readTransferFunctionFile(const std::string& filename, float colorScale=1.0f)
259 {
260 std::string foundFile = osgDB::findDataFile(filename);
261 if (foundFile.empty())
262 {
263 std::cout<<"Error: could not find transfer function file : "<<filename<<std::endl;
264 return 0;
265 }
266
267 std::cout<<"Reading transfer function "<<filename<<std::endl;
268
269 osg::TransferFunction1D::ColorMap colorMap;
270 osgDB::ifstream fin(foundFile.c_str());
271 while(fin)
272 {
273 float value, red, green, blue, alpha;
274 fin >> value >> red >> green >> blue >> alpha;
275 if (fin)
276 {
277 std::cout<<"value = "<<value<<" ("<<red<<", "<<green<<", "<<blue<<", "<<alpha<<")"<<std::endl;
278 colorMap[value] = osg::Vec4(red*colorScale,green*colorScale,blue*colorScale,alpha*colorScale);
279 }
280 }
281
282 if (colorMap.empty())
283 {
284 std::cout<<"Error: No values read from transfer function file: "<<filename<<std::endl;
285 return 0;
286 }
287
288 osg::TransferFunction1D* tf = new osg::TransferFunction1D;
289 tf->assign(colorMap);
290
291 return tf;
292 }
293
294 class FindTransferFunctionPropertyVisitor : public osgVolume::PropertyVisitor
295 {
296 public:
297
298 osg::ref_ptr<osgVolume::TransferFunctionProperty> _tfp;
299
300 #if 0
301 virtual void apply(osgVolume::SwitchProperty& sp)
302 {
303 OSG_NOTICE<<"Found SwitchProperty"<<std::endl;
304 apply(static_cast<osgVolume::CompositeProperty&>(sp));
305 }
306
307 virtual void apply(osgVolume::CompositeProperty& cp)
308 {
309 OSG_NOTICE<<"Found CompositeProperty"<<std::endl;
310 for(unsigned int i=0; i<cp.getNumProperties(); ++i)
311 {
312 cp.getProperty(i)->accept(*this);
313 }
314 }
315 #endif
apply(osgVolume::TransferFunctionProperty & tfp)316 virtual void apply(osgVolume::TransferFunctionProperty& tfp)
317 {
318 OSG_NOTICE<<"Found TransferFunctionProperty "<<&tfp<<std::endl;
319 _tfp = &tfp;
320 }
321 };
322
323
324 class InsertTransferFunctionPropertyVisitor : public osgVolume::PropertyVisitor
325 {
326 public:
327
InsertTransferFunctionPropertyVisitor(osg::TransferFunction1D * tf)328 InsertTransferFunctionPropertyVisitor(osg::TransferFunction1D* tf)
329 {
330 _tfp = new osgVolume::TransferFunctionProperty(tf);
331 }
332
333 osg::ref_ptr<osgVolume::TransferFunctionProperty> _tfp;
334
apply(osgVolume::SwitchProperty & sp)335 virtual void apply(osgVolume::SwitchProperty& sp)
336 {
337 OSG_NOTICE<<"Found SwitchProperty"<<std::endl;
338 for(unsigned int i=0; i<sp.getNumProperties(); ++i)
339 {
340 sp.getProperty(i)->accept(*this);
341 }
342 }
343
apply(osgVolume::CompositeProperty & cp)344 virtual void apply(osgVolume::CompositeProperty& cp)
345 {
346 OSG_NOTICE<<"Found CompositeProperty, inserting transfer function"<<std::endl;
347 if (_tfp.valid()) cp.addProperty(_tfp.get());
348 }
349 };
350
351 class FindVolumeTiles : public osg::NodeVisitor
352 {
353 public:
FindVolumeTiles()354 FindVolumeTiles(): osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {}
355
356 typedef std::vector< osg::ref_ptr<osgVolume::VolumeTile> > Tiles;
357 Tiles _tiles;
358
apply(osg::Group & group)359 void apply(osg::Group& group)
360 {
361 osgVolume::VolumeTile* tile = dynamic_cast<osgVolume::VolumeTile*>(&group);
362 if (tile) _tiles.push_back(tile);
363 else traverse(group);
364 }
365 };
366
367
368
369 class MyScriptCallback : public osg::CallbackObject
370 {
371 public:
MyScriptCallback(osg::ScriptEngine * se,osg::Script * script,const std::string & entryPoint)372 MyScriptCallback(osg::ScriptEngine* se, osg::Script* script, const std::string& entryPoint) : _scriptEngine(se), _script(script) { setName(entryPoint); }
373
run(osg::Object * object,osg::Parameters & inputParameters,osg::Parameters & outputParameters) const374 virtual bool run(osg::Object* object, osg::Parameters& inputParameters, osg::Parameters& outputParameters) const
375 {
376 inputParameters.insert(inputParameters.begin(), object);
377 return _scriptEngine->run(_script.get(), getName(), inputParameters, outputParameters);
378 }
379
380 osg::ref_ptr<osg::ScriptEngine> _scriptEngine;
381 osg::ref_ptr<osg::Script> _script;
382 };
383
384 class MyClass : public osg::Object
385 {
386 public:
MyClass()387 MyClass() {}
MyClass(const MyClass & rhs,const osg::CopyOp copyop=osg::CopyOp::SHALLOW_COPY)388 MyClass(const MyClass& rhs, const osg::CopyOp copyop=osg::CopyOp::SHALLOW_COPY):osg::Object(rhs,copyop) {}
META_Object(local,MyClass)389 META_Object(local,MyClass)
390
391 void myMethod()
392 {
393 osg::CallbackObject* co = osg::getCallbackObject(this, "myMethod");
394 if (co) co->run(this);
395 else myMethodImplementation();
396 }
397
myMethodImplementation()398 virtual void myMethodImplementation()
399 {
400 OSG_NOTICE<<"MyClass::myMethodImplementation()"<<std::endl;
401 }
402 };
403
404
main(int argc,char ** argv)405 int main(int argc, char ** argv)
406 {
407 osg::ArgumentParser arguments(&argc, argv);
408
409 #if 0
410
411 osg::ref_ptr<MyClass> myobject = new MyClass;
412 myobject->getOrCreateUserDataContainer()->addUserObject(new osg::CallbackObject("myMethod"));
413 myobject->myMethod();
414
415 osg::ref_ptr<osg::ScriptEngine> se = osgDB::readFile<osg::ScriptEngine>("ScriptEngine.lua");
416 osg::ref_ptr<osg::Script> script = osgDB::readFile<osg::Script>("script.lua");
417
418 osg::ref_ptr<MyClass> copyobject = new MyClass;
419 copyobject->getOrCreateUserDataContainer()->addUserObject(new MyScriptCallback(se.get(), script.get(), "myMethod"));
420 copyobject->myMethod();
421 #endif
422
423 #if 0
424 osg::ref_ptr<osg::Object> object = osgDB::readNodeFile("load.lua");
425 if (object.valid())
426 {
427 osg::CallbackObject* co = osg::getCallbackObject(object.get(), "method");
428 OSG_NOTICE<<"Have object = "<<object->className()<<std::endl;
429 OSG_NOTICE<<"Have co = "<<co<<std::endl;
430 if (co)
431 {
432 osg::Parameters inputParameters, outputParameters;
433 inputParameters.push_back(new osg::StringValueObject("string",std::string("all there is to celeberate is here.")));
434 co->run(object.get(), inputParameters, outputParameters);
435 OSG_NOTICE<<"outputParameters.size()="<<outputParameters.size()<<std::endl;
436 for(osg::Parameters::iterator itr = outputParameters.begin();
437 itr != outputParameters.end();
438 ++itr)
439 {
440 OSG_NOTICE<<" returned "<<(*itr)->className()<<std::endl;
441 }
442 }
443 }
444 return 0;
445 #endif
446
447
448 #if 1
449 osgViewer::Viewer viewer(arguments);
450
451 viewer.addEventHandler(new osgViewer::StatsHandler());
452
453 osg::ref_ptr<osg::TransferFunction1D> tf;
454 std::string filename;
455 if (arguments.read("--tf",filename))
456 {
457 tf = readTransferFunctionFile(filename, 1.0f);
458 }
459 if (arguments.read("--tf-255",filename))
460 {
461 tf = readTransferFunctionFile(filename,1.0f/255.0f);
462 }
463
464 bool createHistorgram = arguments.read("--histogram");
465
466 #if 0
467 for(int i=1; i<arguments.argc(); ++i)
468 {
469 if (!arguments.isOption(i))
470 {
471 osg::ref_ptr<osg::Image> image;
472 osg::ref_ptr<osgVolume::Volume> volume;
473 osg::ref_ptr<osgVolume::VolumeTile> volumeTile;
474
475 std::string filename = arguments[i];
476 osgDB::FileType fileType = osgDB::fileType(foundFile);
477
478 if (fileType == osgDB::DIRECTORY)
479 {
480 osg::ref_ptr<osg::Image> image = osgDB::readImageFile(foundFile+".dicom", options.get());
481 }
482 else if (fileType == osgDB::REGULAR_FILE)
483 {
484 std::string ext = osgDB::getFileExtension(foundFile);
485 if (ext=="osg" || ext=="ive" || ext=="osgx" || ext=="osgb" || ext=="osgt")
486 {
487 osg::ref_ptr<osg::Object> obj = osgDB::readObjectFile(foundFile);
488 image = dynamic_cast<osg::Image*>(obj.get());
489 volume = dynamic_cast<osgVolume::Volume*>(obj.get());
490 volumeTile = dynamic_cast<osgVolume::VolumeTile*>(obj.get());
491 }
492 else
493 {
494 image = osgDB::readImageFile( foundFile );
495 }
496 }
497 else
498 {
499 // not found image, so fallback to plugins/callbacks to find the model.
500 image = osgDB::readImageFile( filename);
501 }
502
503 if (image.valid())
504 {
505 volumeTile = new osgVolume::VolumeTile;
506 }
507
508 }
509 OSG_NOTICE<<"Argument "<<i<<" "<<arguments[i]<<std::endl;
510 }
511
512 return 1;
513 #else
514
515 osg::ref_ptr<osg::Node> model = osgDB::readRefNodeFiles(arguments);
516 #endif
517 typedef std::vector< osg::ref_ptr<osg::Node> > Nodes;
518 Nodes nodes;
519
520 if (!model && !tf)
521 {
522 OSG_NOTICE<<"Please specify dataset on command line."<<std::endl;
523 return 1;
524 }
525
526 osgVolume::ImageLayer* imageLayer = 0;
527
528
529 if (model.valid())
530 {
531 osg::ref_ptr<osgVolume::VolumeTile> volumeTile = dynamic_cast<osgVolume::VolumeTile*>(model.get());
532 if (volumeTile.valid())
533 {
534 OSG_NOTICE<<"Inserting Volume above VolumeTile."<<std::endl;
535 osg::ref_ptr<osgVolume::Volume> volume = new osgVolume::Volume;
536 volume->addChild(model.get());
537 model = volume.get();
538
539 // volumeTile->setVolumeTechnique(new osgVolume::RayTracedTechnique);
540 // volumeTile->setVolumeTechnique(new osgVolume::FixedFunctionTechnique);
541 }
542
543 nodes.push_back(model.get());
544
545 FindVolumeTiles fvt;
546 model->accept(fvt);
547
548 if (!fvt._tiles.empty())
549 {
550 osgVolume::VolumeTile* tile = fvt._tiles[0].get();
551 imageLayer = dynamic_cast<osgVolume::ImageLayer*>(tile->getLayer());
552 tile->addEventCallback(new osgVolume::PropertyAdjustmentCallback());
553 }
554 }
555
556
557 if (createHistorgram && imageLayer)
558 {
559 Histogram histogram;
560 histogram.analyse(imageLayer->getImage());
561 nodes.push_back(histogram.createGraphicalRepresentation());
562 }
563
564 if (imageLayer)
565 {
566 osgVolume::Property* property = imageLayer->getProperty();
567 if (property)
568 {
569 FindTransferFunctionPropertyVisitor ftfpv;
570 property->accept(ftfpv);
571
572 if (ftfpv._tfp.valid())
573 {
574 if (tf.valid())
575 {
576 OSG_NOTICE<<"Need to replace volumes transfer function"<<std::endl;
577 ftfpv._tfp->setTransferFunction(tf.get());
578 }
579 else
580 {
581 OSG_NOTICE<<"Using volumes transfer function"<<std::endl;
582 tf = dynamic_cast<osg::TransferFunction1D*>(ftfpv._tfp->getTransferFunction());
583 }
584 }
585 else if (tf.valid())
586 {
587 // No existing transfer function but need to assign one
588 OSG_NOTICE<<"Need to assign transfer function to CompositeProperty"<<std::endl;
589 InsertTransferFunctionPropertyVisitor itfpv(tf.get());
590 property->accept(itfpv);
591 }
592 }
593 else if (tf.valid())
594 {
595 OSG_NOTICE<<"Assign transfer function directly"<<std::endl;
596 imageLayer->setProperty(new osgVolume::TransferFunctionProperty(tf.get()));
597 }
598 }
599
600 if (tf.valid())
601 {
602 osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform;
603 transform->setMatrix(osg::Matrix::scale(2.0,1.0,1.0)*osg::Matrix::rotate(osg::DegreesToRadians(90.0), osg::Vec3d(1.0,0.0,0.0)));
604 transform->addChild(new osgUI::TransferFunctionWidget(tf.get()));
605 nodes.push_back(transform.get());
606 }
607
608 if (nodes.empty())
609 {
610 OSG_NOTICE<<"Please specify dataset on command line."<<std::endl;
611 return 1;
612 }
613
614 if (nodes.size()==1)
615 {
616 viewer.setSceneData(nodes[0].get());
617 }
618 else
619 {
620 osg::Vec3d position(0.0,0.0,0.0);
621
622 osg::ref_ptr<osg::Group> group = new osg::Group;
623
624 for(Nodes::iterator itr = nodes.begin();
625 itr != nodes.end();
626 ++itr)
627 {
628 osg::ref_ptr<osg::Node> child = *itr;
629 if (!child) continue;
630 #if 0
631 osg::ComputeBoundsVisitor cbv;
632 child->accept(cbv);
633
634 osg::BoundingBox bb = cbv.getBoundingBox();
635 double scale = 1.0/(bb.xMax()-bb.xMin());
636 #endif
637 osg::BoundingSphere bb = child->getBound();
638 double scale = 0.7/bb.radius();
639
640 osg::ref_ptr<osg::PositionAttitudeTransform> pat = new osg::PositionAttitudeTransform;
641 pat->addChild(child.get());
642 pat->setPosition(position);
643 pat->setPivotPoint(bb.center());
644 pat->setScale(osg::Vec3d(scale, scale, scale));
645 position.x() += 1.1;
646
647 group->addChild(pat.get());
648 }
649
650 viewer.setSceneData(group.get());
651 }
652
653 OSG_NOTICE<<"Reading to run viewer"<<std::endl;
654
655 osgDB::writeNodeFile(*viewer.getSceneData(),"graph.osgt");
656
657 return viewer.run();
658 #endif
659
660 }
661