1
0
mirror of https://git.dev.opencascade.org/repos/occt.git synced 2025-08-09 13:22:24 +03:00

0032433: Visualization, TKService - introduce Wasm_Window implementing Aspect_Window interface using Emscripten SDK

Introduced Wasm_Window implementing Aspect_Window interface.

Aspect_WindowInputListener has been extended by touch input callbacks (moved from AIS_ViewController),
which now implements redirection of single taps to UpdateMouseClick().

AIS_ViewController::FetchNavigationKeys() now requests more frames even if Delta is zero,
but navigation keys are pressed - indicated by a new flag AIS_WalkDelta::IsDefined().

Fixed missing implementation of Xw_Window::DisplayConnection() getter.
The property has been moved to the base class Aspect_Window.

Removed unused Aspect_Convert.hxx.

DRAWEXE targeting Wasm:
- added exposing of FS interface so that it is possible uploading/downloading files to/from emulated file system on JavaScript level;
- added printer redirecting messages to Module.printMessage callback accepting message gravity;
- Run_Appli() now skips std::cin when Module.noExitRuntime is set.
This commit is contained in:
kgv
2021-04-25 17:51:49 +03:00
committed by bugmaster
parent 7b3a032f1e
commit f9ab9f7f1c
28 changed files with 1743 additions and 775 deletions

View File

@@ -17,12 +17,39 @@
#include <ViewerTest_EventManager.hxx>
#include <AIS_AnimationCamera.hxx>
#include <Aspect_DisplayConnection.hxx>
#include <AIS_InteractiveContext.hxx>
#include <AIS_Shape.hxx>
#include <Aspect_Grid.hxx>
#include <Draw.hxx>
#include <Message.hxx>
#include <ViewerTest_ContinuousRedrawer.hxx>
#include <ViewerTest_V3dView.hxx>
#include <ViewerTest.hxx>
#if defined(_WIN32)
//
#elif defined(HAVE_XLIB)
#include <Xw_Window.hxx>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#elif defined(__EMSCRIPTEN__)
#include <Wasm_Window.hxx>
#include <emscripten.h>
#include <emscripten/html5.h>
//! Callback flushing events and redrawing the WebGL canvas.
static void onWasmRedrawView (void* )
{
Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager();
const Handle(V3d_View)& aView = ViewerTest::CurrentView();
const Handle(AIS_InteractiveContext)& aCtx = ViewerTest::GetAISContext();
if (!aViewCtrl.IsNull() && !aView.IsNull() && !aCtx.IsNull())
{
aViewCtrl->ProcessExpose();
}
}
#endif
Standard_IMPORT Standard_Boolean Draw_Interprete (const char* theCommand);
@@ -48,7 +75,8 @@ ViewerTest_EventManager::ViewerTest_EventManager (const Handle(V3d_View)&
: myCtx (theCtx),
myView (theView),
myToPickPnt (Standard_False),
myIsTmpContRedraw (Standard_False)
myIsTmpContRedraw (Standard_False),
myUpdateRequests (0)
{
myViewAnimation = GlobalViewAnimation();
@@ -73,6 +101,9 @@ ViewerTest_EventManager::ViewerTest_EventManager (const Handle(V3d_View)&
addActionHotKeys (Aspect_VKey_NavSlideRight, Aspect_VKey_Right | Aspect_VKeyFlags_SHIFT);
addActionHotKeys (Aspect_VKey_NavSlideUp, Aspect_VKey_Up | Aspect_VKeyFlags_SHIFT);
addActionHotKeys (Aspect_VKey_NavSlideDown, Aspect_VKey_Down | Aspect_VKeyFlags_SHIFT);
// window could be actually not yet set to the View
//SetupWindowCallbacks (theView->Window());
}
//=======================================================================
@@ -89,6 +120,23 @@ ViewerTest_EventManager::~ViewerTest_EventManager()
}
}
// =======================================================================
// function : UpdateMouseClick
// purpose :
// =======================================================================
bool ViewerTest_EventManager::UpdateMouseClick (const Graphic3d_Vec2i& thePoint,
Aspect_VKeyMouse theButton,
Aspect_VKeyFlags theModifiers,
bool theIsDoubleClick)
{
if (theIsDoubleClick && !myView.IsNull() && !myCtx.IsNull())
{
FitAllAuto (myCtx, myView);
return true;
}
return AIS_ViewController::UpdateMouseClick (thePoint, theButton, theModifiers, theIsDoubleClick);
}
//=======================================================================
//function : UpdateMouseButtons
//purpose :
@@ -124,7 +172,6 @@ void ViewerTest_EventManager::ProcessExpose()
{
if (!myView.IsNull())
{
myView->Invalidate();
FlushViewEvents (myCtx, myView, true);
}
}
@@ -136,6 +183,7 @@ void ViewerTest_EventManager::ProcessExpose()
void ViewerTest_EventManager::handleViewRedraw (const Handle(AIS_InteractiveContext)& theCtx,
const Handle(V3d_View)& theView)
{
myUpdateRequests = 0;
AIS_ViewController::handleViewRedraw (theCtx, theView);
// On non-Windows platforms Aspect_Window::InvalidateContent() from rendering thread does not work as expected
@@ -148,10 +196,16 @@ void ViewerTest_EventManager::handleViewRedraw (const Handle(AIS_InteractiveCont
&& (!aRedrawer.IsStarted() || aRedrawer.IsPaused()))
{
myIsTmpContRedraw = true;
#ifndef _WIN32
#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
aRedrawer.Start (theView->Window(), 60.0);
#endif
}
// ask more frames
++myUpdateRequests;
#if defined(__EMSCRIPTEN__)
emscripten_async_call (onWasmRedrawView, this, 0);
#endif
}
else if (myIsTmpContRedraw)
{
@@ -180,7 +234,9 @@ void ViewerTest_EventManager::ProcessConfigure (bool theIsResized)
return;
}
myView->Window()->DoResize();
myView->MustBeResized();
myView->Invalidate();
FlushViewEvents (myCtx, myView, true);
}
}
@@ -191,10 +247,25 @@ void ViewerTest_EventManager::ProcessConfigure (bool theIsResized)
//==============================================================================
void ViewerTest_EventManager::ProcessInput()
{
if (!myView.IsNull())
if (myView.IsNull())
{
FlushViewEvents (myCtx, myView, true);
return;
}
#if defined(__EMSCRIPTEN__)
// Queue onWasmRedrawView() callback to redraw canvas after all user input is flushed by browser.
// Redrawing viewer on every single message would be a pointless waste of resources,
// as user will see only the last drawn frame due to WebGL implementation details.
if (++myUpdateRequests == 1)
{
#if defined(__EMSCRIPTEN__)
emscripten_async_call (onWasmRedrawView, this, 0);
#endif
}
#else
// handle synchronously
ProcessExpose();
#endif
}
// =======================================================================
@@ -538,3 +609,145 @@ void ViewerTest_EventManager::ProcessKeyPress (Aspect_VKey theKey)
Draw_Interprete (aCmd.ToCString());
}
}
#if defined(__EMSCRIPTEN__)
//! Handle browser window resize event.
static EM_BOOL onResizeCallback (int theEventType, const EmscriptenUiEvent* theEvent, void* )
{
Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager();
if (!aViewCtrl.IsNull()
&& !ViewerTest::CurrentView().IsNull())
{
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window());
return aWindow->ProcessUiEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
}
return EM_FALSE;
}
//! Handle mouse input event.
static EM_BOOL onWasmMouseCallback (int theEventType, const EmscriptenMouseEvent* theEvent, void* )
{
Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager();
if (!aViewCtrl.IsNull()
&& !ViewerTest::CurrentView().IsNull())
{
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window());
return aWindow->ProcessMouseEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
}
return EM_FALSE;
}
//! Handle mouse wheel event.
static EM_BOOL onWasmWheelCallback (int theEventType, const EmscriptenWheelEvent* theEvent, void* )
{
Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager();
if (!aViewCtrl.IsNull()
&& !ViewerTest::CurrentView().IsNull())
{
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window());
return aWindow->ProcessWheelEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
}
return EM_FALSE;
}
//! Handle touch input event.
static EM_BOOL onWasmTouchCallback (int theEventType, const EmscriptenTouchEvent* theEvent, void* )
{
Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager();
if (!aViewCtrl.IsNull()
&& !ViewerTest::CurrentView().IsNull())
{
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window());
return aWindow->ProcessTouchEvent (*aViewCtrl, theEventType, theEvent) ? EM_TRUE : EM_FALSE;
}
return EM_FALSE;
}
//! Handle keyboard input event.
static EM_BOOL onWasmKeyCallback (int theEventType, const EmscriptenKeyboardEvent* theEvent, void* )
{
Handle(ViewerTest_EventManager) aViewCtrl = ViewerTest::CurrentEventManager();
if (!aViewCtrl.IsNull()
&& !ViewerTest::CurrentView().IsNull())
{
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (ViewerTest::CurrentView()->Window());
aWindow->ProcessKeyEvent (*aViewCtrl, theEventType, theEvent);
return EM_TRUE;
}
return EM_FALSE;
}
#endif
// ==============================================================================
// function : SetupWindowCallbacks
// purpose :
// ==============================================================================
void ViewerTest_EventManager::SetupWindowCallbacks (const Handle(Aspect_Window)& theWin)
{
#ifdef _WIN32
(void )theWin;
#elif defined(HAVE_XLIB)
// X11
Window anXWin = (Window )theWin->NativeHandle();
Display* anXDisplay = (Display* )theWin->DisplayConnection()->GetDisplayAspect();
XSynchronize (anXDisplay, 1);
// X11 : For keyboard on SUN
XWMHints aWmHints;
memset (&aWmHints, 0, sizeof(aWmHints));
aWmHints.flags = InputHint;
aWmHints.input = 1;
XSetWMHints (anXDisplay, anXWin, &aWmHints);
XSelectInput (anXDisplay, anXWin,
ExposureMask | KeyPressMask | KeyReleaseMask
| ButtonPressMask | ButtonReleaseMask
| StructureNotifyMask
| PointerMotionMask
| Button1MotionMask | Button2MotionMask
| Button3MotionMask | FocusChangeMask);
Atom aDeleteWindowAtom = theWin->DisplayConnection()->GetAtom (Aspect_XA_DELETE_WINDOW);
XSetWMProtocols (anXDisplay, anXWin, &aDeleteWindowAtom, 1);
XSynchronize (anXDisplay, 0);
#elif defined(__EMSCRIPTEN__)
Handle(Wasm_Window) aWindow = Handle(Wasm_Window)::DownCast (theWin);
if (aWindow->CanvasId().IsEmpty()
|| aWindow->CanvasId() == "#")
{
Message::SendFail ("Error: unable registering callbacks to Module.canvas");
return;
}
const char* aTargetId = aWindow->CanvasId().ToCString();
const EM_BOOL toUseCapture = EM_TRUE;
void* anOpaque = NULL; //this; // unused
// make sure to clear previously set listeners (e.g. created by another ViewerTest_EventManager instance)
emscripten_html5_remove_all_event_listeners();
// resize event implemented only for a window by browsers,
// so that if web application changes canvas size by other means it should use another way to tell OCCT about resize
emscripten_set_resize_callback (EMSCRIPTEN_EVENT_TARGET_WINDOW, anOpaque, toUseCapture, onResizeCallback);
emscripten_set_mousedown_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_mouseup_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_mousemove_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_dblclick_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_click_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_mouseenter_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_mouseleave_callback (aTargetId, anOpaque, toUseCapture, onWasmMouseCallback);
emscripten_set_wheel_callback (aTargetId, anOpaque, toUseCapture, onWasmWheelCallback);
emscripten_set_touchstart_callback (aTargetId, anOpaque, toUseCapture, onWasmTouchCallback);
emscripten_set_touchend_callback (aTargetId, anOpaque, toUseCapture, onWasmTouchCallback);
emscripten_set_touchmove_callback (aTargetId, anOpaque, toUseCapture, onWasmTouchCallback);
emscripten_set_touchcancel_callback(aTargetId, anOpaque, toUseCapture, onWasmTouchCallback);
// keyboard input requires a focusable element or EMSCRIPTEN_EVENT_TARGET_WINDOW
emscripten_set_keydown_callback (aTargetId, anOpaque, toUseCapture, onWasmKeyCallback);
emscripten_set_keyup_callback (aTargetId, anOpaque, toUseCapture, onWasmKeyCallback);
#else
(void )theWin;
#endif
}

View File

@@ -22,6 +22,7 @@
#include <TCollection_AsciiString.hxx>
class AIS_InteractiveContext;
class Aspect_Window;
class V3d_View;
DEFINE_STANDARD_HANDLE(ViewerTest_EventManager, Standard_Transient)
@@ -58,6 +59,9 @@ public:
//! Destructor.
Standard_EXPORT virtual ~ViewerTest_EventManager();
//! Setup or adjust window callbacks.
Standard_EXPORT static void SetupWindowCallbacks (const Handle(Aspect_Window)& theWin);
//! Return interactive context.
const Handle(AIS_InteractiveContext)& Context() const { return myCtx; }
@@ -75,6 +79,12 @@ public:
myPickPntArgVec[2] = theArgZ;
}
//! Handle mouse button click event.
Standard_EXPORT virtual bool UpdateMouseClick (const Graphic3d_Vec2i& thePoint,
Aspect_VKeyMouse theButton,
Aspect_VKeyFlags theModifiers,
bool theIsDoubleClick) Standard_OVERRIDE;
//! Handle mouse button press/release event.
Standard_EXPORT virtual bool UpdateMouseButtons (const Graphic3d_Vec2i& thePoint,
Aspect_VKeyMouse theButtons,
@@ -138,6 +148,8 @@ private:
Standard_Boolean myToPickPnt;
Standard_Boolean myIsTmpContRedraw;
unsigned int myUpdateRequests; //!< counter for unhandled update requests
};
#endif // _ViewerTest_EventManager_HeaderFile

View File

@@ -112,6 +112,9 @@
#include <X11/Xutil.h>
#elif defined(__APPLE__)
#include <Cocoa_Window.hxx>
#elif defined(__EMSCRIPTEN__)
#include <Wasm_Window.hxx>
#include <emscripten/emscripten.h>
#else
#include <Aspect_NeutralWindow.hxx>
#endif
@@ -135,10 +138,32 @@ static void VProcessEvents(ClientData,int);
typedef Cocoa_Window ViewerTest_Window;
extern void ViewerTest_SetCocoaEventManagerView (const Handle(Cocoa_Window)& theWindow);
extern void GetCocoaScreenResolution (Standard_Integer& theWidth, Standard_Integer& theHeight);
#elif defined(__EMSCRIPTEN__)
typedef Wasm_Window ViewerTest_Window;
#else
typedef Aspect_NeutralWindow ViewerTest_Window;
#endif
#if defined(__EMSCRIPTEN__)
//! Return DOM id of default WebGL canvas from Module.canvas.
EM_JS(char*, occJSModuleCanvasId, (), {
const aCanvasId = Module.canvas.id;
const aNbBytes = lengthBytesUTF8 (aCanvasId) + 1;
const aStrPtr = Module._malloc (aNbBytes);
stringToUTF8 (aCanvasId, aStrPtr, aNbBytes);
return aStrPtr;
});
//! Return DOM id of default WebGL canvas from Module.canvas.
static TCollection_AsciiString getModuleCanvasId()
{
char* aRawId = occJSModuleCanvasId();
TCollection_AsciiString anId (aRawId != NULL ? aRawId : "");
free (aRawId);
return anId;
}
#endif
static Handle(ViewerTest_Window)& VT_GetWindow()
{
static Handle(ViewerTest_Window) aWindow;
@@ -160,8 +185,6 @@ NCollection_DoubleMap <TCollection_AsciiString, Handle(V3d_View)> ViewerTest_myV
static NCollection_DoubleMap <TCollection_AsciiString, Handle(AIS_InteractiveContext)> ViewerTest_myContexts;
static NCollection_DoubleMap <TCollection_AsciiString, Handle(Graphic3d_GraphicDriver)> ViewerTest_myDrivers;
static void OSWindowSetup();
static struct
{
Quantity_Color FlatColor;
@@ -1663,15 +1686,18 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft
// window fit in the small screens (actual for remote desktops, see #23003).
// The position corresponds to the window's client area, thus some
// gap is added for window frame to be visible.
Standard_Integer aPxLeft = 20;
Standard_Integer aPxTop = 40;
Standard_Integer aPxWidth = 409;
Standard_Integer aPxHeight = 409;
Standard_Integer aPxLeft = 20, aPxTop = 40;
Standard_Integer aPxWidth = 409, aPxHeight = 409;
Standard_Boolean isDefViewSize = Standard_True;
Standard_Boolean toCreateViewer = Standard_False;
const Standard_Boolean isVirtual = Draw_VirtualWindows || theIsVirtual;
if (!theViewToClone.IsNull())
{
theViewToClone->Window()->Size (aPxWidth, aPxHeight);
isDefViewSize = Standard_False;
#if !defined(__EMSCRIPTEN__)
(void )isDefViewSize;
#endif
}
Handle(Graphic3d_GraphicDriverFactory) aFactory = Graphic3d_GraphicDriverFactory::DefaultDriverFactory();
@@ -1692,17 +1718,29 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft
Handle(Graphic3d_GraphicDriver) aGraphicDriver;
ViewerTest_Names aViewNames(theViewName);
if (ViewerTest_myViews.IsBound1 (aViewNames.GetViewName ()))
if (ViewerTest_myViews.IsBound1 (aViewNames.GetViewName()))
{
aViewNames.SetViewName (aViewNames.GetViewerName() + "/" + CreateName<Handle(V3d_View)>(ViewerTest_myViews, "View"));
}
if (thePxLeft != 0)
{
aPxLeft = thePxLeft;
}
if (thePxTop != 0)
{
aPxTop = thePxTop;
}
if (thePxWidth != 0)
{
isDefViewSize = Standard_False;
aPxWidth = thePxWidth;
}
if (thePxHeight != 0)
{
isDefViewSize = Standard_False;
aPxHeight = thePxHeight;
}
// Get graphic driver (create it or get from another view)
const bool isNewDriver = !ViewerTest_myDrivers.IsBound1 (aViewNames.GetDriverName());
@@ -1862,6 +1900,25 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft
aPxLeft, aPxTop,
aPxWidth, aPxHeight);
ViewerTest_SetCocoaEventManagerView (VT_GetWindow());
#elif defined(__EMSCRIPTEN__)
// current EGL implementation in Emscripten supports only one global WebGL canvas returned by Module.canvas property;
// the code should be revised for handling multiple canvas elements (which is technically also possible)
TCollection_AsciiString aCanvasId = getModuleCanvasId();
if (!aCanvasId.IsEmpty())
{
aCanvasId = TCollection_AsciiString("#") + aCanvasId;
}
VT_GetWindow() = new Wasm_Window (aCanvasId);
Graphic3d_Vec2i aRealSize;
VT_GetWindow()->Size (aRealSize.x(), aRealSize.y());
if (!isDefViewSize || (aRealSize.x() <= 0 && aRealSize.y() <= 0))
{
// Wasm_Window wraps an existing HTML element without creating a new one.
// Keep size defined on a web page instead of defaulting to 409x409 (as in case of other platform),
// but resize canvas if vinit has been called with explicitly specified dimensions.
VT_GetWindow()->SetSizeLogical (Graphic3d_Vec2d (aPxWidth, aPxHeight));
}
#else
// not implemented
VT_GetWindow() = new Aspect_NeutralWindow();
@@ -1887,7 +1944,8 @@ TCollection_AsciiString ViewerTest::ViewerInit (const Standard_Integer thePxLeft
ViewerTest_myViews.Bind (aViewNames.GetViewName(), aView);
// Setup for X11 or NT
OSWindowSetup();
SetDisplayConnection (ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection());
ViewerTest_EventManager::SetupWindowCallbacks (VT_GetWindow());
// Set parameters for V3d_View and V3d_Viewer
const Handle (V3d_View) aV3dView = ViewerTest::CurrentView();
@@ -2540,15 +2598,7 @@ void ActivateView (const TCollection_AsciiString& theViewName,
ViewerTest::CurrentView (aView);
ViewerTest::SetAISContext (anAISContext);
aView->Window()->SetTitle (TCollection_AsciiString("3D View - ") + theViewName + "(*)");
#if defined(_WIN32)
VT_GetWindow() = Handle(WNT_Window)::DownCast(ViewerTest::CurrentView()->Window());
#elif defined(HAVE_XLIB)
VT_GetWindow() = Handle(Xw_Window)::DownCast(ViewerTest::CurrentView()->Window());
#elif defined(__APPLE__)
VT_GetWindow() = Handle(Cocoa_Window)::DownCast(ViewerTest::CurrentView()->Window());
#else
VT_GetWindow() = Handle(Aspect_NeutralWindow)::DownCast(ViewerTest::CurrentView()->Window());
#endif
VT_GetWindow() = Handle(ViewerTest_Window)::DownCast(ViewerTest::CurrentView()->Window());
SetDisplayConnection(ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection());
if (theToUpdate)
{
@@ -3473,45 +3523,6 @@ int ViewerMainLoop (Standard_Integer , const char** )
}
#endif
//==============================================================================
//function : OSWindowSetup
//purpose : Setup for the X11 window to be able to catch the event
//==============================================================================
static void OSWindowSetup()
{
#ifdef _WIN32
//
#elif defined(HAVE_XLIB)
// X11
Window anXWin = VT_GetWindow()->XWindow();
SetDisplayConnection (ViewerTest::CurrentView()->Viewer()->Driver()->GetDisplayConnection());
Display* aDisplay = (Display* )GetDisplayConnection()->GetDisplayAspect();
XSynchronize (aDisplay, 1);
// X11 : For keyboard on SUN
XWMHints aWmHints;
memset (&aWmHints, 0, sizeof(aWmHints));
aWmHints.flags = InputHint;
aWmHints.input = 1;
XSetWMHints (aDisplay, anXWin, &aWmHints);
XSelectInput (aDisplay, anXWin,
ExposureMask | KeyPressMask | KeyReleaseMask
| ButtonPressMask | ButtonReleaseMask
| StructureNotifyMask
| PointerMotionMask
| Button1MotionMask | Button2MotionMask
| Button3MotionMask | FocusChangeMask);
Atom aDeleteWindowAtom = GetDisplayConnection()->GetAtom (Aspect_XA_DELETE_WINDOW);
XSetWMProtocols (aDisplay, anXWin, &aDeleteWindowAtom, 1);
XSynchronize (aDisplay, 0);
#else
//
#endif
}
//==============================================================================
//function : VFit
//purpose :