Autor Zpráva
Witiko
Profil
Matematický problém. Mám javascriptovou funkci, jejíž kostru v mírných obměnách používám na veškeré animace a plynulé změny hodnot za určitý čas, funkce vypadá nějak takto:

var calls = [], /* Pole obsahující veškeré timeouty */
    ticks = Math.round(time / (1000 / settings.FPS)),  /* Z času a FPS se vypočítá počet poloh animace */
    delta = finalAngle - currentAngle, /* Rozdíl mezi počáteční a konečnou hodnotou */
    diff = delta / ticks, /* Jeden krok */
    stop = function() { /* Funkce na okamžité přerušení výkonu animace */
      for(var count = 0; count < calls.length; count++) {
        clearTimeout(calls[count]);
      }
    };
for(var index = 0; index <= ticks; index++) {
  (function(index){
    calls.push(window.setTimeout(function(){
      currentAngle = index==ticks?finalAngle:currentAngle + func(index / ticks) * diff; /* Úprava stávající hodnoty */
      setPointerAngle();
      if(typeof callback == "function" && index == ticks) {
        callback();
      }
    },index * (1000 / settings.FPS)));
  })(index);
}

Důležitá je zde funkce func, která určuje způsob, kterým se animace provádí. Pro většinu animací plně postačuje konstantní funkce navracející za všech okolností 1, což způsobí, že je animace zcela lineární a po celou dobu jejího trvání dochází k inkrementaci stejnou rychlostí.

Nicméně jsem došel k scriptu, v němž potřebuji, aby navyšování sledovalo určitý trend. Řekněme, že mám tachometr jehož ručičku touto funkcí animuji každou jednu sekundu od 0 do 320 stupňů nějak takto:
animate("pointer", {time: 1000, finalAngle: 10, callback: function() {
  animate("pointer", {time: 1000, finalAngle: 20, callback: function() {
    animate("pointer", {time: 1000, finalAngle: 40, callback: function() {
      animate("pointer", {time: 1000, finalAngle: 80, callback: function() {
        animate("pointer", {time: 1000, finalAngle: 160, callback: function() {
          animate("pointer", {time: 1000, finalAngle: 320});
        }});
      }});
    }});
  }});
}});

Je možné si všimnout, že je zde stoupavý trend v rychlosti navyšování 2x mezi jednotlivými voláními. Použití původního řešení vyústí v něco takovéhoto:


A vypadá to nějak takto:
http://doodoo.wz.cz/canvas/1.html

Jak vidíte, pohyb je nepřirozený a změna v rychlosti skoková. Proto jsem původní func, která vypadala takto:
function(x) {
  return 1;
}

Přepsal na toto:
function(x) {
  return 1 / options.trend + x * ((2 - (1 / options.trend)) - (1 / options.trend));
}

Tam, kde první funkce navracela neustále 1, zde je navraceno (v případě trendu 2x) 0.5 - 1.5 na základě toho, jsme-li na začátku / konci animace. Funkce func již tedy není konstantní, ale lineární. Toto již vypadá uspokojivěji:


A vypadá to nějak takto:
http://doodoo.wz.cz/canvas/2.html

Nicméně stejně má křivka jednotlivých kroků tvar U. Problém je v tom, že každá další část animace začíná na rychlosti 0.5, což narušuje trend. Aby jej nenarušovala, bylo by třeba začínat vždy na jedna, což ale není s momentální implementací možné, protože jelikož je výsledkem func násobena proměnná diff, je nutné, aby součet návratových hodnot volání funkce func byl roven počtu jejich volání. Tzn.: 32 x func(index / ticks) == 32. Pokud nechápete proč, odpovědí je první script, který jsem zasílal, řádek 12. V opačném případě by došlo k "přejetí" kýžené finální hodnoty. To znamená, že změny ve func budou v tomto případě nejspíš nedostačující a bude asi nutný přepis kostry funkce.

Každá rada jak docílit plynulého navyšování nárůstu drahá.
Witiko
Profil
Mám nedostatek odpovědí chápat tak, že taková otázka patří spíš na matematické fórum, nebo že je otázka komplikovaná a měl bych jí přeformulovat?
TomasJ
Profil
Witiko:
Otázka je příliš komplikovaná :D (aspoň teda pro mě)
Witiko
Profil
TomasJ:
Potřebuji napsat funkci na animaci, u níž se rychlost animace bude na konci lišit x-krát, přičemž nárůst rychlosti animování je plynulý.
TomasJ
Profil
zkus pak snížit při dalším volání čas u toho animate()
xmark
Profil
Witiko:
Nepochopil jsem to dělení do intervalů.
Proč nepoužiješ obecnou funkci y=a*x^2 + b*x + c ?
Witiko
Profil
xmark:
Jednoduše budu každou sekundu pomocí navigator.geolocation zjišťovat informace o rychlosti, kterou se daný šťastlivec se zařízením Android disponujícím GPS pohybuje. A chci, aby se rafička tachometru pohybovala alespoň trochu přirozeně, tzn. aby zareagovala na zrychlování / zpomalování a zrychlila se během animace tak, aby plynule navázala rychlostí na další pohyb, pokud by ten pokračoval ve stejném zrychlování.

To je obecná kvadratická funkce, nějak mě nenapadá, jak ji do aktuálního scriptu zaimplementovat.

TomasJ:
O to právě nejde, to bychom zůstali u lineární funkce, já potřebuji, aby se ta funkce dokázala přizpůsobit zrychlení animace.
Chamurappi
Profil
Reaguji na Witika:
chci, aby se rafička tachometru pohybovala alespoň trochu přirozeně, tzn. aby zareagovala na zrychlování / zpomalování
Znáš zobrazenou hodnotu a znáš žádanou hodnotu. Když při každém snímku posuneš tu zobrazenou k žádané o půlku (nebo čtvrtinu, pětinu, desetinu…) jejich rozdílu, nebude to dostatečně přirozené?
Witiko
Profil
Chamurappi:
Nevím, jestli jsem tě zcela pochopil. Znám zobrazenou hodnotu (to jsi nejspíš myslel počáteční?) a znám kýženou hodnotu. Když při každém snímku posunu tu zobrazenou ke kýžené o danou část jejich rozdílu, která se spočítá na základě času a vytouženého počtu snímků za sekundu, dostanu animaci o zcela konstantní rychlosti posunu mezi původní hodnotou k hodnotě nové bez jakéhokoliv zrychlení rychlosti animace.
Chamurappi
Profil
Reaguji na Witika:
Znám zobrazenou hodnotu (to jsi nejspíš myslel počáteční?)
Myslel jsem zobrazenou.
Máš třeba znázorněnou hodnotu 6 a máš vyjet na 14. Při prvním snímku vezmeš rozdíl (tedy 14 − 6 = 8), vydělíš dvěma (8 / 2 = 4) a přičteš ke znázorněné — a máš znázorněnou hodnotu 10. Při dalším snímku vezmeš rozdíl (tedy 14 − 10 = 4), vydělíš dvěma (4 / 2 = 2) a přičteš ke znázorněné — a máš znázorněnou hodnotu 12. Při dalším snímku vezmeš rozdíl (tedy 12 − 10 = 2), vydělíš dvěma (2 / 2 = 1) a přičteš ke znázorněné — a máš znázorněnou hodnotu 13. Takže řada bude 6, 10, 12, 13, 13.5, 13.75, 13.875 atd.

Čím větším číslem budeš dělit, tím pomalejší bude ten dojezd (s tou dvojkou to při krátkém timeoutu bude hodně rychlé). Pokud nestanovíš minimální velikost posunu, nedojede to přesně na žádanou hodnotu nikdy. Můžeš stanovit i maximální velikost posunu, aby počáteční skok nebyl moc velký. Výhodou tohoto postupného přibližování je, že nevyžaduje žádné plánování budoucích kroků — rozhoduješ se vždy jen podle staré hodnoty a podle té žádané, takže ani nemusíš nijak reagovat na to, že se ta žádaná změní.

Možná jsem tě ale nepochopil a chceš něco složitějšího, než si myslím :-)
Witiko
Profil
Chamurappi:
U tvého příkladu se rychlost animování nezmenší ke konci xkrát, ale prakticky nekonečněkrát (od původního kroku 8 do desetinných míst), pokud se nenastaví minimální hodnota. Já potřebuji, aby na začátku byly rozdíly mezi snímky a a na konci rozdíly mezi snímky x * a, přičemž x je trend - tzn. na začátku animace 10 -> 100 s x = 2 by byla změna o a, řekněme 10 -> 12 a na konci x * a, řekněme 96 -> 100.

Druhá potíž s tebou nabídnutým řešením je je, že opravdu není možné žádné "plánování". Já funkci prakticky předávám jak intervaly mezi jednotlivými snímky, tak i čas, za který chci, aby provedla - což by tvé funkci šlo poručit nejspíš těžko. :(
_es
Profil
Witiko:
Skús ten "problém" formulovať bez JS: len súradnice x a y a čas alebo jeden "krok".
A aby to vyhovovalo na každú funkciu, závislú od x.
Witiko
Profil
trend = ...;
početSnímkůZaSekundu = ...;
trváníAnimace = ...;
startovníHodnota = ...;
konečnáHodnota = ...;
rozdílHodnot = konečnáHodnota - startovníHodnota;
početKroků = trváníAnimace / (1000 / početSnímkůZaSekundu);
základníKrok = rozdílHodnot / početKroků;

// animace, aktuálníKrok = <1; početKroků>

poziceVAnimaci = aktuálníKrok / početKroků;
krok = základníKrok * (1 + (trend - 1) * poziceVAnimaci);
startovníHodnota += krok;


Pokud bychom si velikost jednotlivých kroků vypsali do grafu, vyjde nám lineární funkce y, přičemž y(0) = základníKrok a y(početKroků) = trend * základníKrok. Implementaci znemožňuje jeden fakt - jelikož jsou kroky větší, než základníKrok, dojde k přešvihnutí konečnéHodnoty.

To, co jsem sem napsal je prakticky schématickým opisem mého scriptu z #1, přičemž dávám přednost úpravě scriptu původního před psaním scriptu jiného, jelikož problém leží nejspíš jen ve způsobu rozsekání rozdíluHodnot na početKroků (se zrychlováním musí být více kroků).

Samotná animace je funkce lineární, její rychlost také, když se tyto dvě funkce vzájemně vynásobí, vyjde funkce kvadratická, což bude asi jediný způsob, jak zjistit správný početKroků tak, aby i při zvětšování jednotlivých kroků skončila funkce na konečnéHodnotě. Jenže aktuálně naprosto netuším jak.
_es
Profil
Witiko:
trend = …;
čo presne je trend?
Nejako tam máš tých parametrov veľa.
Skôr som ten svoj dotaz myslel tak, že chceš vykresliť graf nejakej funkcie y = f(x) nejakým špeciálnym spôsobom a nikto zatiaľ nepochopil akým.
Witiko
Profil
_es:
Chci provést plynulou změnu libovolné hodnoty na jinou hodnotu za určitý čas, přičemž interval mezi jednotlivými stejnými přechody bude na začátku změny x a na konci x / trend.

Představ si animaci, kde se celá obrazovka plynule roztemní. U takové funkce je interval mezi jednotlivými stejnými přechody stále x.

Představ si animaci, kde se obrazovka nejprve roztemňuje pomalu, ale ke konci se roztemňuje 2krát (trend-krát) rychleji. To je funkce, kterou já potřebuji.
_es
Profil
Witiko:
Nejspíš je titulek mírně matoucí.
Asi je mätúcich viac vecí.

Chci provést plynulou změnu libovolné hodnoty na jinou hodnotu za určitý čas, přičemž interval mezi jednotlivými stejnými přechody bude na začátku změny x a na konci x / trend.
Čo je interval, čo je prechod, čo je trend v tvojom ponímaní?

Skús to nejako definovať pomocou dvoch priestorových súradníc x a y a jednej časovej (t) súradnice:

Zadanie:
Máš všeobecnú funkciu f: y = f(x)
Akým presne spôsobom ju chceš chceš vykresliť v čase?
Witiko
Profil
Ok, normálně používám na animace prakticky tuto lineární funkci:

a = počáteční hodnota
b = konečná hodnota
Krajní hodnoty argumentu x: Třeba <0; 1>

f: y = a + x * (b - a);

y(0) = a;
y(0,5) = (a + b) / 2;
y(1) = b;

Javascriptem si poté max. hodnotu argumentu x vydělím časem, po který budu řekněme graf funkce vykreslovat / (1000 / Počtem vykreslovaných snímků za sekundu), čímž získám jeden krok. V každý daný interval poté vykreslím hodnotu funkce v daný krok.

Řekněme: interval = 200ms, krok = 0.1

200ms - vykreslím javascriptem y(0.1)
400ms - vykreslím javascriptem y(0.2)
600ms - vykreslím javascriptem y(0.3)
800ms - vykreslím javascriptem y(0.4)
1000ms - vykreslím javascriptem y(0.5)
1200ms - vykreslím javascriptem y(0.6)
1400ms - vykreslím javascriptem y(0.7)
1600ms - vykreslím javascriptem y(0.8)
1800ms - vykreslím javascriptem y(0.9)
2000ms - vykreslím javascriptem y(1)

-------------------------------------------------

Toto mám zmáknuté, nicméně nyní potřebuji funkci, pro kterou bude platit následující:

y(0) = a;
y(1) = b;
ale y(0,5) != (a + b) / 2;

Funkce to bude kvadratická a podmínkou je, aby:
y'(0) * z = y'(1)  // Má jít o derivaci

Tedy, aby byl nárůst funkce na jejím konci z-krát větší, než na jejím začátku. Z budu definovat já a jedná se o daný "trend".
Chamurappi
Profil
Reaguji na Witika:
Hm, to je docela složité zadání, jak z matematické olympiády :-) (a nebo už moc hloupnu)
Proč potřebuješ, aby z byl násobek?

Krajní hodnoty argumentu x: Třeba <0; 1>
Ten lineární graf jde hravě prohnout, když x vstupující do funkce umocníš — ideální na plynulé dojezdy či rozjezdy animací. Ale to ti asi nepomůže.
Witiko
Profil
Proč potřebuješ, aby z byl násobek?

Doufal jsem, že je to očividné. Chci, aby animace, kterou na základě této funkce zobrazím, se na konci animovala z * rychleji - Tzn. aby se animace na konci animovala stejně rychle, jako by se animovala animace s dvakrát tak velkým rozsahem hodnot na svém začátku. Tzn. že když pomocí této funkce udělám animaci ručičky rotující z úhlu 10 stupňů na 20 stupňů, na což navážu animací ručičky rotující z úhlu 20 stupňů na 40 stupňů za stejný čas, bude animace působit jednolitě a stejně, jako samostatná animace ručičky rotující z úhlu 10 na 40 stupňů za dvojnásobný čas.

Neříkejte mi, že se vyjadřuji nepochopitelně, popřípadě mi prosím řekněte, které části je těžké porozumět. :D
xmark
Profil
Witiko:
Pokud to má být spojitá funkce bez zubů, proč se pořád snažíš to dělit do jakýchsi intervalů?
Já jsem to pořád ještě nepochopil, ale mám pocit, že je to čistá matematika. Pohraj si trochu s matlabem a myslím že dojdeš k té kvadratické funkci, co jsem psal. (matlab osobně neznám, jen mám jakousi představu, co to umí)
Witiko
Profil
xmark:
Ona to ale přeci je funkce bez zubů, ale když chci na jejím základě v javascriptu například něco zviditelňovat, rozpohybovat, nebo všeobecně animovat, tak to musím dělat na základě určitého počtu snímků za sekundu, to dá snad rozum, větší počet ani průměrné lidské oko nezachytí. Vycházím z funkce nezubaté, ale nemůžu ve výsledku zobrazit veškeré možné hodnoty funkce, jelikož jich je nekonečno.

Ano, potřebuji kvadratickou funkci. Ale ne všeobecnou, potřebuji jednu velmi konkrétní, pro kterou platí, jak už jsem řekl:
y(0) = a;
y(1) = b;
y'(0) * z = y'(1);

A ano, jelikož nevím, jak přesně v y=a*x^2 + b*x + c ovlivňuje a a b tvar křivky všeobecné kvadratické funkce (vím jen, že c posouvá výsledek funkce), asi si budu muset hrát s nějakým programem na vykreslování grafů.
Marek88
Profil
[#13] Witiko:
Myslel jsem si, že k tomu mám dobrý nápad, ale teprve při psaní mého příspěvku mi došlo v čem je asi problém. Nevím, jestli jsem tě dobře pochopil, ale narazil jsem na to, že nevím jak spočítat aktuání (následující) krok, když znám pozici, horní a dolní meze a trend. A to proto, že trendu udává o kolik se krok celkově zvětší (blbě se z toho počítá to aktuální zvýšení). Nebylo by o hodně jednoduší a přitom s podobným výsledkem použitelné něco takového...?
krok = ... ;
trend = ... ;  // Udává "o kolik" se pokaždé zvýší krok
horniMez = ... ;
aktualniPozice = dolniMez ... ;
rozdil = horniMez - dolniMez;

x = 0;
while(aktualniPozice < horniMez) { // Samozřejmě vím, že se to musí řešit timeoutem, tohle jsem napsal jen tak pro znázornění.
     aktualniPozice = aktualniPozice + krok;
     krok = krok * trend;
     //nebo třeba
     krok = krok + trend^x;       // Pokud trend bude dva, tak trend^x bude postupně 1, 2, 4, 8, 16, 32, ...
                                                // Ke kroku se tím pádem bude přičítat vždy o něco vyšší číslo a bude to zrychlovat.
                                                // Trend bych ale volil jen o málo vyšší než jedna, jinak to poletí hrozně rychle.
                                                // poznámka pro moderátory: tohle čtyři komentáře vidím při editování jako stejně zarovnané - asi by to chtělo neproporcionální písmo do editace nebo alespoň jeho rychlé vypnutí/zapnutí při editaci
     x++;
}
aktualniPozice=horniMez;    // Pokud to přelezlo přes, tak se to vrátí. Nebo se to dá řešit podmínkou ve while cyklu.


Ale už je dost pozdě, možná je to blbost... :)
_es
Profil
Witiko:
potřebuji kvadratickou funkci. Ale ne všeobecnou, potřebuji jednu velmi konkrétní, pro kterou platí, jak už jsem řekl:
y(0) = a;
y(1) = b;
y'(0) * z = y'(1);
a, b, z sú vstupné parametre tej kv. funkcie a y' derivácia?
a, b v tom zadaní nie sú členy kvadratickej rovnice?
Potom teda, aby to nebolo zmätené, ich premenujem:
y = a*x^2 + b*x + c
y' = 2*a*x + b
y(0) = u
y(1) = v;
y'(0) * z = y'(1);

Z toho vychádza:
a = (v-u) * (z-1) / (z+1)
b = 2 * (v-u) / (z+1)
c = u
Witiko
Profil
_es:
To vypadá zajímavě, nicméně y(1) != v.

var x = 1;

var z = 2;
var u = 10;
var v = 100;

var a = (v-u) * (z-1) / (z+1);
var b = 2 * (v - u) / (z+1);
var c = u;

Math.pow(a * x, 2) + (b * x) + c;


Dneska jsem ale nejspíš přišel na řešení jiným způsobem. Tato rovnice:

f:y(x) = počátek + krok + krok * trend * (x - 1)

má přesně takovou křivku, jakou potřebuju. Řekněme, že počátek bude 10 stupňů, krok také 10 stupňů a trend 2:

y(0) = 10
y(1) = 20
y(2) = 40
y(3) = 80
...


Stačí si z výsledků funkce potřebnou část "vyříznout" a použít jí na animaci. Další výhodou je, že při trendu 1 je funkce zcela lineární:

y(0) = 10
y(1) = 20
y(2) = 30
y(3) = 40
...


A pro nalezení argumentu x pro určitou hodnotu y:

x = (y - počátek - krok) / (krok * trend) + 1
_es
Profil
Witiko:
To vypadá zajímavě, nicméně y(1) != v.
… Math.pow(a * x, 2) + (b * x) + c;

Lebo máš tú rovnicu zle zapísanú, má to byť:
a * Math.pow(x, 2) + b * x + c
alebo
a * x * x + b * x + c // rýchlejší a presnejší variant ako prvá možnosť

Další výhodou je, že při trendu 1 je funkce zcela lineární
Veď to v tejto rovnici platí tiež, kvadratický člen a bude nulový.

Vaše odpověď

Mohlo by se hodit

Neumíte-li správně určit příčinu chyby, vkládejte odkazy na živé ukázky.
Užíváte-li nějakou cizí knihovnu, ukažte odpovídajícím, kde jste ji vzali.

Užitečné odkazy:

Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0