//------------------------------------------------------------------------------
// emViewAnimator.cpp
//
// Copyright (C) 2014-2017 Oliver Hamann.
//
// Homepage: http://eaglemode.sourceforge.net/
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License version 3 as published by the
// Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
// more details.
//
// You should have received a copy of the GNU General Public License version 3
// along with this program. If not, see .
//------------------------------------------------------------------------------
#include
#include
//==============================================================================
//=============================== emViewAnimator ===============================
//==============================================================================
emViewAnimator::emViewAnimator(emView & view)
: emEngine(view.GetScheduler()),
View(view)
{
Master=NULL;
ActiveSlave=NULL;
UpperActivePtr=&View.ActiveAnimator;
LastTSC=0;
LastClk=0;
DeactivateWhenIdle=false;
SetEnginePriority(emEngine::HIGH_PRIORITY);
}
emViewAnimator::~emViewAnimator()
{
Deactivate();
}
void emViewAnimator::SetMaster(emViewAnimator * master)
{
emViewAnimator * va;
if (Master!=master) {
if (IsActive()) Deactivate();
if (Master) {
Master=NULL;
UpperActivePtr=&View.ActiveAnimator;
}
if (master) {
for (va=master; va; va=va->Master) if (va==this) return;
Master=master;
UpperActivePtr=&Master->ActiveSlave;
}
}
}
void emViewAnimator::Activate()
{
if (!IsActive() && (!Master || Master->IsActive())) {
if (*UpperActivePtr) {
LastTSC=(*UpperActivePtr)->LastTSC;
LastClk=(*UpperActivePtr)->LastClk;
(*UpperActivePtr)->Deactivate();
}
else if (Master) {
LastTSC=Master->LastTSC;
LastClk=Master->LastClk;
}
*UpperActivePtr=this;
WakeUp();
emDLog("emViewAnimator::Activate: class = %s",typeid(*this).name());
}
}
void emViewAnimator::Deactivate()
{
if (ActiveSlave) {
ActiveSlave->Deactivate();
}
if (*UpperActivePtr==this) {
*UpperActivePtr=NULL;
emDLog("emViewAnimator::Deactivate: class = %s",typeid(*this).name());
}
}
void emViewAnimator::SetDeactivateWhenIdle(bool deactivateWhenIdle)
{
if (DeactivateWhenIdle!=deactivateWhenIdle) {
DeactivateWhenIdle=deactivateWhenIdle;
if (DeactivateWhenIdle && IsActive()) {
WakeUp(); // To be sure to deactivate soon if already idle.
}
}
}
void emViewAnimator::Input(emInputEvent & event, const emInputState & state)
{
if (ActiveSlave) ActiveSlave->Input(event,state);
}
void emViewAnimator::Paint(const emPainter & painter) const
{
if (ActiveSlave) ActiveSlave->Paint(painter);
}
bool emViewAnimator::Cycle()
{
emUInt64 clk,tsc;
double dt;
bool busy;
if (IsActive()) {
tsc=GetScheduler().GetTimeSliceCounter();
if (tsc!=LastTSC) {
clk=GetView().GetInputClockMS();
if (tsc==LastTSC+1) {
dt=(clk-LastClk)*0.001;
if (dt>0.33) dt=0.33;
}
else {
dt=0.01;
}
LastTSC=tsc;
LastClk=clk;
if (dt>0.0) {
busy=CycleAnimation(dt);
}
else {
busy=true;
}
}
else {
busy=true;
}
if (!busy && DeactivateWhenIdle) {
Deactivate();
}
}
else {
busy=false;
}
return busy;
}
//==============================================================================
//=========================== emKineticViewAnimator ============================
//==============================================================================
emKineticViewAnimator::emKineticViewAnimator(emView & view)
: emViewAnimator(view)
{
Velocity[0]=0.0;
Velocity[1]=0.0;
Velocity[2]=0.0;
ZoomFixPointCentered=true;
ZoomFixX=0.0;
ZoomFixY=0.0;
FrictionEnabled=false;
Friction=1000.0;
Busy=false;
}
emKineticViewAnimator::~emKineticViewAnimator()
{
}
void emKineticViewAnimator::Activate()
{
emKineticViewAnimator * oldKVA;
emViewAnimator * va;
double fixX,fixY;
bool fixCentered;
if (!IsActive()) {
oldKVA=NULL;
for (va=GetView().GetActiveAnimator(); va; va=va->GetActiveSlave()) {
oldKVA=dynamic_cast(va);
if (oldKVA) break;
}
if (oldKVA) {
fixCentered=ZoomFixPointCentered;
fixX=ZoomFixX;
fixY=ZoomFixY;
Velocity[0]=oldKVA->Velocity[0];
Velocity[1]=oldKVA->Velocity[1];
Velocity[2]=oldKVA->Velocity[2];
ZoomFixPointCentered=oldKVA->ZoomFixPointCentered;
ZoomFixX=oldKVA->ZoomFixX;
ZoomFixY=oldKVA->ZoomFixY;
if (fixCentered) {
CenterZoomFixPoint();
}
else {
SetZoomFixPoint(fixX,fixY);
}
}
else {
Velocity[0]=0.0;
Velocity[1]=0.0;
Velocity[2]=0.0;
}
emViewAnimator::Activate();
UpdateBusyState();
}
}
void emKineticViewAnimator::Deactivate()
{
emViewAnimator::Deactivate();
}
double emKineticViewAnimator::GetAbsVelocity() const
{
return sqrt(
Velocity[0]*Velocity[0] +
Velocity[1]*Velocity[1] +
Velocity[2]*Velocity[2]
);
}
void emKineticViewAnimator::SetVelocity(int dimension, double velocity)
{
Velocity[dimension]=velocity;
UpdateBusyState();
}
void emKineticViewAnimator::CenterZoomFixPoint()
{
double oldFixX,oldFixY,f,q,dt;
if (!ZoomFixPointCentered) {
oldFixX=ZoomFixX;
oldFixY=ZoomFixY;
ZoomFixPointCentered=true;
UpdateZoomFixPoint();
f=GetView().GetZoomFactorLogarithmPerPixel();
dt=0.01;
q=(1.0-exp(-Velocity[2]*dt*f))/dt;
Velocity[0]+=(oldFixX-ZoomFixX)*q;
Velocity[1]+=(oldFixY-ZoomFixY)*q;
}
}
void emKineticViewAnimator::SetZoomFixPoint(double zoomFixX, double zoomFixY)
{
double oldFixX,oldFixY,f,q,dt;
if (
ZoomFixPointCentered ||
ZoomFixX!=zoomFixX ||
ZoomFixY!=zoomFixY
) {
UpdateZoomFixPoint();
oldFixX=ZoomFixX;
oldFixY=ZoomFixY;
ZoomFixPointCentered=false;
ZoomFixX=zoomFixX;
ZoomFixY=zoomFixY;
f=GetView().GetZoomFactorLogarithmPerPixel();
dt=0.01;
q=(1.0-exp(-Velocity[2]*dt*f))/dt;
Velocity[0]+=(oldFixX-ZoomFixX)*q;
Velocity[1]+=(oldFixY-ZoomFixY)*q;
}
}
void emKineticViewAnimator::SetFrictionEnabled(bool enabled)
{
FrictionEnabled=enabled;
}
void emKineticViewAnimator::SetFriction(double friction)
{
Friction=friction;
}
bool emKineticViewAnimator::CycleAnimation(double dt)
{
double v,v1,v2,f,a;
double dist[3],done[3];
int i;
if (Busy) {
if (IsFrictionEnabled()) {
v=GetAbsVelocity();
a=GetFriction();
if (v-a*dt>0.0) {
f=(v-a*dt)/v;
}
else if (v+a*dt<0.0) {
f=(v+a*dt)/v;
}
else {
f=0.0;
}
}
else {
f=1.0;
}
for (i=0; i<3; i++) {
v1=Velocity[i];
v2=v1*f;
Velocity[i]=v2;
dist[i]=(v1+v2)*0.5*dt;
done[i]=0.0;
}
if (fabs(dist[0])>=0.01 || fabs(dist[1])>=0.01 || fabs(dist[2])>=0.01) {
UpdateZoomFixPoint();
GetView().RawScrollAndZoom(
ZoomFixX,ZoomFixY,
dist[0],dist[1],dist[2],
NULL,
&done[0],&done[1],&done[2]
);
GetView().SetActivePanelBestPossible();
}
for (i=0; i<3; i++) {
if (fabs(done[i])<0.99*fabs(dist[i])) {
Velocity[i]=0.0;
}
}
UpdateBusyState();
}
return Busy;
}
void emKineticViewAnimator::UpdateBusyState()
{
if (IsActive() && GetAbsVelocity()>0.01) {
if (!Busy) {
Busy=true;
WakeUp();
}
}
else {
Velocity[0]=0.0;
Velocity[1]=0.0;
Velocity[2]=0.0;
Busy=false;
}
}
void emKineticViewAnimator::UpdateZoomFixPoint()
{
double sx,sy,sw,sh,x1,y1,x2,y2;
if (ZoomFixPointCentered) {
x1=GetView().GetCurrentX();
y1=GetView().GetCurrentY();
x2=x1+GetView().GetCurrentWidth();
y2=y1+GetView().GetCurrentHeight();
if (GetView().IsPoppedUp()) {
GetView().GetMaxPopupViewRect(&sx,&sy,&sw,&sh);
if (x1sx+sw) x2=sx+sw;
if (y2>sy+sh) y2=sy+sh;
}
ZoomFixX=(x1+x2)*0.5;
ZoomFixY=(y1+y2)*0.5;
}
}
//==============================================================================
//=========================== emSpeedingViewAnimator ===========================
//==============================================================================
emSpeedingViewAnimator::emSpeedingViewAnimator(emView & view)
: emKineticViewAnimator(view)
{
TargetVelocity[0]=0.0;
TargetVelocity[1]=0.0;
TargetVelocity[2]=0.0;
Acceleration=1.0;
ReverseAcceleration=1.0;
Busy=false;
}
emSpeedingViewAnimator::~emSpeedingViewAnimator()
{
}
void emSpeedingViewAnimator::Activate()
{
if (!IsActive()) {
emKineticViewAnimator::Activate();
UpdateBusyState();
}
}
void emSpeedingViewAnimator::Deactivate()
{
emKineticViewAnimator::Deactivate();
}
double emSpeedingViewAnimator::GetAbsTargetVelocity() const
{
return sqrt(
TargetVelocity[0]*TargetVelocity[0] +
TargetVelocity[1]*TargetVelocity[1] +
TargetVelocity[2]*TargetVelocity[2]
);
}
void emSpeedingViewAnimator::SetTargetVelocity(int dimension, double targetVelocity)
{
TargetVelocity[dimension]=targetVelocity;
UpdateBusyState();
}
void emSpeedingViewAnimator::SetAcceleration(double acceleration)
{
Acceleration=acceleration;
}
void emSpeedingViewAnimator::SetReverseAcceleration(double reverseAcceleration)
{
ReverseAcceleration=reverseAcceleration;
}
bool emSpeedingViewAnimator::CycleAnimation(double dt)
{
double v1,v2,vt,adt;
bool frictionEnabled,baseBusy;
int i;
if (Busy) {
frictionEnabled=IsFrictionEnabled();
for (i=0; i<3; i++) {
v1=GetVelocity(i);
vt=TargetVelocity[i];
if (v1*vt<-0.1) adt=ReverseAcceleration*dt;
else if (fabs(v1)vt) {
v2=v1-adt;
}
else if (v1+adt0.01) {
if (!Busy) {
Busy=true;
WakeUp();
}
}
else {
Busy=false;
}
}
//==============================================================================
//=========================== emSwipingViewAnimator ============================
//==============================================================================
emSwipingViewAnimator::emSwipingViewAnimator(emView & view)
: emKineticViewAnimator(view)
{
Gripped=false;
SpringExtension[0]=0.0;
SpringExtension[1]=0.0;
SpringExtension[2]=0.0;
InstantaneousVelocity[0]=GetVelocity(0);
InstantaneousVelocity[1]=GetVelocity(1);
InstantaneousVelocity[2]=GetVelocity(2);
SpringConstant=1.0;
Busy=false;
}
emSwipingViewAnimator::~emSwipingViewAnimator()
{
}
void emSwipingViewAnimator::Activate()
{
if (!IsActive()) {
emKineticViewAnimator::Activate();
SpringExtension[0]=0.0;
SpringExtension[1]=0.0;
SpringExtension[2]=0.0;
InstantaneousVelocity[0]=GetVelocity(0);
InstantaneousVelocity[1]=GetVelocity(1);
InstantaneousVelocity[2]=GetVelocity(2);
UpdateBusyState();
}
}
void emSwipingViewAnimator::Deactivate()
{
if (IsActive()) {
SpringExtension[0]=0.0;
SpringExtension[1]=0.0;
SpringExtension[2]=0.0;
emKineticViewAnimator::Deactivate();
}
}
void emSwipingViewAnimator::SetGripped(bool gripped)
{
if (Gripped!=gripped) {
Gripped=gripped;
if (!Gripped) {
SpringExtension[0]=0.0;
SpringExtension[1]=0.0;
SpringExtension[2]=0.0;
InstantaneousVelocity[0]=GetVelocity(0);
InstantaneousVelocity[1]=GetVelocity(1);
InstantaneousVelocity[2]=GetVelocity(2);
}
}
}
void emSwipingViewAnimator::MoveGrip(int dimension, double distance)
{
if (Gripped) {
SpringExtension[dimension]+=distance;
UpdateBusyState();
}
}
void emSwipingViewAnimator::SetSpringConstant(double springConstant)
{
SpringConstant=springConstant;
}
double emSwipingViewAnimator::GetAbsSpringExtension() const
{
return sqrt(
SpringExtension[0]*SpringExtension[0] +
SpringExtension[1]*SpringExtension[1] +
SpringExtension[2]*SpringExtension[2]
);
}
bool emSwipingViewAnimator::CycleAnimation(double dt)
{
double v1,v2,e1,e2,w;
bool frictionEnabled,baseBusy;
int i;
if (Busy && Gripped) {
for (i=0; i<3; i++) {
e1=SpringExtension[i];
v1=InstantaneousVelocity[i];
if (SpringConstant<1E5 && fabs(SpringExtension[i]/dt)>20.0) {
// Critically damped spring.
w=sqrt(SpringConstant);
e2=(e1+(e1*w-v1)*dt)*exp(-w*dt);
v2=(v1+(e1*w-v1)*dt*w)*exp(-w*dt);
}
else {
v2=0.0;
e2=0.0;
}
SpringExtension[i]=e2;
InstantaneousVelocity[i]=v2;
SetVelocity(i,(e1-e2)/dt);
}
frictionEnabled=IsFrictionEnabled();
SetFrictionEnabled(false);
baseBusy=emKineticViewAnimator::CycleAnimation(dt);
SetFrictionEnabled(frictionEnabled);
}
else {
baseBusy=emKineticViewAnimator::CycleAnimation(dt);
}
UpdateBusyState();
return Busy || baseBusy;
}
void emSwipingViewAnimator::UpdateBusyState()
{
if (
IsActive() && Gripped &&
(GetAbsSpringExtension()>0.01 || GetAbsVelocity()>0.01)
) {
if (!Busy) {
Busy=true;
WakeUp();
}
}
else {
SpringExtension[0]=0.0;
SpringExtension[1]=0.0;
SpringExtension[2]=0.0;
Busy=false;
}
}
//==============================================================================
//=========================== emMagneticViewAnimator ===========================
//==============================================================================
emMagneticViewAnimator::emMagneticViewAnimator(emView & view)
: emKineticViewAnimator(view)
{
CoreConfig=emCoreConfig::Acquire(view.GetRootContext());
MagnetismActive=false;
SetDeactivateWhenIdle();
}
emMagneticViewAnimator::~emMagneticViewAnimator()
{
}
void emMagneticViewAnimator::Activate()
{
emKineticViewAnimator * oldKVA;
emViewAnimator * va;
if (!IsActive()) {
MagnetismActive=false;
oldKVA=NULL;
for (va=GetView().GetActiveAnimator(); va; va=va->GetActiveSlave()) {
oldKVA=dynamic_cast(va);
if (oldKVA) break;
}
if (oldKVA) {
SetFriction(oldKVA->GetFriction());
SetFrictionEnabled(oldKVA->IsFrictionEnabled());
}
else {
SetFriction(1E10);
SetFrictionEnabled(true);
}
emKineticViewAnimator::Activate();
}
}
void emMagneticViewAnimator::Deactivate()
{
emKineticViewAnimator::Deactivate();
}
bool emMagneticViewAnimator::CycleAnimation(double dt)
{
double radiusFactor,minRadiusFactor,speedFactor,maxSpeedFactor;
double x,y,w,h,v,d,t,fdt,k,a,absDist,maxDist;
double dist[3];
bool busy,frictionEnabled;
radiusFactor=CoreConfig->MagnetismRadius;
minRadiusFactor=CoreConfig->MagnetismRadius.GetMinValue();
speedFactor=CoreConfig->MagnetismSpeed;
maxSpeedFactor=CoreConfig->MagnetismSpeed.GetMaxValue();
GetViewRect(&x,&y,&w,&h);
maxDist=(w+h)*0.09*radiusFactor;
if (radiusFactor<=minRadiusFactor*1.0001) {
maxDist=0.0;
}
absDist=CalculateDistance(&dist[0],&dist[1],&dist[2]);
busy=false;
if (absDist<=maxDist && absDist>1E-3) {
if (!MagnetismActive && GetAbsVelocity()<10.0) {
CenterZoomFixPoint();
MagnetismActive=true;
}
busy=true;
}
else {
if (MagnetismActive) {
SetVelocity(0,0.0);
SetVelocity(1,0.0);
SetVelocity(2,0.0);
MagnetismActive=false;
}
if (GetAbsVelocity()>=0.01) {
busy=true;
}
}
if (MagnetismActive) {
if (speedFactor>=maxSpeedFactor*0.9999 || absDist<1.0) {
v=absDist/dt;
}
else {
v=(
GetVelocity(0)*dist[0] +
GetVelocity(1)*dist[1] +
GetVelocity(2)*dist[2]
)/absDist;
if (v<0.0) v=0.0;
d=0.0;
t=0.0;
for (;;) {
fdt = emMin(dt-t,0.01);
if (fdt<1E-10) break;
// Slope of hill.
k=(absDist-d)/maxDist*4.0;
if (fabs(k)>1.0) k=1.0/k;
// Acceleration through rolling downhill.
a=k*maxDist*25.0*speedFactor*speedFactor;
// Damping
a-=fabs(v)*15.0*speedFactor;
v+=a*fdt;
d+=v*fdt;
if (d>=absDist) {
d=absDist;
break;
}
t+=fdt;
}
v=d/dt;
}
SetVelocity(0,v*dist[0]/absDist);
SetVelocity(1,v*dist[1]/absDist);
SetVelocity(2,v*dist[2]/absDist);
}
frictionEnabled=IsFrictionEnabled();
SetFrictionEnabled(frictionEnabled && !MagnetismActive);
if (emKineticViewAnimator::CycleAnimation(dt)) busy=true;
SetFrictionEnabled(frictionEnabled);
return busy;
}
double emMagneticViewAnimator::CalculateDistance(
double * pDX, double * pDY, double * pDZ
) const
{
double dd,vx,vy,vw,vh,zflpp,x,y,w,h,tx,ty,tz,td;
emPanel * svp, * p;
*pDX=1E+10;
*pDY=1E+10;
*pDZ=1E+10;
dd=3E+100;
if ((GetView().GetViewFlags()&emView::VF_POPUP_ZOOM)!=0) {
// ??? emMagneticViewAnimator is still not functioning
// ??? properly with pop-up-zoom.
return sqrt(dd);
}
svp=GetView().GetSupremeViewedPanel();
if (svp) {
GetViewRect(&vx,&vy,&vw,&vh);
zflpp=GetView().GetZoomFactorLogarithmPerPixel();
for (p=svp;;) {
if (p->IsViewed() && p->IsFocusable()) {
p->GetEssenceRect(&x,&y,&w,&h);
x=p->PanelToViewX(x);
y=p->PanelToViewY(y);
w=p->PanelToViewDeltaX(w);
h=p->PanelToViewDeltaY(h);
if (w>1E-3 && h>1E-3) {
// Maximize panel in view (centered).
tx=(x+w*0.5)-(vx+vw*0.5);
ty=(y+h*0.5)-(vy+vh*0.5);
if (w*vh>=h*vw) {
tz=log(vw/w)/zflpp;
}
else {
tz=log(vh/h)/zflpp;
}
td=tx*tx+ty*ty+tz*tz;
if (td=h*vw*minViewInPanelFac) {
tx=x-vx;
if (tx<0.0) {
tx=(x+w)-(vx+vw);
if (tx>0.0) tx=0.0;
}
ty=(y+h*0.5)-(vy+vh*0.5);
tz=log(vh/h)/zflpp;
td=tx*tx+ty*ty+tz*tz;
if (td=w*vh*minViewInPanelFac) {
tx=(x+w*0.5)-(vx+vw*0.5);
ty=y-vy;
if (ty<0.0) {
ty=(y+h)-(vy+vh);
if (ty>0.0) ty=0.0;
}
tz=log(vw/w)/zflpp;
td=tx*tx+ty*ty+tz*tz;
if (tdGetFirstChild()) p=p->GetFirstChild();
else if (p==svp) break;
else if (p->GetNext()) p=p->GetNext();
else {
do {
p=p->GetParent();
} while (p!=svp && !p->GetNext());
if (p==svp) break;
p=p->GetNext();
}
}
}
return sqrt(dd);
}
void emMagneticViewAnimator::GetViewRect(
double * pX, double * pY, double * pW, double * pH
) const
{
if ((GetView().GetViewFlags()&emView::VF_POPUP_ZOOM)!=0) {
GetView().GetMaxPopupViewRect(pX,pY,pW,pH);
}
else {
*pX=GetView().GetHomeX();
*pY=GetView().GetHomeY();
*pW=GetView().GetHomeWidth();
*pH=GetView().GetHomeHeight();
}
}
//==============================================================================
//=========================== emVisitingViewAnimator ===========================
//==============================================================================
emVisitingViewAnimator::emVisitingViewAnimator(emView & view)
: emViewAnimator(view)
{
Animated=false;
Acceleration=5.0;
MaxCuspSpeed=2.0;
MaxAbsoluteSpeed=5.0;
State=ST_NO_GOAL;
VisitType=VT_VISIT;
RelX=RelY=RelA=0;
Adherent=false;
UtilizeView=false;
MaxDepthSeen=-1;
Speed=0.0;
TimeSlicesWithoutHope=0;
GiveUpClock=0;
SetDeactivateWhenIdle();
}
emVisitingViewAnimator::~emVisitingViewAnimator()
{
}
void emVisitingViewAnimator::SetAnimated(bool animated)
{
Animated=animated;
}
void emVisitingViewAnimator::SetAcceleration(double acceleration)
{
Acceleration=acceleration;
}
void emVisitingViewAnimator::SetMaxCuspSpeed(double maxCuspSpeed)
{
MaxCuspSpeed=maxCuspSpeed;
}
void emVisitingViewAnimator::SetMaxAbsoluteSpeed(double maxAbsoluteSpeed)
{
MaxAbsoluteSpeed=maxAbsoluteSpeed;
}
void emVisitingViewAnimator::SetAnimParamsByCoreConfig(const emCoreConfig & coreConfig)
{
double f,fMax;
f=coreConfig.VisitSpeed;
fMax=coreConfig.VisitSpeed.GetMaxValue();
Animated=(fGetIdentity();
else str="";
l1=strlen(str);
l2=strlen(Identity);
if (l1>l2) l1=l2;
tw=painter.GetTextSize(Identity,h,false);
ws=1.0;
if (tw>w) { ws=w/tw; tw=w; }
ch=h;
if (ws<0.5) { ch*=ws/0.5; ws=0.5; }
painter.PaintRect(
x+(w-tw)*0.5,y,tw*l1/l2,h,
emColor(136,255,136,80)
);
painter.PaintRect(
x+(w-tw)*0.5+tw*l1/l2,y,tw*(l2-l1)/l2,h,
emColor(136,136,136,80)
);
painter.PaintText(
x+(w-tw)*0.5,y+(h-ch)*0.5,
Identity,ch,ws,emColor(136,255,136),0,l1
);
painter.PaintText(
x+(w-tw)*0.5+tw*l1/l2,y+(h-ch)*0.5,
Identity.Get()+l1,ch,ws,emColor(136,136,136),0,l2-l1
);
}
bool emVisitingViewAnimator::CycleAnimation(double dt)
{
double relX,relY,relA,distFinal,dirX,dirY,distXY,distZ;
double curveDist,curvePos,deltaX,deltaY,deltaZ,deltaXY,delta;
double zflpp,vx,vy,vw,vh,doneX,doneY,doneZ,done;
int depth,panelsAfter;
bool adherent;
emPanel * nep, * panel;
switch (State) {
case ST_NO_GOAL:
case ST_GIVEN_UP:
case ST_GOAL_REACHED:
return false;
case ST_GIVING_UP:
if (emGetClockMS()IsFocusable()) {
nep->Activate(adherent);
}
else {
panel=nep;
while (panel->GetParent() && !panel->IsFocusable()) {
panel=panel->GetParent();
}
if (!panel->IsInActivePath()) {
panel->Activate(adherent);
}
}
if (Animated) {
if (MaxDepthSeen0) {
State=ST_SEEK;
}
else {
State=ST_GOAL_REACHED;
return false;
}
}
else if (done < delta*0.2) {
if (State==ST_CURVE) {
State=ST_DIRECT;
}
else {
State=ST_SEEK;
}
}
}
if (State==ST_SEEK) {
if (depth+1>=Names.GetCount()) {
GetView().RawVisit(nep,relX,relY,relA);
State=ST_GOAL_REACHED;
return false;
}
else if (GetView().SeekPosPanel!=nep) {
GetView().SetSeekPos(nep,Names[depth+1]);
GetView().RawVisitFullsized(nep);
InvalidatePainting();
TimeSlicesWithoutHope=4;
}
else if (GetView().IsHopeForSeeking()) {
TimeSlicesWithoutHope=0;
}
else {
TimeSlicesWithoutHope++;
if (TimeSlicesWithoutHope>10) {
State=ST_GIVING_UP;
GiveUpClock=emGetClockMS();
InvalidatePainting();
}
}
}
return true;
}
void emVisitingViewAnimator::SetGoal(
VisitTypeEnum visitType, const char * identity, double relX, double relY,
double relA, bool adherent, bool utilizeView, const char * subject
)
{
VisitType=visitType;
RelX=relX;
RelY=relY;
RelA=relA;
Adherent=adherent;
UtilizeView=utilizeView;
Subject=subject;
if (State==ST_NO_GOAL || Identity != identity) {
State=ST_CURVE;
Identity=identity;
Names=emPanel::DecodeIdentity(Identity);
if (IsActive()) {
GetView().SetSeekPos(NULL,NULL);
MaxDepthSeen=-1;
TimeSlicesWithoutHope=0;
GiveUpClock=0;
InvalidatePainting();
}
}
}
void emVisitingViewAnimator::UpdateSpeed(
double pos, double dist, int panelsAfter, double distFinal, double dt
)
{
double s,v;
Speed+=Acceleration*dt;
s=dist+panelsAfter*log(2.0)+distFinal;
if (s<0.0) s=0.0;
v=sqrt(Acceleration*s*2.0);
if (Speed>v) Speed=v;
if (pos<0.0) {
v=sqrt(Acceleration*(-pos)*2.0+MaxCuspSpeed*MaxCuspSpeed);
if (Speed>v) Speed=v;
}
if (Speed>MaxAbsoluteSpeed) Speed=MaxAbsoluteSpeed;
if (Speed>dist/dt) Speed=dist/dt;
}
emPanel * emVisitingViewAnimator::GetNearestExistingPanel(
double * pRelX, double * pRelY, double * pRelA, bool * pAdherent,
int * pDepth, int * pPanelsAfter, double * pDistFinal
) const
{
emPanel * p, * c;
int i;
p=GetView().GetRootPanel();
if (!p || Names.GetCount()<1 || Names[0]!=p->GetName()) {
*pRelX=0.0;
*pRelY=0.0;
*pRelA=0.0;
*pAdherent=false;
*pDepth=0;
*pPanelsAfter=Names.GetCount();
*pDistFinal=0.0;
return NULL;
}
for (i=1; iGetChild(Names[i]);
if (!c) break;
p=c;
}
if (i=1.0) {
*pDistFinal=0.0;
}
else {
*pDistFinal=log(1.0/sqrt(RelA));
}
break;
default:
*pDistFinal=0.0;
break;
}
return p;
}
switch (VisitType) {
case VT_VISIT:
GetView().CalcVisitCoords(p,pRelX,pRelY,pRelA);
break;
case VT_VISIT_REL:
if (RelA<=0.0) {
GetView().CalcVisitFullsizedCoords(p,pRelX,pRelY,pRelA,RelA<-0.9);
}
else {
*pRelX=RelX;
*pRelY=RelY;
*pRelA=RelA;
}
break;
default:
GetView().CalcVisitFullsizedCoords(p,pRelX,pRelY,pRelA,UtilizeView);
break;
}
*pAdherent=Adherent;
*pDepth=Names.GetCount()-1;
*pPanelsAfter=0;
*pDistFinal=0.0;
return p;
}
emPanel * emVisitingViewAnimator::GetNearestViewedPanel(
emPanel * nearestExistingPanel
) const
{
emPanel * p;
p=nearestExistingPanel;
while (p && !p->IsInViewedPath()) {
p=p->GetParent();
}
while (
p &&
p->GetParent() &&
p->GetParent()->IsViewed() && (
!p->IsViewed() ||
p->GetViewedWidth()GetViewedHeight()GetParent();
}
if (p && !p->IsViewed()) {
p=GetView().GetSupremeViewedPanel();
}
return p;
}
void emVisitingViewAnimator::GetDistanceTo(
emPanel * panel, double relX, double relY, double relA,
double * pDirX, double * pDirY, double * pDistXY, double * pDistZ
) const
{
double hx,hy,hw,hh,hp,sx,sy,sw,sh;
double vx,vy,vw,vh,ax,ay,aw,ah,bx,by,bw,bh;
double extremeDist,dx,dy,dz,dxy,t,f;
emPanel * b, * a;
// Home coordinates of the view.
hx=GetView().GetHomeX();
hy=GetView().GetHomeY();
hw=GetView().GetHomeWidth();
hh=GetView().GetHomeHeight();
hp=GetView().GetHomePixelTallness();
// Maximum coordinates of the view.
GetViewRect(&sx,&sy,&sw,&sh);
// Calculate rectangle "b": Where shall the view be at the end,
// in the target panel, in panel coordinates.
vw=sqrt(hw*hh*hp/(relA*panel->GetHeight()));
vh=vw*panel->GetHeight()/hp;
vx=hx+hw*0.5-(relX+0.5)*vw;
vy=hy+hh*0.5-(relY+0.5)*vh;
bx=(sx-vx)/vw;
by=(sy-vy)/vw;
bw=sw/vw;
bh=sh/vw;
// Go up with "b" in the tree until InViewedPath, but
// at least until SVP.
for (b=panel;;) {
if (!b->GetParent()) break;
if (b->IsInViewedPath() && !b->GetParent()->IsViewed()) break;
bx=b->GetLayoutX()+bx*b->GetLayoutWidth();
by=b->GetLayoutY()+by*b->GetLayoutWidth();
bw=bw*b->GetLayoutWidth();
bh=bh*b->GetLayoutWidth();
b=b->GetParent();
}
// Get SVP and rectangle "a" therein: Where is the view now.
a=GetView().GetSupremeViewedPanel();
ax=(sx-a->GetViewedX())/a->GetViewedWidth();
ay=(sy-a->GetViewedY())/a->GetViewedWidth();
aw=sw/a->GetViewedWidth();
ah=sh/a->GetViewedWidth();
// Go up with "a" until reaching "b", so that both rectangles
// are in the same panel.
while (a!=b) {
ax=a->GetLayoutX()+ax*a->GetLayoutWidth();
ay=a->GetLayoutY()+ay*a->GetLayoutWidth();
aw=aw*a->GetLayoutWidth();
ah=ah*a->GetLayoutWidth();
a=a->GetParent();
}
// Calculate 3D distance.
extremeDist=50.0;
dx=bx-ax+(bw-aw)*0.5;
dy=by-ay+(bh-ah)*0.5;
t=aw+ah;
if (t<1E-100) {
dx=0.0;
dy=0.0;
dz=-extremeDist;
}
else {
f=(sw+sh)*GetView().GetZoomFactorLogarithmPerPixel();
dx=dx/t*f;
dy=dy/t*f;
t=(bw+bh)/t;
if (texp(extremeDist)) {
dz = -extremeDist;
}
else {
dz = -log(t);
}
}
// Calculate 2D distance.
dxy=sqrt(dx*dx+dy*dy);
if (dxy<1E-100) {
dxy=0.0;
*pDirX = 1.0;
*pDirY = 0.0;
}
else {
*pDirX = dx/dxy;
*pDirY = dy/dxy;
}
if (dxy>exp(extremeDist)) {
*pDistXY=0.0;
*pDistZ=-extremeDist;
}
else {
*pDistXY=dxy;
*pDistZ=dz;
}
}
void emVisitingViewAnimator::GetViewRect(
double * pX, double * pY, double * pW, double * pH
) const
{
if ((GetView().GetViewFlags()&emView::VF_POPUP_ZOOM)!=0) {
GetView().GetMaxPopupViewRect(pX,pY,pW,pH);
}
else {
*pX=GetView().GetHomeX();
*pY=GetView().GetHomeY();
*pW=GetView().GetHomeWidth();
*pH=GetView().GetHomeHeight();
}
}
double emVisitingViewAnimator::GetDirectDist(double x, double z)
{
double fixX;
if (fabs(z)<0.1) {
return sqrt(x*x+z*z);
}
else {
fixX = x/(1-exp(-z));
return fabs(z) * sqrt(fixX*fixX+1);
}
}
void emVisitingViewAnimator::GetDirectPoint(
double x, double z, double d,
double * pX, double * pZ
)
{
double fixX,dist,t;
if (fabs(z)<0.1) {
dist=sqrt(x*x+z*z);
if (dist<1E-100) {
t=0.0;
}
else {
t=d/dist;
}
*pX = x*t;
*pZ = z*t;
}
else {
fixX = x/(1-exp(-z));
dist = fabs(z) * sqrt(fixX*fixX+1);
t = d/dist;
*pX = fixX * (1-exp(-z*t));
*pZ = z*t;
}
}
void emVisitingViewAnimator::GetCurvePosDist(
double x, double z, double * pCurvePos, double * pCurveDist
)
{
double a,b,aMin,aMax,bMin,bMax;
CurvePoint ap,bp,tp;
bool neg,swap;
int i,j;
neg=false;
swap=false;
if (z<0) {
z=-z;
x/=exp(z);
neg=true;
swap=true;
}
if (x<0) {
x=-x;
neg=!neg;
}
aMin=-x;
aMax=CurveMaxIndex*CurveDeltaDist;
for (i=0;; i++) {
a=(aMin+aMax)*0.5;
ap=GetCurvePoint(a);
tp.X=ap.X+x/exp(ap.Z);
tp.Z=ap.Z+z;
if (aMax-aMin<1E-12 || i>=48) break;
if (tp.X<=0.0) {
aMin=a;
continue;
}
if (tp.X>=CurvePoints[CurveMaxIndex].X) {
aMax=a;
continue;
}
bMin=tp.Z;
bMax=tp.Z+tp.X;
for (j=0;; j++) {
b=(bMin+bMax)*0.5;
bp=GetCurvePoint(b);
if (bMax-bMin<1E-12 || j>=48) break;
if (tp.Z>bp.Z) {
if (tp.X<=bp.X) break;
bMin=b;
}
else {
if (tp.X>=bp.X) break;
bMax=b;
}
}
if (tp.Z>bp.Z) aMin=a; else aMax=a;
}
bMin=tp.Z;
bMax=tp.Z+tp.X;
if (bMin=48) break;
bp=GetCurvePoint(b);
if (tp.Z>bp.Z) bMin=b; else bMax=b;
}
if (neg) {
a=-a;
b=-b;
}
if (swap) {
*pCurvePos=b;
*pCurveDist=a-b;
}
else {
*pCurvePos=a;
*pCurveDist=b-a;
}
}
emVisitingViewAnimator::CurvePoint emVisitingViewAnimator::GetCurvePoint(double d)
{
double t,x1,z1,x2,z2,x3,z3,c1,c2,c3,dx1,dz1,dx2,dz2;
CurvePoint cp;
int i;
if (fabs(d)>=CurveMaxIndex*CurveDeltaDist) {
cp=CurvePoints[CurveMaxIndex];
if (d<0) cp.X = -cp.X;
cp.Z+=fabs(d)-CurveMaxIndex*CurveDeltaDist;
return cp;
}
t=fabs(d)/CurveDeltaDist;
i=(int)t;
if (i<0) i=0; // Can happen when d is a nan.
if (i>=CurveMaxIndex) i=CurveMaxIndex-1;
t-=i;
if (t<0.0) t=0.0;
if (t>1.0) t=1.0;
x1=CurvePoints[i].X;
z1=CurvePoints[i].Z;
x2=CurvePoints[i+1].X;
z2=CurvePoints[i+1].Z;
if (i<=0) {
dx1=CurveDeltaDist*0.5;
dz1=0.0;
}
else {
dx1=(x2-CurvePoints[i-1].X)*0.25;
dz1=(z2-CurvePoints[i-1].Z)*0.25;
}
if (i+2>CurveMaxIndex) {
dx2=0.0;
dz2=CurveDeltaDist*0.5;
}
else {
dx2=(CurvePoints[i+2].X-x1)*0.25;
dz2=(CurvePoints[i+2].Z-z1)*0.25;
}
x3=(x1+dx1+x2-dx2)*0.5;
z3=(z1+dz1+z2-dz2)*0.5;
c1 = (1.0-t)*(1.0-t);
c2 = t*t;
c3 = 2*t*(1.0-t);
cp.X = x1*c1 + x2*c2 + x3*c3;
cp.Z = z1*c1 + z2*c2 + z3*c3;
if (d<0) cp.X = -cp.X;
return cp;
}
const double emVisitingViewAnimator::CurveDeltaDist = 0.0703125;
const emVisitingViewAnimator::CurvePoint emVisitingViewAnimator::CurvePoints[]={
// This table was created with the following program. It's about the
// shortest way respectively the way of minimum cost between two points
// in an (x,z) coordinate system, where x is scrolling, and where z is
// zooming as the natural logarithm of zoom factor, and where moving an
// infinitesimal step in x has the same cost as moving the same step in
// z, at z=0. For other z, cost of x has to be multiplied with exp(z).
// I tried hard but did not find a direct function for the problem. So
// the program solves it with iterations. I could write book how I came
// to this (five different iterative algorithms...). If one ever works
// on this: Be aware of the extreme behavior of the curve when it comes
// to x near 1 - big risks of calculation errors - always check the
// results very carefully (even graphical display of derivative...).
//
// #include
// #include
//
// typedef boost::multiprecision::number<
// boost::multiprecision::mpfr_float_backend<50>
// > FLT;
//
// double FLT2DBL(const FLT & x) {
// return x.convert_to();
// }
//
// static FLT Cost(FLT x1, FLT z1, FLT x2, FLT z2)
// {
// FLT s = (z2-z1)/(x2-x1);
// if (fabs(s)<1E-10) {
// return sqrt(pow(z2-z1,2) + pow((x2-x1)*exp((z2+z1)*0.5),2));
// }
// // Calculate integral from x1 to x2 of:
// // sqrt((s*dx)^2 + (dx*exp(z1+s*(x-x1)))^2)
// // Same as:
// // sqrt(s^2 + exp(z1+s*(x-x1))^2) * dx
// // Solution:
// FLT w1 = sqrt(exp(2*z1) + s*s);
// FLT w2 = sqrt(exp(2*(s*(x2-x1)+z1)) + s*s);
// FLT c1 = log(w1-s)/2 - log(w1+s)/2 + w1/s;
// FLT c2 = log(w2-s)/2 - log(w2+s)/2 + w2/s;
// return c2-c1;
// }
//
// int main(int argc, char * argv[])
// {
// const int curveSize=128;
// FLT curveX[curveSize];
// FLT curveZ[curveSize];
//
// FLT dt=1.0/curveSize*9.0;
// FLT x1=0.0;
// FLT z1=0.0;
// FLT x3=0.0;
// FLT z3=0.0;
// curveX[0]=0.0;
// curveZ[0]=0.0;
// for (int i=1; i1E-3) {
// x3=cos(a)*dt*(1.0-exp(-z3))/z3;
// }
// else {
// x3=cos(a)*dt/exp(z3*0.5);
// }
// if (i==1) {
// x1=-x3;
// z1=z3;
// }
// FLT dx=x3-x1;
// FLT dz=z3-z1;
// FLT l=sqrt(dx*dx+dz*dz);
// FLT q=1E-12;
// FLT x2b=-dz/l*q;
// FLT z2b=dx/l*q;
// FLT cost1 = Cost(x1,z1,0.0,0.0) + Cost(0.0,0.0,x3,z3);
// FLT cost2 = Cost(x1,z1,x2b,z2b) + Cost(x2b,z2b,x3,z3);
// if (cost2