--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


--
-- Should never really happen.
--
motionsensor = motionsensor or {}


--
-- These bones are used to draw the debug
-- kinect skeleton. You just need to loop through
-- and draw a line between each of these bones.
--
motionsensor.DebugBones =
{
	-- Torso
	{ SENSORBONE.HEAD,				SENSORBONE.SHOULDER },
	{ SENSORBONE.SHOULDER,			SENSORBONE.SHOULDER_LEFT },
	{ SENSORBONE.SHOULDER,			SENSORBONE.SHOULDER_RIGHT },
	{ SENSORBONE.SHOULDER,			SENSORBONE.SPINE },
	{ SENSORBONE.SHOULDER,			SENSORBONE.HIP },
	{ SENSORBONE.HIP,				SENSORBONE.HIP_LEFT },
	{ SENSORBONE.HIP,				SENSORBONE.HIP_RIGHT },

	-- Left Arm
	{ SENSORBONE.SHOULDER_LEFT,		SENSORBONE.ELBOW_LEFT },
	{ SENSORBONE.ELBOW_LEFT,		SENSORBONE.WRIST_LEFT },
	{ SENSORBONE.WRIST_LEFT,		SENSORBONE.HAND_LEFT },

	-- Right Arm
	{ SENSORBONE.SHOULDER_RIGHT,	SENSORBONE.ELBOW_RIGHT },
	{ SENSORBONE.ELBOW_RIGHT,		SENSORBONE.WRIST_RIGHT },
	{ SENSORBONE.WRIST_RIGHT,		SENSORBONE.HAND_RIGHT },

	-- left leg
	{ SENSORBONE.HIP_LEFT,			SENSORBONE.KNEE_LEFT },
	{ SENSORBONE.KNEE_LEFT,			SENSORBONE.ANKLE_LEFT },
	{ SENSORBONE.ANKLE_LEFT,		SENSORBONE.FOOT_LEFT },

	-- right leg
	{ SENSORBONE.HIP_RIGHT,			SENSORBONE.KNEE_RIGHT },
	{ SENSORBONE.KNEE_RIGHT,		SENSORBONE.ANKLE_RIGHT },
	{ SENSORBONE.ANKLE_RIGHT,		SENSORBONE.FOOT_RIGHT },
}

motionsensor.ChooseBuilderFromEntity = function( ent )

	local builders = list.Get( "SkeletonConvertor" )

	for k, v in pairs( builders ) do

		if ( v:IsApplicable( ent ) ) then return k end

	end

	return "ValveBiped"

end

motionsensor.ProcessAngle = function( translator, sensor, pos, ang, special_vectors, boneid, v )

	local a = nil
	local b = nil
	local up = special_vectors["up"]

	--
	-- Using a vector from another angle.
	-- If the angle isn't processed yet return
	-- we will be added to the list to process again.
	--
	if ( v.up_up ) then
		if ( !ang[ v.up_up ] ) then return end
		up = ang[ v.up_up ]:Up()
	end

	if ( v.up_dn ) then
		if ( !ang[ v.up_dn ] ) then return end
		up = ang[ v.up_dn ]:Up() * -1
	end

	if ( v.up_fwd ) then
		if ( !ang[ v.up_fwd ] ) then return end
		up = ang[ v.up_fwd ]:Forward()
	end

	if ( v.up_lft ) then
		if ( !ang[ v.up_lft ] ) then return end
		up = ang[ v.up_lft ]:Right() * -1
	end

	if ( v.up_rgt ) then
		if ( !ang[ v.up_rgt ] ) then return end
		up = ang[ v.up_rgt ]:Right()
	end

	--
	-- From -> To vectors
	--
	if ( v.from_sensor ) then	a = sensor[ v.from_sensor ]		end
	if ( v.to_sensor ) then		b = sensor[ v.to_sensor ]		end
	if ( v.from ) then			a = pos[ v.from ]				end
	if ( v.to ) then			b = pos[ v.to ]					end

	--
	-- We can offer special vectors to define 'up'
	--
	if ( isstring( v.up ) ) then
		up = special_vectors[ v.up ]
	end

	if ( a == nil or b == nil or up == nil ) then return end

	ang[ boneid ]	= ( a - b ):GetNormal():AngleEx( up:GetNormal() );

	if ( v.adjust ) then
		ang[ boneid ] = ang[ boneid ] + v.adjust
	end

	return true

end

--
-- Processes the AnglesTable from a skeleton constructor
--
motionsensor.ProcessAnglesTable = function( translator, sensor, pos, rotation )

	if ( !translator.AnglesTable ) then return {} end

	local ang = {}

	local special_vectors = {}
	special_vectors["right"]	= rotation:Right()
	special_vectors["left"]		= special_vectors["right"] * -1
	special_vectors["up"]		= rotation:Up()
	special_vectors["down"]		= special_vectors["up"] * -1
	special_vectors["forward"]	= special_vectors["down"]:Cross( special_vectors["right"] )
	special_vectors["backward"]	= special_vectors["forward"] * -1

	special_vectors["hips_left"]	= ( sensor[SENSORBONE.HIP_RIGHT] - sensor[SENSORBONE.HIP_LEFT] ):GetNormal()
	special_vectors["hips_up"]		= ( sensor[SENSORBONE.HIP] - sensor[SENSORBONE.SHOULDER] ):GetNormal()
	special_vectors["hips_back"]	= special_vectors["hips_up"]:Cross( special_vectors["hips_left"] )
	special_vectors["hips_fwd"]		= special_vectors["hips_back"] * -1

	special_vectors["chest_lft"]	= ( sensor[SENSORBONE.SHOULDER_RIGHT] - sensor[SENSORBONE.SHOULDER_LEFT] ):GetNormal()
	special_vectors["chest_rgt"]	= special_vectors["chest_lft"] * -1
	special_vectors["chest_up"]		= ( sensor[SENSORBONE.SPINE] - sensor[SENSORBONE.SHOULDER] ):GetNormal()
	special_vectors["chest_dn"]		= special_vectors["chest_up"] * -1
	special_vectors["chest_bck"]	= special_vectors["chest_up"]:Cross( special_vectors["chest_lft"] )
	special_vectors["chest_fwd"]	= special_vectors["chest_bck"] * -1

	special_vectors["head_up"]		= ( sensor[SENSORBONE.SHOULDER] - sensor[SENSORBONE.HEAD] ):GetNormal()
	special_vectors["head_back"]	= special_vectors["head_up"]:Cross( special_vectors["chest_lft"] )

	local reprocess = {}

	for k, v in pairs( translator.AnglesTable ) do

		table.insert( reprocess, k )

	end

	for iPasses = 1, 5 do

		local cur_process = reprocess
		reprocess = {}

		for k, v in pairs( cur_process ) do

			if ( !motionsensor.ProcessAngle( translator, sensor, pos, ang, special_vectors, v, translator.AnglesTable[v] ) ) then
				table.insert( reprocess, v )
			end

		end

		if ( table.IsEmpty( reprocess ) ) then
			--DebugInfo( 0, iPasses .. " Passes" )
			return ang
		end

	end

	--
	-- If we got here then we're doing something wrong.
	-- It should have out'ed before completing 5 passes.
	--
	DebugInfo( 0, "motionsensor.ProcessAnglesTable: 4+ passes!" )
	return ang

end

--
-- Processes the PositionTable from a skeleton constructor
--
motionsensor.ProcessPositionTable = function( translator, sensor )

	if ( !translator.PositionTable ) then return {} end

	local pos = {}

	for k, v in pairs( translator.PositionTable ) do

		-- A number means get value straight from the sensor
		if ( isnumber( v ) ) then pos[ k ] = sensor[ v ] end

		if ( istable( v ) and v.type == "lerp" ) then

			pos[ k ] = LerpVector( v.value, sensor[ v.from ], sensor[ v.to ] )

		end

	end

	return pos

end

--
-- Called to build the skeleton
--
motionsensor.BuildSkeleton = function( translator, player, rotation )

	--
	-- The kinect animations are recorded on the skunt, so rotate towards the player
	--
	rotation:RotateAroundAxis( rotation:Up(), -90 )

	--
	-- Pre-get and rotate all the player positions
	--
	local sensor = {}
	for i = 0, 19 do
		sensor[ i ] = player:MotionSensorPos( i )
		sensor[ i ]:Rotate( rotation )
	end

	if ( translator.PrePosition ) then
		translator:PrePosition( sensor )
	end

	--
	-- Fill out the position table..
	--
	local pos = motionsensor.ProcessPositionTable( translator, sensor )

	--
	-- Fill out the angles
	--
	local ang = motionsensor.ProcessAnglesTable( translator, sensor, pos, rotation )

	--
	-- Allow the bone builder to make any last minute changes
	--
	translator:Complete( player, sensor, rotation, pos, ang )

	return pos, ang, sensor

end

