/**
* Name: Pool using Physic Engine
* Author: Arnaud Grignard (2012) --revised by Alexis Drogoul (2021)
* Description: This is a model that allows the user to play a (simplistic) game of pool in order to show how the physics engine works. It also
* demonstrates the effect of different physical properties (friction, restitution, etc.) 
*
* Tags: physics_engine, skill, 3d, spatial_computation, obstacle
*/
model pool3D

/**
 * The model is inheriting from 'physical_world' a special model species that provides access to the physics engine -- and the possibility
 * to manage physical agents. In this model, the world itself is not a physical agent but all the other agents (balls and walls) are 
 * automatically registered (thanks to the default value of 'true' of the variable 'automated_registration').
 */
global parent: physical_world {
	// The dynamics of the agents is a bit different if we use the native (3.0.x) or Java version (2.8.x) of Bullet
	bool use_native <- false;
	bool draw_aabb <- false;
	string library <- "bullet";
	//All the physical characteristics of the balls can be accessed here and modified at will by the user
	float ball_damping <- 0.05 min: 0.0 max: 1.0 on_change: {ask ball {damping<-ball_damping;}};
	float ball_restitution <- 0.8  min: 0.0 max: 1.0 on_change: {ask ball {restitution<-ball_restitution;}};
	float ball_friction <- 0.2  min: 0.0 max: 1.0 on_change: {ask ball {friction<-ball_friction;}};
	float wall_restitution <- 0.7  min: 0.0 max: 1.0 on_change: {ask wall {restitution<-wall_restitution;}};
	float wall_friction <- 0.2  min: 0.0 max: 1.0 on_change: {ask wall {friction<-wall_friction;}};
	float ground_friction <- 0.6  min: 0.0 max: 1.0 on_change: {ask ground {friction<-ground_friction;}};
	float strength <- 120.0  min: 0.0 max: 200.0;
	int width <- 200;
	int height <- 300;
	//Given that very few agents inhabit this world, the step is really small, so as to prevent the physical world to go too fast. 
	//The simulation itself is aligned with this number (see experiment.minimum_cycle_duration)
	float step <-1.0/120;
	//Gives access (or not) to an improved (but slower) detection collision algorithm
	bool better_collision_detection <- false;
	// Artificially high gravity to make sure that the balls stay on the ground
	point gravity <- {0,0,-20};


	//Physical Engine
	geometry shape <- rectangle(width, height);
	ball white;
	point target;

	init {

		float floor <- -4.0;
		float depth <- 4.0;
		create ground from: [
			box({width - 20, height - 24, depth}) at_location {width / 2, height / 2, floor}, 
			box({width - 20, 20, depth}) at_location {width / 2, 6, floor}, 
			box({width - 20, 20, depth}) at_location {width / 2, height - 6, floor}, 
			box({20, height / 2 - 18, depth}) at_location {6, height / 4 + 3, floor}, 
			box({20, height / 2 - 18, depth}) at_location {6, 3 * height / 4 - 3, floor}, 
			box({20, height / 2 - 18, depth}) at_location {width - 6, height / 4 + 3, floor}, 
			box({20, height / 2 - 18, depth}) at_location {width - 6, 3 * height / 4 - 3, floor}
		];
				
		float section <- 10.0;
		float z <- section + section/4;
		create wall from: [
			line([{0,height + section/2,z}, {width,height + section/2,z}], section/2), //down
			line([{0,-section/2,z}, {width,-section/2,z}], section/2), // up
			line([{-section/2,-section/2,z}, {-section/2,height + section/2, z}], section/2), // left
			line([{width+section/2,-section/2,z}, {width+section/2,height + section/2, z}], section/2) // right

		];
		
		create wall with: [inside::true] from: [
			box(width+3*section/2, section/2, section) at_location {width / 2, height + section/2, 0}, // down
			box(width+3*section/2, section/2, section) at_location {width / 2,  -section/2, 0}, // up
			box(section/2, height +  section, section) at_location {-section/2, height / 2, 0}, // left
			box(section/2, height + section, section) at_location {width + section/2, height / 2, 0} // right
		];

		
		do create_white_ball;

		int deltaI <- 0;
		int initX <- 75;
		int initY <- int(height / 8);
		int i <- 0;

		//Create the other balls for the pool
		create ball number: 15 {
			location <- {initX + (i - deltaI) * 10, initY, 0};
			i <- i + 1;
			color <- (i mod 2) = 0 ? #red :  #yellow;
			if (i in [5, 9, 12, 14]) {
				initX <- initX + 5;
				initY <- initY + 9;
				deltaI <- i;
			}

		}

	}
	
	action create_white_ball {
		create ball {
			location <- {width / 2, 4 * height / 5, 0};
			white <- self;
		}
	}

}

//Species representing the ground agents used for the computation of the forces, using the skill static_body
species ground skills: [static_body] {
	float friction <- ground_friction;
}

//Species representing the wall agents of the pool using the skill static_body
species wall skills: [static_body] {
	bool inside;
	float friction <- wall_friction;
	float restitution <- wall_restitution;	
	
	aspect default {
		if (inside) {
			draw shape color: (#darkgreen);
		} else {
			draw shape texture: "../images/wood.jpg";
		}
		
		if (draw_aabb) {draw aabb wireframe: true border: #lightblue;}

	}
}

//Species representing the ball agents, provided with dynamic_body capabilities (a mass, a velocity, damping ...)
species ball skills: [dynamic_body] {
	rgb color <- #white;
	float mass <-2.0;
	geometry shape <- sphere(5);
	float friction <- ball_friction;
	float restitution <- ball_restitution;
	float damping <- ball_damping;
    float angular_damping <- 0.0;
	float contact_damping <- 0.0;
	
	// If any ball falls or goes away, it is destroyed, except the white ball, replaced on the table
	reflex manage_location when: location.z < -20 {
		if (self = white) {
			ask world {
				do create_white_ball;
			}
			target <- nil;
		}
		do die;
	}

	aspect default {
		draw  sphere(5) color: color;
		if (draw_aabb) {draw aabb wireframe: true border: #lightblue;}

	}

}

experiment "Play !" type: gui autorun: true   {
	parameter "Ball Restitution" var: ball_restitution category: "Ball properties" ;
	parameter "Ball Damping (natural deceleration)" var: ball_damping category: "Ball properties" ;
	parameter "Ball Friction" var: ball_friction category: "Ball properties" ;
	parameter "Wall Restitution" var: wall_restitution category: "Wall properties" ;
	parameter "Wall Friction" var: wall_friction category: "Wall properties" ;
	parameter "Ground Friction" var: ground_friction category: "Ground properties";
	parameter "Strength" var: strength category: "Player properties";
	parameter "Draw bounding boxes" var: draw_aabb category: "General";
	
	// Ensure that the simulation does not go too fast
	float minimum_cycle_duration <- 1.0/120;
	
	action _init_ {
		// A trick to make sure the parameters are expanded and visible when the simulation is launched.
		bool previous <- gama.pref_experiment_expand_params;
		gama.pref_experiment_expand_params <- true;
		create simulation;
		gama.pref_experiment_expand_params <- previous;
	}
	
	output {
		display Pool type: opengl antialias: false axes: false{
			camera #default location: {100.0,400.0,300.0} target: {width/2,height/2,-20.0};
			light #ambient intensity: 180;
			light #default intensity: 180 direction: {0.5, 0.5, -1};
			graphics user {
				if (white != nil) and (target != nil) {
					draw line(white, target) color: #white end_arrow: 3;
				}
				if target = nil {
					draw "Choose a target" color: #white font: font("Helvetica", 30, #bold) at: location + {0, 0, 10} perspective: false anchor: #center;
				}
			}

			event "mouse_down"  {
				target <- #user_location;
				float divisor <- distance_to(target, white.location);
				point direction <- (target - white.location) /divisor;
				// When the user hits the mouse, we apply an impulse to the while ball, in the direction of the target. 'velocity' could also be used here
				ask white {
					do apply impulse: {strength * direction.x * 4, strength * direction.y * 4, 0};
				}
			}
			event "mouse_move" {
				target <- #user_location;	
			}
			species ground refresh: false {
				draw shape texture: image_file("../images/mat.jpg");
				if (draw_aabb) {draw aabb wireframe: true border: #lightblue;}
			}
			species wall refresh:false;
			species ball;
		}

	}

}