#help_index "Math/ODE"
#help_file "::/Doc/ODE"
//See /Doc/Credits.DD.
F64 LowPass1(F64 a,F64 y0,F64 y,F64 dt=1.0)
{//First order low pass filter
dt=Exp(-a*dt);
return y0*dt+y*(1.0-dt);
}
U0 ODERstPtrs(CMathODE *ode)
{
I64 s=ode->n_internal*sizeof(F64);
F64 *ptr=ode->array_base;
ode->state_internal=ptr; ptr(I64)+=s;
ode->state_scale=ptr; ptr(I64)+=s;
ode->DstateDt=ptr; ptr(I64)+=s;
ode->initial_state=ptr; ptr(I64)+=s;
ode->tmp0=ptr; ptr(I64)+=s;
ode->tmp1=ptr; ptr(I64)+=s;
ode->tmp2=ptr; ptr(I64)+=s;
ode->tmp3=ptr; ptr(I64)+=s;
ode->tmp4=ptr; ptr(I64)+=s;
ode->tmp5=ptr; ptr(I64)+=s;
ode->tmp6=ptr; ptr(I64)+=s;
ode->tmp7=ptr;
}
public CMathODE *ODENew(I64 n,F64 max_tolerance=1e-6,I64 flags=0)
{//Make differential equation ctrl struct. See flags.
//The tolerance is not precise.
//You can min_tolerance and it will
//dynamically adjust tolerance to utilize
//the CPU.
I64 s=n*sizeof(F64);
CMathODE *ode=CAlloc(sizeof(CMathODE));
ode->t_scale=1.0;
ode->flags=flags;
ode->n_internal=ode->n=n;
ode->h=1e-6;
ode->h_min=1e-64;
ode->h_max=1e32;
ode->max_tolerance=ode->min_tolerance=ode->tolerance_internal=max_tolerance;
ode->win_task=ode->mem_task=Fs;
QueInit(&ode->next_mass);
QueInit(&ode->next_spring);
ode->state=CAlloc(s);
ode->array_base=MAlloc(12*s);
ODERstPtrs(ode);
return ode;
}
public Bool ODEPause(CMathODE *ode,Bool val=ON)
{//Pause ODE.
Bool res;
if (!ode) return OFF;
res=LBEqu(&ode->flags,ODEf_PAUSED,val);
if (val)
while (Bt(&ode->flags,ODEf_BUSY))
Yield;
return res;
}
public U0 ODEDel(CMathODE *ode)
{//Free ODE node, but not masses or springs.
I64 i;
if (!ode) return;
ODEPause(ode);
Free(ode->state);
Free(ode->array_base);
if (ode->slave_tasks) {
for (i=0;i<mp_cnt;i++)
Kill(ode->slave_tasks[i]);
Free(ode->slave_tasks);
}
Free(ode);
}
public I64 ODESize(CMathODE *ode)
{//Mem size of ode ctrl, but not masses and springs.
if (!ode)
return 0;
else
return MSize2(ode->state)+MSize2(ode->array_base)+MSize2(ode);
}
U0 ODESetMassesPtrs(CMathODE *ode,F64 *state,F64 *DstateDt)
{
COrder2D3 *ptr1=state(F64 *)+ode->n,
*ptr2=DstateDt(F64 *)+ode->n;
CMass *tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
tmpm->state=ptr1++;
tmpm->DstateDt=ptr2++;
tmpm=tmpm->next;
}
}
U0 ODEState2Internal(CMathODE *ode)
{
CMass *tmpm;
F64 *old_array_base;
I64 mass_cnt;
if (ode->flags&ODEF_HAS_MASSES) {
mass_cnt=0;
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
mass_cnt++;
tmpm=tmpm->next;
}
old_array_base=ode->array_base;
ode->n_internal=ode->n+6*mass_cnt;
ode->array_base=MAlloc(12*ode->n_internal*sizeof(F64),ode->mem_task);
Free(old_array_base);
ODERstPtrs(ode);
ODESetMassesPtrs(ode,ode->state_internal,ode->state_internal);
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
MemCpy(tmpm->state,&tmpm->saved_state,sizeof(COrder2D3));
tmpm=tmpm->next;
}
}
MemCpy(ode->state_internal,ode->state,ode->n*sizeof(F64));
}
U0 ODEInternal2State(CMathODE *ode)
{
CMass *tmpm;
MemCpy(ode->state,ode->state_internal,ode->n*sizeof(F64));
if (ode->flags&ODEF_HAS_MASSES) {
ODESetMassesPtrs(ode,ode->state_internal,ode->state_internal);
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
MemCpy(&tmpm->saved_state,tmpm->state,sizeof(COrder2D3));
tmpm=tmpm->next;
}
}
}
public U0 ODERenum(CMathODE *ode)
{//Renumber masses and springs.
I64 i;
CSpring *tmps;
CMass *tmpm;
i=0;
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
tmpm->num=i++;
tmpm=tmpm->next;
}
i=0;
tmps=ode->next_spring;
while (tmps!=&ode->next_spring) {
tmps->num=i++;
tmps->end1_num=tmps->end1->num;
tmps->end2_num=tmps->end2->num;
tmps=tmps->next;
}
}
public CMass *MassFind(CMathODE *ode,F64 x,F64 y,F64 z=0)
{//Search for mass nearest to x,y,z.
CMass *tmpm,*best_mass=NULL;
F64 dd,best_dd=F64_MAX;
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
dd=Sqr(tmpm->x-x)+Sqr(tmpm->y-y)+Sqr(tmpm->z-z);
if (dd<best_dd) {
best_dd=dd;
best_mass=tmpm;
}
tmpm=tmpm->next;
}
return best_mass;
}
public CSpring *SpringFind(CMathODE *ode,F64 x,F64 y,F64 z=0)
{//Find spring midpoint nearest x,y,z.
CSpring *tmps,*best_spring=NULL;
F64 dd,best_dd=F64_MAX;
tmps=ode->next_spring;
while (tmps!=&ode->next_spring) {
dd=Sqr((tmps->end1->x+tmps->end2->x)/2-x)+
Sqr((tmps->end1->y+tmps->end2->y)/2-y)+
Sqr((tmps->end1->z+tmps->end2->z)/2-z);
if (dd<best_dd) {
best_dd=dd;
best_spring=tmps;
}
tmps=tmps->next;
}
return best_spring;
}
public U0 MassOrSpringFind(
CMathODE *ode,CMass **res_mass,CSpring **res_spring,
F64 x,F64 y,F64 z=0)
{//Find spring or mass nearest x,y,z.
CMass *tmpm,*best_mass=NULL;
CSpring *tmps,*best_spring=NULL;
F64 dd,best_dd=F64_MAX;
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
dd=Sqr(tmpm->x-x)+Sqr(tmpm->y-y)+Sqr(tmpm->z-z);
if (dd<best_dd) {
best_dd=dd;
best_mass=tmpm;
}
tmpm=tmpm->next;
}
tmps=ode->next_spring;
while (tmps!=&ode->next_spring) {
dd=Sqr((tmps->end1->x+tmps->end2->x)/2-x)+
Sqr((tmps->end1->y+tmps->end2->y)/2-y)+
Sqr((tmps->end1->z+tmps->end2->z)/2-z);
if (dd<best_dd) {
best_dd=dd;
best_spring=tmps;
best_mass=NULL;
}
tmps=tmps->next;
}
if (res_mass) *res_mass =best_mass;
if (res_spring) *res_spring=best_spring;
}
public CMass *MassFindNum(CMathODE *ode,I64 num)
{//Return mass number N.
CMass *tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
if (tmpm->num==num)
return tmpm;
tmpm=tmpm->next;
}
return NULL;
}
public U0 ODERstInactive(CMathODE *ode)
{//Set all masses and springs to ACTIVE for new trial.
CMass *tmpm;
CSpring *tmps;
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
tmpm->flags&=~MSF_INACTIVE;
tmpm=tmpm->next;
}
tmps=ode->next_spring;
while (tmps!=&ode->next_spring) {
tmps->flags&=~SSF_INACTIVE;
tmps=tmps->next;
}
}
U0 ODECalcSprings(CMathODE *ode)
{
CSpring *tmps=ode->next_spring;
CMass *e1,*e2;
F64 d;
CD3 p;
while (tmps!=&ode->next_spring) {
if (tmps->flags&SSF_INACTIVE) {
tmps->displacement=0;
tmps->f=0;
} else {
e1=tmps->end1;
e2=tmps->end2;
d=D3Norm(D3Sub(&p,&e2->state->x,&e1->state->x));
tmps->displacement=d-tmps->rest_len;
tmps->f=tmps->displacement*tmps->const;
if (tmps->f>0 && tmps->flags&SSF_NO_TENSION)
tmps->f=0;
else if (tmps->f<0 && tmps->flags&SSF_NO_COMPRESSION)
tmps->f=0;
if (d>0) {
D3MulEqu(&p,tmps->f/d);
D3AddEqu(&e1->DstateDt->DxDt,&p);
D3SubEqu(&e2->DstateDt->DxDt,&p);
}
}
tmps=tmps->next;
}
}
U0 ODECalcDrag(CMathODE *ode)
{
CMass *tmpm;
F64 d,dd;
CD3 p;
if (ode->drag_v || ode->drag_v2 || ode->drag_v3) {
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
if (!(tmpm->flags & MSF_INACTIVE) &&
tmpm->drag_profile_factor &&
(dd=D3NormSqr(&tmpm->state->DxDt))) {
d=ode->drag_v;
if (ode->drag_v2)
d+=ode->drag_v2*Sqrt(dd);
if (ode->drag_v3)
d+=dd*ode->drag_v3;
D3SubEqu(&tmpm->DstateDt->DxDt,
D3Mul(&p,d*tmpm->drag_profile_factor,&tmpm->state->DxDt));
}
tmpm=tmpm->next;
}
}
}
U0 ODEApplyAccelerationLimit(CMathODE *ode)
{
CMass *tmpm;
F64 d;
if (ode->acceleration_limit) {
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
if (!(tmpm->flags & MSF_INACTIVE) &&
(d=D3Norm(&tmpm->DstateDt->DxDt))>ode->acceleration_limit)
D3MulEqu(&tmpm->DstateDt->DxDt,ode->acceleration_limit/d);
tmpm=tmpm->next;
}
}
}
U0 ODEMPTask(CMathODE *ode)
{
while (TRUE) {
while (!Bt(&ode->mp_not_done_flags,Gs->num))
Yield;
if (ode->mp_derive)
(*ode->mp_derive)(ode,ode->mp_t,
Gs->num,ode->mp_state,ode->mp_DstateDt);
LBtr(&ode->mp_not_done_flags,Gs->num);
}
}
U0 ODEMPWake(CMathODE *ode)
{
I64 i;
if (!ode->slave_tasks) {
ode->slave_tasks=CAlloc(mp_cnt*sizeof(CTask *));
for (i=0;i<mp_cnt;i++)
ode->slave_tasks[i]=Spawn(&ODEMPTask,ode,"ODE Slave",i);
}
for (i=0;i<mp_cnt;i++) {
Suspend(ode->slave_tasks[i],FALSE);
MPInt(I_WAKE,i);
}
}
U0 ODEMPSleep(CMathODE *ode)
{
I64 i;
if (ode->slave_tasks) {
while (ode->mp_not_done_flags)
Yield;
for (i=0;i<mp_cnt;i++)
Suspend(ode->slave_tasks[i]);
}
}
U0 ODECallMPDerivative(CMathODE *ode,F64 t,F64 *state,F64 *DstateDt)
{
ode->mp_t=t;
ode->mp_state=state;
ode->mp_DstateDt=DstateDt;
ode->mp_not_done_flags=1<<mp_cnt-1;
do Yield;
while (ode->mp_not_done_flags);
}
U0 ODECallDerivative(CMathODE *ode,F64 t,F64 *state,F64 *DstateDt)
{
CMass *tmpm;
if (ode->flags&ODEF_HAS_MASSES) {
ODESetMassesPtrs(ode,state,DstateDt);
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
if (!(tmpm->flags&MSF_INACTIVE)) {
D3Zero(&tmpm->DstateDt->DxDt);
D3Copy(&tmpm->DstateDt->x,&tmpm->state->DxDt);
}
tmpm=tmpm->next;
}
ODECalcSprings(ode);
ODECalcDrag(ode);
if (ode->mp_derive)
ODECallMPDerivative(ode,t,state,DstateDt);
if (ode->derive)
(*ode->derive)(ode,t,state,DstateDt);
tmpm=ode->next_mass;
while (tmpm!=&ode->next_mass) {
if (!(tmpm->flags&MSF_INACTIVE)) {
if (tmpm->flags&MSF_FIXED) {
D3Zero(&tmpm->DstateDt->DxDt);
D3Zero(&tmpm->DstateDt->x);
} else if (tmpm->mass)
D3DivEqu(&tmpm->DstateDt->DxDt,tmpm->mass);
}
tmpm=tmpm->next;
}
ODEApplyAccelerationLimit(ode);
} else {
if (ode->mp_derive)
ODECallMPDerivative(ode,t,state,DstateDt);
if (ode->derive)
(*ode->derive)(ode,t,state,DstateDt);
}
}
U0 ODEOneStep(CMathODE *ode)
{
I64 i;
ODECallDerivative(ode,ode->t,ode->state_internal,ode->DstateDt);
for (i=0;i<ode->n_internal;i++)
ode->state_internal[i]+=ode->h*ode->DstateDt[i];
ode->t+=ode->h;
}
U0 ODERK4OneStep(CMathODE *ode)
{
I64 i,n=ode->n_internal;
F64 xh,hh,h6,*dym,*dyt,*yt,*DstateDt;
dym =ode->tmp0;
dyt =ode->tmp1;
yt =ode->tmp2;
DstateDt=ode->tmp3;
hh =0.5*ode->h;
h6 =ode->h / 6.0;
xh =ode->t + hh;
ODECallDerivative(ode,ode->t,ode->state_internal,ode->DstateDt);
for (i=0;i<n;i++)
yt[i]=ode->state_internal[i]+hh*DstateDt[i];
ODECallDerivative(ode,xh,yt,dyt);
for (i=0;i<n;i++)
yt[i]=ode->state_internal[i]+hh*dyt[i];
ODECallDerivative(ode,xh,yt,dym);
for (i=0;i<n;i++) {
yt[i]=ode->state_internal[i]+ode->h*dym[i];
dym[i]+=dyt[i];
}
ode->t+=ode->h;
ODECallDerivative(ode,ode->t,yt,dyt);
for (i=0;i<n;i++)
ode->state_internal[i]+=h6*(DstateDt[i]+dyt[i]+2.0*dym[i]);
}
#define ODEa2 0.2
#define ODEa3 0.3
#define ODEa4 0.6
#define ODEa5 1.0
#define ODEa6 0.875
#define ODEb21 0.2
#define ODEb31 (3.0/40.0)
#define ODEb32 (9.0/40.0)
#define ODEb41 0.3
#define ODEb42 (-0.9)
#define ODEb43 1.2
#define ODEb51 (-11.0/54.0)
#define ODEb52 2.5
#define ODEb53 (-70.0/27.0)
#define ODEb54 (35.0/27.0)
#define ODEb61 (1631.0/55296.0)
#define ODEb62 (175.0/512.0)
#define ODEb63 (575.0/13824.0)
#define ODEb64 (44275.0/110592.0)
#define ODEb65 (253.0/4096.0)
#define ODEc1 (37.0/378.0)
#define ODEc3 (250.0/621.0)
#define ODEc4 (125.0/594.0)
#define ODEc6 (512.0/1771.0)
#define ODEdc1 (37.0/378.0-2825.0/27648.0)
#define ODEdc3 (250.0/621.0-18575.0/48384.0)
#define ODEdc4 (125.0/594.0-13525.0/55296.0)
#define ODEdc5 (-277.0/14336.0)
#define ODEdc6 (512.0/1771.0-0.25)
U0 ODECashKarp(CMathODE *ode)
{
I64 i,n=ode->n_internal;
F64 h=ode->h,*state=ode->state_internal,
*DstateDt=ode->DstateDt,*ak2,*ak3,*ak4,*ak5,*ak6,
*tmpstate,*stateerr,*outstate;
ak2=ode->tmp0;
ak3=ode->tmp1;
ak4=ode->tmp2;
ak5=ode->tmp3;
ak6=ode->tmp4;
tmpstate=ode->tmp5;
outstate=ode->tmp6;
stateerr=ode->tmp7;
for (i=0;i<n;i++)
tmpstate[i]=state[i]+ODEb21*h*DstateDt[i];
ODECallDerivative(ode,ode->t+ODEa2*h,tmpstate,ak2);
for (i=0;i<n;i++)
tmpstate[i]=state[i]+h*(ODEb31*DstateDt[i]+ODEb32*ak2[i]);
ODECallDerivative(ode,ode->t+ODEa3*h,tmpstate,ak3);
for (i=0;i<n;i++)
tmpstate[i]=state[i]+h*(ODEb41*DstateDt[i]+ODEb42*ak2[i]+ODEb43*ak3[i]);
ODECallDerivative(ode,ode->t+ODEa4*h,tmpstate,ak4);
for (i=0;i<n;i++)
tmpstate[i]=state[i]+h*(ODEb51*DstateDt[i]+
ODEb52*ak2[i]+ODEb53*ak3[i]+ODEb54*ak4[i]);
ODECallDerivative(ode,ode->t+ODEa5*h,tmpstate,ak5);
for (i=0;i<n;i++)
tmpstate[i]=state[i]+h*(ODEb61*DstateDt[i]+
ODEb62*ak2[i]+ODEb63*ak3[i]+ODEb64*ak4[i]+ODEb65*ak5[i]);
ODECallDerivative(ode,ode->t+ODEa6*h,tmpstate,ak6);
for (i=0;i<n;i++)
outstate[i]=state[i]+h*(ODEc1*DstateDt[i]+
ODEc3*ak3[i]+ODEc4*ak4[i]+ODEc6*ak6[i]);
for (i=0;i<n;i++)
stateerr[i]=h*(ODEdc1*DstateDt[i]+ODEdc3*ak3[i]+
ODEdc4*ak4[i]+ODEdc5*ak5[i]+ODEdc6*ak6[i]);
}
#define SAFETY 0.9
#define PGROW (-0.2)
#define PSHRNK (-0.25)
#define ERRCON 1.89e-4
U0 ODERK5OneStep(CMathODE *ode)
{
I64 i;
F64 errmax,tmp,*tmpstate=ode->tmp6,*stateerr=ode->tmp7;
while (TRUE) {
ode->h=Clamp(ode->h,ode->h_min,ode->h_max);
ODECashKarp(ode);
errmax=0.0;
for (i=0;i<ode->n_internal;i++) {
tmp=Abs(stateerr[i]/ode->state_scale[i]);
if (tmp>errmax)
errmax=tmp;
}
errmax/=ode->tolerance_internal;
if (errmax<=1.0 || ode->h==ode->h_min) break;
tmp=ode->h*SAFETY*errmax`PSHRNK;
if (tmp<0.1*ode->h)
ode->h*=0.1;
else
ode->h=tmp;
}
ode->t+=ode->h;
if (errmax>ERRCON)
ode->h*=SAFETY*errmax`PGROW;
else
ode->h*=5.0;
ode->h=Clamp(ode->h,ode->h_min,ode->h_max);
MemCpy(ode->state_internal,tmpstate,sizeof(F64)*ode->n_internal);
}
F64 ode_alloced_factor=0.75;
U0 ODEsUpdate(CTask *task)
{/* This routine is called by the window mgron a continuous
basis to allow real-time simulation.It is intended
to provide ress good enough for games.It uses a runge-kutta
integrator which is a better algorithm than doing it with Euler.
It is adaptive step-sized, so it slows down when an important
event is taking place to improve accuracy, but in my implementation
it has a timeout.
*/
I64 i;
F64 d,start_time,timeout_time,t_desired,t_initial,interpolation;
CMathODE *ode;
if (task->next_ode==&task->next_ode)
task->last_ode_time=0;
else if (!Bt(&task->win_inhibit,WIf_SELF_ODE)) {
//See GrUpdateTasks() and GrUpdateTaskODEs().
//We will not pick a time limit based on
//how busy the CPU is, what percent of the
//last refresh cycle was spent on ODE's
//and what the refresh cycle rate was.
start_time=tS;
d=1.0/winmgr.fps;
timeout_time=start_time+
(task->last_ode_time/d+0.1)/(winmgr.last_ode_time/d+0.1)*
ode_alloced_factor*d;
ode=task->next_ode;
while (ode!=&task->next_ode) {
t_initial=ode->t;
d=tS;
if (!(ode->flags&ODEF_STARTED)) {
ode->base_t=d;
ode->flags|=ODEF_STARTED;
}
d-=ode->base_t+t_initial;
t_desired=ode->t_scale*d+t_initial;
if (ode->flags&ODEF_PAUSED)
ode->base_t+=t_desired-ode->t; //Slip
else {
ode->flags|=ODEF_BUSY;
if (ode->flags&ODEF_PAUSED)
ode->base_t+=t_desired-ode->t; //Slip
else {
if (ode->derive || ode->mp_derive) {
if (ode->mp_derive)
ODEMPWake(ode);
ODEState2Internal(ode);
MemCpy(ode->initial_state,ode->state_internal,
ode->n_internal*sizeof(F64));
while (ode->t<t_desired) {
ode->h_max=t_desired-ode->t;
ODECallDerivative(ode,ode->t,ode->state_internal,ode->DstateDt);
for (i=0;i<ode->n_internal;i++)
ode->state_scale[i]=Abs(ode->state_internal[i])+
Abs(ode->DstateDt[i]*ode->h)+ode->tolerance_internal;
ODERK5OneStep(ode);
if (tS>timeout_time) {
ode->base_t+=t_desired-ode->t; //Slip
goto ode_done;
}
}
//Interpolate if end time was not exact.
if (ode->t!=t_desired) {
if (interpolation=ode->t-t_initial) {
interpolation=(t_desired-t_initial)/interpolation;
if (interpolation!=1.0)
for (i=0;i<ode->n_internal;i++)
ode->state_internal[i]=(ode->state_internal[i]-
ode->initial_state[i])*interpolation+
ode->initial_state[i];
}
ode->t=t_desired;
}
ode_done:
ODEInternal2State(ode);
//Convenience call to set vals
ODECallDerivative(ode,ode->t,ode->state_internal,ode->DstateDt);
if (ode->mp_derive)
ODEMPSleep(ode);
}
}
ode->flags&=~ODEF_BUSY;
}
ode->base_t+=(1.0-ode->t_scale)*d;
ode=ode->next;
}
//Now, we will dynamically adjust tolerances.
//We will regulate the tolerances
//to fill the time we decided was
//okay to devote to ODE's.
//Since we might have multiple ODE's
//active we scale them by the same factor.
//This algorithm is probably not stable or very good, but it's something.
//Target is 75% of alloced time.
d=(tS-start_time)/(timeout_time-start_time)-0.75;
ode=task->next_ode;
while (ode!=&task->next_ode) {
if (!(ode->flags&ODEF_PAUSED) && ode->derive) {
if (ode->min_tolerance!=ode->max_tolerance) {
if (d>0)
ode->tolerance_internal*=10.0`d;
else
ode->tolerance_internal*=2.0`d;
}
ode->tolerance_internal=Clamp(ode->tolerance_internal,
ode->min_tolerance,ode->max_tolerance);
}
ode=ode->next;
}
winmgr.ode_time+=task->last_ode_time=tS-start_time;
}
}