#include <cassert>
#include <cstdio>
#include <core/list.h>
#include <scene/3d/spatial.h>
#include <scene/3d/physics_body.h>
#include <scene/3d/immediate_geometry.h>
#include <scene/3d/vehicle_body.h>
#include <scene/animation/animation_tree.h>
#include <scene/animation/animation_node_state_machine.h>
#include <scene/resources/mesh.h>
#include <modules/detour/crowd.h>
#include "smart_object.h"
#include "characters.h"


Characters_::Characters_() : query(memnew(DetourNavigationQuery)),
	initialized(false), debug(NULL),
	crowd(NULL)
{
	smm = NULL;
	no_navmesh = true;
}
AnimationTree *Characters_::get_animation_tree(const Node *npc) const
{
	if (!npc->has_meta("animation_tree"))
		return NULL;
	Object * obj = npc->get_meta("animation_tree");
	if (!obj)
		return NULL;
	AnimationTree *result = Object::cast_to<AnimationTree>(obj);
	return result;
}
bool Characters_::handle_cmdq(Node *npc)
{
	Array cmds, cmd;
	bool handled = false;
	if (npc->has_meta("cmdqueue"))
		cmds = npc->get_meta("cmdqueue");
	AnimationTree *anim = get_animation_tree(npc);
	if (cmds.size() > 0) {
		cmd = cmds[0];
		if (cmd[0] == "anim_state") {
			animation_node_travel(npc, cmd[1]);
			cmds.pop_front();
			handled = true;
		} else if (cmd[0] == "anim_param") {
			String p = "parameters/";
			p += String(cmd[1]);
			anim->set(p, cmd[2]);
			cmds.pop_front();
			handled = true;
		} else if (cmd[0] == "delay") {
			int d = cmd[1];
			cmds.pop_front();
			if (d > 0) {
				d--;
				Array ncmd;
				ncmd.push_back("delay");
				ncmd.push_back(d);
				cmds.push_front(ncmd);
			}
			handled = true;
		} else if (cmd[0] == "jump_to_obj") {
			Object *obj = cmd[1];
			Spatial *sp = Object::cast_to<Spatial>(obj);
			Spatial *npc_sp = Object::cast_to<Spatial>(npc);
			Transform g = sp->get_global_transform();
			npc_sp->set_global_transform(g);
			Transform orientation = npc->get_meta("orientation");
			orientation.basis = g.basis;
			npc->set_meta("orientation", orientation);
			cmds.pop_front();
			handled = true;
		} else if (cmd[0] == "jump_to_obj_rot") {
			Object *obj = cmd[1];
			Spatial *sp = Object::cast_to<Spatial>(obj);
			Spatial *npc_sp = Object::cast_to<Spatial>(npc);
			Transform xform;
			Transform g = sp->get_global_transform();
			xform.origin = g.origin;
			xform.basis = g.basis.rotated(Vector3(0, 1, 0), Math_PI);
			npc_sp->set_global_transform(xform);
			Transform orientation = npc->get_meta("orientation");
			orientation.basis = xform.basis;
			npc->set_meta("orientation", orientation);
			cmds.pop_front();
			handled = true;
		}
	}

	if (cmds.size() == 0 && npc->has_meta("cmdqueue"))
		npc->remove_meta("cmdqueue");
	else if (cmds.size() > 0 && handled)
		npc->set_meta("cmdqueue", cmds);
	return handled;
}
String Characters_::get_animation_node(const Node *npc)
{
	AnimationTree *anim_tree = get_animation_tree(npc);
	if (!anim_tree)
		return "";
	Ref<AnimationNodeStateMachinePlayback> playback;
	playback = anim_tree->get("parameters/state/playback");
	if (!playback.ptr())
		return "";
	return playback->get_current_node();
}
void Characters_::animation_node_travel(Node *npc, const String &anim)
{
	AnimationTree *anim_tree = get_animation_tree(npc);
	if (!anim_tree)
		return;
	Ref<AnimationNodeStateMachinePlayback> playback;
	playback = anim_tree->get("parameters/state/playback");
	if (!playback.ptr())
		return;
	playback->travel(anim);
}
void Characters_::set_walk_speed(Node *npc, float speed, float strafe)
{
	AnimationTree *anim_tree = get_animation_tree(npc);
	if (!anim_tree)
		return;
	Vector2 spd(speed, strafe);
	anim_tree->set("parameters/state/locomotion/loc/blend_position", spd);
}
float Characters_::get_walk_speed(const Node *npc) const
{
	AnimationTree *anim_tree = get_animation_tree(npc);
	if (!anim_tree)
		return 0.0f;
	Vector2 spd = anim_tree->get("parameters/state/locomotion/loc/blend_position");
	return spd.x;
}
void Characters_::rotate_to_agent(Spatial *obj)
{
	if (!obj->has_meta("agent_id"))
		return;
	Transform orientation = obj->get_meta("orientation");
#if 0
	if (get_animation_node(obj).begins_with("turn_"))
		return;
#endif
	Vector3 agent_velocity = obj->get_meta("agent_velocity");
	Vector3 agent_position = obj->get_meta("agent_position");
	Transform g = obj->get_global_transform();
	Vector3 to = agent_position - g.origin + agent_velocity;
	Vector3 dir = orientation.xform(Vector3(0.0f, 0.0f, -1.0f));
	to.y = 0.0f;
	Vector2 to2(to.x, to.z);
	Vector2 dir2(dir.x, dir.z);
	float angle = dir2.angle_to(to2);
	if (fabs(angle) < Math_PI / 8.0f || to.length_squared() < 1.9f * 1.9f) {
		if (get_animation_node(obj).begins_with("turn_"))
			animation_node_travel(obj, "locomotion");
		Transform rot = Transform().looking_at(to, Vector3(0.0f, 1.0f, 0.0f));
		orientation.basis = rot.basis;
		obj->set_meta("orientation", orientation);
		g.basis = orientation.basis;
		obj->set_global_transform(g);
	} else {
		if (get_animation_node(obj).begins_with("turn_"))
			return;
		Vector3 sk = orientation.xform_inv(to);
		if (sk.z > 1.9f)
			animation_node_travel(obj, "turn_left");
		/* CCW */
		else if (sk.x > 0.0f)
			animation_node_travel(obj, "turn_left");
		/* CW */
		else if (sk.x < 0.0f)
			animation_node_travel(obj, "turn_right");
	}
}
void Characters_::speed_to_agent(Spatial *obj)
{
	if (!crowd)
		return;
	float delta = get_physics_process_delta_time();
	float cur_speed = get_walk_speed(obj);
	float new_speed;
	Vector3 agent_velocity = obj->get_meta("agent_velocity");
	Vector3 agent_position = obj->get_meta("agent_position");
	int agent_id = obj->get_meta("agent_id");
	bool update_speed = false;
	Transform g = obj->get_global_transform();
#if 0
	KinematicBody *kb = Object::cast_to<KinematicBody>(obj);
#endif
	if (obj->has_meta("cur_speed")) {
		float tmp = obj->get_meta("cur_speed");
		if (cur_speed != tmp) {
#if 0
			printf("something is very wrong\n");
#endif
			cur_speed = cur_speed + (tmp - cur_speed) * delta;
			set_walk_speed(obj, cur_speed, 0.0f);
		}
	}
	if (agent_position.distance_squared_to(g.origin) > 0.0f) {
		agent_velocity = agent_position - g.origin + agent_velocity;
		Vector3 apos = g.xform_inv(agent_position);
		float dinc = -apos.z;
		float xinc = apos.x;
		// printf("position mismatch %f\n", dinc);
		if (fabs(dinc) > 0.2f || fabs(xinc) > 0.2f)
			crowd->reset_agent(agent_id);
		Vector3 v = agent_velocity;
		v.y = 0.0f;
		float spd = v.length();
#if 0
		cur_speed = get_walk_speed(obj);
		if (obj->has_meta("cur_speed"))
			cur_speed = obj->get_meta("cur_speed");
#endif

		if (spd > 40.0f)
			new_speed = 0.5f;
		else if (spd > 0.0f)
			new_speed = 0.1f + 0.4f * (spd / 60.0f);
		else
			new_speed = 0.0f;
#if 0
		if (dinc > 0.15f)
			new_speed += 0.1f * delta;
		if (dinc < -0.15f)
			new_speed -= 0.1f * delta;
#endif
		if (new_speed > 0.0f)
			new_speed = cur_speed +
				(new_speed - cur_speed) * delta;
		new_speed = CLAMP(new_speed, 0.0f, 1.0f);
		if (new_speed != cur_speed) {
#if 0
			printf("cur_speed = %f %f spd = %f\n", cur_speed, dinc, spd);
			printf("new speed = %f cur_speed = %f\n", new_speed, cur_speed);
#endif
			if (new_speed == 0.0f && cur_speed > 0.0f) {
				animation_node_travel(obj, "stop_walking");
				printf("stopping 1\n");
				obj->set_meta("cur_speed", 0.0f);
				cur_speed = new_speed;
				update_speed = true;
			}
			else if (cur_speed == 0.0f && new_speed > 0.0f) {
				animation_node_travel(obj, "start_walking");
				printf("starting 1\n");
				obj->set_meta("cur_speed", new_speed);
				cur_speed = new_speed;
				update_speed = true;
			} else if (cur_speed >= 0.0f && new_speed > 0.0f) {
				obj->set_meta("cur_speed", new_speed);
				cur_speed = new_speed;
				update_speed = true;
			}
		}
//		printf("cur_speed = %f speed = %f new_speed = %f\n", cur_speed, get_walk_speed(obj), new_speed);
		if (new_speed > 0.0f) {
			if (dinc > 0.15f) {
				new_speed = CLAMP(new_speed + delta * 2.5f, 0.1f, 1.0f);
				update_speed = true;
			} else if (dinc < -0.15f) {
				new_speed = CLAMP(new_speed - delta * 2.5f, 0.1f, 1.0f);
				update_speed = true;
			}
		}
	}
	if (update_speed)
		set_walk_speed(obj, new_speed, 0.0f);
	if (cur_speed > 0.0f)
		assert(get_walk_speed(obj) > 0.0f);
}
bool Characters_::has_arrived(Object *obj)
{
	if (!crowd)
		return false;
	Spatial *sp = Object::cast_to<Spatial>(obj);
	if (!obj->has_meta("agent_id"))
		return false;
	if (!obj->has_meta("_target"))
		return false;
	int agent_id = obj->get_meta("agent_id");
	bool crowd_arrived = crowd->has_arrived(agent_id);
	if (crowd_arrived) {
		Vector3 cpos = obj->get_meta("agent_position");
		Vector3 spos = sp->get_global_transform().origin;
		if (cpos.distance_squared_to(spos) <
				arrive_precision * arrive_precision)
			return true;
	}
	return false;
}
void Characters_::update_arrived(Object *obj)
{
	if (!crowd)
		return;
	Spatial *sp = Object::cast_to<Spatial>(obj);
	int agent_id = obj->get_meta("agent_id");
	if (obj->has_meta("climb"))
		obj->remove_meta("climb");
	crowd->clear_agent_target(agent_id);
	if (!sp)
		return;
	printf("stopping 2\n");
	set_walk_speed(sp, 0.0f, 0.0f);
	obj->set_meta("cur_speed", 0.0f);
	animation_node_travel(sp, "stop_walking");
	if (obj->has_meta("_target_jumpto")) {
		Transform xform = obj->get_meta("_target_jumpto");
		sp->set_global_transform(xform);
		obj->remove_meta("_target_jumpto");
	}
	if (obj->has_meta("_target_animation")) {
		String anim = obj->get_meta("_target_animation");
		animation_node_travel(sp, anim);
		obj->remove_meta("_target_animation");
	}
	if (obj->has_meta("_target_cmdq")) {
		Array cmds;
		if (obj->has_meta("cmdqueue"))
			cmds = obj->get_meta("cmdqueue");
		Array tcmds = obj->get_meta("_target_cmdq");
		cmds.append_array(tcmds);
		obj->set_meta("cmdqueue", cmds);
		obj->remove_meta("_target_cmdq");
	}
}
void Characters_::character_physics(Object *obj)
{
	float delta = get_physics_process_delta_time();
	Vector3 h_velocity, velocity;
	AnimationTree *animtree;
	Transform orientation, root_motion, g;
	Object *tmp;

	tmp = obj->get_meta("animation_tree");
	animtree = Object::cast_to<AnimationTree>(tmp);

	orientation = obj->get_meta("orientation");
	root_motion = animtree->get_root_motion_transform();
	root_motion.origin = root_motion_mod.xform(root_motion.origin);
	orientation *= root_motion;
	h_velocity = orientation.origin / delta;
	velocity = h_velocity;
	KinematicBody *kb = Object::cast_to<KinematicBody>(obj);
	if (kb) {
		bool go = false;
		if (!obj->has_meta("vehicle") && !obj->has_meta("cmdqueue") /*&& !obj->has_meta("smart_object")*/) {
			go = true;
		} else if (obj->has_meta("cmdqueue") && obj->has_meta("cmdq_walk")) {
			go = true;
		} else if (obj->has_meta("cmdqueue") && obj->has_meta("climb")) {
			go = true;
		}
		if (!kb->is_on_floor() && !obj->has_meta("climb") && !obj->has_meta("vehicle"))
			velocity += Vector3(0.0f, -9.8f, 0.0f);
		if (go)
			velocity = kb->move_and_slide(velocity, Vector3(0.0f, 1.0f, 0.0f), true, 4, 0.785f, false);
	}
	orientation.origin = Vector3();
	orientation.orthonormalize();
	obj->set_meta("orientation", orientation);
	Spatial *sp = Object::cast_to<Spatial>(obj);
	if (sp) {
		g = sp->get_global_transform();
		g.basis = orientation.basis;
		sp->set_global_transform(g);

		if (obj->has_meta("vehicle")) {
			tmp = obj->get_meta("vehicle");
			VehicleBody *vehicle = Object::cast_to<VehicleBody>(tmp);
			if (obj->has_meta("vehicle_offset")) {
				Transform xform = obj->get_meta("vehicle_offset");
				g = vehicle->get_global_transform() * xform;
				sp->set_global_transform(g);
			}
		}
	}
	Node *node = Object::cast_to<Node>(obj);
	if (node) {
		if (node->has_node(String("blackboard"))) {
			Node *bb = node->get_node(String("blackboard"));
			bb->call("set", "velocity", velocity);
		}
	}
	if (!crowd)
		return;
	if (obj->has_meta("smart_object"))
		return;
	if (obj->has_meta("cmdqueue") && !obj->has_meta("cmdq_walk"))
		return;
	/* agent creation stuff */
	if (!obj->has_meta("agent_id")) {
		float agent_data_f[] = {0.35f, 1.5f, 1.8f, 40.0f};
		PoolVector<float> agent_data;
		agent_data.resize(4);
		memcpy(agent_data.write().ptr(), agent_data_f, sizeof(agent_data_f));
		crowd->add_agent(obj, 0, false, agent_data);
	}
	if (!obj->has_meta("agent_id"))
		return;
	if (!node)
		return;
	if (!obj->has_meta("_target"))
		return;
	float fwd_probe = forward_probe(node, 0.5f, 0.05f, 1.9f, 0.2f);
	bool climb = false;
	bool bumped = false;
	if (obj->has_meta("bumped")) {
		bumped = true;
		float remaining = obj->get_meta("bumped");
		remaining -= delta;
		if (remaining >= 0.0f)
			obj->set_meta("bumped", remaining);
		else
			obj->remove_meta("bumped");
	}
	if (fwd_probe < 0.1f) {
		if (obj->has_meta("climb"))
			climb = true;
		if (get_animation_node(node) != "climb1")
			climb = false;
	} else if (fwd_probe < 1.0f)
		climb = true;
	else if (fwd_probe >= 1.0f && !bumped) {
		int agent_id = obj->get_meta("agent_id");
		printf("bumped into wall %f\n", fwd_probe);
		bumped = true;
		printf("stopping 3\n");
		set_walk_speed(node, 0.0f, 0.0f);
		obj->set_meta("bumped", 3.0f);
		obj->set_meta("cur_speed", 0.0f);
		animation_node_travel(node, "stop_walking");
		crowd->reset_agent(agent_id);
	}
	if (climb && !obj->has_meta("climb")) {
		animation_node_travel(node, "climb1");
		obj->set_meta("climb", true);
	} else if (!climb && obj->has_meta("climb")) {
		animation_node_travel(node, "locomotion");
		obj->remove_meta("climb");
	}
	if (climb)
		return;
	/* agent stuff */
	Vector3 agent_velocity = obj->get_meta("agent_velocity");
	Vector3 agent_position = obj->get_meta("agent_position");
	g = sp->get_global_transform();
	if (!bumped)
		speed_to_agent(sp);
	if (((agent_position - g.origin) * delta + agent_velocity).length() == 0.0f) {
		float wspd = obj->get_meta("cur_speed");
		if (wspd >= 0.0f) {
			printf("stopping 4\n");
			set_walk_speed(node, 0.0f, 0.0f);
			animation_node_travel(node, "stop_walking");
			obj->set_meta("cur_speed", 0.0f);
		}
	} else
		rotate_to_agent(sp);
	if (has_arrived(obj)) {
		printf("ARRIVED\n");
		Vector3 where;
		update_arrived(obj);
		bool smart = false;
		SmartObject *sm = NULL;
		if (obj->has_meta("_target_node")) {
			Object *o = ObjectDB::get_instance(obj->get_meta("_target_node"));
			if (o) {
				sm = Object::cast_to<SmartObject>(o);
				if (sm)
					smart = true;
			}
		}
		if (!smart) {
			printf("ARRIVED simple\n");
			agent_walk_stop(obj);
			where = obj->get_meta("_target");
			emit_signal("arrived", obj, where);
			printf("arrived to %ls\n", String(where).c_str());
		} else {
			printf("ARRIVED smart\n");
			smm->arrived(sm, sp);
		}
	} else {
		if (obj->has_meta("_target_node")) {
			Node *tn = obj->get_meta("_target_node");
			if (tn) {
				PhysicsBody *pbt = Object::cast_to<PhysicsBody>(tn);
				if (pbt) {
					Vector3 target_cur = pbt->get_global_transform().origin;
					obj->set_meta("_target", target_cur);
				} else {
					Spatial *spt = Object::cast_to<Spatial>(tn);
					if (spt) {
						Vector3 target_cur = spt->get_global_transform().origin;
						obj->set_meta("_target", target_cur);
					}
				}
			}
		}
	}
}
void Characters_::agent_walk_stop(Object *obj)
{
	if (obj->has_meta("_target"))
		obj->remove_meta("_target");
	if (obj->has_meta("_target_node"))
		obj->remove_meta("_target_node");
}
void Characters_::walkto_agent_node(Node *ch, const Node *target)
{
	if (!ch->has_meta("agent_id"))
		return;
	ch->set_meta("_target_node", target->get_instance_id());
	const Spatial *sp = Object::cast_to<Spatial>(target);
	if (sp) {
		Vector3 tpos = sp->get_global_transform().origin;
		walkto_agent(ch, tpos);
	}
}
void Characters_::walkto_agent(Node *ch, const Vector3 &target)
{
	if (!crowd)
		return;
	if (ch->has_meta("_target")) {
		Vector3 otarget = ch->get_meta("_target");
		if (otarget == target)
			return;
	}
	assert(ch->has_meta("agent_id"));
	int agent_id = ch->get_meta("agent_id");
	ch->set_meta("_target", target);
	crowd->set_agent_target_position(agent_id, target);
	printf("target set to %ls\n", String(target).c_str());
}
void Characters_::_notification(int p_what)
{
	List<Node *> char_node_list;
	List<Node *> frozen_node_list;
	List<Node *>::Element *e;
	switch(p_what) {
	case NOTIFICATION_PROCESS:
		if (!initialized)
			return;
		get_tree()->get_nodes_in_group("character", &char_node_list);
		get_tree()->get_nodes_in_group("frozen", &frozen_node_list);
		if (debug) {
			debug->clear();
			debug->begin(Mesh::PRIMITIVE_LINES);
			debug->set_color(Color(1, 0, 0, 1));
		}
		for (e = char_node_list.front(); e; e = e->next()) {
			if (debug)
				debug->set_color(Color(1, 0, 0, 1));
			Node *ch = e->get();
			if (!ch->has_meta("animation_tree"))
				continue;
			process_character(ch, false);
			if (debug) {
				Spatial *sp = Object::cast_to<Spatial>(ch);
				debug->set_color(Color(1, 0.5f, 1, 1));
				Transform xf = sp->get_global_transform();
				Vector3 p1 = xf.origin;
				// Vector3 b = -xf.basis[2];
				// b.x = -b.x;
				xf.origin = Vector3();
				Vector3 b = xf.xform(Vector3(0.0f, 0.0f, -1.0f));
				Vector3 p2 = p1 + b * 2.0f;
				debug->add_vertex(p1);
				debug->add_vertex(p2);
				if (ch->has_meta("agent_velocity")) {
					Vector3 p3 = p1 + ch->get_meta("agent_velocity");
					debug->set_color(Color(0.5f, 1, 0.5f, 1));
					debug->add_vertex(p1);
					debug->add_vertex(p3);
				}
			}
		}
		if (debug)
			debug->set_color(Color(1, 0, 0, 1));
		for (e = frozen_node_list.front(); e; e = e->next()) {
			Node *ch = e->get();
			if (!ch->has_meta("animation_node"))
				continue;
			process_character(ch, true);
		}
		if (debug)
			debug->end();
		break;
	case NOTIFICATION_PHYSICS_PROCESS:
		if (!initialized)
			return;
		get_tree()->get_nodes_in_group("character", &char_node_list);
#if 0
		if (debug) {
			debug->clear();
			debug->begin(Mesh::PRIMITIVE_LINES);
			debug->set_color(Color(1, 0, 0, 1));
		}
#endif
		for (e = char_node_list.front(); e; e = e->next()) {
			Node *ch = e->get();
			if (!ch->has_meta("animation_tree"))
				continue;
			if (!ch->has_meta("orientation"))
				continue;
			character_physics(ch);
			Spatial *sp = Object::cast_to<Spatial>(ch);
			Vector3 direction = sp->get_global_transform().xform(Vector3(0, 0, -1));
			ch->set_meta("direction", direction);
		}
#if 0
		if (debug)
			debug->end();
#endif
		break;
	case NOTIFICATION_READY:
		arrive_precision = GLOBAL_DEF("ai/locomotion/arrive_precision", 0.2f);
		set_process(true);
		set_physics_process(true);
		smm = memnew(SmartObjectManager);
		add_child(smm);
		break;
	}
}
void Characters_::_bind_methods()
{
	ClassDB::bind_method(D_METHOD("walkto", "ch", "target"), &Characters_::walkto);
	ClassDB::bind_method(D_METHOD("walkto_node", "ch", "target"), &Characters_::walkto_node);
	ClassDB::bind_method(D_METHOD("set_navmesh", "mesh", "xform"), &Characters_::set_navmesh);
	ClassDB::bind_method(D_METHOD("forward_probe", "body", "lookahead", "start_height", "end_height", "height_step"), &Characters_::forward_probe);
	ClassDB::bind_method(D_METHOD("get_animation_node", "npc"), &Characters_::get_animation_node);
	ClassDB::bind_method(D_METHOD("handle_cmdq", "npc"), &Characters_::handle_cmdq);
	ClassDB::bind_method(D_METHOD("animation_node_travel", "npc", "anim"), &Characters_::animation_node_travel);
	ClassDB::bind_method(D_METHOD("set_walk_speed", "npc", "speed", "strafe"), &Characters_::set_walk_speed);
	ClassDB::bind_method(D_METHOD("get_walk_speed", "npc"), &Characters_::get_walk_speed);
	ClassDB::bind_method(D_METHOD("set_crowd", "crowd"), &Characters_::set_crowd_);
	ClassDB::bind_method(D_METHOD("get_crowd"), &Characters_::get_crowd);
	ClassDB::bind_method(D_METHOD("walkto_agent", "ch", "target"), &Characters_::walkto_agent);
	ClassDB::bind_method(D_METHOD("walkto_agent_node", "ch", "target"), &Characters_::walkto_agent_node);
	ClassDB::bind_method(D_METHOD("character_physics", "obj"), &Characters_::character_physics);
	ClassDB::bind_method(D_METHOD("set_root_motion_mod", "xform"), &Characters_::set_root_motion_mod);
	ClassDB::bind_method(D_METHOD("get_root_motion_mod"), &Characters_::get_root_motion_mod);
	ADD_SIGNAL(MethodInfo("arrived", PropertyInfo(Variant::OBJECT, "obj"), PropertyInfo(Variant::VECTOR3, "where")));
}

void Characters_::set_root_motion_mod(const Transform &xform)
{
	root_motion_mod = xform;
}
Transform Characters_::get_root_motion_mod() const
{
	return root_motion_mod;
}

void Characters_::process_frozen_character(Node *npc, const Vector3 &tposition)
{
	float delta = npc->get_process_delta_time();
	Vector3 tgt = tposition;
	Spatial *sp = Object::cast_to<Spatial>(npc);
	assert(sp);
	Vector3 position = sp->get_global_transform().origin;
	float dst = position.distance_squared_to(tgt);
	tgt.y = position.y;
	float tconst = delta / dst;
	Transform xform = sp->get_global_transform();
	xform = xform.looking_at(tgt, Vector3(0, 1, 0));
	xform.origin = position.linear_interpolate(tgt, tconst);
	sp->set_global_transform(xform);
}

void Characters_::process_normal_character(Node *npc, const Vector3 &tposition)
{
#if 0
	Spatial *sp = Object::cast_to<Spatial>(npc);
	assert(sp);
	float delta = npc->get_process_delta_time();
	Vector3 tgt = tposition;
	Vector3 position = sp->get_global_transform().origin;
	tgt.y = position.y;
#endif
}

void Characters_::process_character(Node *node, bool frozen)
{
	int id = node->get_instance_id();
	Spatial *sp = Object::cast_to<Spatial>(node);
	if (!sp)
		return;
	Vector3 target;
	Vector3 position = sp->get_global_transform().origin;
	if (static_targets.has(id))
		target = static_targets[id];
	else if (dynamic_targets.has(id)) {
		int target_id = dynamic_targets[id];
		Object * obj = ObjectDB::get_instance(target_id);
		if (!obj) {
			dynamic_targets.erase(id);
			return;
		}
		Spatial *target_node = Object::cast_to<Spatial>(obj);
		if (!target_node) {
			dynamic_targets.erase(id);
			return;
		}
		target = target_node->get_global_transform().origin;
	} else /* no target - no problem */
		return;
	float distance = position.distance_squared_to(target);
	int i;
	if (!paths.has(id)) {
		Vector<Vector3> points;
		Vector<int> flags;
		if (!no_navmesh) {
			Vector3 start = query->nearest_point(position, Vector3(1, 1, 1), filter);
			Vector3 end = query->nearest_point(target, Vector3(1, 1, 1), filter);
			query->find_path_array(start, end, Vector3(1, 1, 1), filter, points, flags);
			assert(points.size() > 0);
			paths[id] = points;
		} else 
			paths[id] = Vector<Vector3>();
	}
	if (debug)
		for (i = 0; i < paths[id].size() - 1; i++) {
			debug->add_vertex(paths[id][i]);
			debug->add_vertex(paths[id][i + 1]);
		}
	if (distance < 0.5) {
		if (static_targets.has(id))
			static_targets.erase(id);
		if (dynamic_targets.has(id))
			dynamic_targets.erase(id);
		if (paths.has(id))
			paths.erase(id);
		emit_signal("arrived", node);
	} else if (frozen) {
		Vector3 tgt = target;
		if (paths[id].size() > 0)
			tgt = paths[id][0];
		float dst = position.distance_squared_to(tgt);
		while (dst < 0.0001f && paths[id].size() > 0) {
			paths[id].erase(tgt);
			tgt = paths[id][0];
			dst = position.distance_squared_to(tgt);
		}
		process_frozen_character(node, tgt);

	} else {
		Vector3 tgt = target;
		if (paths[id].size() > 0)
			tgt = paths[id][0];
		float dst = position.distance_squared_to(tgt);
		while (dst < 0.0001f && paths[id].size() > 0) {
			paths[id].erase(tgt);
			tgt = paths[id][0];
			dst = position.distance_squared_to(tgt);
		}
		process_normal_character(node, tgt);
	}
}

Characters_::~Characters_()
{
	memdelete(query);
}

void Characters_::walkto(const Node *ch, const Vector3 &target)
{
	static_targets[ch->get_instance_id()] = target;
}

void Characters_::walkto_node(const Node *ch, const Node *target)
{
	dynamic_targets[ch->get_instance_id()] = target->get_instance_id();
}

void Characters_::set_navmesh(Ref<DetourNavigationMesh> mesh, const Transform &xform)
{
	if (mesh.is_null()) {
		no_navmesh = true;
		initialized = true;
		return;
	}
	query->init(mesh, xform);
	filter.instance();
	debug = memnew(ImmediateGeometry);
	add_child(debug);
	no_navmesh = false;
	initialized = true;
	Ref<SpatialMaterial> mat;
	mat.instance();
	mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
	mat->set_flag(SpatialMaterial::FLAG_DISABLE_DEPTH_TEST, true);
	mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
	debug->set_material_override(mat);
}
float Characters_::forward_probe(Node *body, float lookahead,
			float start_height, float end_height,
			float height_step)
{
	float result = 0.0f, d;
	Ref<World> world;
	PhysicsDirectSpaceState *space;
	PhysicsDirectSpaceState::RayResult rr;
	Set<RID> exclude;
	PhysicsBody *pbody = Object::cast_to<PhysicsBody>(body);
	assert(pbody);

	world = pbody->get_world();
	if (!world.ptr())
		return result;
	space = PhysicsServer::get_singleton()->space_get_direct_state(world->get_space());
	d = start_height;
	exclude.insert(pbody->get_rid());
	while (d < end_height) {
		Transform g = pbody->get_global_transform();
		Vector3 o = g.origin + Vector3(0, 1, 0) * d;
		Vector3 f = g.xform(Vector3(0, 0, -1) * lookahead + Vector3(0, 1, 0) * d);
		if (space->intersect_ray(o, f, rr, exclude)) {
			int against = rr.collider_id;
			Object * obj = ObjectDB::get_instance(against);
			if (obj) {
				Node *col = Object::cast_to<Node>(obj);
				if (col && !col->is_in_group("character"))
					result = d;
			}
		}
		d += height_step;
	}
	return result;
}

void Characters_::set_crowd(DetourCrowdManager *crowd)
{
	this->crowd = crowd;
}
void Characters_::set_crowd_(Node *crowd_node)
{
	DetourCrowdManager *crowd =
		Object::cast_to<DetourCrowdManager>(crowd_node);
	if (crowd)
		set_crowd(crowd);
}

DetourCrowdManager *Characters_::get_crowd() const
{
	return crowd;
}

