text: #include "vcl/vcl.hpp"
  #include <iostream>
  
  using namespace vcl;
  
  struct gui_parameters {
  	bool display_frame = false;
  	bool display_wireframe = false;
  };
  
  struct user_interaction_parameters {
  	vec2 mouse_prev;
  	timer_fps fps_record;
  	mesh_drawable global_frame;
  	gui_parameters gui;
  	bool cursor_on_gui;
  };
  user_interaction_parameters user;
  
  struct scene_environment
  {
  	camera_around_center camera;
  	mat4 projection;
  	vec3 light;
  };
  scene_environment scene;
  
  
  void mouse_move_callback(GLFWwindow* window, double xpos, double ypos);
  void window_size_callback(GLFWwindow* window, int width, int height);
  
  void initialize_data();
  void display_interface();
  void display_terrain();
  
  
  
  mesh_drawable ground;
  mesh_drawable billboard_grass;
  mesh_drawable billboard_bird;
  
  
  
  int main(int, char* argv[])
  {
  	std::cout << "Run " << argv[0] << std::endl;
  
  	int const width = 1280, height = 1024;
  	GLFWwindow* window = create_window(width, height);
  	window_size_callback(window, width, height);
  	std::cout << opengl_info_display() << std::endl;;
  
  	imgui_init(window);
  	glfwSetCursorPosCallback(window, mouse_move_callback);
  	glfwSetWindowSizeCallback(window, window_size_callback);
  	
  	std::cout<<"Initialize data ..."<<std::endl;
  	initialize_data();
  
  	std::cout<<"Start animation loop ..."<<std::endl;
  	user.fps_record.start();
  	glEnable(GL_DEPTH_TEST);
  	while (!glfwWindowShouldClose(window))
  	{
  		scene.light = scene.camera.position();
  		user.fps_record.update();
  		
  		glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
  		glClear(GL_COLOR_BUFFER_BIT);
  		glClear(GL_DEPTH_BUFFER_BIT);
  		imgui_create_frame();
  		if(user.fps_record.event) {
  			std::string const title = "VCL Display - "+str(user.fps_record.fps)+" fps";
  			glfwSetWindowTitle(window, title.c_str());
  		}
  
  		ImGui::Begin("GUI",NULL,ImGuiWindowFlags_AlwaysAutoResize);
  		user.cursor_on_gui = ImGui::IsAnyWindowFocused();
  
  		if(user.gui.display_frame) draw(user.global_frame, scene);
  
  		display_interface();
  		display_terrain();
  
  
  		ImGui::End();
  		imgui_render_frame(window);
  		glfwSwapBuffers(window);
  		glfwPollEvents();
  	}
  
  	imgui_cleanup();
  	glfwDestroyWindow(window);
  	glfwTerminate();
  
  	return 0;
  }
  
  void display_terrain()
  {
  	draw(ground, scene);
  
  	// Enable use of alpha component as color blending for transparent elements
      //  new color = previous color + (1-alpha) current color
      glEnable(GL_BLEND);
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  
  	// Disable depth buffer writing
      //  - Transparent elements cannot use depth buffer
      //  - They are supposed to be display from furest to nearest elements
      glDepthMask(false);
  
  	billboard_bird.transform.rotate = scene.camera.orientation();
  	draw(billboard_bird, scene);
  
  	billboard_grass.transform.rotate = rotation();
  	draw(billboard_grass, scene);
  	billboard_grass.transform.rotate = rotation(vec3{0,0,1}, 3.14f/2);
  	draw(billboard_grass, scene);
  
  	glDepthMask(true);
  
  	if (user.gui.display_wireframe)
  	{
  		draw_wireframe(billboard_bird, scene, {1,0,0});
  
  		billboard_grass.transform.rotate = rotation();
  		draw_wireframe(billboard_grass, scene, {0,0,1});
  		billboard_grass.transform.rotate = rotation(vec3{0,0,1}, 3.14f/2);
  		draw_wireframe(billboard_grass, scene, {0,0,1});
  	}
  }
  
  
  void initialize_data()
  {
  	// Basic setups of shaders and camera
  	GLuint const shader_mesh = opengl_create_shader_program(opengl_shader_preset("mesh_vertex"), opengl_shader_preset("mesh_fragment"));
  	mesh_drawable::default_shader = shader_mesh;
  	mesh_drawable::default_texture = opengl_texture_to_gpu(image_raw{1,1,image_color_type::rgba,{255,255,255,255}});
  
  	user.global_frame = mesh_drawable(mesh_primitive_frame());
  	user.gui.display_frame = false;
  	scene.camera.distance_to_center = 2.5f;
  	scene.camera.look_at({-3,1,2}, {0,0,0.5}, {0,0,1});
  
  	// create a flat ground
  	ground = mesh_drawable(mesh_primitive_quadrangle({-3,-3,0},{3,-3,0},{3,3,0},{-3,3,0}));
  
  	// create a billboard of grass
  	billboard_grass = mesh_drawable(mesh_primitive_quadrangle({-1,0,0},{1,0,0},{1,0,2},{-1,0,2}));
  	billboard_grass.transform.scale = 0.4f;
  	billboard_grass.transform.translate = {0.5f, 0.5f, 0.0f};
  	billboard_grass.texture = opengl_texture_to_gpu(image_load_png("assets/grass.png"));
  
  	// create a billboard of bird
  	billboard_bird = mesh_drawable(mesh_primitive_quadrangle());
  	billboard_bird.transform.translate = {0.5f,-1,1};
  	billboard_bird.texture = opengl_texture_to_gpu(image_load_png("assets/bird.png"));
  
  
  }
  
  
  
  void display_interface()
  {
  	ImGui::Checkbox("Frame", &user.gui.display_frame);
  	ImGui::Checkbox("Wireframe", &user.gui.display_wireframe);
  }
  
  
  void window_size_callback(GLFWwindow* , int width, int height)
  {
  	glViewport(0, 0, width, height);
  	float const aspect = width / static_cast<float>(height);
  	scene.projection = projection_perspective(50.0f*pi/180.0f, aspect, 0.1f, 100.0f);
  }
  
  
  void mouse_move_callback(GLFWwindow* window, double xpos, double ypos)
  {
  	vec2 const  p1 = glfw_get_mouse_cursor(window, xpos, ypos);
  	vec2 const& p0 = user.mouse_prev;
  	glfw_state state = glfw_current_state(window);
  
  	auto& camera = scene.camera;
  	if(!user.cursor_on_gui){
  		if(state.mouse_click_left && !state.key_ctrl)
  			scene.camera.manipulator_rotate_trackball(p0, p1);
  		if(state.mouse_click_left && state.key_ctrl)
  			camera.manipulator_translate_in_plane(p1-p0);
  		if(state.mouse_click_right)
  			camera.manipulator_scale_distance_to_center( (p1-p0).y );
  	}
  
  	user.mouse_prev = p1;
  }
  
  void opengl_uniform(GLuint shader, scene_environment const& current_scene)
  {
  	opengl_uniform(shader, "projection", current_scene.projection);
  	opengl_uniform(shader, "view", current_scene.camera.matrix_view());
  	opengl_uniform(shader, "light", current_scene.light, false);
  }
  
  
  
  