Page 1 of 1

Ennakkoon ampuminen

Posted: Tue Oct 02, 2012 4:55 pm
by Pate5
Hei,

minulla on eräässä projektissani ongelmana ennakon ottaminen ampuessa ja päätin tuoda ongelman foorumin matikkaguruille.

Minulla on kohteen 3-ulotteinen paikkavektori ja 3-ulotteinen nopeusvektori. Haluan saada selville, mihin suuntaan ammus täytyy ampua origosta, kun sen nopeus tiedetään.

A on kohteen paikkavektori
B on kohteen nopeusvektori
t on kohtaamiseen kuluva aika
C on ammuksen nopeusvektori, |C| tiedetään

t ja C ovat tuntemattomia.

Yritin ratkaista yhtälöä mutta en onnistunut kun en keksinyt, miten pystyn hyödyntämään |C|:tä. :(

Re: Ennakkoon ampuminen

Posted: Tue Oct 02, 2012 5:32 pm
by legend
Minun on ei pitäisi kysyä mitään, sillä en osaa kuitenkaaa vastata, mutta ihan uteliaisuuttani kysyn...
Tarkoitatko, että ampujalla A on kohde B. B:n sijainti, suunta ja nopeus tiedetään.
Pitäisi laskea kuinka paljon pitäisi ampua eteenpäin, jotta ammus osuisi B:tä.

Re: Ennakkoon ampuminen

Posted: Tue Oct 02, 2012 5:38 pm
by Jonez
legend, luulen että tässä tarkoitetaan että ampuja on origossa O, eli kohteen sijainti on A, eli O+A, ja kohteen tuleva sijainti seuraavassa updatessa on A+B.

En ole vektoreita pitkään aikaan laskenut, mutta... Jos törmäyskohta on piste D, niin eikö D = A + rB ja D = tC, tai jotain vastaavaa... :) Tuostahan saisi yhtälöryhmällä t:n ja r:n, eikö...?

Edit. ainii eiku C:tä ei tiedetty... No jos teet siitä yksikkövektorin tai jotain... En tiiä :D

Re: Ennakkoon ampuminen

Posted: Tue Oct 02, 2012 6:16 pm
by axu
Jonez wrote:legend, luulen että tässä tarkoitetaan että ampuja on origossa O, eli kohteen sijainti on A, eli O+A, ja kohteen tuleva sijainti seuraavassa updatessa on A+B.

En ole vektoreita pitkään aikaan laskenut, mutta... Jos törmäyskohta on piste D, niin eikö D = A + rB ja D = tC, tai jotain vastaavaa... :) Tuostahan saisi yhtälöryhmällä t:n ja r:n, eikö...?
Tuota... mistäs tuo r pöllähti tuonne? B:llähän on tietenkin sama kerroin kuin C:llä, eli aika ampumisesta osumiseen (kuten Pate5:n kuvassa olikin). Tästä seuraa, että A + tB = tC. Vielä en keksinyt, miten C:n pituuden tietäminen auttaa, mutta selvästi se on ratkaisevassa roolissa.

Re: Ennakkoon ampuminen

Posted: Tue Oct 02, 2012 11:55 pm
by koodaaja
Ongelma on varsin mielenkiintoinen, mutta lähestymistapa ei ehkä se kaikista helpoin. Kuten kaavastasi näkee, t|C| muodostaa origokeskisen pallon, jonka voi lausua myös seuraavasti: sqrt(x²+y²+z²) = r <=> x²+y²+z² = r² <=> (ax+bx*t)²+(ay+by*t)²+(az+bz*t)² = t²|C|² <=> ax²+ax*bx*t+bx²t²+ay²+ay*by*t+by²t²+az²+az*bz*t+bz²t² - t²|C|² = 0 <=> t²(bx²+by²+bz²-|C|²)+t(ax*bx+ay*by+az*bz)+ax²+ay²+az², joka on toisen asteen yhtälö muotoa at²+bt+c=0, jossa a,b ja c ovat kaikki vakiollisia, joten sen menee suoraan t = (-b(+-)sqrt(b²-4ac))/2a, mihin kannattaa ohjelmoidessa käyttää jopa välimuuttujia. Neliöjuuren sisäinen diskriminantti (kiitos cce:lle terminologiamuistutuksesta!) b²-4ac kertoo törmäyksistä, sen ollessa negatiivinen ei ammus ehdi enää mitenkään osumaan, sen ollessa 0 se ehtii juuri yhteen pisteeseen ja diskriminantin positiivisilla arvoilla mahdollisia törmäyskohtia on kaksi. Kannattanee myös tarkistaa, että osuminen tapahtuu varmasti tulevaisuudessa, muuten kaava voi antaa t:lle negatiivisenkin arvon. Parhaan tuloksen takaamiseksi kannattanee myös kahden mahdollisuuden tapauksessa valita aiempi vielä tulevaisuudessa oleva jotta osuma tapahtuu mahdollisimman pian. Ajan laskemisen jälkeen tunnetaankin etäisyys (a+b*t) ja aika (t), joten ammuksen nopeusvektori saadaan yksinkertaisesti näistä jakolaskulla (s = vt <=> v = s/t). Esimerkki seuraa.

Code: Select all

SCREEN 640, 480

Type pari
    Field tx#
    Field ty#
    Field tz#
    Field txv#
    Field tyv#
    Field tzv#
    
    Field px#
    Field py#
    Field pz#
    Field pxv#
    Field pyv#
    Field pzv#
    
    Field t0#
    Field ct#
EndType

start = Timer()

Repeat
    
    ft# = (Timer()-start)*.001
    
    If Timer()>t
        p.pari = New(pari)
        
        p\tx = Rnd(-80,-40)
        p\ty = Rnd(-10, 10)
        p\tz = Rnd(-10, 10)
        p\txv = Rnd(5, 10)
        p\tyv = Rnd(-2, 2)
        p\tzv = Rnd(-2, 2)
        
        p\px = Rnd(-5, 5)
        p\py = Rnd(-16,-15)
        p\pz = Rnd(-15,-13)
        
        ax# = p\tx-p\px
        ay# = p\ty-p\py
        az# = p\tz-p\pz
        bx# = p\txv
        by# = p\tyv
        bz# = p\tzv
        
        s# = Rnd(8,20) // projectile speed
        
        a# = bx*bx+by*by+bz*bz-s*s
        b# = ax*bx+ay*by+az*bz
        c# = ax*ax+ay*ay+az*az
        d# = b*b-4*a*c
        
        If d<.0 Then
            Delete p // no hits, don't shoot
        Else
            If d>.0 Then
                ct1# = (-b-Sqrt(d))/(2*a)
                ct2# = (-b+Sqrt(d))/(2*a)
                If ct1>.0 Then
                    p\ct = ct1
                    If ct2>.0 And ct2<ct1 Then p\ct = ct2
                Else
                    p\ct = ct2
                EndIf
            ElseIf d = .0 Then
                p\ct = -b/(2*a)
            EndIf
            p\pxv = (p\tx+p\txv*p\ct-p\px)/p\ct
            p\pyv = (p\ty+p\tyv*p\ct-p\py)/p\ct
            p\pzv = (p\tz+p\tzv*p\ct-p\pz)/p\ct
            p\t0 = ft
            SetWindow Str(p\ct)
            If p\ct < .0 Then Delete p // no point in shooting
        EndIf
        
        t = Timer()+1000
    EndIf
    k = 0
    For p.pari = Each pari
        pt# = ft-p\t0
        s1# = 50.0/(20.0+p\tz+p\tzv*pt)
        s2# = 50.0/(20.0+p\pz+p\pzv*pt)
        Circle 320+s1*(p\tx+p\txv*pt-3), 240-s1*(p\ty+p\tyv*pt-3), 6*s1
        Circle 320+s2*(p\px+p\pxv*pt-3), 240-s2*(p\py+p\pyv*pt-3), 6*s2
        If pt>p\ct Then Delete p
        k = k + 1
    Next p
    DrawScreen
Forever

Re: Ennakkoon ampuminen

Posted: Wed Oct 03, 2012 12:04 am
by naputtelija
koodaaja wrote:Ongelma on varsin mielenkiintoinen, mutta lähestymistapa ei ehkä se kaikista helpoin. Kuten kaavastasi näkee, t|C| muodostaa origokeskisen ympyrän, jonka voi lausua myös seuraavasti: sqrt(x²+y²+z²) = r <=> x²+y²+z² = r² <=> (ax+bx*t)²+(ay+by*t)²+(az+bz*t)² = t²|C|² <=> ax²+ax*bx*t+bx²t²+ay²+ay*by*t+by²t²+az²+az*bz*t+bz²t² - t²|C|² = 0 <=> t²(bx²+by²+bz²-|C|²)+t(ax*bx+ay*by+az*bz)+ax²+ay²+az², joka on toisen asteen yhtälö muotoa at²+bt+c=0, jossa a,b ja c ovat kaikki vakiollisia, joten sen menee suoraan t = (-b(+-)sqrt(b²-4ac))/2a, mihin kannattaa ohjelmoidessa käyttää jopa välimuuttujia. Neliöjuuren sisäinen diskriminantti (kiitos cce:lle terminologiamuistutuksesta!) b²-4ac kertoo törmäyksistä, sen ollessa negatiivinen ei ammus ehdi enää mitenkään osumaan, sen ollessa 0 se ehtii juuri yhteen pisteeseen ja diskriminantin positiivisilla arvoilla mahdollisia törmäyskohtia on kaksi. Kannattanee myös tarkistaa, että osuminen tapahtuu varmasti tulevaisuudessa, muuten kaava voi antaa t:lle negatiivisenkin arvon. Parhaan tuloksen takaamiseksi kannattanee myös kahden mahdollisuuden tapauksessa valita aiempi vielä tulevaisuudessa oleva jotta osuma tapahtuu mahdollisimman pian. Esimerkki seuraa.

Code: Select all

SCREEN 640, 480

Type pari
    Field tx#
    Field ty#
    Field tz#
    Field txv#
    Field tyv#
    Field tzv#
    
    Field px#
    Field py#
    Field pz#
    Field pxv#
    Field pyv#
    Field pzv#
    
    Field t0#
    Field ct#
EndType

start = Timer()

Repeat
    
    ft# = (Timer()-start)*.001
    
    If Timer()>t
        p.pari = New(pari)
        
        p\tx = Rnd(-80,-40)
        p\ty = Rnd(-10, 10)
        p\tz = Rnd(-10, 10)
        p\txv = Rnd(5, 10)
        p\tyv = Rnd(-2, 2)
        p\tzv = Rnd(-2, 2)
        
        p\px = Rnd(-5, 5)
        p\py = Rnd(-16,-15)
        p\pz = Rnd(-15,-13)
        
        ax# = p\tx-p\px
        ay# = p\ty-p\py
        az# = p\tz-p\pz
        bx# = p\txv
        by# = p\tyv
        bz# = p\tzv
        
        s# = Rnd(8,20) // projectile speed
        
        a# = bx*bx+by*by+bz*bz-s*s
        b# = ax*bx+ay*by+az*bz
        c# = ax*ax+ay*ay+az*az
        d# = b*b-4*a*c
        
        If d<.0 Then
            Delete p // no hits, don't shoot
        Else
            If d>.0 Then
                ct1# = (-b-Sqrt(d))/(2*a)
                ct2# = (-b+Sqrt(d))/(2*a)
                If ct1>.0 Then
                    p\ct = ct1
                    If ct2>.0 And ct2<ct1 Then p\ct = ct2
                Else
                    p\ct = ct2
                EndIf
            ElseIf d = .0 Then
                p\ct = -b/(2*a)
            EndIf
            p\pxv = (p\tx+p\txv*p\ct-p\px)/p\ct
            p\pyv = (p\ty+p\tyv*p\ct-p\py)/p\ct
            p\pzv = (p\tz+p\tzv*p\ct-p\pz)/p\ct
            p\t0 = ft
            SetWindow Str(p\ct)
            If p\ct < .0 Then Delete p // no point in shooting
        EndIf
        
        t = Timer()+1000
    EndIf
    k = 0
    For p.pari = Each pari
        pt# = ft-p\t0
        s1# = 50.0/(20.0+p\tz+p\tzv*pt)
        s2# = 50.0/(20.0+p\pz+p\pzv*pt)
        Circle 320+s1*(p\tx+p\txv*pt-3), 240-s1*(p\ty+p\tyv*pt-3), 6*s1
        Circle 320+s2*(p\px+p\pxv*pt-3), 240-s2*(p\py+p\pyv*pt-3), 6*s2
        If pt>p\ct Then Delete p
        k = k + 1
    Next p
    DrawScreen
Forever
Epic solve, dear koodaaja. Epic solve indeed. :D

Re: Ennakkoon ampuminen

Posted: Wed Oct 03, 2012 2:34 pm
by valscion
koodaaja wrote:Ongelma on varsin mielenkiintoinen, mutta lähestymistapa ei ehkä se kaikista helpoin. Kuten kaavastasi näkee, t|C| muodostaa origokeskisen pallon, jonka voi lausua myös seuraavasti: sqrt(x²+y²+z²) = r <=> x²+y²+z² = r² <=> (ax+bx*t)²+(ay+by*t)²+(az+bz*t)² = t²|C|² <=> ax²+ax*bx*t+bx²t²+ay²+ay*by*t+by²t²+az²+az*bz*t+bz²t² - t²|C|² = 0 <=> t²(bx²+by²+bz²-|C|²)+t(ax*bx+ay*by+az*bz)+ax²+ay²+az², joka on toisen asteen yhtälö muotoa at²+bt+c=0, jossa a,b ja c ovat kaikki vakiollisia, joten sen menee suoraan t = (-b(+-)sqrt(b²-4ac))/2a, mihin kannattaa ohjelmoidessa käyttää jopa välimuuttujia. Neliöjuuren sisäinen diskriminantti (kiitos cce:lle terminologiamuistutuksesta!) b²-4ac kertoo törmäyksistä, sen ollessa negatiivinen ei ammus ehdi enää mitenkään osumaan, sen ollessa 0 se ehtii juuri yhteen pisteeseen ja diskriminantin positiivisilla arvoilla mahdollisia törmäyskohtia on kaksi. Kannattanee myös tarkistaa, että osuminen tapahtuu varmasti tulevaisuudessa, muuten kaava voi antaa t:lle negatiivisenkin arvon. Parhaan tuloksen takaamiseksi kannattanee myös kahden mahdollisuuden tapauksessa valita aiempi vielä tulevaisuudessa oleva jotta osuma tapahtuu mahdollisimman pian. Ajan laskemisen jälkeen tunnetaankin etäisyys (a+b*t) ja aika (t), joten ammuksen nopeusvektori saadaan yksinkertaisesti näistä jakolaskulla (s = vt <=> v = s/t). Esimerkki seuraa.

Code: Select all

... koodia ...
Mietinkin että ongelmalle olisi varmasti jokin helposti ymmärrettävä ratkaisu ja olin oikeassa :) olen iloinen kun kerrankin ymmärsin kaiken selityksestä alusta loppuun asti. Lihavoin ja suurensin muuten koodaaja yhtä kohtaa laskuistasi, jossa olit vielä kerran unohtanut merkitä lausekkeen olevan yhtäsuuri nollan kanssa.

Re: Ennakkoon ampuminen

Posted: Wed Oct 03, 2012 3:56 pm
by Pate5
Kiitos loistavasta vastauksesta koodaaja!

Re: Ennakkoon ampuminen

Posted: Wed Oct 03, 2012 7:09 pm
by koodaaja
Haa, tiesihän sen että joku virhe sinne jää! Hatunnosto VesQlle siitä, että ylipäänsä luit kaavan johtamisen noin tarkasti. Huomionarvoista on myös, että kun pallon säde pidetään vakiona, ei tapahdu muuta kuin että -|C|² hyppää t²-termistä vakiotermiin, ja saadaan pallon ja säteen törmäyksen kaava, jonka tajusin lopullisen kaavan saatuani myös johtaneeni muutamaan kertaan raytrace-juttuja viritellessäni, joten ongelma oli ainakin alitajuisesti tuttu.

Mutta kuten sanottua, ongelma on usein helpoin ratkaista muuttamalla sitä!