@name 3-seg IK tests
@persist L1 L2 L3 [H1 H2 H3 H4]:entity
@persist [E B P]:entity M
@persist V_Offset V_Count V_TI V_Ready

interval(100)

function ik3(L1_or_L3, L2, BasePos:vector, EndPos:vector, HoloIndex1, HoloIndex2, HoloIndex3)
{
    #[
        prerequesites:
            L1 == L3
            holos set up must be facing forward while stacked up vertically, on top of each other.
    
		explanation:
			  	B----------C
			   /|          |\
       / |          | \
			 /  |          |  \
			A---E----------F---D
			
			The distance is limited from being in any way extraneous.
			AE is found by subtracting EF from AD (L2 from Dist) and dividing it by 2.
			With 2 lengths of a right triangle defined (AB and AE), we can use inverse trigonometric functions to solve for the angle BAE.
			With one angle solved in this trapezoid, we can solve for the rest very easily (ABC is supplementary to BAE and the other side has congruency). At this point, it might be apparent why L1 and L3 have to be the same length.
			
			This can also be thought of as an isoceles triangle split in half and seperated by a rectangle.
    ]#
    
    local RawDist = BasePos:distance(EndPos)
    local Dist = clamp(RawDist, L2 - (L1_or_L3 * 2), (L1_or_L3 * 2) + L2)
    
    local AE = (Dist - L2) / 2
    
    local BAE = acos(AE / L1_or_L3)
    
    holoAng(HoloIndex1, (EndPos - BasePos):toAngle() + ang(90 - BAE, 0, 0))
    holoAng(HoloIndex2, holoEntity(HoloIndex1):toWorld(ang(BAE, 0, 0)))
    holoAng(HoloIndex3, holoEntity(HoloIndex2):toWorld(ang(BAE, 0, 0)))
}

function number cosine(A, B, C)
{
    return acos((A^2 + B^2 - C^2) / (2*A*B))
}

function ik3z(L1_or_L3, L2, BasePos:vector, EndPos:vector, HoloIndex1, HoloIndex2, HoloIndex3)
{
    #[
        prerequesites:
            L1 == L3
            holos set up must be facing forward while stacked up vertically, on top of each other.
		explanation:
				   A
				  /|
				 / |
			    / _E_---C
			  B--- |   /
				   |  /
				   | /
				   |/
				   D
			
			The distance is limited so that it cannot be extraneous.
			Here, we can bisect this Z form into 2 triangles using the distance line.
			AE is AD/2 (Distance / 2), BE is BC/2 (L2 / 2). The same applies correspondingly to the triangles below.
			With that, we have all 3 side lengths of a triangle; we can now use the law of cosines to solve for the angles.
    ]#
    
    local RawDist = BasePos:distance(EndPos)
    local Dist = clamp(RawDist, (L1_or_L3 * 2) - L2, (L1_or_L3 * 2) + L2)
    
    local AE = Dist / 2
    local BE = L2 / 2
    local BAE = cosine(L1_or_L3, AE, BE)
    local ABE = cosine(L1_or_L3, BE, AE)
    
    holoAng(HoloIndex1, (EndPos - BasePos):toAngle() + ang(90 - BAE, 0, 0))
    holoAng(HoloIndex2, holoEntity(HoloIndex1):toWorld(ang(180 - ABE, 0, 0)))
    holoAng(HoloIndex3, holoEntity(HoloIndex2):toWorld(ang(180 + ABE, 0, 0)))
}

function vector bezier2(Src:vector, End:vector, CurvePoint1:vector, CurvePoint2:vector, Progress:number)
{
    local M1 = mix(mix(End, CurvePoint2, Progress), mix(CurvePoint2, CurvePoint1, Progress), Progress)
    local M2 = mix(mix(CurvePoint2, CurvePoint1, Progress), mix(CurvePoint1, Src, Progress), Progress)
    return mix(M1, M2, Progress)
}

function vector bezier4([Source CurvePoint1 CurvePoint2 CurvePoint3 CurvePoint4 End]:vector, Progress)
{
    return ((1 - Progress)^5 * Source) + (5*Progress*(1-Progress)^4 * CurvePoint1) + (10*Progress^2*(1 - Progress)^3 * CurvePoint2) + (10*Progress^3*(1 - Progress)^2 * CurvePoint3) + (5*Progress^4*(1 - Progress)*CurvePoint4) + (Progress^5 * End)
}

if(first())
{
    B = E = entity()
    
    L1 = L3 = 120 # the prerequisite is that L1 == L3
    L2 = L1
    
    H1 = holoCreate(1, B:toWorld(vec(0, 0, 0)), vec(0.5)) # arbitraty offset vector
    holoParent(1, B)
    
    H2 = holoCreate(2, H1:toWorld(vec(0, 0, L1)), vec(0.5))
    holoParent(2, 1)
    
    H3 = holoCreate(3, H2:toWorld(vec(0, 0, L2)), vec(0.5))
    holoParent(3, 2)
    
    H4 = holoCreate(4, H3:toWorld(vec(0, 0, L3)), vec(0.5))
    holoParent(4, 3)
    
    holoCreate(5, (H1:pos() + H2:pos()) / 2, vec(0.25, 0.25, L1 / 12))
    holoParent(5, 1)
    
    holoCreate(6, (H2:pos() + H3:pos()) / 2, vec(0.25, 0.25, L2 / 12))
    holoParent(6, 2)
    
    holoCreate(7, (H3:pos() + H4:pos()) / 2, vec(0.25, 0.25, L3 / 12))
    holoParent(7, 3)
    
    P = propSpawn("models/hunter/blocks/cube025x025x025.mdl", E:pos() + vec(0, 0, 32), 0)
    P:setAlpha(128)
    
    runOnLast(1)
    
    # Visual [holos index] offset
    V_Offset = 100
    V_Count = ceil(L1 / 3)
    
    for(I = 1, 7)
    {
        holoAlpha(I, 0)
    }
}

if(V_Ready)
{
    for(I = 0, V_Count + 1)
    {
        local Exp = 0.85 # Just trying to find a good way to distribute the holos along the curve given their size; linear wasnt too good
        local Hier = bezier4(H1:pos(), H2:pos(), H2:pos(), H3:pos(), H3:pos(), H4:pos(), ((I) / V_Count) ^ Exp)
        local Fore = bezier4(H1:pos(), H2:pos(), H2:pos(), H3:pos(), H3:pos(), H4:pos(), ((I + 1) / V_Count) ^ Exp)
        holoPos(V_Offset + I, Hier)
        holoAng(V_Offset + I, (Fore - Hier):toAngle())
    }
}
else
{
    while(holoCanCreate() & (V_TI <= V_Count))
    {
        holoCreate(V_Offset + V_TI)
        holoModel(V_Offset + V_TI, "models/noesis/donut.mdl")
        holoScale(V_Offset + V_TI, mix(vec(0.5) * (L1/60), vec(1.1) * (L1/60), (V_TI / V_Count)))
        holoMaterial(V_Offset + V_TI, "models/antlion/antlion_innards")
        
        V_TI++
    }
    
    if(V_TI == V_Count + 1)
    {
        V_Ready = 1
    }
}

Pos =  P ? P:pos() : owner():aimPos()

if(changed(owner():keyUse()) & owner():keyUse())
{
    M = !M
}

if(M)
{
    ik3z(L1, L2, H1:pos(), Pos, 1, 2, 3)
}
else
{
    ik3(L1, L2, H1:pos(), Pos, 1, 2, 3)
}

if(last())
{
    P:propDelete()
}
