/**
* Name: Boids 3D Analysis
* Author: 
* Description:  This model shows the movement of boids following a goal and creating a flock. 
* Four experiments are proposed : Simple is the 3D display of the boids like a real world, 
* Trajectory Analysis is about the analysis of the trajectories of the boids, Space Time Cube adds two 
* 	displays to see the movement of the boids using the time as the z-axis, and the last one represents the 
* 	differents cameras available in GAMA.
* Tags: gui, skill, 3d, camera, multi_level
*/
model boids

global torus: torus_environment {
//Number of boids to represent
	int number_of_agents parameter: 'Number of agents' <- 10 min: 1 max: 500;
	//Number of obstacles to represent
	int number_of_obstacles parameter: 'Number of obstacles' <- 4 min: 0;
	//Size of the boids
	int boids_size parameter: 'Boids size' <- 20 min: 1;
	//Maximal speed allowed for the boids
	float maximal_speed parameter: 'Maximal speed' <- 15.0 min: 0.1 max: 15.0;
	//Speed radius
	float radius_speed parameter: 'radius speed' <- 0.5 min: 0.1;
	//Cohesion factor of the boid group  in the range of a boid agent
	int cohesion_factor parameter: 'Cohesion Factor' <- 200;
	//Alignment factor used for the boid group in the range of a boid agent
	int alignment_factor parameter: 'Alignment Factor' <- 100;
	//Minimal distance to move
	float minimal_distance parameter: 'Minimal Distance' <- 30.0;
	//Maximal turn done by the boids
	int maximal_turn parameter: 'Maximal Turn' <- 45 min: 0 max: 359;

	//Parameters of the environment and the simulations
	int width_and_height_of_environment parameter: 'Width/Height of the Environment' <- 800;
	bool torus_environment parameter: 'Toroidal Environment ?' <- false;
	bool apply_cohesion <- true parameter: 'Apply Cohesion ?';
	bool apply_alignment <- true parameter: 'Apply Alignment ?';
	bool apply_separation <- true parameter: 'Apply Separation ?';
	bool apply_goal <- true parameter: 'Follow Goal ?';
	bool apply_avoid <- true parameter: 'Apply Avoidance ?';
	bool apply_wind <- true parameter: 'Apply Wind ?';
	bool moving_obstacles <- false parameter: 'Moving Obstacles ?';
	int bounds <- int(width_and_height_of_environment / 20);
	//Wind vector 
	point wind_vector <- {0, 0} parameter: 'Direction of the wind';
	int goal_duration <- 30 update: (goal_duration - 1);
	//Goal location
	point goal <- {rnd(width_and_height_of_environment - 2) + 1, rnd(width_and_height_of_environment - 2) + 1};
	list images of: image_file <- [file('../images/bird1.png'), file('../images/bird2.png'), file('../images/bird3.png')];
	string file_path_to_ocean <- '../images/ocean.jpg';
	int xmin <- bounds;
	int ymin <- bounds;
	int xmax <- (width_and_height_of_environment - bounds);
	int ymax <- (width_and_height_of_environment - bounds);
	geometry shape <- square(width_and_height_of_environment);

	// flock's parameter 
	float two_boids_distance const: true init: 30.0;
	int merging_distance const: true init: 30;
	bool create_flock init: false;

	init {
	//Create the boids and place them randomlly
		create boids number: number_of_agents {
			location <- {rnd(width_and_height_of_environment - 2) + 1, rnd(width_and_height_of_environment - 2) + 1};
		}

		//Create an obstacle and place it randomly
		create obstacle number: number_of_obstacles {
			location <- {rnd(width_and_height_of_environment - 2) + 1, rnd(width_and_height_of_environment - 2) + 1};
		}
		//Create a goal and place it at the goal location
		create boids_goal number: 1 {
			location <- goal;
		}

		create aggregatedboids;
	}

	//Reflex to create flock of boids considering the neighbours of each boids 
	reflex create_flocks {
		if create_flock {
		//Create a map using a boid agent as a key and the list of all its neighbours as the value for the key
			map> potentialBoidsNeighboursMap;

			//Search all the boids within the two boids distance from a boid agent and put them in the map
			loop one_boids over: boids {
				list free_neighbours <- boids overlapping (one_boids.shape + (two_boids_distance));
				remove one_boids from: free_neighbours;
				if !(empty(free_neighbours)) {
					add (one_boids::free_neighbours) to: potentialBoidsNeighboursMap;
				}

			}

			//Sorting of all the boids considered as key in the map by the length of their neighbours
			list sorted_free_boids <- (potentialBoidsNeighboursMap.keys) sort_by (length(potentialBoidsNeighboursMap at each));
			//Removing of all the boids which has been considered as a key of the map, but  which are already included in a bigger list of neighbours by one of them neighbours
			loop one_boids over: sorted_free_boids {
				list one_boids_neighbours <- potentialBoidsNeighboursMap at one_boids;
				if (one_boids_neighbours != nil) {
					loop one_neighbour over: one_boids_neighbours {
						remove one_neighbour from: potentialBoidsNeighboursMap;
					}

				}

			}
			//Remove all the duplicates key of potentialBoidsNeighboursMap
			list boids_neighbours <- (potentialBoidsNeighboursMap.keys);
			loop one_key over: boids_neighbours {
				put (remove_duplicates((potentialBoidsNeighboursMap at (one_key)) + one_key)) at: one_key in: potentialBoidsNeighboursMap;
			}

			//Create a flock of boids considering the key of potentialBoidsNeighboursMap
			loop one_key over: (potentialBoidsNeighboursMap.keys) {
				list micro_agents <- potentialBoidsNeighboursMap at one_key;
				if ((length(micro_agents)) > 1) {
					create flock number: 1 with: [color::rgb([rnd(255), rnd(255), rnd(255)])] {
						capture micro_agents as: boids_delegation;
					}

				}

			}

		}

	}

}
//Species boids_goal which represents the goal followed by the boids agent, using the skill moving
species boids_goal skills: [moving] {
	float range const: true init: 20.0;
	int radius <- 3;

	//Reflex to make the goal move in circle
	reflex wander_in_circle {
		location <- {world.shape.width / 2 + world.shape.width / 2 * cos(time * radius_speed), world.shape.width / 2 + world.shape.width / 2 * sin(time * radius_speed)};
		goal <- location;
	}

	aspect default {
		draw circle(10) color: rgb('red');
		draw circle(40) color: rgb('orange') wireframe: true;
	}

}

//Species flock which represents the flock of boids agents, managing the boids agents captured
species flock {
//Represent the cohesion index of the flock
	float cohesionIndex <- two_boids_distance update: (two_boids_distance + (length(members)));
	rgb color <- rgb([64, 64, 64]);
	geometry shape update: !(empty(members)) ? ((polygon(members collect
	(boids_delegation(each)).location)) + 2.0) : (polygon([{rnd(width_and_height_of_environment), rnd(width_and_height_of_environment)}]));

	//Species that will represent the boids agents captured or inside a flock
	species boids_delegation parent: boids topology: topology(world.shape) {
		list others -> ((boids_delegation overlapping (shape + range))) - self;

		//Action to compute the mass center of the flock
		action compute_mass_center type: point {
			loop o over: others {
				if dead(o) {
					write 'in ' + name + ' agent with others contains death agents';
				}

			}

			return (length(others) > 0) ? (mean(others collect (each.location))) : location;
		}

		reflex separation when: apply_separation {
		}

		reflex alignment when: apply_alignment {
		}
		//Reflex to apply the cohesion on the boids agents
		reflex cohesion when: apply_cohesion {
			point acc <- compute_mass_center() - location;
			acc <- acc / cohesion_factor;
			velocity <- velocity + acc;
		}

		reflex avoid when: apply_avoid {
		}

	}
	//Reflex to capture boids agents and release captured boids agents
	reflex capture_release_boids {
		list removed_components <- boids_delegation where ((each distance_to location) > cohesionIndex);
		if !(empty(removed_components)) {
			release removed_components;
		}

		list added_components <- boids where ((each distance_to location) < cohesionIndex);
		if !(empty(added_components)) {
			capture added_components as: boids_delegation;
		}

	}
	//Reflexe to kill the flock if the boids agents contained is lower than 2
	reflex dispose when: ((length(members)) < 2) {
		release list(members);
		do die;
	}
	//Reflex to merge the flocks too close from each other
	reflex merge_nearby_flocks {
		list nearby_flocks <- (flock overlapping (shape + merging_distance));
		if !(empty(nearby_flocks)) {
			nearby_flocks <- nearby_flocks sort_by (length(each.members));
			flock largest_flock <- nearby_flocks at ((length(nearby_flocks)) - 1);
			remove largest_flock from: nearby_flocks;
			list added_components;
			loop one_flock over: nearby_flocks {
				release list(one_flock.members) returns: released_boids;
				loop rb over: released_boids {
					add boids(rb) to: added_components;
				}

			}

			if !(empty(added_components)) {
				ask largest_flock {
					capture added_components as: boids_delegation;
				}

			}

		}

	}

	aspect default {
		draw shape color: color;
	}

}

//Species to represent the boids aggregated
species aggregatedboids {

	reflex updateLocation {
		location <- mean(boids collect (each.location));
	}

	aspect base {
		draw circle(10) color: #white;
	}

}
//Species to represent the boids agent using the skill moving
species boids skills: [moving] {
//Speed of the agent
	float speed max: maximal_speed <- maximal_speed;
	//Range of movement for the neighbours
	float range <- minimal_distance * 2;
	//Velocity of the agent
	point velocity <- {0, 0};
	float hue <- rnd(360) / 360;

	//List of the neighbours boids
	list others update: ((boids overlapping (circle(range))) - self);

	//Point of the mass center of the "flock" considered as the neighbours agents
	point mass_center update: (length(others) > 0) ? (mean(others collect (each.location))) : location;

	//Reflex to do the separation of the agents with the other boids in the minimal distance
	reflex separation when: apply_separation {
		point acc <- {0, 0};
		loop boid over: (boids at_distance minimal_distance) {
			acc <- acc - ((boid.location) - location);
		}

		velocity <- velocity + acc;
	}

	//Reflex to do the alignement of the boids
	reflex alignment when: apply_alignment {
		point acc <- mean(others collect (each.velocity)) - velocity;
		velocity <- velocity + (acc / alignment_factor);
	}

	//Reflex to apply the cohesion using the mass center of the "flock"
	reflex cohesion when: apply_cohesion {
		point acc <- mass_center - location;
		acc <- acc / cohesion_factor;
		velocity <- velocity + acc;
	}
	//Reflex to avoid the obstacles
	reflex avoid when: apply_avoid {
		point acc <- {0, 0};
		list nearby_obstacles <- (obstacle overlapping (circle(range)));
		loop obs over: nearby_obstacles {
			acc <- acc - ((location of obs) - my (location));
		}

		velocity <- velocity + acc;
	}
	//action to represent the bounding of the movement of the boids
	action bounding {
		if !(torus_environment) {
			if (location.x) < xmin {
				velocity <- velocity + {bounds, 0};
			} else if (location.x) > xmax {
				velocity <- velocity - {bounds, 0};
			}

			if (location.y) < ymin {
				velocity <- velocity + {0, bounds};
			} else if (location.y) > ymax {
				velocity <- velocity - {0, bounds};
			}

		}

	}
	//Reflex to follow the goal 
	reflex follow_goal when: apply_goal {
		velocity <- velocity + ((goal - location) / cohesion_factor);
	}
	//Reflex to apply the wind vector
	reflex wind when: apply_wind {
		velocity <- velocity + wind_vector;
	}
	//action to move  
	action do_move {
		if (((velocity.x) as int) = 0) and (((velocity.y) as int) = 0) {
			velocity <- {(rnd(4)) - 2, (rnd(4)) - 2};
		}

		point old_location <- location;
		do goto target: location + velocity;
		velocity <- location - old_location;
	}

	//Reflex to do the movement, calling both bounding and do_move actions
	reflex movement {
		do bounding;
		do do_move;
	}

	aspect basic {
		draw triangle(boids_size) color: rgb('black');
	}

	aspect image {
		draw (images at (rnd(2))) size: boids_size rotate: heading color: rgb('black');
	}

	aspect dynamicColor {
		rgb cc <- hsb(float(heading) / 360.0, 1.0, 1.0);
		draw triangle(20) size: 15 rotate: 90 + heading color: cc border: cc depth: 5;
	}

}

//Species which represents the obstacles using the skill moving
species obstacle skills: [moving] {
	float speed <- 0.1;

	aspect default {
		draw triangle(20) color: rgb('yellow') depth: 5;
	}

}

experiment "Simple" type: gui {
	float minimum_cycle_duration <- 0.05;
	output {
		display RealBoids type: opengl {
			image 'background' file: file_path_to_ocean;
			species boids aspect: dynamicColor position: {0, 0, 0.1} trace: 30;
			species boids_goal transparency: 0.2 position: {0, 0, 0.1};
			species obstacle position: {0, 0, 0.1};
		}

	}

}

experiment "Trajectory Analysis" type: gui {
	float minimum_cycle_duration <- 0.05;
	output {
		layout #split;
		display RealBoids type: opengl{
			image 'background' file: file_path_to_ocean;
			species boids aspect: dynamicColor transparency: 0.5 position: {0, 0, 0.1};
			species boids_goal transparency: 0.2 position: {0, 0, 0.1};
			species obstacle position: {0, 0, 0.1};
		}

		display AggregatedBoidsTrajectory type: opengl{
			image 'background' file: file_path_to_ocean;
			species aggregatedboids aspect: base trace: 100 fading: true;
			species boids_goal aspect: default;
		}

	}

}

experiment "Space & Time Cube" type: gui {
	float minimum_cycle_duration <- 0.05;
	output {
		layout #split;
		display RealBoids type: opengl{
			image 'background' file: file_path_to_ocean;
			species boids aspect: dynamicColor transparency: 0.5 position: {0, 0, 0.1};
			species boids_goal transparency: 0.2 position: {0, 0, 0.1};
			species obstacle position: {0, 0, 0.1};
		}

		display SpaceTimeCubeAll type: opengl camera:#from_up_front{
			image 'background' file: file_path_to_ocean refresh: false;
			species boids trace: 100 {
				draw triangle(20) size: 15 rotate: heading color: hsb(float(heading) / 360.0, 1.0, 1.0) border: hsb(float(heading) / 360.0, 1.0, 1.0) depth: 5 at:
				{location.x, location.y, location.z + time};
			}

			species boids_goal trace: 100 {
				draw sphere(10) color: rgb('yellow') at: {location.x, location.y, location.z + time};
			}

		}

		display SpaceTimeCubeAggregated type: opengl camera:#from_up_front{
			image 'background' file: file_path_to_ocean refresh: false;
			species aggregatedboids trace: 500 {
				draw sphere(10) color: rgb('red') at: {location.x, location.y, location.z + time};
			}

			species boids_goal trace: 500 {
				draw sphere(10) color: rgb('yellow') at: {location.x, location.y, location.z + time};
			}

		}

	}

}

experiment "Multiple views" type: gui {
	float minimum_cycle_duration <- 0.05;
	output  synchronized: true{
		layout #split;
		display RealBoids type: java2D antialias: false{
			image 'background' file: file_path_to_ocean refresh: false;
			species boids aspect: dynamicColor transparency: 0.5 position: {0, 0, 0.1};
			species boids_goal transparency: 0.2 position: {0, 0, 0.25};
			species obstacle;
			species boids aspect: dynamicColor transparency: 0.2 position: {0, 0, 0.24};
		}

		display ThirdPerson type: opengl antialias: false {
			camera "default" dynamic: true location: {int(first(boids).location.x), int(first(boids).location.y), 500} target:
		    {int(first(boids).location.x), int(first(boids).location.y), 0};
			image 'background' file: file_path_to_ocean;
			species obstacle;
			species boids aspect: dynamicColor transparency: 0.2;
			species boids_goal transparency: 0.2;
		}

		display FirstPerson type: opengl antialias: false {
			camera "default" dynamic: true location: {int(first(boids).location.x), int(first(boids).location.y), 5} target:
			{cos(first(boids).heading) * first(boids).speed + int(first(boids).location.x), sin(first(boids).heading) * first(boids).speed + int(first(boids).location.y), 5};
			image 'background' file: file_path_to_ocean;
			species obstacle;
			species boids aspect: dynamicColor transparency: 0.2;
			species boids_goal transparency: 0.2;
		}

	}

}