POV-Ray : Newsgroups : povray.general : [3.8] Forward And Backward Reaching Inverse Kinematics Server Time: 16 Feb 2019 05:21:39 GMT
 [3.8] Forward And Backward Reaching Inverse Kinematics (Message 1 to 10 of 13)
 From: ingo Subject: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 1 Dec 2018 11:14:24 Message:
```Did a quick search but nothing showed up for POV-Ray & FABRIK. So here's
my first attempt at the most basic version. Next steps will be more
complex skeletons with multiple ends and then joint constraints. I hope
that the latter can nicely done with the trace function.

Dictionaries are nice.

//==
// Pov-Ray   : 3.8
// Scene File: FABRIK.inc
// Author    : Ingo Janssen
// Date      : 01-12-2018
// Version   : 1a
//--
// Forward And Backward Reaching Inverse Kinematics (FABRIK) by Andreas
Aristidou.
// http://www.andreasaristidou.com/FABRIK.html
// http://www.andreasaristidou.com/publications/papers/FABRIK.pdf
//
http://www.andreasaristidou.com/publications/papers/Extending_FABRIK_wit
h_Model_C%CE%BFnstraints.pdf
// Excellent code inspiration & guide, by unknown author:
// https://developer.roblox.com/articles/Inverse-Kinematics-for-
Animation#FABRIK
//

#version 3.8;

#macro FABRIK(VecArr, VecTarget)
// init
#local self = dictionary {
["Joints"]      : VecArr,
["_O"]          : VecArr[0],
// should be replaced by SetTarget
["Target"]      : VecTarget,
// use .SetTarget(vec) to set a new target and iterate
// still to be done somehowe.
["SetTarget"]   : false,
["Tolerance"]   : 0.1,
["MaxIter"]     : 100,
["_M"]          : false,
["_N"]          : false,
["_Reachable"]  : false,
["_Lengths"]    : false,
["_TotalLength"]: false,
};
#local self._N = dimension_size(self.Joints, 1);
#local self._M = self._N-1; //max index of array

#macro LengthsArr()
//#debug "LengthsArr :\n"
#local Lengths = array[self._N-1];
#local i = 0;
#while (i < (self._N-1))
#local Lengths[i] = vlength(self.Joints[i]-self.Joints[i+1]);
//#debug concat("i : ",str(i,0,0)," len : ",str(Lengths
[i],0,3),"\n")
#local i = i + 1;
#end
Lengths
#end
#local self._Lengths = LengthsArr();

#macro LengthsSum()
//#debug "LengthsSum :\n"
#local Length = 0;
#local i = 0;
#while (i < dimension_size(self._Lengths,1))
#local Length = Length + self._Lengths[i];
//#debug concat("i : ",str(i,0,0)," len : ",str(self._Lengths
[i],0,3)," sum : ",str(Length,0,3),"\n")
#local i = i + 1;
#end
Length
#end
#local self._TotalLength = LengthsSum();

#macro Reach ()
#debug "Reach : "
#if (vlength(self._O-self.Target) > self._TotalLength)
#local Reachable = false;
#debug "False\n"
#else
#local Reachable = true;
#debug "True\n"
#end
Reachable
#end
#local self._Reachable = Reach();

#macro Backwards(Joints)
#debug "  Backwards :\n"
#local Joints[self._M] = self.Target;
#for (i, self._M-1, 0, -1)
#local l = self._Lengths[i]/vlength(Joints[i+1]-Joints[i]);
#local Pos =  (1-l) * Joints[i+1] + l * Joints[i];
#local Joints[i] = Pos;
#debug concat("    i : ",str(i,0,0)," pos : ", vstr
(3,Pos,",",0,3),"\n")
#end
Joints
#end

#macro Forward(Joints)
#debug "  Forward :\n"
// set first point back to its original position
#local Joints[0] = self._O;
#for (i, 0, self._M-1, 1)
#local l = self._Lengths[i]/vlength(Joints[i+1]-Joints[i]);
#local Pos =  (1-l) * Joints[i] + l * Joints[i+1];
#local Joints[i+1] = Pos;
#debug concat("    i : ",str(i,0,0)," pos : ", vstr
(3,Pos,",",0,3),"\n")
#end
Joints
#end

#macro Stretch(Joints)
#debug "Stretch :\n"
#for (i, 0, self._M-1, 1)
#local l = self._Lengths[i]/vlength(self.Target-Joints[i]);
#local Joints[i+1] = (1-l) * Joints[i] + l * self.Target;
#debug concat("i : ",str(i,0,0), " pos : ", vstr(3,Joints[i+
1],",",0,3),"\n")
#end
Joints
#end

#macro Solve(Joints)
#debug "Solving :\n"
#if (self._Reachable = false)
#local Joints = Stretch(Joints);
#else
#local itercount = 0;
#local dif = vlength(self.Joints[self._M] - self.Target);
#while (dif > self.Tolerance)
#debug concat("  dif : ",str(dif,0,3)," iter : ",str
(itercount,0,0),"\n")
#local Joints = Backwards(Joints);
#local Joints = Forward(Joints);
#local dif = vlength(Joints[self._M] - self.Target);
#local itercount = itercount + 1;
#if (itercount > self.MaxIter)
#local dif = 0;
#end
#end
#debug concat("\n  dif : ",str(dif,0,3)," end iter\n\n")
#end
Joints
#end
#local self.Joints = Solve(self.Joints)

self
#end

//==
// testscene
//
#global_settings {assumed_gamma 1.0 }
#default {pigment{rgb 1} finish{ ambient 0.2 diffuse 0.9 }}
light_source{<1000,1000,-1000>, rgb 1}
plane{-z,0 pigment{checker rgb 0.8 rgb 0.5}}
camera {location <0, 4.5,-15> look_at <0,4.5,0>}

#macro Connect(Arr, RGB)
#local l = dimension_size(Arr,1);
#local i = 0;
union{
#while (i < l-1)
sphere{Arr[i],0.2 pigment{rgb RGB}}
cylinder{Arr[i],Arr[i+1],0.1  pigment{rgb RGB}}
#local i=i+1;
#end
sphere{Arr[l-1],0.2 pigment{rgb <0,1,0>}}
}
#end

#declare S = array[10]{
<0,0,0>,<1,1,0>,<2,2,0>,
<3,3,0>,<4,4,0>,<5,5,0>,
<6,6,0>,<7,7,0>,<8,8,0>,
<9,9,0>
};

#for (i,9,-9,-1)
#declare T = <i,9,0>;
#declare Spine = FABRIK(S, T);
Connect(Spine.Joints, <1,0,0>)
#end
```
 From: ingo Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 1 Dec 2018 16:18:41 Message:
```An updated version. Can now initialise the FABRIK without giving a

#declare Spine = FABRIK(somearraywithvectorsthatarejoints);
#declare Spine.Target = SetTarget(Spine, T);
and it calculates the new positions of the joints

ingo

//==
// Pov-Ray   : 3.8
// Scene File: FABRIK.inc
// Author    : Ingo Janssen
// Date      : 01-12-2018
// Version   : 1a1
//--
// Forward And Backward Reaching Inverse Kinematics (FABRIK) by Andreas
Aristidou.
// http://www.andreasaristidou.com/FABRIK.html
// http://www.andreasaristidou.com/publications/papers/FABRIK.pdf
//
http://www.andreasaristidou.com/publications/papers/Extending_FABRIK_wit
h_Model_C%CE%BFnstraints.pdf
// Excellent code inspiration & guide, by unknown author:
// https://developer.roblox.com/articles/Inverse-Kinematics-for-
Animation#FABRIK
//

#version 3.8;

#macro FABRIK(VecArr)
#local self = dictionary {};

#macro Init(self, VecArr)
#macro LengthsArr(self, VecArr)
#local self._Lengths = array[self._N-1];
#local i = 0;
#while (i < (self._N-1))
#local self._Lengths[i] = vlength(VecArr[i]-VecArr[i+1]);
#local i = i + 1;
#end
#end

#macro LengthsSum(self)
#local self._Length = 0;
#local i = 0;
#while (i < dimension_size(self._Lengths,1))
#local self._Length = self._Length + self._Lengths[i];
#local i = i + 1;
#end
#end

#local self.Joints = VecArr;
#local self.Tolerance = 0.1;
#local self.MaxIter = 100;
#local self._O = VecArr[0];
#local self._N = dimension_size(VecArr, 1);
#local self._M = self._N-1; //max index of array
LengthsArr(self, VecArr)
LengthsSum(self)
#end
Init(self, VecArr)

// internals

#macro _Reach(self)
#debug "Reach : "
#if (vlength(self._O-self.Target) > self._Length)
#local self._Reachable = false;
#debug "False\n"
#else
#local self._Reachable = true;
#debug "True\n"
#end
#end

#macro _Backwards(self)
#debug "  Backwards :\n"
#local self.Joints[self._M] = self.Target;
#for (i, self._M-1, 0, -1)
#local l = self._Lengths[i]/vlength(self.Joints[i+1]-self.Joints
[i]);
#local Pos =  (1-l) * self.Joints[i+1] + l * self.Joints[i];
#local self.Joints[i] = Pos;
#debug concat("    i : ",str(i,0,0)," pos : ", vstr
(3,Pos,",",0,3),"\n")
#end
#end

#macro _Forward(self)
#debug "  Forward :\n"
// set first point back to its original position
#local self.Joints[0] = self._O;
#for (i, 0, self._M-1, 1)
#local l = self._Lengths[i]/vlength(self.Joints[i+1]-self.Joints
[i]);
#local Pos =  (1-l) * self.Joints[i] + l * self.Joints[i+1];
#local self.Joints[i+1] = Pos;
#debug concat("    i : ",str(i,0,0)," pos : ", vstr
(3,Pos,",",0,3),"\n")
#end
#end

#macro _Stretch(self)
#debug "Stretch :\n"
#for (i, 0, self._M-1, 1)
#local l = self._Lengths[i]/vlength(self.Target-self.Joints[i]);
#local self.Joints[i+1] = (1-l) * self.Joints[i] + l *
self.Target;
#debug concat("i : ",str(i,0,0), " pos : ", vstr(3,self.Joints[i+
1],",",0,3),"\n")
#end
#end

#macro _Solve(self)
#debug "Solving :\n"
#if (self._Reachable = false)
_Stretch(self)
#else
#local itercount = 0;
#local dif = vlength(self.Joints[self._M] - self.Target);
#while (dif > self.Tolerance)
#debug concat("  dif : ",str(dif,0,3)," iter : ",str
(itercount,0,0),"\n")
_Backwards(self)
_Forward(self)
#local dif = vlength(self.Joints[self._M] - self.Target);
#local itercount = itercount + 1;
#if (itercount > self.MaxIter)
#local dif = 0;
#end
#end
#debug concat("\n  dif : ",str(dif,0,3)," end iter\n\n")
#end
true
#end

// external
#macro SetTarget (self, Target)
#local self.Target = Target;
_Reach(self)
_Solve(self)
#end

self
#end

//==
// testscene
//
#global_settings {assumed_gamma 1.0 }
#default {pigment{rgb 1} finish{ ambient 0.2 diffuse 0.9 }}
light_source{<1000,1000,-1000>, rgb 1}
plane{-z,0 pigment{checker rgb 0.8 rgb 0.5}}
camera {location <0, 0,-20> look_at <0,0,0>}

#macro Connect(Arr, RGB, r)
#local l = dimension_size(Arr,1);
#local i = 0;
union{
#while (i < l-1)
sphere{Arr[i],r pigment{rgb RGB}}
cylinder{Arr[i],Arr[i+1],r/2  pigment{rgb RGB}}
#local i=i+1;
#end
sphere{Arr[l-1],r pigment{rgb <0,1,0>}}
}
#end

#declare S = array[10]{
<0,0,0>,<1,1,0>,<2,2,0>,
<3,3,0>,<4,4,0>,<5,5,0>,
<6,6,0>,<7,7,0>,<8,8,0>,
<9,9,0>
};

#declare Spine = FABRIK(S);
#macro Draw(T)
#declare Spine.Target = SetTarget(Spine, T);
Connect(Spine.Joints, <0,0,1>, 0.2)
#end
#for (i,0,3,1)
#switch(i)
#case(0)
#for (n,9,-9,-1)
#declare T = <n,9,0>;
Draw(T)
#end
#break
#case(1)
#for (n,9,-9,-1)
#declare T = <-9,n,0>;
Draw(T)
#end
#break
#case(2)
#for (n,-9,9,1)
#declare T = <n,-9,0>;
Draw(T)
#end
#break
#case(3)
#for (n,-9,9,1)
#declare T = <9,n,0>;
Draw(T)
#end
#break
#end

#end
```
 From: Stephen Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 1 Dec 2018 17:45:53 Message: <5c02c8d1@news.povray.org>
```On 01/12/2018 16:18, ingo wrote:
> // Forward And Backward Reaching Inverse Kinematics (FABRIK) by Andreas
> Aristidou.
> //http://www.andreasaristidou.com/FABRIK.html

Very interesting. I also took a look at your you tube channel with the
about 400 BVH files from MIT. Who opened the course data to the public.
I could have used your methods for cleaning up the foot slippage and
joint deformities. That was a lot of work in Poser, before exporting the
meshes to PoseRay then into Mesh2 for Pov.

--

Regards
Stephen
```
 From: ingo Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 1 Dec 2018 18:37:11 Message:
```in news:5c02c8d1@news.povray.org Stephen wrote:

> On 01/12/2018 16:18, ingo wrote:
>> // Forward And Backward Reaching Inverse Kinematics (FABRIK) by
>> Andreas Aristidou.
>> //http://www.andreasaristidou.com/FABRIK.html
>
> Very interesting. I also took a look at your you tube channel with
> the motion capture.

Not my channel Stephen, I just stumbled over Andreas' work yesterday and
implemented it in POV-Ray (peeking a lot at other code), well, just a part
of it for now.

Cheers,

ingo
```
 From: Stephen Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 2 Dec 2018 04:51:52 Message: <5c0364e8\$1@news.povray.org>
```On 01/12/2018 18:37, ingo wrote:
> in news:5c02c8d1@news.povray.org Stephen wrote:
>
>> On 01/12/2018 16:18, ingo wrote:
>>> // Forward And Backward Reaching Inverse Kinematics (FABRIK) by
>>> Andreas Aristidou.
>>> //http://www.andreasaristidou.com/FABRIK.html
>>
>> Very interesting. I also took a look at your you tube channel with
>> the motion capture.
>
> Not my channel Stephen, I just stumbled over Andreas' work yesterday and
> implemented it in POV-Ray (peeking a lot at other code), well, just a part
> of it for now.
>

My oops then. :)
Good luck with your IK project.

--

Regards
Stephen
```
 From: And Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 2 Dec 2018 10:55:00 Message:
```ingo <ing### [at] tagpovrayorg> wrote:
> in news:5c02c8d1@news.povray.org Stephen wrote:
>
> > On 01/12/2018 16:18, ingo wrote:
> >> // Forward And Backward Reaching Inverse Kinematics (FABRIK) by
> >> Andreas Aristidou.
> >> //http://www.andreasaristidou.com/FABRIK.html
> >
> > Very interesting. I also took a look at your you tube channel with
> > the motion capture.
>

interest
```
 From: ingo Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 5 Dec 2018 08:49:16 Message:
```Another update. Before implementing more complex structures, I made it
possible to solve multiple independen IK's with one Fabrik 'object'

ingo

----%<----%<----%<----%<----%<----
//==
// Pov-Ray   : 3.8
// Scene File: FABRIK.inc
// Author    : Ingo Janssen
// Date      : 01-12-2018
// Rev Date  : 05-12-2018
// Version   : 1a2
//--
// Forward And Backward Reaching Inverse Kinematics (FABRIK) by Andreas
Aristidou.
// http://www.andreasaristidou.com/FABRIK.html
// http://www.andreasaristidou.com/publications/papers/FABRIK.pdf
//
http://www.andreasaristidou.com/publications/papers/Extending_FABRIK_wit
h_Model_C%CE%BFnstraints.pdf
// Excellent code inspiration & guide, by unknown author:
// https://developer.roblox.com/articles/Inverse-Kinematics-for-
Animation#FABRIK
//

#version 3.8;

//==
// macros starting with an underscore should not be called 'from the
outside'.
// only chains mentioned in the process dictionary will be initialised
// absence of the process dictionary will result in an error
//
// The FABRIK macro needs a rather complex datastructure as input, to be
explained later.
// Create an 'instance' of the Fabrik 'object': #declare S = FABRIK(F);
where F is the
// datastructure. Fabrik 'returns' an updated/initialised version of the
input structure.
//
// To run the IK solver use SetTarget(S, T) where S is the afore
mentioned insatance of
// Fabrik and T a datastructure holding the target(s)
//
#macro FABRIK(self)
#ifndef (self.Proc)
#error "\nNo Process 'Proc' dictionary defined\n"
#end

// add FABRIK global defaults, they can be overridden by adding a
Default
// dictionary to a Chain dictionary. The Chain dict is checked first
for
// these values, if not available the 'global' ones are used.
#local self.Default = dictionary {
["Tolerance"] : 0.1,
["MaxIter"] : 100
}

#macro _Init(self)
#macro _Lengths(self, Chain)
#local self[Chain]._Lengths = array[self[Chain]._N-1];
#local self[Chain]._Length = 0;
#local i = 0;
#while (i < (self[Chain]._N-1))
#local self[Chain]._Lengths[i] = vlength(self[Chain].Joint[i]-
self[Chain].Joint[i+1]);
#local self[Chain]._Length = self[Chain]._Length + self[Chain].
_Lengths[i];
#local i = i + 1;
#end
#end

#for (i,0,dimension_size(self.Proc,1)-1,1)
#ifdef (self[self.Proc[i]].Joint)
#local Chain = self.Proc[i];
#debug concat("init chain : ", Chain, "\n")
#local self[Chain]._O = self[Chain].Joint[0];
#local self[Chain]._N = dimension_size(self[Chain].Joint, 1);
#local self[Chain]._M = self[Chain]._N-1; //max index of array
_Lengths(self, Chain)
#end
#end
#end
_Init(self)

// internals
#macro _Reach(self, Chain) //SimpleSolver & EndPoint
#debug "Reach : "
#if (vlength(self[Chain]._O - self[Chain].Target) > self[Chain].
_Length)
#local self[Chain]._Reachable = false;
#debug "False\n"
#else
#local self[Chain]._Reachable = true;
#debug "True\n"
#end
#end

#macro _Backwards(self, Chain)
#debug "  Backwards :\n"
#local self[Chain].Joint[self[Chain]._M] = self[Chain].Target;
#for (i, self[Chain]._M-1, 0, -1)
#local l = self[Chain]._Lengths[i]/vlength(self[Chain].Joint[i+1]-
self[Chain].Joint[i]);
#local Pos =  (1-l) * self[Chain].Joint[i+1] + l * self
[Chain].Joint[i];
//if constraint
//check constraint
//if not within constraint adjust Pos
//2B done
#local self[Chain].Joint[i] = Pos;
#debug concat("    i : ",str(i,0,0)," pos : ", vstr
(3,Pos,",",0,3),"\n")
#end
#end

#macro _Forward(self, Chain)
#debug "  Forward :\n"
// set first point back to its original position
#local self[Chain].Joint[0] = self[Chain]._O;
#for (i, 0, self[Chain]._M-1, 1)
#local l = self[Chain]._Lengths[i]/vlength(self[Chain].Joint[i+1]-
self[Chain].Joint[i]);
#local Pos =  (1-l) * self[Chain].Joint[i] + l * self[Chain].Joint
[i+1];
//if constraint
//check constraint
//if not within constraint adjust Pos
//2B done
#local self[Chain].Joint[i+1] = Pos;
#debug concat("    i : ",str(i,0,0)," pos : ", vstr
(3,Pos,",",0,3),"\n")
#end
#end

#macro _Stretch(self, Chain)
#debug "Stretch :\n"
#for (i, 0, self[Chain]._M-1, 1)
#local l = self[Chain]._Lengths[i]/vlength(self[Chain].Target-self
[Chain].Joint[i]);
#local self[Chain].Joint[i+1] = (1-l) * self[Chain].Joint[i] + l *
self[Chain].Target;
#debug concat("i : ",str(i,0,0), " pos : ", vstr(3,self
[Chain].Joint[i+1],",",0,3),"\n")
#end
#end

#macro _SolveSimple(self, Chain)
#debug "Solving :\n"
#if (self[Chain]._Reachable = false)
_Stretch(self, Chain)
#else
#local itercount = 0;
#local dif = vlength(self[Chain].Joint[self[Chain]._M] - self
[Chain].Target);
#while (dif > self.Default.Tolerance)  //2B done also check chain
local defaults
#debug concat("  dif : ",str(dif,0,3)," iter : ",str
(itercount,0,0),"\n")
_Backwards(self, Chain)
_Forward(self, Chain)
#local dif = vlength(self[Chain].Joint[self[Chain]._M] - self
[Chain].Target);
#local itercount = itercount + 1;
#if (itercount > self.Default.MaxIter)//2B done also check chain
local defaults
#local dif = 0;
#end
#end
#debug concat("\n  dif : ",str(dif,0,3)," end iter\n\n")
#end
#end

// external
#macro SetTarget (self, T)
#for (i, 0, dimension_size(T.Index,1)-1,1)
#local Chain = T.Index[i];
#debug concat("set target : ", Chain, "\n")
#local self[Chain].Target = T[Chain];
_Reach(self, T.Index[i])
_SolveSimple(self, T.Index[i])
#end
#end

self
#end

/*test scene*/

#global_settings{assumed_gamma 1.0 }
#default{pigment{rgb 1} finish{ ambient 0.2 diffuse 0.9 }}
light_source{<1000,1000,-1000>, rgb 1}
//plane{-z,0 pigment{checker rgb 0.8 rgb 0.5}}
camera{location <0,0,-8> look_at <0,0,0>}

// Two 'independent' chains
#declare F = dictionary{
["Proc"]: array[2]{"Base1", "Base2"},
["Base1"]   : dictionary {
["Joint"] : array[6]{<0,0,0>, < 0,1,0>, < 0,2,0>, < 0,3,0>,
<0,4,0>, <0,5,0>},
}
["Base2"]   : dictionary {
["Joint"] : array[6]{<0,0,0>, < 0,1,0>, < 0,2,0>, < 0,3,0>,
<0,4,0>, <0,5,0>},
}
};
#declare S = FABRIK(F);

#declare T = dictionary {
["Index"] : array[2]{"Base1", "Base2"},
["Base1"] : <4,0,0>,
["Base2"] : <-4,0,0>
}
SetTarget(S, T)

#for (i,0,dimension_size(S.Proc,1)-1,1)
#ifdef (S[S.Proc[i]].Joint)
#local Chain = S.Proc[i];
#for (n,0,S[Chain]._M-1,1)
sphere{S[Chain].Joint[n], 0.2}
cylinder{S[Chain].Joint[n],S[Chain].Joint[n+1],0.2}
#end
sphere{S[Chain].Joint[n], 0.2 pigment{rgb x}} //end points
#end
#end
```
 From: ingo Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 10 Dec 2018 15:38:04 Message:
```Added an updated version in p.b.scene-files FABRIK IK
<XnsA9B4A7C06381seed7@news.povray.org>
3CXnsA9B4A7C06381seed7%40news.povray.org%3E/

Still needs a lot of work but the basics do work.

I have an implementation for moving other nodes than the end nodes but
it's complicated to integrate. I have an implementation for movement
restriction, simplified from what is in the paper. Closed loops is one I
havn't looked at yet.

Specifying the whole FABRIC dictionary as it is now is tedious and will be
simplified. Most of the data there can be derived during initialisation,

ingo
```
 From: ingo Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 19 Dec 2018 18:51:12 Message:
```in news:XnsA9AB7C841FAFAseed7@news.povray.org ingo wrote:

> So here's
> my first attempt at the most basic version.

Several more attempts followed. I put the code in a Fossil at
https://chiselapp.com/user/ingo/repository/POV-Ray-FABRIK/home
It runs smoothly now. Updated the interface to the macro to make it easier
to experiment. All still not well documented.

Next will be the addition of movement constraints.

ingo
```
 From: jr Subject: Re: [3.8] Forward And Backward Reaching Inverse Kinematics Date: 19 Dec 2018 23:55:01 Message:
```hi,

ingo <ing### [at] tagpovrayorg> wrote:
> ... I put the code in a Fossil at
> https://chiselapp.com/user/ingo/repository/POV-Ray-FABRIK/home

> It runs smoothly now. Updated the interface to the macro to make it easier
> to experiment. All still not well documented.
>
> Next will be the addition of movement constraints.

looking forward to more playing.  :-)

also, a "feature request": during initialisation, a means to enable or disable
the debug output for that scene would be handy.

regards, jr.
```