|
-
- //
- // Lol Engine - EasyMesh tutorial
- //
- // Copyright: (c) 2011-2013 Sam Hocevar <sam@hocevar.net>
- // (c) 2012-2013 Benjamin "Touky" Huet <huet.benjamin@gmail.com>
- // This program is free software; you can redistribute it and/or
- // modify it under the terms of the Do What The Fuck You Want To
- // Public License, Version 2, as published by Sam Hocevar. See
- // http://www.wtfpl.net/ for more details.
- //
-
- #if defined HAVE_CONFIG_H
- # include "config.h"
- #endif
-
- #include <cfloat> /* for FLT_MAX */
-
- #include "core.h"
-
- using namespace std;
- using namespace lol;
-
- static int const TEXTURE_WIDTH = 256;
-
- LOLFX_RESOURCE_DECLARE(shinyfur);
- LOLFX_RESOURCE_DECLARE(shinymvtexture);
-
- #define IPT_CAM_RESET "Cam_Center"
- #define IPT_CAM_FORWARD "Cam_Forward"
- #define IPT_CAM_BACKWARD "Cam_Backward"
- #define IPT_CAM_ZOOM_OUT "Cam_Zoom_In"
- #define IPT_CAM_ZOOM_IN "Cam_Zoom_Out"
-
- #define IPT_MESH_UPDATE "Mesh_Update"
- #define IPT_MESH_RESET "Mesh_Reset"
- #define IPT_MESH_PREV "Mesh_Previous"
- #define IPT_MESH_NEXT "Mesh_Next"
-
- #define IPT_MESH_LEFT "Mesh_Left"
- #define IPT_MESH_RIGHT "Mesh_Right"
- #define IPT_MESH_UP "Mesh_Up"
- #define IPT_MESH_DOWN "Mesh_Down"
- #define IPT_MESH_SCALE_UP "Mesh_Scale_Up"
- #define IPT_MESH_SCALE_DOWN "Mesh_Scale_Down"
- #define IPT_MESH_OFFSET_UP "Mesh_Offset_Up"
- #define IPT_MESH_OFFSET_DOWN "Mesh_Offset_Down"
- #define IPT_MESH_ROT_LEFT "Mesh_Rot_Left"
- #define IPT_MESH_ROT_RIGHT "Mesh_Rot_Right"
- #define IPT_MESH_ROT_UP "Mesh_Rot_Up"
- #define IPT_MESH_ROT_DOWN "Mesh_Rot_Down"
-
- #define MIN_FOV 0.1f
-
- #define WITH_FUR 0
- #define WITH_TEXTURE 0
-
- class MeshViewer : public WorldEntity
- {
- public:
- void SetFov(float new_fov=60.0f, vec2 video_size = vec2(Video::GetSize()))
- {
- if (new_fov > MIN_FOV)
- Scene::GetDefault()->GetCamera()->SetProjection(mat4::perspective(new_fov, video_size.x, video_size.y, .1f, 1000.f));
- else
- Scene::GetDefault()->GetCamera()->SetProjection(mat4::ortho(video_size.x, video_size.y, .1f, 1000.f));
- }
-
- MeshViewer(char const *file_name = "data/mesh-buffer.txt")
- : m_file_name(file_name)
- {
- //Input setup
- Input::LinkActionToKey(IPT_CAM_RESET, Key::Return);
- Input::LinkActionToKey(IPT_CAM_ZOOM_IN, Key::PageUp);
- Input::LinkActionToKey(IPT_CAM_ZOOM_OUT, Key::PageDown);
-
- Input::LinkActionToKey(IPT_MESH_LEFT, Key::Left);
- Input::LinkActionToKey(IPT_MESH_RIGHT, Key::Right);
- Input::LinkActionToKey(IPT_MESH_UP, Key::Up);
- Input::LinkActionToKey(IPT_MESH_DOWN, Key::Down);
-
- Input::LinkActionToKey(IPT_MESH_UPDATE, Key::Space);
- Input::LinkActionToKey(IPT_MESH_RESET, Key::KP0);
- Input::LinkActionToKey(IPT_MESH_PREV, Key::KPPlus);
- Input::LinkActionToKey(IPT_MESH_NEXT, Key::KPMinus);
-
- Input::LinkActionToKey(IPT_MESH_OFFSET_DOWN, Key::KP1);
- Input::LinkActionToKey(IPT_MESH_OFFSET_UP, Key::KP3);
- Input::LinkActionToKey(IPT_MESH_SCALE_DOWN, Key::KP7);
- Input::LinkActionToKey(IPT_MESH_SCALE_UP, Key::KP9);
-
- Input::LinkActionToKey(IPT_MESH_ROT_LEFT, Key::KP4);
- Input::LinkActionToKey(IPT_MESH_ROT_RIGHT, Key::KP6);
- Input::LinkActionToKey(IPT_MESH_ROT_UP, Key::KP8);
- Input::LinkActionToKey(IPT_MESH_ROT_DOWN, Key::KP5);
-
- m_mesh_shown = 0;
- m_angle = 0;
- m_default_texture = NULL;
-
- //Camera Setup
- m_fov_zoom_damp = .0f;
- m_fov_damp = 60.0f;
- m_fov = 60.0f;
- m_camera = new Camera();
- SetFov(m_fov_damp);
- m_camera->SetView(vec3(0.f, 0.f, 10.f),
- vec3(0.f, 0.f, 0.f),
- vec3(0.f, 1.f, 0.f));
- Scene::GetDefault()->PushCamera(m_camera);
-
- //Lights setup
- m_lights << new Light();
- m_lights.Last()->SetPosition(vec4(4.f, -1.f, -4.f, 0.f));
- m_lights.Last()->SetColor(vec4(.0f, .2f, .5f, 1.f));
- Ticker::Ref(m_lights.Last());
-
- m_lights << new Light();
- m_lights.Last()->SetPosition(vec4(8.f, 2.f, 6.f, 1.f));
- m_lights.Last()->SetColor(vec4(.5f, .3f, .0f, 1.f));
- Ticker::Ref(m_lights.Last());
-
- //Speed damp
- m_mesh_rotate_damp = vec2(.0f);
- m_mesh_screen_move_damp = vec2(.0f);
- m_mesh_move_damp = vec2(.0f);
-
- //Actual values
- SetDefaultMeshTransform();
-
- //Actual values damp
- m_mesh_rotation_damp = vec2(.0f);
- m_mesh_screen_offset_damp = vec2(.0f);
- m_mesh_offset_damp = vec2(.0f);
-
-
- m_mat = mat4::rotate(m_mesh_rotation.x, vec3(1, 0, 0)) *
- mat4::rotate(m_angle, vec3(0, 1, 0)) *
- mat4::rotate(m_mesh_rotation.y, vec3(0, 1, 0));
-
- m_stream_update_time = 2.0f;
- m_stream_update_timer = 1.0f;
- }
-
- ~MeshViewer()
- {
- Scene::GetDefault()->PopCamera(m_camera);
- for (int i = 0; i < m_lights.Count(); ++i)
- Ticker::Unref(m_lights[i]);
- }
-
- void SetDefaultMeshTransform()
- {
- m_mesh_rotation = vec2(25.0f, .0f);
- m_mesh_screen_offset = vec2(.54f, .0f);
- m_mesh_offset = vec2(-.64f, .07f);
- }
-
- virtual void TickGame(float seconds)
- {
- WorldEntity::TickGame(seconds);
-
- //TODO : This should probably be "standard LoL behaviour"
- {
- //Shutdown logic
- if (Input::WasReleased(Key::Escape))
- Ticker::Shutdown();
- }
-
- //--
- //Update Mesh BBox - Get the Min/Max needed
- //--
- vec2 screen_min_max[2] = { vec2(FLT_MAX), vec2(-FLT_MAX) };
- vec3 cam_min_max[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) };
- vec3 world_min_max[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) };
- int mesh_id = m_meshes.Count() - 1;
- for (; mesh_id >= 0; mesh_id--)
- if (m_meshes[mesh_id].m2)
- break;
-
- mat4 world_cam = Scene::GetDefault()->GetCamera()->GetView();
- mat4 cam_screen = Scene::GetDefault()->GetCamera()->GetProjection();
-
- if (m_meshes.Count() && mesh_id >= 0)
- {
- for (int i = 0; i < m_meshes[mesh_id].m1.GetVertexCount(); i++)
- {
- //--
- mat4 LocalPos = m_mat * mat4::translate(m_meshes[mesh_id].m1.GetVertexLocation(i));
- vec3 vpos = LocalPos.v3.xyz;
-
- world_min_max[0] = min(vpos.xyz, world_min_max[0]);
- world_min_max[1] = max(vpos.xyz, world_min_max[1]);
-
- //--
- LocalPos = world_cam * LocalPos;
- vpos = LocalPos.v3.xyz;
-
- cam_min_max[0] = min(vpos.xyz, cam_min_max[0]);
- cam_min_max[1] = max(vpos.xyz, cam_min_max[1]);
-
- //--
- LocalPos = cam_screen * LocalPos;
- vpos = (LocalPos.v3 / LocalPos.v3.w).xyz;
-
- screen_min_max[0] = min(vpos.xy, screen_min_max[0]);
- screen_min_max[1] = max(vpos.xy, screen_min_max[1]);
- }
- }
- else
- {
- world_min_max[1] = vec3(.0f);
- world_min_max[0] = vec3(.0f);
- cam_min_max[1] = vec3(.0f);
- cam_min_max[0] = vec3(.0f);
- screen_min_max[0] = vec2(.0f);
- screen_min_max[1] = vec2(.0f);
- }
- //[0] : center, [1] : size.
- vec3 BBox[2] = { vec3(.0f), vec3(.0f) };
- BBox[1] = world_min_max[1] - world_min_max[0];
- BBox[0] = world_min_max[0] + BBox[1] * .5f;
- vec3 BBox_mod = BBox[1];
- #if 0
-
- //--
- //Camera movement handling
- //--
- if (Input::WasReleased(IPT_CAM_RESET))
- SetFov();
-
- //Auto Fov
- float local_max = max(max(lol::abs(world_min_max[0].x), lol::abs(world_min_max[0].y)),
- max( lol::abs(world_min_max[1].x), lol::abs(world_min_max[1].y)));
- float fov_ratio = max(max(lol::abs(screen_min_max[0].x), lol::abs(screen_min_max[0].y)),
- max(lol::abs(screen_min_max[1].x), lol::abs(screen_min_max[1].y)));
-
- //Fov modification
- float fov_zoom = (float)(Input::GetStatus(IPT_CAM_ZOOM_OUT) - Input::GetStatus(IPT_CAM_ZOOM_IN));
- m_fov_zoom_damp = damp(m_fov_zoom_damp, fov_zoom, (fov_zoom == .0f)?(.15f):(0.5f), seconds);
- m_fov = max(.0f, m_fov + seconds * 10.0f * m_fov_zoom_damp);
- m_fov_damp = damp(m_fov_damp, m_fov, .2f, seconds);
-
- if (m_fov_damp < MIN_FOV)
- {
- vec2 tmp = vec2(Video::GetSize());
- SetFov(0, vec2(local_max * 2.2f) * (tmp / vec2(tmp.y)));
- }
- else
- SetFov(m_fov_damp);
-
- //Move modification
- vec3 campos = Scene::GetDefault()->GetCamera()->GetPosition();
- if (m_fov_damp < MIN_FOV)
- Scene::GetDefault()->GetCamera()->SetView(vec3(campos.xy, 10.f), quat(1.f));
- else if (fov_ratio > .0f)
- Scene::GetDefault()->GetCamera()->SetView(vec3(campos.xy, campos.z * fov_ratio * 1.1f), quat(1.f));
- #else
- Camera* cur_cam = Scene::GetDefault()->GetCamera();
- vec3 min_max_diff = (cam_min_max[1] - cam_min_max[0]);
- float screen_size = max(max(lol::abs(min_max_diff.x), lol::abs(min_max_diff.y)),
- max( lol::abs(min_max_diff.x), lol::abs(min_max_diff.y)));
- float fov_ratio = max(max(lol::abs(screen_min_max[0].x), lol::abs(screen_min_max[0].y)),
- max(lol::abs(screen_min_max[1].x), lol::abs(screen_min_max[1].y)));
-
- float fov_zoom = (float)(Input::GetStatus(IPT_CAM_ZOOM_OUT) - Input::GetStatus(IPT_CAM_ZOOM_IN));
- m_fov_zoom_damp = damp(m_fov_zoom_damp, fov_zoom, (fov_zoom == .0f)?(.15f):(0.5f), seconds);
- m_fov = max(.0f, m_fov + seconds * 10.0f * m_fov_zoom_damp);
- m_fov_damp = damp(m_fov_damp, m_fov, .2f, seconds);
-
- if (m_fov_damp < MIN_FOV)
- cur_cam->SetProjection(mat4::ortho(screen_size * fov_ratio * 1.1f, 1600.f / 600.f, 1000.f));
- else if (fov_ratio > .0f)
- cur_cam->SetProjection(mat4::shifted_perspective(m_fov_damp, screen_size * fov_ratio * 1.1f, 1600.f / 600.f, 1000.f));
-
- vec3 cam_center = cam_min_max[0] + min_max_diff * .5f;
-
- vec4 test = inverse(world_cam) * vec4(.0f,.0f,-1.0f,1.f);
- test = test;
- test = inverse(world_cam) * vec4(.0f,.0f,.0f,1.f);
- test = inverse(world_cam) * vec4(.0f,.0f,1.0f,1.f);
-
- vec3 eye = (inverse(world_cam) * vec4(vec3(cam_center.xy, cam_min_max[1].z), 1.f)).xyz;
- vec3 target = (inverse(world_cam) * vec4(vec3(cam_center.xy, cam_min_max[0].z), 1.f)).xyz;
- if (eye == target)
- cur_cam->SetView(vec3(.0f), vec3(.0f, .0f, -1.f), vec3(0.f, 1.f, 0.f));
- else
- cur_cam->SetView(eye, target, vec3(0,1,0));
- #endif
-
- //--
- //Mesh movement handling
- //--
- if (Input::WasReleased(IPT_MESH_RESET))
- SetDefaultMeshTransform();
-
- m_mesh_shown += ((int)Input::WasReleased(IPT_MESH_NEXT)) - ((int)Input::WasReleased(IPT_MESH_PREV));
- m_mesh_shown = clamp(m_mesh_shown, 0, max(m_meshes.Count(), 1) - 1);
-
- vec2 new_move = vec2(.0f);
-
- new_move = vec2((float)(Input::GetStatus(IPT_MESH_RIGHT) - Input::GetStatus(IPT_MESH_LEFT)),
- (float)(Input::GetStatus(IPT_MESH_UP) - Input::GetStatus(IPT_MESH_DOWN)));
- m_mesh_screen_move_damp = vec2(damp(m_mesh_screen_move_damp.x, new_move.x, (new_move.x == .0f)?(.15f):(0.5f), seconds),
- damp(m_mesh_screen_move_damp.y, new_move.y, (new_move.y == .0f)?(.15f):(0.5f), seconds));
-
- new_move = vec2((float)(Input::GetStatus(IPT_MESH_OFFSET_UP) - Input::GetStatus(IPT_MESH_OFFSET_DOWN)),
- (float)(Input::GetStatus(IPT_MESH_SCALE_UP) - Input::GetStatus(IPT_MESH_SCALE_DOWN)));
- m_mesh_move_damp = vec2(damp(m_mesh_move_damp.x, new_move.x, (new_move.x == .0f)?(.15f):(0.5f), seconds),
- damp(m_mesh_move_damp.y, new_move.y, (new_move.y == .0f)?(.15f):(0.5f), seconds));
-
- new_move = vec2((float)(Input::GetStatus(IPT_MESH_ROT_UP) - Input::GetStatus(IPT_MESH_ROT_DOWN)),
- (float)(Input::GetStatus(IPT_MESH_ROT_RIGHT) - Input::GetStatus(IPT_MESH_ROT_LEFT)));
- m_mesh_rotate_damp = vec2(damp(m_mesh_rotate_damp.x, new_move.x, (new_move.x == .0f)?(.15f):(0.5f), seconds),
- damp(m_mesh_rotate_damp.y, new_move.y, (new_move.y == .0f)?(.15f):(0.5f), seconds));
-
- vec2 mesh_screen_move = seconds * 0.6f * m_mesh_screen_move_damp;
- vec2 mesh_offset_move = seconds * vec2(.6f, .2f) * m_mesh_move_damp;
- vec2 mesh_rotate = seconds * vec2(40.0f, 60.0f) * m_mesh_rotate_damp;
-
- //Add movement
- m_mesh_screen_offset += mesh_screen_move;
- m_mesh_offset += mesh_offset_move;
- m_mesh_rotation += mesh_rotate;
-
- //Compute damp
- m_mesh_rotation_damp = damp(m_mesh_rotation_damp, m_mesh_rotation, .2f, seconds);
- m_mesh_screen_offset_damp = damp(m_mesh_screen_offset_damp, m_mesh_screen_offset, .2f, seconds);
- m_mesh_offset_damp = damp(m_mesh_offset_damp, m_mesh_offset, .2f, seconds);
-
- //Clamp necessary
- m_mesh_rotation.x = clamp(m_mesh_rotation.x, -90.0f, 90.0f);
- m_mesh_offset.y = max(.0f, m_mesh_offset.y);
-
- //m_angle += seconds * 70.0f;
- m_mat = mat4::rotate(m_mesh_rotation.x, vec3(1, 0, 0)) *
- mat4::rotate(m_angle, vec3(0, 1, 0)) *
- mat4::rotate(m_mesh_rotation.y, vec3(0, 1, 0));
-
- //--
- //File management
- //--
- if (Input::WasReleased(IPT_MESH_UPDATE))
- m_stream_update_time = m_stream_update_timer + 1.0f;
- m_stream_update_time += seconds;
-
- if (m_stream_update_time > m_stream_update_timer)
- {
- m_stream_update_time = 0.f;
-
- File f;
- f.Open(m_file_name.C(), FileAccess::Read);
- String cmd = f.ReadString();
- f.Close();
-
- for (int i = 0; i < cmd.Count() - 1; i++)
- {
- if (cmd[i] == '/' && cmd[i + 1] == '/')
- {
- int j = i;
- for (; j < cmd.Count(); j++)
- {
- if (cmd[j] == '\r' || cmd[j] == '\n')
- break;
- }
- String new_cmd = cmd.Sub(0, i);
- if (j < cmd.Count())
- new_cmd += cmd.Sub(j, cmd.Count() - j);
- cmd = new_cmd;
- i--;
- }
- }
-
- if (cmd.Count()
- && (!m_cmdlist.Count() || cmd != m_cmdlist.Last()))
- {
- m_cmdlist << cmd;
-
- //Create a new mesh
- m_meshes.Push(EasyMesh(), false, .0f, vec3(.0f));
- if (!m_meshes.Last().m1.Compile(cmd.C()))
- m_meshes.Pop();
- //else
- // m_meshes.Last().m1.ComputeTexCoord(0.2f, 2);
- }
- }
- }
-
- virtual void TickDraw(float seconds)
- {
- WorldEntity::TickDraw(seconds);
-
- //TODO : This should probably be "standard LoL behaviour"
- {
- if (Input::WasReleased(Key::F1))
- Video::SetDebugRenderMode(DebugRenderMode::Default);
- if (Input::WasReleased(Key::F2))
- Video::SetDebugRenderMode(DebugRenderMode::Wireframe);
- if (Input::WasReleased(Key::F3))
- Video::SetDebugRenderMode(DebugRenderMode::Lighting);
- if (Input::WasReleased(Key::F4))
- Video::SetDebugRenderMode(DebugRenderMode::Normal);
- if (Input::WasReleased(Key::F5))
- Video::SetDebugRenderMode(DebugRenderMode::UV);
- }
-
- if (!m_default_texture)
- {
- m_texture_shader = Shader::Create(LOLFX_RESOURCE_NAME(shinymvtexture));
- m_texture_uni = m_texture_shader->GetUniformLocation("u_Texture");
- //m_image = new Image("data/test-texture.png");
- m_default_texture = Tiler::Register("data/test-texture.png", ivec2(0), ivec2(0,1));
-
- //ivec2 size = m_image->GetSize();
- //// m_image->GetFormat()
- //m_texture = new Texture(m_image->GetSize(), PixelFormat::ABGR_8);
- //m_texture->SetData(m_image->GetData());
- // PixelFormat::ABGR_8
- }
- else if (m_texture && m_default_texture)
- m_texture_shader->SetUniform(m_texture_uni, m_default_texture->GetTexture(), 0);
-
- for (int i = 0; i < m_meshes.Count(); i++)
- {
- if (!m_meshes[i].m2)
- {
- //Fur support
- #if WITH_FUR
- m_meshes[i].m1.MeshConvert(Shader::Create(LOLFX_RESOURCE_NAME(shinyfur)));
- #elif WITH_TEXTURE
- //m_meshes[i].m1.MeshConvert(m_texture_shader);
- //m_meshes[i].m1.MeshConvert();
- m_meshes[i].m1.MeshConvert(new DefaultShaderData(((1 << VertexUsage::Position) | (1 << VertexUsage::Normal) |
- (1 << VertexUsage::Color) | (1 << VertexUsage::TexCoord)),
- m_texture_shader, true));
- #else
- m_meshes[i].m1.MeshConvert();
- #endif
- m_meshes[i].m2 = true;
- }
- }
-
- Video::SetClearColor(vec4(0.0f, 0.0f, 0.0f, 1.0f));
-
- mat4 default_proj = Scene::GetDefault()->GetCamera()->GetProjection();
- int max_drawn = m_meshes.Count() - m_mesh_shown;
- for (int i = max_drawn; i < m_meshes.Count(); i++)
- m_meshes[i].m4 = vec3(.0f);
- for (int i = 0; i < max_drawn; i++)
- {
- float new_scale = max(.0f, 1.0f - (m_mesh_offset_damp.y * (float)(max_drawn - (i + 1))));
- m_meshes[i].m3 = damp(m_meshes[i].m3, new_scale, .35f, seconds);
- if (m_meshes[i].m3 > .0f)
- {
- vec3 new_mesh_offset = vec3(.0f);
- //damping calculations
- for (int j = max_drawn - 1; j > i; j--)
- {
- float ofs_scale = max(.0f, 1.0f - (m_mesh_offset_damp.y * (float)(max_drawn - (j + 0))));
- new_mesh_offset = new_mesh_offset + vec3(m_meshes[j].m3 * ofs_scale * ofs_scale * m_mesh_offset_damp.x, .0f, .0f);
- }
- m_meshes[i].m4 = damp(m_meshes[i].m4, new_mesh_offset, .35f, seconds);
-
- Scene::GetDefault()->GetCamera()->SetProjection(
- mat4::translate(m_meshes[i].m4) *
- mat4::translate(vec3(m_mesh_screen_offset_damp, .0f)) *
- mat4::scale(vec3(vec2(m_meshes[i].m3), 1.0f)) *
- default_proj);
- #if WITH_FUR
- for (int j=0; j < 40; j++)
- m_meshes[i].m1.Render(m_mat, 0.1 * j);
- #else
- m_meshes[i].m1.Render(m_mat);
- #endif
- Video::Clear(ClearMask::Depth);
- }
- }
- Scene::GetDefault()->GetCamera()->SetProjection(default_proj);
- }
-
- private:
- Camera * m_camera;
- float m_angle;
- mat4 m_mat;
-
- //Mesh infos
- //Move damping
- vec2 m_mesh_rotate_damp;
- vec2 m_mesh_screen_move_damp;
- vec2 m_mesh_move_damp;
-
- //Move transform damping
- vec2 m_mesh_rotation_damp;
- vec2 m_mesh_screen_offset_damp;
- vec2 m_mesh_offset_damp;
-
- vec2 m_mesh_rotation; //Meshes rotation
- vec2 m_mesh_screen_offset;//Meshes screen offset
- vec2 m_mesh_offset; //Mesh Offset after first mesh (x: offset, y: scale)
- int m_mesh_shown;
-
- //File data
- String m_file_name;
- Array<String> m_cmdlist;
- float m_stream_update_time;
- float m_stream_update_timer;
-
- //misc datas
- Array<EasyMesh, bool, float, vec3> m_meshes;
- Array<Light *> m_lights;
- Shader * m_texture_shader;
- TileSet * m_default_texture;
- Texture * m_texture;
- ShaderUniform m_texture_uni;
- Image * m_image;
- float m_fov;
- float m_fov_damp;
- float m_fov_zoom_damp;
- mat4 m_fov_compensation;
- };
-
- //The basic main :
- int main(int argc, char **argv)
- {
- System::Init(argc, argv);
-
- Application app("MeshViewer", ivec2(1600, 600), 60.0f);
- if (argc > 1)
- new MeshViewer(argv[1]);
- else
- new MeshViewer();
- app.Run();
-
- return EXIT_SUCCESS;
- }
|