1 #include <osg/ClusterCullingCallback>
2 #include <osgDB/ReadFile>
3 #include <osgDB/WriteFile>
4 #include <string.h>
5 #include <iostream>
6 #include <sstream>
7 #include "PosterPrinter.h"
8
9 /* PagedLoadingCallback: Callback for loading paged nodes while doing intersecting test */
10 struct PagedLoadingCallback : public osgUtil::IntersectionVisitor::ReadCallback
11 {
readNodeFilePagedLoadingCallback12 virtual osg::Node* readNodeFile( const std::string& filename )
13 {
14 return osgDB::readRefNodeFile( filename ).release();
15 }
16 };
17 static osg::ref_ptr<PagedLoadingCallback> g_pagedLoadingCallback = new PagedLoadingCallback;
18
19 /* LodCullingCallback: Callback for culling LODs and selecting the highest level */
20 class LodCullingCallback : public osg::NodeCallback
21 {
22 public:
operator ()(osg::Node * node,osg::NodeVisitor * nv)23 virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
24 {
25 osg::LOD* lod = static_cast<osg::LOD*>(node);
26 if ( lod && lod->getNumChildren()>0 )
27 lod->getChild(lod->getNumChildren()-1)->accept(*nv);
28 }
29 };
30 static osg::ref_ptr<LodCullingCallback> g_lodCullingCallback = new LodCullingCallback;
31
32 /* PagedCullingCallback: Callback for culling paged nodes and selecting the highest level */
33 class PagedCullingCallback : public osg::NodeCallback
34 {
35 public:
operator ()(osg::Node * node,osg::NodeVisitor * nv)36 virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
37 {
38 osg::PagedLOD* pagedLOD = static_cast<osg::PagedLOD*>(node);
39 if ( pagedLOD && pagedLOD->getNumChildren()>0 )
40 {
41 unsigned int numChildren = pagedLOD->getNumChildren();
42 bool updateTimeStamp = nv->getVisitorType()==osg::NodeVisitor::CULL_VISITOR;
43 if ( nv->getFrameStamp() && updateTimeStamp )
44 {
45 double timeStamp = nv->getFrameStamp()?nv->getFrameStamp()->getReferenceTime():0.0;
46 unsigned int frameNumber = nv->getFrameStamp()?nv->getFrameStamp()->getFrameNumber():0;
47
48 pagedLOD->setFrameNumberOfLastTraversal( frameNumber );
49 pagedLOD->setTimeStamp( numChildren-1, timeStamp );
50 pagedLOD->setFrameNumber( numChildren-1, frameNumber );
51 pagedLOD->getChild(numChildren-1)->accept(*nv);
52 }
53
54 // Request for new child
55 if ( !pagedLOD->getDisableExternalChildrenPaging() &&
56 nv->getDatabaseRequestHandler() &&
57 numChildren<pagedLOD->getNumRanges() )
58 {
59 if ( pagedLOD->getDatabasePath().empty() )
60 {
61 nv->getDatabaseRequestHandler()->requestNodeFile(
62 pagedLOD->getFileName(numChildren), nv->getNodePath(),
63 1.0, nv->getFrameStamp(),
64 pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() );
65 }
66 else
67 {
68 nv->getDatabaseRequestHandler()->requestNodeFile(
69 pagedLOD->getDatabasePath()+pagedLOD->getFileName(numChildren), nv->getNodePath(),
70 1.0, nv->getFrameStamp(),
71 pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() );
72 }
73 }
74 }
75 //node->traverse(*nv);
76 }
77 };
78 static osg::ref_ptr<PagedCullingCallback> g_pagedCullingCallback = new PagedCullingCallback;
79
80 /* PosterVisitor: A visitor for adding culling callbacks to newly allocated paged nodes */
PosterVisitor()81 PosterVisitor::PosterVisitor()
82 : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
83 _appliedCount(0), _needToApplyCount(0),
84 _addingCallbacks(true)
85 {
86 }
87
apply(osg::LOD & node)88 void PosterVisitor::apply( osg::LOD& node )
89 {
90 /*if ( !hasCullCallback(node.getCullCallback(), g_lodCullingCallback.get()) )
91 {
92 if ( !node.getName().empty() )
93 {
94 PagedNodeNameSet::iterator itr = _pagedNodeNames.find( node.getName() );
95 if ( itr!=_pagedNodeNames.end() )
96 {
97 insertCullCallback( node, g_lodCullingCallback.get() );
98 _appliedCount++;
99 }
100 }
101 }
102 else if ( !_addingCallbacks )
103 {
104 node.removeCullCallback( g_lodCullingCallback.get() );
105 _appliedCount--;
106 }*/
107 traverse( node );
108 }
109
apply(osg::PagedLOD & node)110 void PosterVisitor::apply( osg::PagedLOD& node )
111 {
112 if ( !hasCullCallback(node.getCullCallback(), g_pagedCullingCallback.get()) )
113 {
114 for ( unsigned int i=0; i<node.getNumFileNames(); ++i )
115 {
116 if ( node.getFileName(i).empty() ) continue;
117
118 PagedNodeNameSet::iterator itr = _pagedNodeNames.find( node.getFileName(i) );
119 if ( itr!=_pagedNodeNames.end() )
120 {
121 node.addCullCallback( g_pagedCullingCallback.get() );
122 _appliedCount++;
123 }
124 break;
125 }
126 }
127 else if ( !_addingCallbacks )
128 {
129 node.removeCullCallback( g_pagedCullingCallback.get() );
130 if ( _appliedCount>0 ) _appliedCount--;
131 }
132 traverse( node );
133 }
134
135 /* PosterIntersector: A simple polytope intersector for updating pagedLODs in each image-tile */
PosterIntersector(const osg::Polytope & polytope)136 PosterIntersector::PosterIntersector( const osg::Polytope& polytope )
137 : _intersectionVisitor(0), _parent(0), _polytope(polytope)
138 {}
139
PosterIntersector(double xMin,double yMin,double xMax,double yMax)140 PosterIntersector::PosterIntersector( double xMin, double yMin, double xMax, double yMax )
141 : Intersector(osgUtil::Intersector::PROJECTION),
142 _intersectionVisitor(0), _parent(0)
143 {
144 _polytope.add( osg::Plane( 1.0, 0.0, 0.0,-xMin) );
145 _polytope.add( osg::Plane(-1.0, 0.0, 0.0, xMax) );
146 _polytope.add( osg::Plane( 0.0, 1.0, 0.0,-yMin) );
147 _polytope.add( osg::Plane( 0.0,-1.0, 0.0, yMax) );
148 }
149
clone(osgUtil::IntersectionVisitor & iv)150 osgUtil::Intersector* PosterIntersector::clone( osgUtil::IntersectionVisitor& iv )
151 {
152 osg::Matrix matrix;
153 if ( iv.getProjectionMatrix() ) matrix.preMult( *iv.getProjectionMatrix() );
154 if ( iv.getViewMatrix() ) matrix.preMult( *iv.getViewMatrix() );
155 if ( iv.getModelMatrix() ) matrix.preMult( *iv.getModelMatrix() );
156
157 osg::Polytope transformedPolytope;
158 transformedPolytope.setAndTransformProvidingInverse( _polytope, matrix );
159
160 osg::ref_ptr<PosterIntersector> pi = new PosterIntersector( transformedPolytope );
161 pi->_intersectionVisitor = &iv;
162 pi->_parent = this;
163 return pi.release();
164 }
165
enter(const osg::Node & node)166 bool PosterIntersector::enter( const osg::Node& node )
167 {
168 if ( !node.isCullingActive() ) return true;
169 if ( _polytope.contains(node.getBound()) )
170 {
171 if ( node.getCullCallback() )
172 {
173 const osg::ClusterCullingCallback* cccb =
174 dynamic_cast<const osg::ClusterCullingCallback*>( node.getCullCallback() );
175 if ( cccb && cccb->cull(_intersectionVisitor, 0, NULL) ) return false;
176 }
177 return true;
178 }
179 return false;
180 }
181
reset()182 void PosterIntersector::reset()
183 {
184 _intersectionVisitor = NULL;
185 Intersector::reset();
186 }
187
intersect(osgUtil::IntersectionVisitor & iv,osg::Drawable * drawable)188 void PosterIntersector::intersect( osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable )
189 {
190 if ( !_polytope.contains(drawable->getBoundingBox()) ) return;
191 if ( iv.getDoDummyTraversal() ) return;
192
193 // Find and collect all paged LODs in the node path
194 osg::NodePath& nodePath = iv.getNodePath();
195 for ( osg::NodePath::iterator itr=nodePath.begin(); itr!=nodePath.end(); ++itr )
196 {
197 osg::PagedLOD* pagedLOD = dynamic_cast<osg::PagedLOD*>(*itr);
198 if ( pagedLOD )
199 {
200 // FIXME: The first non-empty getFileName() is used as the identity of this paged node.
201 // This should work with VPB-generated terrains but maybe unusable with others.
202 for ( unsigned int i=0; i<pagedLOD->getNumFileNames(); ++i )
203 {
204 if ( pagedLOD->getFileName(i).empty() ) continue;
205 if ( _parent->_visitor.valid() )
206 _parent->_visitor->insertName( pagedLOD->getFileName(i) );
207 break;
208 }
209 continue;
210 }
211
212 /*osg::LOD* lod = dynamic_cast<osg::LOD*>(*itr);
213 if ( lod )
214 {
215 if ( !lod->getName().empty() && _parent->_visitor.valid() )
216 _parent->_visitor->insertName( lod->getName() );
217 }*/
218 }
219 }
220
221 /* PosterPrinter: The implementation class of high-res rendering */
PosterPrinter()222 PosterPrinter::PosterPrinter():
223 _outputTiles(false), _outputTileExt("bmp"),
224 _isRunning(false), _isFinishing(false), _lastBindingFrame(0),
225 _currentRow(0), _currentColumn(0),
226 _camera(0), _finalPoster(0)
227 {
228 _intersector = new PosterIntersector(-1.0, -1.0, 1.0, 1.0);
229 _visitor = new PosterVisitor;
230 _intersector->setPosterVisitor( _visitor.get() );
231 }
232
init(const osg::Camera * camera)233 void PosterPrinter::init( const osg::Camera* camera )
234 {
235 if ( _camera.valid() )
236 init( camera->getViewMatrix(), camera->getProjectionMatrix() );
237 }
238
init(const osg::Matrixd & view,const osg::Matrixd & proj)239 void PosterPrinter::init( const osg::Matrixd& view, const osg::Matrixd& proj )
240 {
241 if ( _isRunning ) return;
242 _images.clear();
243 _visitor->clearNames();
244 _tileRows = (int)(_posterSize.y() / _tileSize.y());
245 _tileColumns = (int)(_posterSize.x() / _tileSize.x());
246 _currentRow = 0;
247 _currentColumn = 0;
248 _currentViewMatrix = view;
249 _currentProjectionMatrix = proj;
250 _lastBindingFrame = 0;
251 _isRunning = true;
252 _isFinishing = false;
253 }
254
frame(const osg::FrameStamp * fs,osg::Node * node)255 void PosterPrinter::frame( const osg::FrameStamp* fs, osg::Node* node )
256 {
257 // Add cull callbacks to all existing paged nodes,
258 // and advance frame when all callbacks are dispatched.
259 if ( addCullCallbacks(fs, node) )
260 return;
261
262 if ( _isFinishing )
263 {
264 if ( (fs->getFrameNumber()-_lastBindingFrame)>2 )
265 {
266 // Record images and the final poster
267 recordImages();
268 if ( _finalPoster.valid() )
269 {
270 std::cout << "Writing final result to file..." << std::endl;
271 osgDB::writeImageFile( *_finalPoster, _outputPosterName );
272 }
273
274 // Release all cull callbacks to free unused paged nodes
275 removeCullCallbacks( node );
276 _visitor->clearNames();
277
278 _isFinishing = false;
279 std::cout << "Recording images finished." << std::endl;
280 }
281 }
282
283 if ( _isRunning )
284 {
285 // Every "copy-to-image" process seems to be finished in 2 frames.
286 // So record them and dispatch camera to next tiles.
287 if ( (fs->getFrameNumber()-_lastBindingFrame)>2 )
288 {
289 // Record images and unref them to free memory
290 recordImages();
291
292 // Release all cull callbacks to free unused paged nodes
293 removeCullCallbacks( node );
294 _visitor->clearNames();
295
296 if ( _camera.valid() )
297 {
298 std::cout << "Binding sub-camera " << _currentRow << "_" << _currentColumn
299 << " to image..." << std::endl;
300 bindCameraToImage( _camera.get(), _currentRow, _currentColumn );
301 if ( _currentColumn<_tileColumns-1 )
302 {
303 _currentColumn++;
304 }
305 else
306 {
307 if ( _currentRow<_tileRows-1 )
308 {
309 _currentRow++;
310 _currentColumn = 0;
311 }
312 else
313 {
314 _isRunning = false;
315 _isFinishing = true;
316 }
317 }
318 }
319 _lastBindingFrame = fs->getFrameNumber();
320 }
321 }
322 }
323
addCullCallbacks(const osg::FrameStamp * fs,osg::Node * node)324 bool PosterPrinter::addCullCallbacks( const osg::FrameStamp* fs, osg::Node* node )
325 {
326 if ( !_visitor->inQueue() || done() )
327 return false;
328
329 _visitor->setAddingCallbacks( true );
330 _camera->accept( *_visitor );
331 _lastBindingFrame = fs->getFrameNumber();
332
333 std::cout << "Dispatching callbacks to paged nodes... "
334 << _visitor->inQueue() << std::endl;
335 return true;
336 }
337
removeCullCallbacks(osg::Node * node)338 void PosterPrinter::removeCullCallbacks( osg::Node* node )
339 {
340 _visitor->setAddingCallbacks( false );
341 _camera->accept( *_visitor );
342 }
343
bindCameraToImage(osg::Camera * camera,int row,int col)344 void PosterPrinter::bindCameraToImage( osg::Camera* camera, int row, int col )
345 {
346 std::stringstream stream;
347 stream << "image_" << row << "_" << col;
348
349 osg::ref_ptr<osg::Image> image = new osg::Image;
350 image->setName( stream.str() );
351 image->allocateImage( (int)_tileSize.x(), (int)_tileSize.y(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
352 _images[TilePosition(row,col)] = image.get();
353
354 // Calculate projection matrix offset of each tile
355 osg::Matrix offsetMatrix =
356 osg::Matrix::scale(_tileColumns, _tileRows, 1.0) *
357 osg::Matrix::translate(_tileColumns-1-2*col, _tileRows-1-2*row, 0.0);
358 camera->setViewMatrix( _currentViewMatrix );
359 camera->setProjectionMatrix( _currentProjectionMatrix * offsetMatrix );
360
361 // Check intersections between the image-tile box and the model
362 osgUtil::IntersectionVisitor iv( _intersector.get() );
363 iv.setReadCallback( g_pagedLoadingCallback.get() );
364 _intersector->reset();
365 camera->accept( iv );
366 if ( _intersector->containsIntersections() )
367 {
368 // Apply a cull callback to every paged node obtained, to force the highest level displaying.
369 // This will be done by the PosterVisitor, who already records all the paged nodes.
370 }
371
372 // Reattach cameras and new allocated images
373 camera->setRenderingCache( NULL ); // FIXME: Uses for reattaching camera with image, maybe inefficient?
374 camera->detach( osg::Camera::COLOR_BUFFER );
375 camera->attach( osg::Camera::COLOR_BUFFER, image.get(), 0, 0 );
376 }
377
recordImages()378 void PosterPrinter::recordImages()
379 {
380 for ( TileImages::iterator itr=_images.begin(); itr!=_images.end(); ++itr )
381 {
382 osg::Image* image = (itr->second).get();
383 if ( _finalPoster.valid() )
384 {
385 // FIXME: A stupid way to combine tile images to final result. Any better ideas?
386 unsigned int row = itr->first.first, col = itr->first.second;
387 for ( int t=0; t<image->t(); ++t )
388 {
389 unsigned char* source = image->data( 0, t );
390 unsigned char* target = _finalPoster->data( col*(int)_tileSize.x(), t + row*(int)_tileSize.y() );
391 memcpy( target, source, image->s() * 4 * sizeof(unsigned char) );
392 }
393 }
394
395 if ( _outputTiles )
396 osgDB::writeImageFile( *image, image->getName()+"."+_outputTileExt );
397 }
398 _images.clear();
399 }
400