1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // This file is part of the "Irrlicht Engine".
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
4 
5 #include "COctreeSceneNode.h"
6 #include "Octree.h"
7 #include "ISceneManager.h"
8 #include "IVideoDriver.h"
9 #include "ICameraSceneNode.h"
10 #include "IMeshCache.h"
11 #include "IAnimatedMesh.h"
12 #include "IMaterialRenderer.h"
13 #include "os.h"
14 #include "CShadowVolumeSceneNode.h"
15 
16 namespace irr
17 {
18 namespace scene
19 {
20 
21 
22 //! constructor
COctreeSceneNode(ISceneNode * parent,ISceneManager * mgr,s32 id,s32 minimalPolysPerNode)23 COctreeSceneNode::COctreeSceneNode(ISceneNode* parent, ISceneManager* mgr,
24 					 s32 id, s32 minimalPolysPerNode)
25 	: IMeshSceneNode(parent, mgr, id), StdOctree(0), LightMapOctree(0),
26 	TangentsOctree(0), VertexType((video::E_VERTEX_TYPE)-1),
27 	MinimalPolysPerNode(minimalPolysPerNode), Mesh(0), Shadow(0),
28 	UseVBOs(OCTREE_USE_HARDWARE), UseVisibilityAndVBOs(OCTREE_USE_VISIBILITY),
29 	BoxBased(OCTREE_BOX_BASED)
30 {
31 #ifdef _DEBUG
32 	setDebugName("COctreeSceneNode");
33 #endif
34 }
35 
36 
37 //! destructor
~COctreeSceneNode()38 COctreeSceneNode::~COctreeSceneNode()
39 {
40 	if (Shadow)
41 		Shadow->drop();
42 	deleteTree();
43 }
44 
45 
OnRegisterSceneNode()46 void COctreeSceneNode::OnRegisterSceneNode()
47 {
48 	if (IsVisible)
49 	{
50 		// because this node supports rendering of mixed mode meshes consisting of
51 		// transparent and solid material at the same time, we need to go through all
52 		// materials, check of what type they are and register this node for the right
53 		// render pass according to that.
54 
55 		video::IVideoDriver* driver = SceneManager->getVideoDriver();
56 
57 		PassCount = 0;
58 		u32 transparentCount = 0;
59 		u32 solidCount = 0;
60 
61 		// count transparent and solid materials in this scene node
62 		for (u32 i=0; i<Materials.size(); ++i)
63 		{
64 			const video::IMaterialRenderer* const rnd =
65 				driver->getMaterialRenderer(Materials[i].MaterialType);
66 
67 			if (rnd && rnd->isTransparent())
68 				++transparentCount;
69 			else
70 				++solidCount;
71 
72 			if (solidCount && transparentCount)
73 				break;
74 		}
75 
76 		// register according to material types counted
77 
78 		if (solidCount)
79 			SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
80 
81 		if (transparentCount)
82 			SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
83 
84 		ISceneNode::OnRegisterSceneNode();
85 	}
86 }
87 
88 
89 //! renders the node.
render()90 void COctreeSceneNode::render()
91 {
92 	video::IVideoDriver* driver = SceneManager->getVideoDriver();
93 
94 	if (VertexType == -1 || !driver)
95 		return;
96 
97 	ICameraSceneNode* camera = SceneManager->getActiveCamera();
98 	if (!camera)
99 		return;
100 
101 	bool isTransparentPass =
102 		SceneManager->getSceneNodeRenderPass() == scene::ESNRP_TRANSPARENT;
103 	++PassCount;
104 
105 	driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
106 
107 	if (Shadow)
108 		Shadow->updateShadowVolumes();
109 
110 	SViewFrustum frust = *camera->getViewFrustum();
111 
112 	//transform the frustum to the current absolute transformation
113 	if ( !AbsoluteTransformation.isIdentity() )
114 	{
115 		core::matrix4 invTrans(AbsoluteTransformation, core::matrix4::EM4CONST_INVERSE);
116 		frust.transform(invTrans);
117 	}
118 
119 	const core::aabbox3d<float> &box = frust.getBoundingBox();
120 
121 	switch (VertexType)
122 	{
123 	case video::EVT_STANDARD:
124 		{
125 			if (BoxBased)
126 				StdOctree->calculatePolys(box);
127 			else
128 				StdOctree->calculatePolys(frust);
129 
130 			const Octree<video::S3DVertex>::SIndexData* d = StdOctree->getIndexData();
131 
132 			for (u32 i=0; i<Materials.size(); ++i)
133 			{
134 				if ( 0 == d[i].CurrentSize )
135 					continue;
136 
137 				const video::IMaterialRenderer* const rnd = driver->getMaterialRenderer(Materials[i].MaterialType);
138 				const bool transparent = (rnd && rnd->isTransparent());
139 
140 				// only render transparent buffer if this is the transparent render pass
141 				// and solid only in solid pass
142 				if (transparent == isTransparentPass)
143 				{
144 					driver->setMaterial(Materials[i]);
145 					driver->drawIndexedTriangleList(
146 						&StdMeshes[i].Vertices[0], StdMeshes[i].Vertices.size(),
147 						d[i].Indices, d[i].CurrentSize / 3);
148 				}
149 			}
150 
151 			// for debug purposes only
152 			if (DebugDataVisible && !Materials.empty() && PassCount==1)
153 			{
154 				const core::aabbox3df& box = frust.getBoundingBox();
155 				core::array< const core::aabbox3d<f32>* > boxes;
156 				video::SMaterial m;
157 				m.Lighting = false;
158 				driver->setMaterial(m);
159 				if ( DebugDataVisible & scene::EDS_BBOX_BUFFERS )
160 				{
161 					StdOctree->getBoundingBoxes(box, boxes);
162 					for (u32 b=0; b!=boxes.size(); ++b)
163 						driver->draw3DBox(*boxes[b]);
164 				}
165 
166 				if ( DebugDataVisible & scene::EDS_BBOX )
167 					driver->draw3DBox(Box,video::SColor(0,255,0,0));
168 			}
169 		}
170 		break;
171 	case video::EVT_2TCOORDS:
172 		{
173 			if (BoxBased)
174 				LightMapOctree->calculatePolys(box);
175 			else
176 				LightMapOctree->calculatePolys(frust);
177 
178 			const Octree<video::S3DVertex2TCoords>::SIndexData* d = LightMapOctree->getIndexData();
179 
180 			for (u32 i=0; i<Materials.size(); ++i)
181 			{
182 				if ( 0 == d[i].CurrentSize )
183 					continue;
184 
185 				const video::IMaterialRenderer* const rnd = driver->getMaterialRenderer(Materials[i].MaterialType);
186 				const bool transparent = (rnd && rnd->isTransparent());
187 
188 				// only render transparent buffer if this is the transparent render pass
189 				// and solid only in solid pass
190 				if (transparent == isTransparentPass)
191 				{
192 					driver->setMaterial(Materials[i]);
193 					if (UseVBOs)
194 					{
195 						if (UseVisibilityAndVBOs)
196 						{
197 							u16* oldPointer = LightMapMeshes[i].Indices.pointer();
198 							const u32 oldSize = LightMapMeshes[i].Indices.size();
199 							LightMapMeshes[i].Indices.set_free_when_destroyed(false);
200 							LightMapMeshes[i].Indices.set_pointer(d[i].Indices, d[i].CurrentSize, false, false);
201 							LightMapMeshes[i].setDirty(scene::EBT_INDEX);
202 							driver->drawMeshBuffer ( &LightMapMeshes[i] );
203 							LightMapMeshes[i].Indices.set_pointer(oldPointer, oldSize);
204 							LightMapMeshes[i].setDirty(scene::EBT_INDEX);
205 						}
206 						else
207 							driver->drawMeshBuffer ( &LightMapMeshes[i] );
208 					}
209 					else
210 						driver->drawIndexedTriangleList(
211 							&LightMapMeshes[i].Vertices[0],
212 							LightMapMeshes[i].Vertices.size(),
213 							d[i].Indices, d[i].CurrentSize / 3);
214 				}
215 			}
216 
217 			// for debug purposes only
218 			if (DebugDataVisible && !Materials.empty() && PassCount==1)
219 			{
220 				const core::aabbox3d<float> &box = frust.getBoundingBox();
221 				core::array< const core::aabbox3d<f32>* > boxes;
222 				video::SMaterial m;
223 				m.Lighting = false;
224 				driver->setMaterial(m);
225 				if ( DebugDataVisible & scene::EDS_BBOX_BUFFERS )
226 				{
227 					LightMapOctree->getBoundingBoxes(box, boxes);
228 					for (u32 b=0; b<boxes.size(); ++b)
229 						driver->draw3DBox(*boxes[b]);
230 				}
231 
232 				if ( DebugDataVisible & scene::EDS_BBOX )
233 					driver->draw3DBox(Box,video::SColor(0,255,0,0));
234 			}
235 		}
236 		break;
237 	case video::EVT_TANGENTS:
238 		{
239 			if (BoxBased)
240 				TangentsOctree->calculatePolys(box);
241 			else
242 				TangentsOctree->calculatePolys(frust);
243 
244 			const Octree<video::S3DVertexTangents>::SIndexData* d =  TangentsOctree->getIndexData();
245 
246 			for (u32 i=0; i<Materials.size(); ++i)
247 			{
248 				if ( 0 == d[i].CurrentSize )
249 					continue;
250 
251 				const video::IMaterialRenderer* const rnd = driver->getMaterialRenderer(Materials[i].MaterialType);
252 				const bool transparent = (rnd && rnd->isTransparent());
253 
254 				// only render transparent buffer if this is the transparent render pass
255 				// and solid only in solid pass
256 				if (transparent == isTransparentPass)
257 				{
258 					driver->setMaterial(Materials[i]);
259 					driver->drawIndexedTriangleList(
260 						&TangentsMeshes[i].Vertices[0], TangentsMeshes[i].Vertices.size(),
261 						d[i].Indices, d[i].CurrentSize / 3);
262 				}
263 			}
264 
265 			// for debug purposes only
266 			if (DebugDataVisible && !Materials.empty() && PassCount==1)
267 			{
268 				const core::aabbox3d<float> &box = frust.getBoundingBox();
269 				core::array< const core::aabbox3d<f32>* > boxes;
270 				video::SMaterial m;
271 				m.Lighting = false;
272 				driver->setMaterial(m);
273 				if ( DebugDataVisible & scene::EDS_BBOX_BUFFERS )
274 				{
275 					TangentsOctree->getBoundingBoxes(box, boxes);
276 					for (u32 b=0; b<boxes.size(); ++b)
277 						driver->draw3DBox(*boxes[b]);
278 				}
279 
280 				if ( DebugDataVisible & scene::EDS_BBOX )
281 					driver->draw3DBox(Box,video::SColor(0,255,0,0));
282 			}
283 		}
284 		break;
285 	}
286 }
287 
288 
289 //! Removes a child from this scene node.
290 //! Implemented here, to be able to remove the shadow properly, if there is one,
291 //! or to remove attached childs.
removeChild(ISceneNode * child)292 bool COctreeSceneNode::removeChild(ISceneNode* child)
293 {
294 	if (child && Shadow == child)
295 	{
296 		Shadow->drop();
297 		Shadow = 0;
298 	}
299 
300 	return ISceneNode::removeChild(child);
301 }
302 
303 
304 //! Creates shadow volume scene node as child of this node
305 //! and returns a pointer to it.
addShadowVolumeSceneNode(const IMesh * shadowMesh,s32 id,bool zfailmethod,f32 infinity)306 IShadowVolumeSceneNode* COctreeSceneNode::addShadowVolumeSceneNode(
307 		const IMesh* shadowMesh, s32 id, bool zfailmethod, f32 infinity)
308 {
309 	if (!SceneManager->getVideoDriver()->queryFeature(video::EVDF_STENCIL_BUFFER))
310 		return 0;
311 
312 	if (!shadowMesh)
313 		shadowMesh = Mesh; // if null is given, use the mesh of node
314 
315 	if (Shadow)
316 		Shadow->drop();
317 
318 	Shadow = new CShadowVolumeSceneNode(shadowMesh, this, SceneManager, id,  zfailmethod, infinity);
319 	return Shadow;
320 }
321 
322 
323 //! returns the axis aligned bounding box of this node
getBoundingBox() const324 const core::aabbox3d<f32>& COctreeSceneNode::getBoundingBox() const
325 {
326 	return Box;
327 }
328 
329 
330 //! creates the tree
331 /* This method has a lot of duplication and overhead. Moreover, the tangents mesh conversion does not really work. I think we need a a proper mesh implementation for octrees, which handle all vertex types internally. Converting all structures to just one vertex type is always problematic.
332 Thanks to Auria for fixing major parts of this method. */
createTree(IMesh * mesh)333 bool COctreeSceneNode::createTree(IMesh* mesh)
334 {
335 	if (!mesh)
336 		return false;
337 
338 	MeshName = SceneManager->getMeshCache()->getMeshName(mesh);
339 
340     mesh->grab();
341 	deleteTree();
342 
343 	Mesh = mesh;
344 
345 	const u32 beginTime = os::Timer::getRealTime();
346 
347 	u32 nodeCount = 0;
348 	u32 polyCount = 0;
349 	u32 i;
350 
351 	Box = mesh->getBoundingBox();
352 
353 	if (mesh->getMeshBufferCount())
354 	{
355 		// check for "larger" buffer types
356 		VertexType = video::EVT_STANDARD;
357 		u32 meshReserve = 0;
358 		for (i=0; i<mesh->getMeshBufferCount(); ++i)
359 		{
360 			const IMeshBuffer* b = mesh->getMeshBuffer(i);
361 			if (b->getVertexCount() && b->getIndexCount())
362 			{
363 				++meshReserve;
364 				if (b->getVertexType() == video::EVT_2TCOORDS)
365 					VertexType = video::EVT_2TCOORDS;
366 				else if (b->getVertexType() == video::EVT_TANGENTS)
367 					VertexType = video::EVT_TANGENTS;
368 			}
369 		}
370 		Materials.reallocate(Materials.size()+meshReserve);
371 
372 		switch(VertexType)
373 		{
374 		case video::EVT_STANDARD:
375 			{
376 				StdMeshes.reallocate(StdMeshes.size() + meshReserve);
377 				for (i=0; i<mesh->getMeshBufferCount(); ++i)
378 				{
379 					IMeshBuffer* b = mesh->getMeshBuffer(i);
380 
381 					if (b->getVertexCount() && b->getIndexCount())
382 					{
383 						Materials.push_back(b->getMaterial());
384 
385 						StdMeshes.push_back(Octree<video::S3DVertex>::SMeshChunk());
386 						Octree<video::S3DVertex>::SMeshChunk &nchunk = StdMeshes.getLast();
387 						nchunk.MaterialId = Materials.size() - 1;
388 
389 						u32 v;
390 						nchunk.Vertices.reallocate(b->getVertexCount());
391 						switch (b->getVertexType())
392 						{
393 						case video::EVT_STANDARD:
394 							for (v=0; v<b->getVertexCount(); ++v)
395 								nchunk.Vertices.push_back(((video::S3DVertex*)b->getVertices())[v]);
396 							break;
397 						case video::EVT_2TCOORDS:
398 							for (v=0; v<b->getVertexCount(); ++v)
399 								nchunk.Vertices.push_back(((video::S3DVertex2TCoords*)b->getVertices())[v]);
400 							break;
401 						case video::EVT_TANGENTS:
402 							for (v=0; v<b->getVertexCount(); ++v)
403 								nchunk.Vertices.push_back(((video::S3DVertexTangents*)b->getVertices())[v]);
404 							break;
405 						}
406 
407 						polyCount += b->getIndexCount();
408 
409 						nchunk.Indices.reallocate(b->getIndexCount());
410 						for (v=0; v<b->getIndexCount(); ++v)
411 							nchunk.Indices.push_back(b->getIndices()[v]);
412 					}
413 				}
414 
415 				StdOctree = new Octree<video::S3DVertex>(StdMeshes, MinimalPolysPerNode);
416 				nodeCount = StdOctree->getNodeCount();
417 			}
418 			break;
419 		case video::EVT_2TCOORDS:
420 			{
421 				LightMapMeshes.reallocate(LightMapMeshes.size() + meshReserve);
422 
423 				for ( i=0; i < mesh->getMeshBufferCount(); ++i)
424 				{
425 					IMeshBuffer* b = mesh->getMeshBuffer(i);
426 
427 					if (b->getVertexCount() && b->getIndexCount())
428 					{
429 						Materials.push_back(b->getMaterial());
430 						LightMapMeshes.push_back(Octree<video::S3DVertex2TCoords>::SMeshChunk());
431 						Octree<video::S3DVertex2TCoords>::SMeshChunk& nchunk = LightMapMeshes.getLast();
432 						nchunk.MaterialId = Materials.size() - 1;
433 
434 						if (UseVisibilityAndVBOs)
435 						{
436 							nchunk.setHardwareMappingHint(scene::EHM_STATIC, scene::EBT_VERTEX);
437 							nchunk.setHardwareMappingHint(scene::EHM_DYNAMIC, scene::EBT_INDEX);
438 						}
439 						else
440 							nchunk.setHardwareMappingHint(scene::EHM_STATIC);
441 
442 						u32 v;
443 						nchunk.Vertices.reallocate(b->getVertexCount());
444 						switch (b->getVertexType())
445 						{
446 						case video::EVT_STANDARD:
447 							for (v=0; v<b->getVertexCount(); ++v)
448 								nchunk.Vertices.push_back(((video::S3DVertex*)b->getVertices())[v]);
449 							break;
450 						case video::EVT_2TCOORDS:
451 							for (v=0; v<b->getVertexCount(); ++v)
452 								nchunk.Vertices.push_back(((video::S3DVertex2TCoords*)b->getVertices())[v]);
453 							break;
454 						case video::EVT_TANGENTS:
455 							for (v=0; v<b->getVertexCount(); ++v)
456 								nchunk.Vertices.push_back(((video::S3DVertexTangents*)b->getVertices())[v]);
457 							break;
458 						}
459 
460 						polyCount += b->getIndexCount();
461 						nchunk.Indices.reallocate(b->getIndexCount());
462 						for (v=0; v<b->getIndexCount(); ++v)
463 							nchunk.Indices.push_back(b->getIndices()[v]);
464 					}
465 				}
466 
467 				LightMapOctree = new Octree<video::S3DVertex2TCoords>(LightMapMeshes, MinimalPolysPerNode);
468 				nodeCount = LightMapOctree->getNodeCount();
469 			}
470 			break;
471 		case video::EVT_TANGENTS:
472 			{
473 				TangentsMeshes.reallocate(TangentsMeshes.size() + meshReserve);
474 
475 				for (u32 i=0; i<mesh->getMeshBufferCount(); ++i)
476 				{
477 					IMeshBuffer* b = mesh->getMeshBuffer(i);
478 
479 					if (b->getVertexCount() && b->getIndexCount())
480 					{
481 						Materials.push_back(b->getMaterial());
482 						TangentsMeshes.push_back(Octree<video::S3DVertexTangents>::SMeshChunk());
483 						Octree<video::S3DVertexTangents>::SMeshChunk& nchunk = TangentsMeshes.getLast();
484 						nchunk.MaterialId = Materials.size() - 1;
485 
486 						u32 v;
487 						nchunk.Vertices.reallocate(b->getVertexCount());
488 						switch (b->getVertexType())
489 						{
490 						case video::EVT_STANDARD:
491 							for (v=0; v<b->getVertexCount(); ++v)
492 							{
493 								const video::S3DVertex& tmpV = ((video::S3DVertex*)b->getVertices())[v];
494 								nchunk.Vertices.push_back(video::S3DVertexTangents(tmpV.Pos, tmpV.Color, tmpV.TCoords));
495 							}
496 							break;
497 						case video::EVT_2TCOORDS:
498 							for (v=0; v<b->getVertexCount(); ++v)
499 							{
500 								const video::S3DVertex2TCoords& tmpV = ((video::S3DVertex2TCoords*)b->getVertices())[v];
501 								nchunk.Vertices.push_back(video::S3DVertexTangents(tmpV.Pos, tmpV.Color, tmpV.TCoords));
502 							}
503 							break;
504 						case video::EVT_TANGENTS:
505 							for (v=0; v<b->getVertexCount(); ++v)
506 								nchunk.Vertices.push_back(((video::S3DVertexTangents*)b->getVertices())[v]);
507 							break;
508 						}
509 
510 						polyCount += b->getIndexCount();
511 						nchunk.Indices.reallocate(b->getIndexCount());
512 						for (v=0; v<b->getIndexCount(); ++v)
513 							nchunk.Indices.push_back(b->getIndices()[v]);
514 					}
515 				}
516 
517 				TangentsOctree = new Octree<video::S3DVertexTangents>(TangentsMeshes, MinimalPolysPerNode);
518 				nodeCount = TangentsOctree->getNodeCount();
519 			}
520 			break;
521 		}
522 	}
523 
524 	const u32 endTime = os::Timer::getRealTime();
525 	c8 tmp[255];
526 	sprintf(tmp, "Needed %ums to create Octree SceneNode.(%u nodes, %u polys)",
527 		endTime - beginTime, nodeCount, polyCount/3);
528 	os::Printer::log(tmp, ELL_INFORMATION);
529 
530 	return true;
531 }
532 
533 
534 //! returns the material based on the zero based index i. To get the amount
535 //! of materials used by this scene node, use getMaterialCount().
536 //! This function is needed for inserting the node into the scene hirachy on a
537 //! optimal position for minimizing renderstate changes, but can also be used
538 //! to directly modify the material of a scene node.
getMaterial(u32 i)539 video::SMaterial& COctreeSceneNode::getMaterial(u32 i)
540 {
541 	if ( i >= Materials.size() )
542 		return ISceneNode::getMaterial(i);
543 
544 	return Materials[i];
545 }
546 
547 
548 //! returns amount of materials used by this scene node.
getMaterialCount() const549 u32 COctreeSceneNode::getMaterialCount() const
550 {
551 	return Materials.size();
552 }
553 
554 
555 //! Writes attributes of the scene node.
serializeAttributes(io::IAttributes * out,io::SAttributeReadWriteOptions * options) const556 void COctreeSceneNode::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
557 {
558 	ISceneNode::serializeAttributes(out, options);
559 
560 	out->addInt("MinimalPolysPerNode", MinimalPolysPerNode);
561 	out->addString("Mesh", MeshName.c_str());
562 }
563 
564 
565 //! Reads attributes of the scene node.
deserializeAttributes(io::IAttributes * in,io::SAttributeReadWriteOptions * options)566 void COctreeSceneNode::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
567 {
568 	const s32 oldMinimal = MinimalPolysPerNode;
569 
570 	MinimalPolysPerNode = in->getAttributeAsInt("MinimalPolysPerNode");
571 	io::path newMeshStr = in->getAttributeAsString("Mesh");
572 
573 	IMesh* newMesh = 0;
574 
575 	if (newMeshStr == "")
576 		newMeshStr = MeshName;
577 
578 	IAnimatedMesh* newAnimatedMesh = SceneManager->getMesh(newMeshStr.c_str());
579 
580 	if (newAnimatedMesh)
581 		newMesh = newAnimatedMesh->getMesh(0);
582 
583 	if (newMesh && ((MeshName != newMeshStr) || (MinimalPolysPerNode != oldMinimal)))
584 	{
585 		// recalculate tree
586 		createTree(newMesh);
587 	}
588 
589 	ISceneNode::deserializeAttributes(in, options);
590 }
591 
592 
deleteTree()593 void COctreeSceneNode::deleteTree()
594 {
595 	delete StdOctree;
596 	StdOctree = 0;
597 	StdMeshes.clear();
598 
599 	delete LightMapOctree;
600 	LightMapOctree = 0;
601 	LightMapMeshes.clear();
602 
603 	delete TangentsOctree;
604 	TangentsOctree = 0;
605 	TangentsMeshes.clear();
606 
607 	Materials.clear();
608 
609 	if(Mesh)
610 		Mesh->drop();
611 }
612 
setMesh(IMesh * mesh)613 void COctreeSceneNode::setMesh(IMesh* mesh)
614 {
615 	createTree(mesh);
616 }
617 
getMesh(void)618 IMesh* COctreeSceneNode::getMesh(void)
619 {
620 	return Mesh;
621 }
622 
setReadOnlyMaterials(bool readonly)623 void COctreeSceneNode::setReadOnlyMaterials(bool readonly)
624 {
625 	// Do nothing
626 }
627 
isReadOnlyMaterials() const628 bool COctreeSceneNode::isReadOnlyMaterials() const
629 {
630 	return false;
631 }
632 
633 
634 } // end namespace scene
635 } // end namespace irr
636 
637