2011-09-09 11 views
12

Sto tentando di emulare un effetto di animazione nel codice (quasi qualsiasi linguaggio farebbe come sembra essere matematica piuttosto che linguaggio). Essenzialmente, è l'emulazione di un sistema di molle di massa. Ho guardato WPF/Silverlight ElasticEase e questo sembra essere abbastanza vicino a quello che sto cercando, ma non del tutto.Effetto smorzamento del sistema Spring-Mass (o è questo ElasticEase?)

Prima di tutto, ecco quello che sto cercando - un oggetto, un viaggio di un certo numero di secondi, colpendo una posizione e subito rallentare per ocsillate per un certo numero di secondi a riposare nello stesso punto in cui lo smorzamento è stato applicato. Quindi, per visualizzarlo, diciamo che ho una tela da 600w/900h e ho un quadrato che inizia ad animare da 900px a 150px in un TranslateTransform.Y. Occorrono 4 secondi per raggiungere un'altezza di 150px (187,5px al secondo), in quale fase viene immerso e si sposta solo di circa 35px in più per 0,4 secondi (87,5 px al secondo) a 115px in altezza, quindi rimbalza per 1 secondo a 163px di altezza (48px e 48px al secondo) e poi rimbalza fino a 146px (17px e 17px al secondo) e così via fino a quando le ocillazioni lo rallentano fino alla posizione finale di riposo di 150px. Il periodo di ocillazione è di 16 secondi.

L'esempio che ho descritto sopra è in alto a sinistra rettangolo blu qui: enter image description here

Ecco ciò che io voglio sapere in anticipo - la distanza dei pixel e il numero di secondi necessari per andare dal punto A al punto B, il numero di secondi per l'ocillazione. Cose come la massa non sembrano avere importanza.

Ho provato ElasticEase e il problema sembra essere che non riesco a far viaggiare l'oggetto senza attenuazione per 4 secondi e quindi "rimbalzare" per i successivi 16 secondi. Il .Springiness è anche sempre un po 'troppo, anche se ho impostato per essere un numero molto alto come 20.

ILSpy dello show la sua funzione:

protected override double EaseInCore(double normalizedTime) 
     { 
      double num = Math.Max(0.0, (double)this.Oscillations); 
      double num2 = Math.Max(0.0, this.Springiness); 
      double num3; 
      if (DoubleUtil.IsZero(num2)) 
      { 
       num3 = normalizedTime; 
      } 
      else 
      { 
       num3 = (Math.Exp(num2 * normalizedTime) - 1.0)/(Math.Exp(num2) - 1.0); 
      } 
      return num3 * Math.Sin((6.2831853071795862 * num + 1.5707963267948966) * normalizedTime); 
     } 

Ho incluso 2 video e ed Excel file in una cartella zippata su DropBox. Credo che questa domanda sarà più di un work-in-progress come la gente fa più domande di chiarimento.

(NOTA BENE: Non so di cosa sto parlando quando si tratta di gran parte di questa roba)

+0

Il tuo problema è (come sospetti) uno di matematica/fisica. Si tratta di un problema di oscillazione attenuato nel primo corso di fisica standard, ma ci vorrà qualche centinaio di parole (e alcune formule matematiche, che rendono piuttosto male in SO) da spiegare completamente. Il problema di base è che lo smorzamento è esponenziale, quindi quando lo discretizzi, le oscillazioni della coda sono perse. Sei sicuro di volere una spiegazione completa invece di http://en.wikipedia.org/wiki/Damping? :) –

+0

Grazie a @belisarius. La matematica/fisica è davvero al di là di me con questo, soprattutto considerando che l'esempio sopra non sembra prendere in considerazione la massa. Qualsiasi esempio di spiegazione e/o codice che potresti fornire che potrebbe aiutarmi a iniziare con questo con le variabili conosciute che ho prima di me sarebbe molto utile. –

+0

Ci proverò, ma come previsto, mi ci vorrà un sacco di tempo per farlo bene, forse un fisico/studente che insegue una taglia potrebbe anticiparmi :). A proposito, la massa è lì, ma tutte le altre costanti del problema (costante elastico e coeff di smorzamento) sono divise dalla massa per ottenere un problema indipendente dalla massa (dato che Fm * a == 0 è uguale a zero, puoi dividere tutti i termini in base alla costante che desideri). La F è la composizione di due forze, quella elastica, proporzionale alla costante elastica k e la posizione, e la forza di smorzamento, proporzionale alla velocità e una costante di smorzamento. –

risposta

8

Salta la fisica e basta andare dritto per l'equazione.

parametri: “Ecco ciò che io voglio sapere in anticipo - la distanza del pixel [D] e il numero di secondi [T0] che serve per andare dal punto A al punto B, il numero di secondi per l'oscillazione [T1 ]. "Inoltre, aggiungo come parametri liberi: la dimensione massima dell'oscillazione, Amax, la costante di tempo di smorzamento, Tc e una frequenza di fotogrammi, Rf, cioè, a che ora si desidera un nuovo valore di posizione. Presumo che non si vuole calcolare questo per sempre, quindi mi limiterò a fare 10 secondi, ttotal, ma ci sono una varietà di condizioni di arresto ragionevoli ...

codice: Ecco il codice (in Python). La cosa più importante è l'equazione, trovato in def Y(t):

from numpy import pi, arange, sin, exp 

Ystart, D = 900., 900.-150. # all time units in seconds, distance in pixels, Rf in frames/second 
T0, T1, Tc, Amax, Rf, Ttotal = 5., 2., 2., 90., 30., 10. 

A0 = Amax*(D/T0)*(4./(900-150)) # basically a momentum... scales the size of the oscillation with the speed 

def Y(t): 
    if t<T0: # linear part 
     y = Ystart-(D/T0)*t 
    else: # decaying oscillations 
     y = Ystart-D-A0*sin((2*pi/T1)*(t-T0))*exp(-abs(T0-t)/Tc) 
    return y 

y_result = [] 
for t in arange(0, Ttotal, 1./Rf): # or one could do "for i in range(int(Ttotal*Rf))" to stick with ints  
    y = Y(t) 
    y_result.append(y) 

L'idea è movimento lineare fino al punto, seguito da un'oscillazione decomposizione. L'oscillazione è fornita dal sin e il decadimento moltiplicandolo per il exp. Ovviamente, modifica i parametri per ottenere qualsiasi distanza, dimensione dell'oscillazione, ecc. Che desideri.

enter image description here

note:

  1. maggior parte delle persone nei commenti suggeriscono approcci fisica. Non li ho usati perché se si specifica un determinato movimento, è un po 'eccessivo fare per iniziare con la fisica, andare alle equazioni differenziali e quindi calcolare il movimento e modificare i parametri per ottenere l'ultima cosa . Potresti anche andare direttamente all'ultima cosa. A meno che non si abbia un'intuizione per la fisica da cui vogliono lavorare.
  2. Spesso in problemi come questo si vuole mantenere una velocità continua (prima derivata), ma si dice "rallenta immediatamente", quindi non l'ho fatto qui.
  3. Si noti che il periodo e l'ampiezza dell'oscillazione non saranno esattamente come specificati quando si applica lo smorzamento, ma probabilmente è più dettagliato di quanto si interessi.
  4. Se è necessario esprimere questo come una singola equazione, è possibile farlo utilizzando una "funzione Heaviside", per attivare e disattivare i contributi.

A rischio di rendere questo troppo a lungo, ho capito che potevo fare una gif in GIMP, quindi questo è quello che sembra:

enter image description here

posso postare il codice completo per rendere le trame se c'è interesse, ma in pratica sto solo chiamando Y con valori D e T0 diversi per ogni timestep. Se dovessi farlo di nuovo, potrei aumentare lo smorzamento (cioè diminuire Tc), ma è un po 'fastidioso quindi lo lascerò così com'è.

+0

D'accordo, in realtà non hai bisogno di massa. Avere velocità e accelerazione è sufficiente. Trovato fuori quando si esegue un layout grafico a molla. Il modo ingenuo sarebbe quello di fare una simulazione fisica completa con velocità, massa, forza, ecc. Quindi si scopre che si sta solo facendo un doppio lavoro con fastidiosi effetti collaterali. – gjvdkamp

+0

Che spiegazione geniale !! Grazie! Ho intenzione di lavorare con questo nel prossimo giorno o due per vedere se riesco a farlo funzionare. –

+0

@Otaku Sembra che la derivata su D non sia continua. Potresti percepire un brusco cambiamento di velocità quando il corpo raggiunge per la prima volta la posizione di riposo –

5

Stavo pensando alle stesse linee di @ tom10. (Ho anche considerato un IEasingFunction che ha preso uno IList<IEasingFunction>, ma sarebbe stato complicato hackerare il comportamento desiderato su quelli esistenti).

// Based on the example at 
// http://msdn.microsoft.com/en-us/library/system.windows.media.animation.easingfunctionbase.aspx 
namespace Org.CheddarMonk 
{ 
    public class OtakuEasingFunction : EasingFunctionBase 
    { 
     // The time proportion at which the cutoff from linear movement to 
     // bounce occurs. E.g. for a 4 second movement followed by a 16 
     // second bounce this would be 4/(4 + 16) = 0.2. 
     private double _CutoffPoint; 
     public double CutoffPoint { 
      get { return _CutoffPoint; } 
      set { 
       if (value <= 0 || value => 1 || double.IsNaN(value)) { 
        throw new ArgumentException(); 
       } 
       _CutoffPoint = value; 
      } 
     } 

     // The size of the initial bounce envelope, as a proportion of the 
     // animation distance. E.g. if the animation moves from 900 to 150 
     // and you want the maximum bounce to be no more than 35 you would 
     // set this to 35/(900 - 150) ~= 0.0467. 
     private double _EnvelopeHeight; 
     public double EnvelopeHeight { 
      get { return _EnvelopeHeight; } 
      set { 
       if (value <= 0 || double.IsNaN(value)) { 
        throw new ArgumentException(); 
       } 
       _EnvelopeHeight = value; 
      } 
     } 

     // A parameter controlling how fast the bounce height should decay. 
     // The higher the decay, the sooner the bounce becomes negligible. 
     private double _EnvelopeDecay; 
     public double EnvelopeDecay { 
      get { return _EnvelopeDecay; } 
      set { 
       if (value <= 0 || double.IsNaN(value)) { 
        throw new ArgumentException(); 
       } 
       _EnvelopeDecay = value; 
      } 
     } 

     // The number of half-bounces. 
     private int _Oscillations; 
     public int Oscillations { 
      get { return _Oscillations; } 
      set { 
       if (value <= 0) { 
        throw new ArgumentException(); 
       } 
       _Oscillations = value; 
      } 
     } 

     public OtakuEasingFunction() { 
      // Sensible default values. 
      CutoffPoint = 0.7; 
      EnvelopeHeight = 0.3; 
      EnvelopeDecay = 1; 
      Oscillations = 3; 
     } 

     protected override double EaseInCore(double normalizedTime) { 
      // If we get an out-of-bounds value, be nice. 
      if (normalizedTime < 0) return 0; 
      if (normalizedTime > 1) return 1; 

      if (normalizedTime < _CutoffPoint) { 
       return normalizedTime/_CutoffPoint; 
      } 

      // Renormalise the time. 
      double t = (normalizedTime - _CutoffPoint)/(1 - _CutoffPoint); 
      double envelope = EnvelopeHeight * Math.Exp(-t * EnvelopeDecay); 
      double bounce = Math.Sin(t * Oscillations * Math.PI); 
      return envelope * bounce; 
     } 

     protected override Freezable CreateInstanceCore() { 
      return new OtakuEasingFunction(); 
     } 
    } 
} 

Questo è un codice non verificato, ma non dovrebbe essere troppo male per eseguire il debug in caso di problemi. Non sono sicuro di quali attributi (se presenti) debbano essere aggiunti alle proprietà per l'editor XAML per gestirli correttamente.

+0

Wow Peter! Questo è fantastico! Ci metterò alla prova il prossimo giorno o due e ti faccio sapere. –

+0

Peter, quello che hai fornito è fantastico, altrettanto buono come quello di Tom. Aprirò un'altra taglia su questo e ti darò i punti in 2 giorni. –