/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "scene3d/scene.h"
		#include "scene3d/mitem_event_interface.h"
		#include "ui/ui.h"
		#include "motion/motion_automation_source.h"
		#include "automation/automation_source_group.h"
		#include "physic/physic_world.h"
		#include "core/object.h"
		#include "core/camera.h"
		#include "script/script_engine_types.h"
		#include "script/script_variant.h"
		#include "sort/sort.h"

		using namespace GS;
		using namespace GS::Core;
		using namespace GS::S3D;


//------------------------------------------------------------------------------
void				Scene::SetAsScriptGlobalScene(Script::IVM *_vm)
{
	if	(!_vm)
		_vm = vm;
	if	(!_vm || !_vm->IsOpen())
		return;

	_vm->Set("g_scene", Script::Variant(this, Script::typetag_Scene3d));
	_vm->Set("g_clock_fq", Platform::Get().GetClockFrequency());
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void			Scene::SetMotion(const char *name, Automation::SourceGroup **group, float blend, Automation::Player::AddSourceMode mode, float weight)
{
	if	(group)
		*group = new Automation::SourceGroup;

	SceneMotion *scene_motion = NULL;
	ListForeachPtr(SceneMotion *, m, motion.motions)
		if	(m->name == name)
		{
			scene_motion = m;
			break;
		}

	if	(scene_motion)
	{
		// Start item motions.
		ListForeachPtr(SceneMotion::ItemMotion *, m, scene_motion->item_motions)
			if	(MItem *i = ItemFromUid(m->uid))
				i->automation_player->StartAutomation(new Automation::MotionSource(m->motion, group ? *group : NULL), blend, mode, weight);
	}
	else	// fallback to the legacy per-item set motion
		ArrayListForeachPtr(MItem *, item, GetActiveList())
			if	(Motion *motion = item->automation_player->GetMotion(name))
				item->automation_player->StartAutomation(new Automation::MotionSource(motion, group ? *group : NULL), blend, mode, weight);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::SynchronizePhysics()
{
	Matrix4 m;
	ArrayListForeachPtr(MItem *, item, active_list)
		if	(item->physic_item)
			switch (item->physic_item_desc.physic_mode)
			{
				case PhysicItemDesc::Mode_Kinematic:
				case PhysicItemDesc::Mode_Static:
					item->physic_item->SetEngineMatrix(item->GetBaseItem()->GetMatrix());
					break;

				case PhysicItemDesc::Mode_Dynamic:
				case PhysicItemDesc::Mode_Vehicle:
				case PhysicItemDesc::Mode_Character:
					item->physic_item->GetGraphicMatrix(m);
					item->physic_item->SetEngineMatrix(m);
					item->GetBaseItem()->SetMatrix(m);
					break;

				default: break;
			}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::UpdateItemTransformation(MItem *i, const Time &dt)
{
	i->Update(dt);

	if	(Core::Item *b = i->GetBaseItem())
	{
		if	(i->mitem_flags.IsSet(MItem::Flag_Billboard))
		{
			Vector4 p, s;

			if	(Core::Item *link = b->GetParent())
			{
				p = b->GetPosition() * link->GetMatrix();
				s = b->GetScale() * link->GetScale();
			}
			else
			{
				p = b->GetPosition();
				s = b->GetScale();
			}

			Matrix3 view_matrix(Matrix3::FromMatrix4(current_camera ? current_camera->GetMatrix() : Matrix4::IdentityMatrix()) * Matrix3::RotationMatrixZAxis(b->GetRotation().z));
			Matrix4 billboard_matrix(Matrix4::TransformationMatrix(p, view_matrix, s));
			b->SnapshotTransformation(billboard_matrix, false, true);
		}

		/*
			[EJ] Force matrix update here so that const references can access
			up-to-date informations.
		*/
		b->GetMatrix();
		b->GetInverseMatrix();
	}
}
void				Scene::Update(uint eval_flag)
{
	ScopedSystemProfile(profiler.update);

	{
		ScopedSystemProfile(profiler.process_queues);

		ProcessItemRemovalQueue();
		/*
			Note: This step is intentionally delayed for one update in order to
				  evaluate newly activated items before displaying them.
		*/
		ProcessItemActivationQueue();

		/*
			Store item previous position/rotation (disregarding the inactive flag).
			Note: This is done here because the physic system will alter item's matrix.
		*/
		ArrayListForeachPtr(MItem *, i, active_list)
			if	(Core::Item *b = i->GetBaseItem())
				b->SetPreviousMatrix(b->GetMatrix());
	}

	// Update clock.
	if	(clock->GetRefCount() == 1)		// Do not update external clock.
		clock->Update();
	Time dt = Time::fromSec(clock->GetDeltaf());

	// Update triggers (result is cached in trigger).
	if	(eval_flag & SceneUpdateTrigger)
	{
		ScopedSystemProfile(profiler.update_triggers);
		UpdateTriggers();
	}

	// Update physics.
	if	(physic_world.IsValid() && (eval_flag & SceneUpdatePhysic))
	{
		{
			ScopedSystemProfile(profiler.physic_step);
			physic_world->Step(dt);
		}
		{
			ScopedSystemProfile(profiler.synchronize_physics);
			SynchronizePhysics();
		}
	}

	// Update item transformations.
	{
		ScopedSystemProfile(profiler.update_item_transformation);

		// Evaluate items and hierarchies (matrix).
	#if	0
		Array <Sort::Entry <int, MItem *> > hierarchy_in(active_list.GetCount()), hierarchy_out(active_list.GetCount());

		int count = 0;
		ArrayListForeachPtr(MItem *, i, active_list)
			if	(Item *b = i->GetBaseItem())
			{
				hierarchy_in[count].o = i;
				hierarchy_in[count].v = 0;
				for (Item *p = b; p; p = p->GetParent())
					++hierarchy_in[count].v;
				++count;
			}

		Array <Sort::Entry <int, MItem *> > *sorted = nSort::ByteSort(count, &hierarchy_in, &hierarchy_out);

		for (int n = 0; n < count; ++n)
			UpdateItemTransformation((*sorted)[n].o, dt);
	#else
		ArrayListForeachPtr(MItem *, i, active_list)
			UpdateItemTransformation(i, dt);
	#endif
	}

	{
		ScopedSystemProfile(profiler.update_skin);

		// Synchronize all skins with the final matrices.
		ArrayListForeachPtr(MItem *, i, active_list)
			if	(i->GetItemType() == Type_Object)
				((Object *)i->GetBaseItem())->UpdateSkin();
	}

	// Script events.
	if	(eval_flag & SceneUpdateEvent)
	{
		{
			ScopedSystemProfile(profiler.scene_on_update);
			scene_event->OnUpdate(this);
		}

		{
			ScopedSystemProfile(profiler.items_on_update);
			ArrayListForeachPtr(MItem *, i, active_list)
				i->item_event->OnUpdate(i);
		}
	}

	// Update UI.
	if	(ui)
	{
		ScopedSystemProfile(profiler.ui_update);
		ui->Update();
	}
}
//------------------------------------------------------------------------------
