//------------------------------------------------------------------------------ // 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