hkpShape* vHavokShapeFactory::CreateConvexShapeFromStaticMeshInstances(const VisStaticMeshInstCollection &meshInstances, hkvMat4 &refTransform, bool shrinkByCvxRadius)
{
  VVERIFY_OR_RET_VAL(meshInstances.GetLength()==1, NULL);

  // Get the collision mesh for the static mesh instance
  VisStaticMeshInstance_cl *pMeshInstance = meshInstances[0];
  VisStaticMesh_cl *pMesh = pMeshInstance->GetMesh();
  IVCollisionMesh *pColMesh = pMesh->GetCollisionMesh(true, true);
  VVERIFY_OR_RET_VAL(pColMesh!=NULL, NULL);

  // We transform each static mesh into the reference space.
  hkvMat4 mTransform = refTransform;
  mTransform = mTransform.multiply (pMeshInstance->GetTransform());

  hkGeometry geom;
  int iNumColMeshes = hkvMath::Max(pColMesh->GetSubmeshCount(), 1);
  for (int i=0;i<iNumColMeshes;i++)
    BuildGeomFromCollisionMesh(pColMesh, i, mTransform, false, geom);
 
  // Set the build configuration to set planes equations and connectivity automatically
  hkpConvexVerticesShape::BuildConfig config;
  config.m_createConnectivity = true;
  config.m_shrinkByConvexRadius = shrinkByCvxRadius;

  hkStridedVertices stridedVerts;
  stridedVerts.m_numVertices = geom.m_vertices.getSize();
  stridedVerts.m_striding    = sizeof(hkVector4);
  stridedVerts.m_vertices    = (const hkReal*)geom.m_vertices.begin();

  // Create convex shape
  hkvConvexVerticesShape *pConvexShape = new hkvConvexVerticesShape(pColMesh->GetFileTime(), stridedVerts, config);
  
  return pConvexShape;
}
Beispiel #2
0
bool VisionApp_cl::DeInitInput()
{
  VVERIFY_OR_RET_VAL(m_bInputInitialized,false);

  VULPSTATUSMESSAGE_0("Deinitializing input");

  VInputManager::DeInit();

  m_bInputInitialized = false;  
  return true;

}
Beispiel #3
0
bool VisionApp_cl::InitInput()
{
  VVERIFY_OR_RET_VAL(!m_bInputInitialized,false);

#if defined(WIN32) && !defined(_VISION_WINRT)
  int eInputModeKeyboard = VGL_DIRECTINPUT;
  if ( (m_iInitFlags & VAPP_USE_DINPUT_KEYBOARD) ==0)
    eInputModeKeyboard = VGL_WINDOWSINPUT;

  int eInputModeMouse = VGL_DIRECTINPUT;
  if ( (m_iInitFlags & VAPP_USE_DINPUT_MOUSE) ==0)
    eInputModeMouse = VGL_WINDOWSINPUT;

  VInputManagerPC::Init(eInputModeKeyboard, eInputModeMouse, (m_iInitFlags&VAPP_MOUSE_HIDECURSOR)!=0, 
    (m_iInitFlags&VAPP_MOUSE_NONEXCLUSIVE)==0,
    (m_iInitFlags&VAPP_KEYBOARD_NONEXCLUSIVE)==0);
#else
  VInputManager::Init();
#endif

  m_bInputInitialized = true;
  return true;
}
hkRefNew<hkpShape> vHavokShapeFactory::CreateConvexHullShapeFromMesh(VBaseMesh *pMesh, const hkvVec3& vScaleIn, const char **szShapeCacheId, int iCreationFlags)
{
  VVERIFY_OR_RET_VAL(pMesh != NULL, NULL);

  const bool bAllowStaticMeshCaching = vHavokPhysicsModule_GetDefaultWorldRuntimeSettings().m_bEnableShapeCaching==TRUE;
  const bool bCacheShape = iCreationFlags & VShapeCreationFlags_CACHE_SHAPE;
  const vHavokPhysicsModule *pModule = vHavokPhysicsModule::GetInstance();
  VASSERT(pModule != NULL);
  const bool bForceHktShapeCaching = pModule->IsHktShapeCachingEnforced();
  const bool bShrinkShape = iCreationFlags & VShapeCreationFlags_SHRINK;

  char szShapeId[512];
  if (bCacheShape)
  {
    VASSERT(szShapeCacheId != NULL);

    // Check whether shape has been already cached for this model with the respective scaling.
    vHavokShapeFactory::GetIDStringForConvexShape(szShapeId, pMesh->GetFilename(), vScaleIn, bShrinkShape);
    hkpShape *pCachedShape = FindShape(szShapeId, szShapeCacheId);
    if (pCachedShape)
    {
      pCachedShape->addReference();
      return pCachedShape;
    }
 
    // Check if shape is already cached on disk 
    if (bAllowStaticMeshCaching)
    {
      pCachedShape = vHavokCachedShape::LoadConvexShape(pMesh, vScaleIn, bShrinkShape);
      if (pCachedShape)
      {
        *szShapeCacheId = AddShape(szShapeId, pCachedShape);

  #ifdef HK_DEBUG_SLOW
        const hkClass* loadedClassType = hkVtableClassRegistry::getInstance().getClassFromVirtualInstance(pCachedShape);
        HK_ASSERT2(0x695cc897, loadedClassType && (hkString::strCmp( loadedClassType->getName(), "hkvConvexVerticesShape" ) == 0), "Serialized in a unexpected cached Havok shape type!");
  #endif

        return pCachedShape;
      }
      else if(!Vision::Editor.IsInEditor() && !bForceHktShapeCaching)
      {
        Vision::Error.Warning("Cached HKT file for %s is missing. Please generate HKT file (see documentation for details).", pMesh->GetFilename());
      }
    }
  }

  // Get the collision mesh for the specified mesh
  const IVCollisionMesh *pColMesh = (iCreationFlags & VShapeCreationFlags_USE_VCOLMESH) ? pMesh->GetCollisionMesh(true, true) : pMesh->GetTraceMesh(true, true);
  VASSERT(pColMesh != NULL);

  hkvMat4 tranform;
  tranform.setScalingMatrix(vScaleIn);

  hkGeometry geom;

  const int iNumColMeshes = hkvMath::Max(pColMesh->GetSubmeshCount(), 1);
  for (int i=0;i<iNumColMeshes;i++)
    BuildGeomFromCollisionMesh(pColMesh, i, tranform, true, geom);
  VVERIFY_OR_RET_VAL(geom.m_vertices.getSize() > 0, NULL);

  // Set the build configuration to set planes equations and connectivity automatically
  hkpConvexVerticesShape::BuildConfig config;
  config.m_createConnectivity = true;
  config.m_shrinkByConvexRadius = bShrinkShape;

  hkStridedVertices stridedVerts;
  stridedVerts.m_numVertices = geom.m_vertices.getSize();
  stridedVerts.m_striding    = sizeof(hkVector4);
  stridedVerts.m_vertices    = (const hkReal*)geom.m_vertices.begin();

  // Create convex shape
  hkvConvexVerticesShape *pConvexShape = new hkvConvexVerticesShape(pColMesh->GetFileTime(), stridedVerts, config);

  // Add shape to cache
  if (bCacheShape)
    *szShapeCacheId = AddShape(szShapeId, pConvexShape);

  // Only cache shape to HKT file when inside vForge or when enforced.
  if ((Vision::Editor.IsInEditor() && bAllowStaticMeshCaching && bCacheShape) || bForceHktShapeCaching)
    vHavokCachedShape::SaveConvexShape(pMesh, vScaleIn, bShrinkShape, pConvexShape);

  return pConvexShape;
}
hkpShape* vHavokShapeFactory::CreateMeshShapeFromStaticMeshInstances(const VisStaticMeshInstCollection &meshInstances, hkvMat4 &refTransform, 
                                                                     bool bAllowPerTriCollisionInfo, VisWeldingType_e eWeldingType)
{
  int iCount = meshInstances.GetLength();
  VVERIFY_OR_RET_VAL(iCount>0, NULL);

  hkGeometry geom;
  hkInt64 iFileTime = 0;

  // Iterate all the passed mesh instances
  hkvMeshMaterialCache materials;
  hkArray<hkUint8> collisionMask;
  int subPartIndex = 0;
  for (int i = 0; i < iCount; i++)
  { 
    // Get the collision mesh for the static mesh instance
    const VisStaticMeshInstance_cl *pMeshInstance = meshInstances[i];
    VisStaticMesh_cl *pMesh = pMeshInstance->GetMesh();
    IVCollisionMesh *pColMesh = pMesh->GetCollisionMesh(true, true);
    if (pColMesh == NULL)
    {
      VASSERT(false);
      continue;
    }

    // Simply retrieve file time from last static mesh instance
    iFileTime = pColMesh->GetFileTime();

    // Create the Havok triangle sub part
    {
      int startingNumTris = geom.m_triangles.getSize();

      // We transform each static mesh into the reference space.
      hkvMat4 mTransform = refTransform;
      mTransform = mTransform.multiply (pMeshInstance->GetTransform());

      int iNumColMeshes = hkvMath::Max(pColMesh->GetSubmeshCount(), 1);
      for (int i=0;i<iNumColMeshes;i++)
      {
        int startingNumVerts = geom.m_vertices.getSize();
        BuildGeomFromCollisionMesh(pColMesh, i, mTransform, false, geom);
        int endNumVerts = geom.m_vertices.getSize();
        int endNumTris = geom.m_triangles.getSize();
        VASSERT( (endNumVerts - startingNumVerts) > 0 );

        hkUint32 cdMask = 0;
        if (bAllowPerTriCollisionInfo && (pColMesh->GetSubmeshCount() > 0))
        {
          const VPhysicsSubmesh& submesh = pColMesh->GetSubmeshes()[i];
          cdMask = submesh.iGroupFilter & 0xff; // only lower 8 bits stored in compressed mesh.

          int numCD = endNumTris - collisionMask.getSize();
          hkUint8* cdi = collisionMask.expandBy( numCD );
          hkString::memSet(cdi, cdMask, numCD);
        }
      }
   
      // ColMesh can have Materials. Also need material to store the orig static mesh index (so if count > 1)
      if ( pColMesh->GetTriSrfIndices() )
      {
        VColMeshMaterial *pColMaterials = pColMesh->GetMaterials();
        VASSERT(pColMaterials);
        int iNumMats = pColMesh->GetMaterialCount();
        int matIndexOffset = materials.getSize();

        for(int ii=0;ii<iNumMats;ii++)
        {
          hkvMeshMaterial& mat = materials.expandOne();
          // in Havok there is no StaticFriction, DynamicFriction, anisotropic StaticFriction/ DynamicFriction
          // instead there is only Friction available
          // so DynamicFriction is used for Havok Friction -> is there a better workaround?
          mat.m_fFriction = pColMaterials[ii].fDynamicFriction; 

          mat.m_fRestitution = pColMaterials[ii].fRestitution;
          if (! pColMaterials[ii].szUserData.IsEmpty()) 
            mat.m_userData = pColMaterials[ii].szUserData.GetChar();   

          mat.m_iOrignalSubMeshIndex = subPartIndex;
        }

        // reindex geom mat id
        if (matIndexOffset> 0)
        {
          for (int ti=startingNumTris; ti < geom.m_triangles.getSize(); ++ti)
          {
            VASSERT(geom.m_triangles[ti].m_material < iNumMats);
            VASSERT(geom.m_triangles[ti].m_material >= 0);
            geom.m_triangles[ti].m_material += matIndexOffset;
          }
        }
      }

      // per instance part
      ++subPartIndex;     
    }
  }

  const bool bHaveTriCDData = collisionMask.getSize() > 0;
  vHavokCompressedInfoCinfo ci( &geom, bHaveTriCDData ? collisionMask.begin() : HK_NULL );
  ci.m_weldingType = vHavokConversionUtils::VisToHkWeldingType(eWeldingType);
  ci.m_collisionFilterInfoMode = bHaveTriCDData ? hkpBvCompressedMeshShape::PER_PRIMITIVE_DATA_8_BIT : hkpBvCompressedMeshShape::PER_PRIMITIVE_DATA_NONE;
  
  hkvBvCompressedMeshShape *pCompressedMeshShape = HK_NULL;
  if ( materials.getSize() > 0 && materials.getSize() < 255)
  {
    ci.m_userDataMode = hkpBvCompressedMeshShape::PER_PRIMITIVE_DATA_8_BIT; 
    pCompressedMeshShape = new hkvBvCompressedMeshShape(ci, materials, iFileTime);
  }
  else
  {
    ci.m_userDataMode = hkpBvCompressedMeshShape::PER_PRIMITIVE_DATA_NONE; 
    pCompressedMeshShape = new hkvBvCompressedMeshShape(ci, iFileTime);
  }
  VASSERT_MSG(pCompressedMeshShape->getNumChildShapes() > 0, "hkvBvCompressedMeshShape could not be created for static model!");

  pCompressedMeshShape->SetupMaterials();

  return pCompressedMeshShape;
}
hkRefNew<hkpShape> vHavokShapeFactory::CreateShapeFromStaticMeshInstances(const VisStaticMeshInstCollection &meshInstances, int iCreationFlags, const char **szShapeCacheId)
{
  int iCount = meshInstances.GetLength();
  VVERIFY_OR_RET_VAL(iCount>0 && szShapeCacheId!=NULL, NULL);
  
  // Actual mesh scale, which is only used for caching.
  hkvVec3 vScale(hkvNoInitialization);
  ExtractScaling(meshInstances[0]->GetTransform(), vScale);

  char szShapeId[512];
  const bool bAllowStaticMeshCaching = vHavokPhysicsModule_GetDefaultWorldRuntimeSettings().m_bEnableShapeCaching==TRUE;
  const vHavokPhysicsModule *pModule = vHavokPhysicsModule::GetInstance();
  VASSERT(pModule != NULL);
  const bool bForceHktShapeCaching = pModule->IsHktShapeCachingEnforced();

  // For single mesh instances the per instance welding type is used. For merged mesh instances the global merged welding type is used. 
  VisWeldingType_e eWeldingType = (iCount==1) ? meshInstances[0]->GetWeldingType() : (VisWeldingType_e)vHavokPhysicsModule_GetWorldSetupSettings().m_iMergedStaticWeldingType;

  const bool bAllowPerTriCollisionInfo = iCreationFlags & VShapeCreationFlags_ALLOW_PERTRICOLINFO;
  const bool bShrinkByCvxRadius = iCreationFlags & VShapeCreationFlags_SHRINK;

  // Check whether shape has been already cached for this mesh with the respective scaling.
  // We are just caching single static mesh instances and no static mesh collections.
  hkpShape *pCachedShape = HK_NULL; 
  if (iCount == 1)
  {
    // first, find the convex version
    GetIDStringForConvexShape(szShapeId, meshInstances[0]->GetMesh()->GetFilename(), vScale, bShrinkByCvxRadius);
    pCachedShape = FindShape(szShapeId, szShapeCacheId);
    if (!pCachedShape)
    {
      // then find the mesh version
      GetIDStringForMeshShape(szShapeId, meshInstances[0]->GetMesh()->GetFilename(), vScale, meshInstances[0]->GetCollisionBehavior(), eWeldingType);
      pCachedShape = FindShape(szShapeId, szShapeCacheId);
    }
    if (pCachedShape)
    {
      pCachedShape->addReference();
      return pCachedShape; 
    }

    if (bAllowStaticMeshCaching)
    {
      // first, find the convex version
      pCachedShape = vHavokCachedShape::LoadConvexShape(meshInstances[0]->GetMesh(), vScale, bShrinkByCvxRadius);
      if (pCachedShape)
      {
        *szShapeCacheId = AddShape(szShapeId, pCachedShape);
#ifdef HK_DEBUG_SLOW
        const hkClass* loadedClassType = hkVtableClassRegistry::getInstance().getClassFromVirtualInstance(pCachedShape);
        HK_ASSERT2(0x5432c902, loadedClassType && (hkString::strCmp( loadedClassType->getName(), "hkvConvexVerticesShape" ) == 0), "Serialized in a unexpected cached Havok shape type!");
#endif
        return pCachedShape; 
      }
      else
      {
        // then find the mesh version
        pCachedShape = vHavokCachedShape::LoadMeshShape(meshInstances[0]->GetMesh(), vScale, meshInstances[0]->GetCollisionBehavior(), eWeldingType);
      }
      if (pCachedShape )
      {
        *szShapeCacheId = AddShape(szShapeId, pCachedShape);
#ifdef HK_DEBUG_SLOW
        const hkClass* loadedClassType = hkVtableClassRegistry::getInstance().getClassFromVirtualInstance(pCachedShape);
        HK_ASSERT2(0x5432c902, loadedClassType && (hkString::strCmp( loadedClassType->getName(), "hkvBvCompressedMeshShape" ) == 0), "Serialized in a unexpected cached Havok shape type!");
#endif
        hkvBvCompressedMeshShape *pCompressedMeshShape = reinterpret_cast<hkvBvCompressedMeshShape*>(pCachedShape);
        pCompressedMeshShape->SetupMaterials();
        return pCachedShape; 
      }
      else if(!Vision::Editor.IsInEditor() && !bForceHktShapeCaching)
      {
        Vision::Error.Warning("Cached HKT file for %s is missing. Please generate HKT file (see documentation for details).", meshInstances[0]->GetMesh()->GetFilename());
      }
    }
  }

  // Get the reference transformation. We use the first static mesh as reference
  // transformation, and thus as the position of the corresponding rigid body.
  hkvMat4 referenceTransform = meshInstances[0]->GetTransform();

  // Remove any scaling from the reference matrix. This one has to be applied to
  // the Havok shapes, and thus needs to be part of the mesh transforms.
  referenceTransform.setScalingFactors(hkvVec3 (1));

  // We need the inverse referenceTransform to transform each instance into reference space
  referenceTransform.invert();
  referenceTransform.setRow (3, hkvVec4 (0, 0, 0, 1));

  // Determine collision type from first static mesh instance.
  const VisStaticMeshInstance_cl *pMeshInstance = meshInstances[0];
  VisStaticMesh_cl *pMesh = pMeshInstance->GetMesh();
  IVCollisionMesh *pColMesh = pMesh->GetCollisionMesh(true, true);
  const bool bIsConvex = pColMesh->GetType()==VIS_COLMESH_GEOTYPE_CONVEXHULL;

  // Only create a convex shape if a single mesh instance is used, since otherwise merging multiple mesh instances in one single convex hull
  // will provide in most cases not the desired behavior. Moreover we can only create either a convex hull or a concave mesh shape, therefore
  // mesh instances with different collision type can't be merged into one shape.
  hkpShape *pShape = (bIsConvex && iCount==1) ?
    CreateConvexShapeFromStaticMeshInstances(meshInstances, referenceTransform, bShrinkByCvxRadius) : 
    CreateMeshShapeFromStaticMeshInstances(meshInstances, referenceTransform, bAllowPerTriCollisionInfo, eWeldingType);

  // We are just caching single static mesh instances and no static mesh collections.
  if (iCount == 1)
  {
     *szShapeCacheId = AddShape(szShapeId, pShape);

    // Only cache shape to HKT file when inside vForge or when enforced.
    if ((Vision::Editor.IsInEditor() && bAllowStaticMeshCaching) || bForceHktShapeCaching)
    {
      if (bIsConvex)
        vHavokCachedShape::SaveConvexShape(meshInstances[0]->GetMesh(), vScale, bShrinkByCvxRadius, (hkvConvexVerticesShape*)pShape);
      else
        vHavokCachedShape::SaveMeshShape(meshInstances[0]->GetMesh(), vScale, meshInstances[0]->GetCollisionBehavior(), eWeldingType, (hkvBvCompressedMeshShape*)pShape);
    }
  }
  return pShape;
}
// -------------------------------------------------------------------------- //
// Havok Shape - Mesh                                                         //
// -------------------------------------------------------------------------- //
hkRefNew<hkpShape> vHavokShapeFactory::CreateShapeFromMesh(VBaseMesh* pMesh, const hkvVec3& vScale, const char **szShapeCacheId,
                                                           int iCreationFlags, VisWeldingType_e eWeldingType)
{
  VVERIFY_OR_RET_VAL(pMesh != NULL, NULL);

  const bool bAllowStaticMeshCaching = vHavokPhysicsModule_GetDefaultWorldRuntimeSettings().m_bEnableShapeCaching==TRUE;
  const bool bCacheShape = iCreationFlags & VShapeCreationFlags_CACHE_SHAPE;  
  const vHavokPhysicsModule *pModule = vHavokPhysicsModule::GetInstance();
  VASSERT(pModule != NULL);
  const bool bForceHktShapeCaching = pModule->IsHktShapeCachingEnforced();
  
  char szShapeId[512];
  if (bCacheShape)
  {
    VASSERT(szShapeCacheId != NULL);

    // Check whether shape has been already cached for this model with the respective scaling
    vHavokShapeFactory::GetIDStringForMeshShape(szShapeId, pMesh->GetFilename(), vScale, VisStaticMeshInstance_cl::VIS_COLLISION_BEHAVIOR_CUSTOM, eWeldingType);
    hkpShape *pCachedShape = FindShape(szShapeId, szShapeCacheId);
    if (pCachedShape)
    {
      pCachedShape->addReference();
      return pCachedShape;
    }

    // Check if shape is already cached on disk
    if (bAllowStaticMeshCaching )
    {
      pCachedShape = vHavokCachedShape::LoadMeshShape(pMesh, vScale, VisStaticMeshInstance_cl::VIS_COLLISION_BEHAVIOR_CUSTOM, eWeldingType);
      if (pCachedShape)
      {
        *szShapeCacheId = AddShape(szShapeId, pCachedShape);

#ifdef HK_DEBUG_SLOW
        const hkClass* loadedClassType = hkVtableClassRegistry::getInstance().getClassFromVirtualInstance(pCachedShape);
        HK_ASSERT2(0x695cc897, loadedClassType && (hkString::strCmp( loadedClassType->getName(), "hkvBvCompressedMeshShape" ) == 0), "Serialized in a unexpected cached Havok shape type!");
#endif

        hkvBvCompressedMeshShape* pCompressedMeshShape = reinterpret_cast<hkvBvCompressedMeshShape*>(pCachedShape);
        pCompressedMeshShape->SetupMaterials(); // just to be sure we don't try to access it as material ptr ever
        return pCompressedMeshShape;
      }
      else if(!Vision::Editor.IsInEditor() && !bForceHktShapeCaching)
      {
        Vision::Error.Warning("Cached HKT file for %s is missing. Please generate HKT file (see documentation for details).", pMesh->GetFilename());
      }
    }
  }
 
  hkGeometry geom;

  // Get the collision mesh for the specified mesh
  IVCollisionMesh *pColMesh = (iCreationFlags & VShapeCreationFlags_USE_VCOLMESH) ? pMesh->GetCollisionMesh(true, true) : pMesh->GetTraceMesh(true, true);
  VASSERT(pColMesh != NULL);

  hkvMat4 tranform;
  tranform.setScalingMatrix(vScale);

  int iNumColMeshes = hkvMath::Max(pColMesh->GetSubmeshCount(), 1);
  for (int i=0;i<iNumColMeshes;i++)
    BuildGeomFromCollisionMesh(pColMesh, i, tranform, false, geom);
  VVERIFY_OR_RET_VAL(geom.m_vertices.getSize() > 0, NULL);

  ///XX A DynamicMesh can be animated. We are treating it as static here.

  hkpDefaultBvCompressedMeshShapeCinfo ci( &geom );
  ci.m_collisionFilterInfoMode = hkpBvCompressedMeshShape::PER_PRIMITIVE_DATA_NONE; // Collision info
  ci.m_userDataMode = hkpBvCompressedMeshShape::PER_PRIMITIVE_DATA_NONE; // Materials
  ci.m_weldingType = vHavokConversionUtils::VisToHkWeldingType(eWeldingType);
  hkvBvCompressedMeshShape* pCompressedMeshShape = new hkvBvCompressedMeshShape(ci, pColMesh->GetFileTime());

  if (pCompressedMeshShape->getNumChildShapes() <= 0)
  {
    pCompressedMeshShape->removeReference();

    const char *szMeshFilename = (pMesh->GetFilename() != NULL) ? pMesh->GetFilename() : "UnnamedMesh";
    Vision::Error.Warning("Physics Shape for [%s] is empty. Volume too small?",  szMeshFilename);
    return NULL;
  }
  
  if (bCacheShape)
    *szShapeCacheId = AddShape(szShapeId, pCompressedMeshShape);

  // Only cache shape to HKT file when inside vForge or when enforced.
  if ((Vision::Editor.IsInEditor() && bAllowStaticMeshCaching && bCacheShape) || bForceHktShapeCaching)   
    vHavokCachedShape::SaveMeshShape(pMesh, vScale, VisStaticMeshInstance_cl::VIS_COLLISION_BEHAVIOR_CUSTOM, eWeldingType, pCompressedMeshShape);

  return pCompressedMeshShape;
}