2019-04-10 23:30:48 -07:00
# include <string>
2010-04-21 07:48:36 -07:00
# include "Engine.h"
2019-03-09 12:42:40 -08:00
# include "UserInterface.h"
# include "View.h"
2019-04-10 23:41:14 -07:00
# include "Utility.h"
2019-03-09 12:42:40 -08:00
2019-03-07 19:18:07 -08:00
using std : : cout ;
2019-03-09 04:43:36 -08:00
using std : : wcerr ;
2019-03-07 19:18:07 -08:00
using std : : endl ;
using std : : wstring ;
using std : : wstringstream ;
using namespace irr ;
using namespace irr : : core ;
using namespace irr : : scene ;
using namespace irr : : video ;
using namespace irr : : gui ;
2010-04-21 07:48:36 -07:00
/* //////////////////////////////////////////////////////////////////////////
PRIVATE METHODS
/////////////////////////////////////////////////////////////////////// */
void Engine : : setupScene ( )
{
// Setup Light
2010-08-16 05:23:20 -07:00
m_SceneLight = m_Scene - > addLightSceneNode ( ) ;
m_SceneLight - > setID ( SIID_LIGHT ) ;
m_SceneLight - > setLightType ( ELT_DIRECTIONAL ) ;
m_SceneLight - > getLightData ( ) . AmbientColor = SColorf ( 0.2f , 0.2f , 0.2f ) ;
m_SceneLight - > getLightData ( ) . DiffuseColor = SColorf ( 0.8f , 0.8f , 0.8f ) ;
2010-04-21 07:48:36 -07:00
m_Scene - > setAmbientLight ( SColorf ( 0.2f , 0.2f , 0.2f ) ) ;
// Setup Camera
2019-03-09 12:42:40 -08:00
// (so z-forward characters face camera partially (formerly vector3df( 0, 0, -10 ), vector3df())
2019-03-09 13:41:56 -08:00
m_CamPos = vector3df ( 4.5 , 3 , 9 ) ;
m_CamTarget = vector3df ( 0 , 3 , 0 ) ;
ICameraSceneNode * camera = m_Scene - > addCameraSceneNode ( nullptr , m_CamPos , m_CamTarget ) ; // this will be overridden by View m_Yaw and m_Pitch--see "calculate m_Yaw" further down
2019-04-10 23:30:48 -07:00
camera - > setAspectRatio ( static_cast < f32 > ( m_Driver - > getScreenSize ( ) . Width ) / static_cast < f32 > ( m_Driver - > getScreenSize ( ) . Height ) ) ;
2010-04-21 07:48:36 -07:00
}
IGUIEnvironment * Engine : : getGUIEnvironment ( ) const
{
return m_Device - > getGUIEnvironment ( ) ;
}
void Engine : : drawAxisLines ( )
{
SMaterial * lineX = new SMaterial ( ) ;
lineX - > Lighting = false ;
lineX - > EmissiveColor = SColor ( 255 , 255 , 0 , 0 ) ;
lineX - > Thickness = 1.0f ;
SMaterial * lineY = new SMaterial ( * lineX ) ;
lineY - > EmissiveColor = SColor ( 255 , 0 , 255 , 0 ) ;
SMaterial * lineZ = new SMaterial ( * lineX ) ;
lineZ - > EmissiveColor = SColor ( 255 , 0 , 0 , 255 ) ;
m_Driver - > setTransform ( ETS_WORLD , matrix4 ( ) ) ;
m_Driver - > setMaterial ( * lineX ) ;
m_Driver - > draw3DLine ( vector3df ( ) , vector3df ( 5 , 0 , 0 ) , SColor ( 255 , 255 , 0 , 0 ) ) ;
2019-04-10 23:30:48 -07:00
position2d < s32 > textPos = m_Scene - > getSceneCollisionManager ( ) - > getScreenCoordinatesFrom3DPosition ( vector3df ( 5.2f , 0 , 0 ) ) ;
2019-03-07 19:18:07 -08:00
dimension2d < u32 > textSize ;
if ( m_AxisFont ! = nullptr ) {
textSize = m_AxisFont - > getDimension ( L " X+ " ) ;
m_AxisFont - > draw ( L " X+ " , rect < s32 > ( textPos , textSize ) , SColor ( 255 , 255 , 0 , 0 ) , true , true ) ;
}
2010-04-21 07:48:36 -07:00
m_Driver - > setMaterial ( * lineY ) ;
m_Driver - > draw3DLine ( vector3df ( ) , vector3df ( 0 , 5 , 0 ) , SColor ( 255 , 0 , 255 , 0 ) ) ;
2019-04-10 23:30:48 -07:00
textPos = m_Scene - > getSceneCollisionManager ( ) - > getScreenCoordinatesFrom3DPosition ( vector3df ( 0 , 5.2f , 0 ) ) ;
2019-03-07 19:18:07 -08:00
if ( m_AxisFont ! = nullptr ) {
textSize = m_AxisFont - > getDimension ( L " Y+ " ) ;
m_AxisFont - > draw ( L " Y+ " , rect < s32 > ( textPos , textSize ) , SColor ( 255 , 0 , 255 , 0 ) , true , true ) ;
}
2010-04-21 07:48:36 -07:00
m_Driver - > setMaterial ( * lineZ ) ;
m_Driver - > draw3DLine ( vector3df ( ) , vector3df ( 0 , 0 , 5 ) , SColor ( 255 , 0 , 0 , 255 ) ) ;
2019-04-10 23:30:48 -07:00
textPos = m_Scene - > getSceneCollisionManager ( ) - > getScreenCoordinatesFrom3DPosition ( vector3df ( 0 , 0 , 5.2f ) ) ;
2019-03-07 19:18:07 -08:00
if ( m_AxisFont ! = nullptr ) {
textSize = m_AxisFont - > getDimension ( L " Z+ " ) ;
m_AxisFont - > draw ( L " Z+ " , rect < s32 > ( textPos , textSize ) , SColor ( 255 , 0 , 0 , 255 ) , true , true ) ;
}
2010-04-21 07:48:36 -07:00
delete lineX ;
delete lineY ;
delete lineZ ;
}
void Engine : : drawBackground ( )
{
dimension2d < u32 > screenSize = m_Driver - > getScreenSize ( ) ;
2019-04-10 23:30:48 -07:00
m_Driver - > draw2DRectangle ( rect < s32 > ( 0 , 0 , static_cast < s32 > ( screenSize . Width ) , static_cast < s32 > ( screenSize . Height ) ) ,
2010-04-21 07:48:36 -07:00
SColor ( 255 , 128 , 128 , 255 ) ,
SColor ( 255 , 128 , 128 , 255 ) ,
SColor ( 255 , 224 , 224 , 255 ) ,
SColor ( 255 , 224 , 224 , 255 ) ) ;
}
void Engine : : checkResize ( )
{
if ( ( m_WindowSize - > Width ! = m_Driver - > getScreenSize ( ) . Width ) | | ( m_WindowSize - > Height ! = m_Driver - > getScreenSize ( ) . Height ) )
{
m_WindowSize - > Width = m_Driver - > getScreenSize ( ) . Width ;
m_WindowSize - > Height = m_Driver - > getScreenSize ( ) . Height ;
2010-04-23 00:28:59 -07:00
// Send custom event
IEventReceiver * eventReceiver = m_Device - > getEventReceiver ( ) ;
SEvent event ;
event . EventType = EET_USER_EVENT ;
event . UserEvent . UserData1 = UEI_WINDOWSIZECHANGED ;
eventReceiver - > OnEvent ( event ) ;
2019-04-10 23:30:48 -07:00
//m_UserInterface->
2010-04-21 07:48:36 -07:00
}
}
2010-04-23 00:28:59 -07:00
s32 Engine : : getNumberOfVertices ( )
{
IMesh * mesh = m_LoadedMesh - > getMesh ( ) - > getMesh ( 0 , 255 , - 1 , - 1 ) ;
int vertices = 0 ;
2019-04-10 23:30:48 -07:00
for ( irr : : u32 bufferIndex = 0 ; bufferIndex < mesh - > getMeshBufferCount ( ) ; bufferIndex + + )
2010-04-23 00:28:59 -07:00
vertices + = mesh - > getMeshBuffer ( bufferIndex ) - > getVertexCount ( ) ;
cout < < vertices < < endl ;
return vertices ;
}
2010-04-21 07:48:36 -07:00
/* //////////////////////////////////////////////////////////////////////////
PUBLIC METHODS
/////////////////////////////////////////////////////////////////////// */
Engine : : Engine ( )
{
2019-03-10 06:55:31 -07:00
// For monitoring single press: see
// <http://irrlicht.sourceforge.net/forum/viewtopic.php?p=210744>
for ( u32 i = 0 ; i < KEY_KEY_CODES_COUNT ; + + i )
KeyIsDown [ i ] = false ;
for ( u32 i = 0 ; i < KEY_KEY_CODES_COUNT ; + + i )
keyState [ i ] = 0 ;
LMouseState = 0 ;
RMouseState = 0 ;
2019-03-07 21:52:29 -08:00
this - > worldFPS = 60 ;
this - > prevFPS = 30 ;
2019-03-09 05:56:23 -08:00
this - > textureExtensions . push_back ( L " png " ) ;
this - > textureExtensions . push_back ( L " jpg " ) ;
2019-03-10 06:55:31 -07:00
this - > textureExtensions . push_back ( L " bmp " ) ;
2010-04-22 01:44:10 -07:00
# if WIN32
2019-03-07 10:23:54 -08:00
m_Device = createDevice ( EDT_DIRECT3D9 , dimension2d < u32 > ( 1024 , 768 ) , 32 , false , false , false , nullptr ) ;
2010-04-22 01:44:10 -07:00
# else
2019-03-07 10:23:54 -08:00
m_Device = createDevice ( EDT_OPENGL , dimension2d < u32 > ( 1024 , 768 ) , 32 , false , false , false , nullptr ) ;
2010-04-22 01:44:10 -07:00
# endif
2010-04-21 07:48:36 -07:00
m_Device - > setResizable ( true ) ;
m_EventHandler = new EventHandler ( m_Device ) ;
m_Device - > setEventReceiver ( m_EventHandler ) ;
m_Driver = m_Device - > getVideoDriver ( ) ;
m_Scene = m_Device - > getSceneManager ( ) ;
2019-03-07 10:23:54 -08:00
wstringstream windowTitle ;
2019-03-07 13:32:07 -08:00
windowTitle < < L " b3view (Blitz3D/Irrlicht Viewer) [ " < < m_Driver - > getName ( ) < < L " ] " ;
2010-04-22 01:44:10 -07:00
m_Device - > setWindowCaption ( windowTitle . str ( ) . c_str ( ) ) ;
2010-04-21 07:48:36 -07:00
setupScene ( ) ;
// Setup User Interface
m_UserInterface = new UserInterface ( this ) ;
m_EventHandler - > addEventReceiver ( ERT_USERINTERFACE , m_UserInterface ) ;
// Setup 3D View
m_View = new View ( this ) ;
m_EventHandler - > addEventReceiver ( ERT_3DVIEW , m_View ) ;
// Load font for displaying Axis names
2010-04-23 01:13:44 -07:00
m_AxisFontFace = new CGUITTFace ( ) ;
2019-03-09 13:41:56 -08:00
// NOTE: m_FontPath is modified y UserInterface constructor above if font was missing
if ( m_AxisFontFace - > load ( m_FontPath . c_str ( ) ) ) {
2019-03-07 19:18:07 -08:00
m_AxisFont = new CGUITTFont ( m_UserInterface - > getGUIEnvironment ( ) ) ;
m_AxisFont - > attach ( m_AxisFontFace , 14 ) ;
m_AxisFont - > AntiAlias = false ;
}
else {
delete m_AxisFontFace ;
m_AxisFontFace = nullptr ;
}
2010-04-21 07:48:36 -07:00
// Set Engine enabled
m_RunEngine = true ;
2019-03-07 10:23:54 -08:00
m_LoadedMesh = nullptr ;
2010-04-21 07:48:36 -07:00
// Store actual window size
m_WindowSize = new dimension2d < u32 > ( ) ;
m_WindowSize - > Width = m_Driver - > getScreenSize ( ) . Width ;
m_WindowSize - > Height = m_Driver - > getScreenSize ( ) . Height ;
2019-03-09 12:42:40 -08:00
2019-03-10 06:55:31 -07:00
// (do not calculate m_Yaw and m_Pitch here--see View constructor)
2019-03-09 12:42:40 -08:00
2019-03-07 21:52:29 -08:00
this - > playAnimation ( ) ;
2010-04-21 07:48:36 -07:00
}
Engine : : ~ Engine ( )
{
m_Device - > drop ( ) ;
delete m_WindowSize ;
2010-04-23 01:13:44 -07:00
delete m_AxisFont ;
delete m_AxisFontFace ;
2010-04-21 07:48:36 -07:00
}
2010-04-22 05:16:15 -07:00
void Engine : : loadMesh ( const wstring & fileName )
{
2019-03-09 05:56:23 -08:00
this - > m_PreviousPath = fileName ; // even if bad, set this
// to allow F5 to reload
2019-03-07 10:23:54 -08:00
if ( m_LoadedMesh ! = nullptr )
2010-04-22 05:16:15 -07:00
m_LoadedMesh - > remove ( ) ;
2019-03-09 05:56:23 -08:00
irr : : scene : : IAnimatedMesh * mesh = m_Scene - > getMesh ( fileName . c_str ( ) ) ;
if ( mesh ! = nullptr ) {
m_LoadedMesh = m_Scene - > addAnimatedMeshSceneNode ( mesh ) ;
Utility : : dumpMeshInfoToConsole ( m_LoadedMesh ) ;
2019-03-10 06:55:31 -07:00
if ( Utility : : toLower ( Utility : : extensionOf ( fileName ) ) = = L " 3ds " ) {
m_View - > setZUp ( true ) ;
}
else {
m_View - > setZUp ( false ) ;
}
if ( m_LoadedMesh ! = nullptr ) {
ICameraSceneNode * camera = this - > m_Scene - > getActiveCamera ( ) ;
aabbox3d < f32 > box = m_LoadedMesh - > getTransformedBoundingBox ( ) ;
2019-04-10 23:30:48 -07:00
//vector3d<float> extents = box.getExtent();
2019-03-10 06:55:31 -07:00
if ( m_View - > zUp ( ) ) {
float oldDist = m_CamPos . getDistanceFrom ( m_CamTarget ) ;
float newDist = oldDist ;
if ( oldDist ! = 0 ) {
vector3d < float > center = box . getCenter ( ) ;
vector3df edges [ 8 ] ;
box . getEdges ( edges ) ;
/*
/ 3 - - - - - - - - / 7
/ | / |
/ | / |
1 - - - - - - - - - 5 |
| 2 - - - | - 6
| / | /
| / | /
0 - - - - - - - - - 4 /
*/
newDist = 0 ;
for ( int i = 0 ; i < 8 ; i + + ) {
float tryDist = center . getDistanceFrom ( edges [ i ] ) ;
if ( tryDist > newDist ) newDist = tryDist ;
}
newDist * = 2 ; // so camera doesn't touch model
if ( ! Utility : : equalsApprox < float > ( newDist , oldDist ) ) {
float scale = newDist / oldDist ; // already checked 0
vector3df oldCamPos = camera - > getPosition ( ) ;
m_CamPos = oldCamPos ;
m_CamPos . X = m_CamPos . X * scale ;
m_CamPos . Y = m_CamPos . Y * scale ;
m_CamPos . Z = m_CamPos . Z * scale ;
oldCamPos = m_CamPos ;
m_View - > setCameraDistance ( m_CamPos . getDistanceFrom ( m_CamTarget ) ) ;
camera - > setPosition ( m_CamPos ) ;
}
}
}
2019-04-08 19:52:18 -07:00
m_LoadedMesh - > setMaterialType ( video : : EMT_TRANSPARENT_ALPHA_CHANNEL_REF ) ;
// EMT_TRANSPARENT_ALPHA_CHANNEL: constant transparency
2019-03-10 06:55:31 -07:00
}
2019-03-09 05:56:23 -08:00
}
2010-04-22 05:16:15 -07:00
}
2019-03-07 19:18:07 -08:00
void Engine : : reloadMesh ( )
{
2019-03-07 21:52:29 -08:00
if ( this - > m_PreviousPath . length ( ) > 0 ) {
loadMesh ( this - > m_PreviousPath ) ;
2019-03-07 19:18:07 -08:00
}
}
2019-03-09 04:43:36 -08:00
void Engine : : reloadTexture ( )
{
if ( this - > m_PrevTexturePath . length ( ) > 0 ) {
2019-04-10 23:30:48 -07:00
if ( wcslen ( this - > m_UserInterface - > texturePathEditBox - > getText ( ) ) = = 0 )
2019-04-08 19:52:18 -07:00
loadTexture ( this - > m_UserInterface - > texturePathEditBox - > getText ( ) ) ;
else
loadTexture ( this - > m_PrevTexturePath ) ;
2019-03-09 04:43:36 -08:00
}
}
2019-03-07 23:30:06 -08:00
bool Engine : : loadTexture ( const wstring & fileName )
2019-03-07 14:17:42 -08:00
{
2019-03-07 23:30:06 -08:00
ITexture * texture = this - > m_Driver - > getTexture ( fileName . c_str ( ) ) ;
bool ret = false ;
if ( texture ! = nullptr ) {
m_LoadedMesh - > setMaterialTexture ( 0 , texture ) ;
ret = true ;
}
this - > m_PrevTexturePath = fileName ;
2019-04-08 19:52:18 -07:00
this - > m_UserInterface - > texturePathEditBox - > setText ( this - > m_PrevTexturePath . c_str ( ) ) ;
2019-03-07 23:30:06 -08:00
return ret ;
2019-03-07 14:17:42 -08:00
}
2019-04-08 19:52:18 -07:00
void Engine : : setMeshDisplayMode ( bool wireframe , bool lighting , bool textureInterpolation )
2010-08-16 05:23:20 -07:00
{
2019-04-08 19:52:18 -07:00
if ( m_LoadedMesh ! = nullptr ) {
2019-04-10 23:30:48 -07:00
for ( u32 materialIndex = 0 ; materialIndex < m_LoadedMesh - > getMaterialCount ( ) ; materialIndex + + )
2010-08-16 05:23:20 -07:00
{
2019-04-08 19:52:18 -07:00
// Set Wireframe display
m_LoadedMesh - > getMaterial ( materialIndex ) . Wireframe = wireframe ;
// Set Lighting
if ( ! lighting )
{
m_LoadedMesh - > getMaterial ( materialIndex ) . Lighting = false ;
m_LoadedMesh - > getMaterial ( materialIndex ) . EmissiveColor = SColor ( 255 , 255 , 255 , 255 ) ;
}
else
{
m_LoadedMesh - > getMaterial ( materialIndex ) . Lighting = true ;
m_LoadedMesh - > getMaterial ( materialIndex ) . EmissiveColor = SColor ( 255 , 0 , 0 , 0 ) ;
}
// m_LoadedMesh->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL); //already done on load
// m_LoadedMesh->setMaterialFlag(video::E_ALPHA_SOURCE, true); // requires EMT_ONETEXTURE
if ( textureInterpolation ) {
m_LoadedMesh - > setMaterialFlag ( video : : EMF_BILINEAR_FILTER , true ) ;
m_LoadedMesh - > setMaterialFlag ( video : : EMF_TRILINEAR_FILTER , true ) ;
}
else {
m_LoadedMesh - > setMaterialFlag ( video : : EMF_BILINEAR_FILTER , false ) ;
m_LoadedMesh - > setMaterialFlag ( video : : EMF_TRILINEAR_FILTER , false ) ;
//m_LoadedMesh->setMaterialFlag(video::E_ALPHA_SOURCE, true);
// below doesn't work for some reason:
// video::SMaterial mat = m_LoadedMesh->getMaterial(materialIndex);
// mat.UseMipMaps = false;
// mat.setFlag(video::EMF_BILINEAR_FILTER, false);
// mat.setFlag(video::EMF_TRILINEAR_FILTER, false);
// below would require patching Irrlicht:
// GLint filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST
// // above is used by glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
}
2010-08-16 05:23:20 -07:00
}
}
2019-04-08 19:52:18 -07:00
else debug ( ) < < " WARNING in setMeshDisplayMode: No mesh is loaded " < < endl ;
2010-08-16 05:23:20 -07:00
}
2019-03-07 21:52:29 -08:00
bool Engine : : isAnimating ( )
{
return this - > isPlaying ;
}
void Engine : : playAnimation ( )
{
if ( this - > animationFPS ( ) < 1 ) {
this - > setAnimationFPS ( 5 ) ;
}
if ( ! this - > isAnimating ( ) ) {
if ( this - > m_LoadedMesh ! = nullptr ) {
if ( this - > prevFPS < 1 ) this - > prevFPS = 5 ;
this - > m_LoadedMesh - > setAnimationSpeed ( this - > prevFPS ) ;
}
}
this - > isPlaying = true ;
}
void Engine : : pauseAnimation ( )
{
if ( this - > isAnimating ( ) ) {
this - > prevFPS = animationFPS ( ) ;
if ( this - > m_LoadedMesh ! = nullptr ) {
this - > prevFPS = this - > m_LoadedMesh - > getAnimationSpeed ( ) ;
this - > m_LoadedMesh - > setAnimationSpeed ( 0 ) ;
}
}
this - > isPlaying = false ;
}
void Engine : : toggleAnimation ( )
{
if ( this - > isAnimating ( ) ) {
this - > pauseAnimation ( ) ;
debug ( ) < < " paused " < < this - > animationFPS ( ) < < " fps " < < endl ;
}
else {
this - > playAnimation ( ) ;
debug ( ) < < " unpaused " < < this - > animationFPS ( ) < < " fps " < < endl ;
}
}
void Engine : : setAnimationFPS ( u32 animationFPS )
{
if ( this - > m_LoadedMesh ! = nullptr ) {
this - > m_LoadedMesh - > setAnimationSpeed ( animationFPS ) ;
}
}
2019-03-10 06:55:31 -07:00
void Engine : : setZUp ( bool zUp )
{
if ( this - > m_View ! = nullptr ) {
this - > m_View - > setZUp ( zUp ) ;
}
}
2019-03-07 21:52:29 -08:00
u32 Engine : : animationFPS ( )
{
u32 ret = 0 ;
if ( this - > m_LoadedMesh ! = nullptr ) {
ret = this - > m_LoadedMesh - > getAnimationSpeed ( ) ;
}
return ret ;
}
2010-04-21 07:48:36 -07:00
void Engine : : run ( )
{
2019-03-07 21:52:29 -08:00
u32 timePerFrame = 1000.0f ;
if ( this - > worldFPS > 0 ) {
2019-04-10 23:30:48 -07:00
timePerFrame = static_cast < u32 > ( 1000.0f / this - > worldFPS ) ;
2019-03-07 21:52:29 -08:00
}
2010-04-21 07:48:36 -07:00
ITimer * timer = m_Device - > getTimer ( ) ;
2019-03-07 21:52:29 -08:00
// Run the Device with fps frames/sec
2010-04-21 07:48:36 -07:00
while ( m_Device - > run ( ) & & m_RunEngine )
{
u32 startTime = timer - > getRealTime ( ) ;
checkResize ( ) ;
2019-03-07 21:52:29 -08:00
if ( this - > m_LoadedMesh ! = nullptr ) {
if ( isPlaying ) {
this - > m_LoadedMesh - > setLoopMode ( true ) ;
2019-04-03 07:35:27 -07:00
this - > m_UserInterface - > playbackSetFrameEditBox - > setText ( Utility : : toWstring ( this - > m_LoadedMesh - > getFrameNr ( ) ) . c_str ( ) ) ;
2019-03-07 21:52:29 -08:00
}
else {
this - > m_LoadedMesh - > setLoopMode ( false ) ;
}
}
2010-04-21 07:48:36 -07:00
m_Driver - > beginScene ( ) ;
drawBackground ( ) ; // Draw Background
drawAxisLines ( ) ; // Draw XYZ Axis
m_Scene - > drawAll ( ) ; // Draw Scenegraph
2010-04-23 00:28:59 -07:00
2010-04-21 07:48:36 -07:00
m_UserInterface - > getGUIEnvironment ( ) - > drawAll ( ) ;
2010-04-23 00:28:59 -07:00
m_UserInterface - > drawStatusLine ( ) ;
2010-04-21 07:48:36 -07:00
m_Driver - > endScene ( ) ;
u32 sleepTime = timePerFrame - ( timer - > getRealTime ( ) - startTime ) ;
if ( sleepTime > 0 & & sleepTime < timePerFrame )
m_Device - > sleep ( sleepTime , false ) ;
}
}