Döngüdeki değerler arasında nasıl geçiş yapabilirim (örneğin, ton veya döndürme)?


25

joint animation example

Demoyu Gör

Eklemi tuvalin ortasından, fare imlecinin açısına doğru düzgün bir şekilde döndürmeye çalışıyorum. Elimde işler var, ancak farenin açısına ulaşmak için mümkün olan en kısa mesafeyi canlandırmak istiyorum. Sorun yatay değerde ( 3.14ve -3.14) değer etrafında döndüğünde oluşur . Yönün nasıl değiştiğini ve geri dönmesinin uzun sürdüğünü görmek için bu alana fareyle bakın.

İlgili Kod

// ease the current angle to the target angle
joint.angle += ( joint.targetAngle - joint.angle ) * 0.1;

// get angle from joint to mouse
var dx = e.clientX - joint.x,
    dy = e.clientY - joint.y;  
joint.targetAngle = Math.atan2( dy, dx );

"Boşlukta" bile olsa en kısa mesafeyi döndürmesini nasıl sağlayabilirim?



1
@ VaughanHilts Durumumu nasıl kullanacağımı bilmiyorum. Daha fazla detay verebilir misiniz?
jackrugile

Yanıtlar:


11

Her zaman en iyi yöntem değildir ve hesaplama açısından daha pahalı olabilir (bunun sonuçta verilerinizi nasıl sakladığınıza bağlı olmasına rağmen), ancak 2D değerlerin kullanılmasının çoğu durumda oldukça iyi çalıştığını savunacağım. İstediğiniz açıyı değiştirmek yerine, istediğiniz normalleştirilmiş yön vektörünü kullanabilirsiniz.

Bu yöntemin “açıya giden en kısa yolu seç” yöntemine göre avantajı, ikiden fazla değer arasında enterpolasyon yapmanız gerektiğinde çalışmasıdır.

Ton değerlerini sıralarken, huebir [cos(hue), sin(hue)]vektörle değiştirebilirsiniz .

Sizin durumunuzda, normalleştirilmiş eklem yönünü kaybedersiniz:

// get normalised direction from joint to mouse
var dx = e.clientX - joint.x,
    dy = e.clientY - joint.y;
var len = Math.sqrt(dx * dx + dy * dy);
dx /= len ? len : 1.0; dy /= len ? len : 1.0;
// get current direction
var dirx = cos(joint.angle),
    diry = sin(joint.angle);
// ease the current direction to the target direction
dirx += (dx - dirx) * 0.1;
diry += (dy - diry) * 0.1;

joint.angle = Math.atan2(diry, dirx);

2D vektör sınıfını kullanabiliyorsanız kod daha kısa olabilir. Örneğin:

// get normalised direction from joint to mouse
var desired_dir = normalize(vec2(e.clientX, e.clientY) - joint);
// get current direction
var current_dir = vec2(cos(joint.angle), sin(joint.angle));
// ease the current direction to the target direction
current_dir += (desired_dir - current_dir) * 0.1;

joint.angle = Math.atan2(current_dir.y, current_dir.x);

Teşekkürler, bu harika çalışıyor: codepen.io/jackrugile/pen/45c356f06f08ebea0e58daa4d06d204f Yaptıklarınızın çoğunu anlıyorum, ancak normalleştirme sırasında kodunuzun 5. satırında biraz daha ayrıntılı bilgi verebilir misiniz? Tam olarak orada ne olduğundan emin değilim dx /= len...
Jackrugile

1
Bir vektörü uzunluğuna bölmek normalizasyon olarak adlandırılır . Uzunluk 1 olmasını sağlar. len ? len : 1.0Kısmi, farenin tam olarak eklem pozisyonuna yerleştirildiği nadir durumlarda, sadece sıfıra bölmeyi önler. Bu yazılmış olabilir: if (len != 0) dx /= len;.
sam hocevar

-1. Bu cevap çoğu durumda optimal olmaktan uzaktır. Ya ve arasında enterpolasyon yapıyorsanız 180°? Vektör formunda: [1, 0]ve [-1, 0]. İnterpolasyon vektörler ya verecek , 180°ya durumunda 0 hata ile bölmek, t=0.5.
Gustavo Maciel

@GustavoMaciel "çoğu durumda" değil, pratikte asla gerçekleşmeyen çok özel bir köşe davası. Ayrıca, sıfır ile bölme yoktur, kodu kontrol edin.
sam hocevar

@GustavoMaciel kodu tekrar kontrol ettiğinde, aslında son derece güvenlidir ve tarif ettiğiniz köşe durumunda bile tam olarak çalışması gerekir.
sam hocevar

11

İşin püf noktası, (en azından Öklid uzayında) açıların 2 x pi ile periyodik olduğunu hatırlamaktır. Geçerli açı ile hedef açı arasındaki fark çok büyükse (örneğin imleç sınırı aştıysa), geçerli açıyı uygun şekilde 2 * pi ekleyerek veya çıkararak ayarlayın.

Bu durumda, aşağıdakileri deneyebilirsiniz: (Daha önce Javascript’te daha önce hiç programlamadım, bu yüzden kodlama stilimi affedin.)

  var dtheta = joint.targetAngle - joint.angle;
  if (dtheta > Math.PI) joint.angle += 2*Math.PI;
  else if (dtheta < -Math.PI) joint.angle -= 2*Math.PI;
  joint.angle += ( joint.targetAngle - joint.angle ) * joint.easing;

EDIT : Bu uygulamada, imleci eklemin merkezi çevresinde çok hızlı hareket ettirmek sarsıntıya neden olur. Amaçlanan davranış budur, çünkü eklemin açısal hızı daima ile orantılıdır dtheta. Bu davranış istenmiyorsa, derzinin açısal ivmesi üzerine bir başlık yerleştirilerek sorun kolayca çözülebilir.

Bunu yapmak için, eklemin hızını takip etmemiz ve maksimum hızlanma uygulamamız gerekir:

  joint = {
    // snip
    velocity: 0,
    maxAccel: 0.01
  },

Sonra, rahatlığımız için, kırpma işlevini tanıtacağız:

function clip(x, min, max) {
  return x < min ? min : x > max ? max : x
}

Şimdi, hareket kodumuz böyle gözüküyor. Öncelikle, gerektiği gibi dthetaayarlayarak joint.angle, önceki gibi hesaplıyoruz :

  var dtheta = joint.targetAngle - joint.angle;
  if (dtheta > Math.PI) joint.angle += 2*Math.PI;
  else if (dtheta < -Math.PI) joint.angle -= 2*Math.PI;

Sonra, derzleri derhal hareket ettirmek yerine, bir hedef hız hesaplıyoruz ve cliponu kabul edilebilir aralığımız içinde zorlamak için kullanıyoruz .

  var targetVel = ( joint.targetAngle - joint.angle ) * joint.easing;
  joint.velocity = clip(targetVel,
                        joint.velocity - joint.maxAccel,
                        joint.velocity + joint.maxAccel);
  joint.angle += joint.velocity;

Bu, sadece bir boyutta hesaplamalar yaparken yön değiştirirken bile düzgün hareket sağlar. Ayrıca, eklemin hız ve ivmesinin bağımsız olarak ayarlanmasını sağlar. Buraya bakınız demo: http://codepen.io/anon/pen/HGnDF/


Bu yöntem çok yaklaşıyor, ancak faremi çok hızlı tutarsam yanlış yoldan hafifçe zıplamaya başlar. Demoyu izleyin
jackrugile

1
Yanlış yoldan hafifçe atlayarak ne demek istediğinizi anlamadım; Benim için eklem her zaman doğru yönde hareket eder. Elbette, farenizi merkezin etrafında çok hızlı bir şekilde hareket ettirirseniz, eklemi dışarı atarsınız ve bir yönde diğerine doğru hareket ettikçe titrer. Amaçlanan davranış budur, çünkü dönme hızı her zaman orantılıdır dtheta. Eklemin biraz ivme kazanmasını mı düşünüyorsunuz?
David Zhang

Eklemin dışına çıkarak yaratılan sarsıntılı hareketi ortadan kaldırmak için son düzenlememe bakın.
David Zhang

Hey, demo bağlantınızı denedim, ancak sanırım orijinal adıma işaret ediyor. Yeni bir tane mi kurtardın? İyi değilse, bu gece bunu daha sonra uygulayabilmeli ve nasıl performans gösterdiğine bakmalıyım. Sanırım düzenlemede anlattıkların, aradığım şey. Size geri döner, teşekkürler!
Jackrugile

1
Evet, haklısın. Üzgünüm, benim hatam. Bunu düzelteceğim.
David Zhang

3

Verilen diğer cevapları seviyorum. Çok teknik!

İsterseniz, bunu başarmak için çok basit bir yöntem var. Bu örnekler için açıları kabul edeceğiz. Kavram, renkler gibi diğer değer türlerine göre tahmin edilebilir.

double MAX_ANGLE = 360.0;
double startAngle = 300.0;
double endAngle = 15.0;
double distanceForward = 0.0;  // Clockwise
double distanceBackward = 0.0; // Counter-Clockwise

// Calculate both distances, forward and backward:
distanceForward = endAngle - startAngle;    // -285.00
distanceBackward = startAngle - endAngle;   // +285.00

// Which direction is shortest?
// Forward? (normalized to 75)
if (NormalizeAngle(distanceForward) < NormalizeAngle(distanceBackward)) {
    // Adjust for 360/0 degree wrap
    if (endAngle < startAngle) endAngle += MAX_ANGLE; // Will be above 360
}

// Backward? (normalized to 285)
else {
    // Adjust for 360/0 degree wrap
    if (endAngle > startAngle) endAngle -= MAX_ANGLE; // Will be below 0
}

// Now, Lerp between startAngle and endAngle. 

// EndAngle can be above 360 if wrapping clockwise past 0, or
// EndAngle can be below 0 if wrapping counter-clockwise before 0.
// Normalize each returned Lerp value to bring angle in range of 0 to 360 if required.  Most engines will automatically do this for you.


double NormalizeAngle(double angle) {
    while (angle < 0) 
        angle += MAX_ANGLE;
    while (angle >= MAX_ANGLE) 
        angle -= MAX_ANGLE;
    return angle;
}

Bunu tarayıcıda yeni oluşturdum ve hiç test edilmedim. Umarım ilk denemede mantığı bulmuşumdur.

[Düzenle] 2017/06/02 - Mantığı biraz açıklığa kavuşturdu.

DistanceForward ve distanceBackwards değerini hesaplayarak başlayın ve sonuçların aralığın dışına çıkmasını sağlayın (0-360).

Normalleştirme açıları bu değerleri tekrar (0-360) aralığına getirir. Bunu yapmak için, değer sıfıra gelinceye kadar 360 ekler ve değer 360'ın üzerindeyken 360 çıkarır. Elde edilen başlangıç ​​/ bitiş açıları eşdeğerdir (-285, 75 ile aynıdır).

Ardından distanceForward veya distanceBackward öğelerinin en küçük Normalize açısını bulabilirsiniz. örnekte distanceForward 75 olur ve bu, distanceBackward'ın (300) normalleştirilmiş değerinden daha küçüktür.

DistanceForward en küçük AND endAngle <startAngle ise, endAngle öğesini 360 ekleyerek 360'ın ötesine uzatın (örnekte 375 olur).

DistanceBackward en küçük AND endAngle> startAngle ise, 360'ı çıkararak endAngle öğesini 0'ın altına uzatın.

Şimdi startAngle'dan (300) yeni endAngle'a (375) geçeceksiniz. Motor, sizin için 360'ı çıkararak 360'ın üzerindeki değerleri otomatik olarak ayarlamalıdır. Aksi halde motor değerleri sizin için normalleştirmezse 300'den 360'a, sonradan 0-15'e kadar lp yapmanız gerekir.


Her ne kadar atan2temel fikir basit olsa da , bu, biraz yer ve performanstan tasarruf sağlayabilir (x ve y'yı depolamaya, ne de günah, cos ve atan2'yi çok sık hesaplamak gerekmez). Bence bu çözümün neden doğru olduğu konusunda biraz daha genişlemeye değer (örneğin, SLERP'in alıntılar için yaptığı gibi, bir daire veya alandaki en kısa yolu seçmek). Bu soru ve cevaplar, çoğu oyun programcısının karşılaştığı çok yaygın bir konu olduğundan topluluk wiki'sine konulmalıdır.
teodron

Mükemmel cevap. Ayrıca David Zhang'ın verdiği diğer cevabı da beğeniyorum. Ancak hızlı bir dönüş yaptığım zaman düzenlediği çözümü kullanarak her zaman garip bir titremeye kapılıyorum. Ama cevabın benim oyunumda mükemmel uyuyor. Cevabınızın arkasındaki matematik teorisi ile ilgileniyorum. Her ne kadar basit görünse de, neden farklı yönlerin normalize açı mesafelerini karşılaştırmamız gerektiği açık değil.
newguy

Kodumun işe yaramasına sevindim. Belirtildiği gibi, bu gerçek bir projede test edilmemiş, sadece tarayıcıya yazılmıştır. Bu soruyu cevaplamayı bile hatırlamıyorum (üç yıl önce)! Ancak, baktığımda, toplam derece farkını karşılaştırmak ve en kısa farkı almak için test değerlerinin bu aralığın ötesine / ötesine geçmesine izin vermek için aralığı (0-360) genişletiyormuşum gibi görünüyor. Normalleştirme sadece bu değerleri tekrar (0-360) aralığına getirir. Böylece distanceForward 75 (-285 + 360) olur, bunlar distanceBackward'tan (285) daha küçüktür, bu yüzden en kısa mesafedir.
Doug.McFarlane

DistanceForward en kısa mesafe olduğundan, IF yan tümcesinin ilk kısmı kullanılır. EndAngle (15), StartAngle'dan (300) daha küçük olduğundan, 375'i almak için 360 ekleriz. Böylece, StartAngle'dan (300) yeni endAngle'a (375) geçersiniz. Motor, sizin için 360'ı çıkararak 360'ın üzerindeki değerleri otomatik olarak ayarlamalıdır.
Doug.McFarlane

Daha fazla netlik eklemek için cevabı değiştirdim.
Doug.McFarlane
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.