kurse:efcomputergrafik:kw4

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
kurse:efcomputergrafik:kw4 [2020/01/23 06:39] – [Bewegung mit konstantem Geschwindigkeitsbetrag 1] Ivo Blöchligerkurse:efcomputergrafik:kw4 [2020/02/12 20:06] (current) – [Beschleunigung bei konstantem Geschwindigkeisbetrag 1] Ivo Blöchliger
Line 1: Line 1:
 +====== Nötige Methode für die Bezier-Klasse ======
 +  * x(t): Liefert die Position zum Parameter $t$.
 +  * v(t): Liefert die Geschwindigkeit zum Parameter $t$ ("exakt").
 +  * len(tmin, tmax): Liefert die Länge der Kurve zwischen den Parametern tmin, tmax (angenähert).
 +  * vnorm(t): Geschwindkeitsvektor mit Länge 1 zum Parameter $t$ ("exakt").
 +  * adl(t): Die Ableitung von vnorm nach der **Länge** der Kurve
 +  * 
  
 +===== Todo =====
 +  * forward: Interpolation der verbleibenden Länge, wenn über die Kurve hinaus gegangen wird
 +  * adl(t):
 +  * a(t,v,g): Effektiver Beschleunigungsvektor beim Punkt $p(t)$, wenn der Geschwindigkeitsbetrag v und Inverse Erdbeschleunigung gegeben ist.
 +  * koordsyst(t): Orthonormales Koordinatensystem (Liste von 3 Vektoren) mit $x$ parallel zu $v$, $y$ parallel zu $a$ und $z = x \times y$.
 +<hidden Python Code>
 +<code python bezier2.py>
 +# -*- coding: utf-8 -*-
 +
 +from vector import Vector
 +
 +class Bezier:
 +    
 +    def __init__(self,pts):
 +        if len(pts)==4:
 +            self.pts = [p.copy() for p in pts]
 +        if len(pts)==3:
 +            self.pts = [pts[0].copy(), 1/3*pts[0]+2/3*pts[1], 1/3*pts[2]*2/3*pts[1], pts[2].copy()]
 +        if len(pts)==2:
 +            self.pts = [pts[0].copy(), 2/3*pts[0]+1/3*pts[1], 1/3*pts[0]+2/3*pts[1], pts[1].copy()]
 +        
 +    # Punkt für Parameter t (in [0,1])        
 +    def x(self,t):
 +        return (1.0-t)**3*self.pts[0]+\
 +               3*(1.0-t)**2*t*self.pts[1]+\
 +               3*(1.0-t)*t**2*self.pts[2]+\
 +               t**3*self.pts[3]
 +
 +    # Geschwindigkeit (dx/dt) in t-Parametrierung
 +    def v(self,t):
 +        return -3*(1.0-t)**2*self.pts[0]+\
 +               3*(1.0-4*t+3*t**2)*self.pts[1]+\
 +               3*(2*t-3*t**2)*self.pts[2]+\
 +               3*t**2*self.pts[3]
 +
 +    # Normalisierte Geschwindigkeit
 +    def vnorm(self,t):
 +        return self.v(t).normalize()
 +    
 +    
 +    def adl(self,t, dt=0.000001): # Ableitung der normalisierten Geschwindigkeit nach der Länge
 +        dl = self.v(t).length*dt # (self.x(t+dt)-self.x(t-dt)).length
 +        return (self.vnorm(t+dt/2)-self.vnorm(t-dt/2))/dl
 +        
 +    # Effektive Beschleunigung im Punkt zum Parameter t, mit
 +    # gegebenem Geschwindigkeitsbetrag und Gravitationsvektor
 +    def aeff(self, t, v, g):
 +        return self.adl(t)*v*v-g
 +    
 +    # Orthonnormales Bahn-Koordinatensystem im Punkt zum Parameter t, mit
 +    # gegebenem Geschwindigkeitsbetrag und Gravitationsvektor.
 +    # Resultat (v,a, v cross a)
 +    def koordsyst(self, t, v, g):
 +        x = self.vnorm(t)
 +        # Effektive Beschleunigung
 +        aeff = self.aeff(t,v,g)
 +        # Projektion von aeff auf x (d.h. normalisierte Geschwindigkeit)
 +        av = x.dot(aeff)/(x.dot(x))*x
 +        # Effektive Beschleunigung in Normalebene zur Bahn.
 +        # (ist mathematisch rechtwinklig!)
 +        aa = (aeff-av).normalize()
 +        z = x.cross(aa)
 +        return (x,aa,z)
 +        
 +        
 +        
 +        
 +   
 +    # Laenge der Bezier-Kurve
 +    def length(self, tmin=0.0, tmax=1.0, steps=1000):
 +        l = 0.0
 +        # Zeitschritt
 +        dt = (tmax-tmin)/steps
 +        for i in range(steps):
 +            # Den i-ten Zeitpunkt von steps Zeitpunkten
 +            ti = (i+0.5)*dt+tmin
 +            l += self.v(ti).length*dt
 +        return l
 +    
 +    # Input: Startzeitpunkt t, Streckenlaenge l
 +    # Output: Endzeitpunkt t oder -Restlaenge
 +    def forward(self, l, startt, dt=0.0001):
 +        lastl = l
 +        lastt = startt
 +        while (startt<1.0 and l>0):
 +            dl = self.v(startt).length*dt
 +            lastl = l
 +            lastt = startt
 +            l -= dl
 +            startt += dt
 +        if startt<1.0:  # Noch auf der Bezierkurve
 +            return (startt-lastt)*lastl/(lastl+abs(l))+lastt
 +        
 +        if l>0: # Es ist noch Laenge uebrig, also auf naechster Kurve
 +            return -(lastl - (self.x(1)-self.x(lastt)).length)
 +        return 1.0 # Sonst genau auf dem Ende der Kurve
 +    
 +
 +    # Zeichnet den Spline
 +    # Achtung: Funktioniert nur, wenn diese Datei ausgeführt wird...
 +    def draw(self, steps=1000):
 +        move(self.pts[0][0], self.pts[0][1]);
 +        for i in range(1,steps+1):
 +            p = self.x(1.0*i/steps)
 +            lineTo((p[0],p[1]))
 +                        
 +
 +if __name__== "__main__":
 +    from gpanel import *
 +    makeGPanel(Size(600,600))
 +    window(0, 599, 0, 599)
 +    b = Bezier([Vector([100,500,0]), Vector([200,0,0]), Vector([300,300,0]), Vector([500,300,0])])
 +    b.draw()
 +    print(b.length(0,1,10000))
 +    t = 0.0
 +    while (t>=0.0):
 +        p = b.x(t)
 +        a = b.adl(t)
 +        pa = p+a*10000
 +        # move(p.x, p.y)
 +        # circle(5)
 +        line(p.x,p.y, pa.x, pa.y)
 +        t = b.forward(10,t)
 +        print(t)
 +
 +</code>
 +</hidden>
 +
 +====== Bahn-Koordinatensystem ======
 +===== Bewegung mit konstantem Geschwindigkeitsbetrag 1 =====
 +Kreisbewegung mit Geschwindigkeitsbetrag 1:
 +
 +\begin{align*}
 +x(t) & =  r \cdot \textrm{e}^{\textrm{i}t \cdot \frac{1}{r}}\\
 +v(t) & =  x'(t) = \textrm{i}\textrm{e}^{\textrm{i}t \cdot \frac{1}{r}}\\
 +a(t) & =  v'(t) = -\frac{1}{r} \cdot \textrm{e}^{\textrm{i}t \cdot \frac{1}{r}}\\
 +\end{align*}
 +
 +Beträgt jetzt der Betrag der Geschwindigkeit $\lambda$ anstatt 1, ändern sich die Gleichungen wie  folgt:
 +
 +\begin{align*}
 +x(t) & =  r \cdot \textrm{e}^{\textrm{i} \lambda t \cdot \frac{1}{r}}\\
 +v(t) & =  x'(t) =  \textrm{i}\lambda  \cdot \textrm{e}^{\textrm{i} \lambda t \cdot \frac{1}{r}}\\
 +a(t) & =  v'(t) =  -\lambda^2 \cdot \frac{1}{r} \cdot \textrm{e}^{\textrm{i} \lambda t \cdot \frac{1}{r}}\\
 +\end{align*}
 +
 +D.h., wenn der Betrag der Geschwindigkeit von $1$ auf $\lambda$ erhöht wird, wird der Betrag der Beschleunigung mit $\lambda^2$ multipliziert.
 +
 +===== Beschleunigung bei konstantem Geschwindigkeisbetrag 1 =====
 +Sei $\ell(t)$ die Bogenlänge der Bezierkurve, also $\ell(t) = \int_0^t |p'(s)| \textrm{d}s$. Insbesondere gilt dann
 +$\ell'(t) = |p'(t)|$. (Die Länge ändert sich so stark, wie der Punkt in $t$ schnell ist).
 +
 +Sei $t(\ell)$ die Umkehrfunktion, d.h. der $t$-Parameter für eine bestimmte Länge. Die Ableitung ist dann
 +(nach der Formel $(f^{-1})' = \frac{1}{f'(f^{-1})}$ mit $f^{-1}=t$ und $f=\ell$):
 +$$
 +\frac{\textrm{d}t(\ell)}{\textrm{d}\ell} = \frac{1}{\ell'(t(\ell))} = \frac{1}{|p'(t(\ell))|}.
 +$$
 +(D.h. der Parameter $t$ ändert sich mit der Länge indirekt proportional zur $t$-Geschwindigkeit).
 +
 +Anstatt der "normalen" Parametrierung in $t \in [0,1]$, stellen wir uns eine Parametrierung $p_{\ell}$ vor, so dass die Bogenlänge der Bezierkurve von $p_{\ell}(0)$ bis $p_{\ell}(\ell)$ genau $\ell$ beträgt.
 +
 +Es gilt $p_{\ell}(\ell) = p(t(\ell))$ und
 +\[
 +v_n(t(\ell)) := \frac{\textrm{d}p_{\ell}(\ell)}{\textrm{d} \ell} = p'(t(\ell)) \cdot \frac{\textrm{d}t(\ell)}{\textrm{d}\ell} = 
 +p'(t(\ell)) \cdot \frac{1}{|p'(t(\ell))|}
 +\]
 +d.h. man erhält den auf den Betrag 1 normalisierten Geschwindgkeitsvektor (was ja genau der Sinn der $p_{\ell}$ Parametrierung ist).
 +
 +Die zweite Ableitung von $p_{\ell}$ nach $\ell$ ergibt die Beschleunigung $a_n(\ell)$, die rechtwinklig auf $v_n$ steht (sonst würde sich der Betrag von $v_n$ ändern).
 +
 +Diese zweite Ableitung berechnen wir nummerisch durch Ableiten der ersten:
 +\[
 +a_n(t(\ell)) := \frac{\mathrm{d}v_n(\ell)}{\mathrm{d}\ell} \approx \frac{v_n(t(\ell)+\Delta t)-v_n(t(\ell)-\Delta t)}{\ell(t+\Delta t)-\ell(t-\Delta t)} \approx \frac{v_n(t(\ell)+\Delta t)-v_n(t(\ell)-\Delta t)}{|p(t+\Delta t)-p(t-\Delta t)|}
 +\]
 +
 +Algebraisch erhält man folgendes:
 +\[
 +\frac{\mathrm{d}}{\mathrm{d}\ell} \left(\frac{p'}{|p'|}\right) = 
 +\frac{p'' - e \cdot \left( e \cdot p''\right)}{|p'|^2}
 +\]
 +mit $e=\frac{p'}{|p'|}$. Das ist bis auf den Faktor $|p'|^2$ das Gram-Schmidt Verfahren. Sachen gibts...
 +===== Effektive Beschleunigung und Komponente in Bahnnormalebene =====
 +Sei $v_{\text{eff}}(t) \in \mathbb{R}$ der Betrag der effektiven Bahngeschwindigkeit im Punkt zum entsprechenden $t$-Parameter.
 +Die effektive Beschleunigung ist also 
 +\[
 +a_{\text{eff}}(t) =  (v_{\text{eff}}(t))^2 \cdot a_n(t) -g
 +\]
 +wobei $g$ die Gravitationsbeschleunigung ist.
 +
 +Diese Beschleunigung zerlegen wir in zwei Komponenten, eine tangential zur Bahn $a_t$, eine rechtwinklig dazu $a_r$. Die Komponente $a_t$ sorgt für die Beschleunigung des Zugs, die Komponente $a_r$ ist für die Bahnneigung relevant. Diese sollte nämlich rechtwinklig zu $a_r$ sein.
 +
 +$a_t$ ist die Projektion von $a_{\text{eff}}$ auf $v_n$. Es gilt (mit $|v_n|=1$):
 +\[
 +a_t = (\vec a_{\text{eff}} \cdot \vec v_n) \cdot \vec v_n \qquad \text{Skalarprodukt in der Klammer!}
 +\]
 +Und damit 
 +\[
 +a_r = a_{\text{eff}}-a_t
 +\]
 +
 +Damit bilden wir ein Koordinatensystem $K(t)$ mit Ursprung $p(t)$ und Einheitsvektoren
 +\[
 +e_1 = v_n(t), \qquad e_2 = \frac{a_r(t)}{|a_r(t)|}, \qquad e_3 = v_n(t) \times a_r(t) \cdot \frac{1}{|a_r(t)|}
 +\]
 +
 +===== Bau der Bahn =====
 +In geeigneter Schrittgrösse der Kurve entlang gehen, an jedem Punkt mit $K(t)$ Schienen-Punkte definieren.
 +Eventuell Stützen definieren.
 +
 +Blender Code analog zur Generierung der glatten Kurve.
 +===== Simulation der Bewegung =====
 +Zustand: $t$ (Ort auf der Bahn), $v_{\text{eff}}$ (aktueller Betrag der Geschwindigkeit)
 +
 +Schritt: Zeit um 1/framerate vorrücken, der Bahn folgen (z.B. um die Strecke, die mit $v_{\text{eff}}$ in dieser Zeit zurückgelegt würde, oder genauere schrittweise Simulation). Aus der Höhendifferenz und eventuell Reibung die neue Geschwindigkeit berechnen.
 +
 +Kamera entsprechend positionieren und Keyframe setzen:
 +
 +<code python>
 +cam = bpy.data.objects['Camera']
 +frame = 0
 +cam.animation_data_clear()
 +cam.matrix_world = ( (y.x,y.y,y.z,1), (-an.x, -an.y, -an.z, 1),  (-vv.x,-vv.y,-vv.z,1),  (pp.x, pp.y, pp.z, 0))
 +cam.keyframe_insert(data_path="rotation_euler", frame=frame)
 +cam.keyframe_insert(data_path="location", frame=frame)
 +frame+=1
 +</code>
 +Siehe auch https://blender.stackexchange.com/questions/108938/how-to-interpret-the-camera-world-matrix
 +
 +D.h. die erste Koordinatenrichtung ist rechts, die zweite oben und die dritte ist entgegen der Blickrichtung.
 +
 +===== Blender =====
 +Bezier Klasse laden in Blender:
 +<code python bahn.py>
 +# Nimmt die Bezierkurven aus myspline und erzeugt 
 +# die Bahn und die Kamera-Animation
 +
 +# Import in Blender 2.8 (see https://devtalk.blender.org/t/2-80-using-multiple-internal-scripts-breaking-change/6980 )
 +Bezier = bpy.data.texts["bezier.py"].as_module().Bezier
 +
 +obj = bpy.data.objects['mySpline']
 +
 +# Kurvenpunkte auslesen
 +mypoints=[]
 +if obj.type == 'CURVE':
 +    for subcurve in obj.data.splines:
 +        curvetype = subcurve.type
 +        if curvetype == 'BEZIER':
 +            for bezpoint in subcurve.bezier_points:
 +                mypoints.append(bezpoint.handle_left)
 +                mypoints.append(bezpoint.co)
 +                mypoints.append(bezpoint.handle_right)
 +                
 +
 +# Sammlung von Bezierkurven erzeugen
 +mySplines = []
 +numpoints = len(mypoints)
 +totalLength = 0
 +for i in range(numpoints//3):
 +    mySplines.append(Bezier((mypoints[i*3+1],
 +        mypoints[i*3+2], 
 +        mypoints[(i*3+3)%numpoints], 
 +        mypoints[(i*3+4)%numpoints])))
 +    totalLength+=mySplines[-1].length()
 +
 +
 +# Bahn erzeugen
 +try:
 +    bpy.ops.collection.objects_remove(bpy.data.collections['Rails'])
 +except:
 +    pass
 +    
 +railsCol = bpy.data.collections.new('Rails')
 +linksCol = bpy.data.collections.new('RailLinks')
 +railsCol.children.link(linksCol)
 +bpy.context.scene.collection.children.link(railsCol)
 +               
 +abstand = 0.2  # Bahnpunkte
 +ldone = 0  # Erledigte Bahnstrecke
 +i=0  # Aktuelle Bezierkurve
 +t = 0 # Aktuelle t-Parameter
 +g = Vector(0,0,-9.81) # Gravitationbeschleunigung
 +hmax = 40  # Hoehe fuer v=0
 +# Bahnpunkte: Ctrl-Links, Knoten, Ctrl-Rechts
 +railspts=[[],[],[]]  # Bahnpunkte, Schiene L, Schiene R, Träger 
 +while(ldone<totalLength):
 +    dl = abstand;
 +    tnext = -1
 +    while(tnext<0):
 +        tnext = mySplines[i].forward(dl, t)
 +        if (tnext<0) : # We get the negative remaining length
 +            i=(i+1)%numSplines
 +            t = 0
 +            dl=abs(tnext)
 +        else:
 +            ldone+=dl
 +    t = tnext
 +    # Potentielle Energie mgh
 +    ekin = (hmax-mySplines[i].x(t).z)*abs(g.z)
 +    # Ek = 1/2 * m * v^2
 +    v = (2*ekin)**0.5
 +    # Koordinatensystem (vorne, oben, rechts)
 +    k = mySplines[i].koordsyst(t,v,g)
 +    # Bahnpunkte berechnen
 +    #
 +    #
 +    #
 +    
 +
 +
 +# Blender-Kurven aus den Bahnpunkten erzeugen
 +for j in range(3):
 +    curvedata = bpy.data.curves.new(name="rail"+str(j), type='CURVE')
 +    curvedata.dimensions = '3D'
 +    objectdata = bpy.data.objects.new("rail"+str(j), curvedata)    
 +    objectdata.location = (0,0,0)
 +    objectdata.data.bevel_depth = 0.01
 +
 +    railsCol.objects.link(objectdata)
 + 
 +    polyline = curvedata.splines.new('BEZIER'   
 +    polyline.bezier_points.add(len(railspts[j])-1)    
 + 
 +    for idx, (h1, knot, h2) in enumerate(railspts[j]):
 +        point = polyline.bezier_points[idx]
 +        point.co = knot
 +        point.handle_left = h1
 +        point.handle_right = h2
 +        point.handle_left_type = 'ALIGNED'
 +        point.handle_right_type = 'ALIGNED'
 +
 +</code>