반복되는 값 (예 : 색조 또는 회전) 사이를 어떻게 이동합니까?


25

조인트 애니메이션 예제

데모보기

캔버스 중심을 중심으로 마우스 포인터 각도를 향해 관절을 부드럽게 회전 시키려고합니다. 내가 가지고있는 것은 아니지만 마우스 각도에 도달 할 수있는 최단 거리를 애니메이션으로 만들고 싶습니다. 값이 수평선 ( 3.14-3.14) 에서 반복 될 때 문제가 발생합니다 . 해당 영역을 마우스로 가리켜 서 방향이 어떻게 전환되는지 확인하고 먼 길을 돌아갑니다.

관련 코드

// 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 );

"갭을 가로 질러"가장 짧은 거리를 어떻게 회전시킬 수 있습니까?



1
@VaughanHilts 상황을 어떻게 사용할지 잘 모르겠습니다. 더 자세히 설명해 주시겠습니까?
jackrugile

답변:


11

항상 최선의 방법은 아니며 계산 비용이 많이 들지만 (결국 데이터를 저장하는 방법에 따라 다르지만) 대부분의 경우 2D 값을 lerping하는 것이 합리적으로 잘 작동한다고 주장합니다. 원하는 각도를 유지하는 대신 원하는 정규화 된 방향 벡터를 이동할 수 있습니다 .

"가장 짧은 경로를 선택하십시오"방법에 ​​비해이 방법의 장점 중 하나는 둘 이상의 값을 보간해야 할 때 작동한다는 것입니다.

색상 값을 lerping 때 대체 할 수 hue[cos(hue), sin(hue)]벡터.

귀하의 경우 정규화 된 관절 방향을 견딜 수 있습니다.

// 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 벡터 클래스를 사용할 수 있으면 코드가 더 짧아 질 수 있습니다. 예를 들어 :

// 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);

고맙습니다. 여기서 잘 작동합니다 : codepen.io/jackrugile/pen/45c356f06f08ebea0e58daa4d06d204f 대부분의 작업을 이해하고 있지만 정규화 중에 코드의 5 번째 줄에 대해 좀 더 자세히 설명 할 수 있습니까? 정확히 무슨 일이 일어나고 있는지 잘 모르겠습니다 dx /= len.
jackrugile

1
벡터를 길이로 나누는 것을 정규화 라고 합니다. 그것은 길이 1.이 보장 len ? len : 1.0단지 마우스가 공동 위치에 정확히 배치되는 드문 경우에 0으로 분열을 방지 부분. 작성되었을 수 있습니다 if (len != 0) dx /= len;.
sam hocevar

-1. 이 답변은 대부분의 경우 최적이 아닙니다. 와 사이에 보간하는 경우 어떻게해야 180°합니까? 벡터 형식으로 : [1, 0][-1, 0]. 벡터를 보간하면의 경우 , 180°또는 0으로 나누기 오류가 발생 t=0.5합니다.
구스타보 메이 엘

@GustavoMaciel "대부분의 경우"는 아닙니다. 그것은 실제로는 결코 일어나지 않는 매우 구체적인 사례입니다. 또한 0으로 나누기가 없습니다. 코드를 확인하십시오.
sam hocevar

@GustavoMaciel은 코드를 다시 확인했으며 실제로 매우 안전하며 설명하는 코너 케이스에서도 정확히 작동합니다.
sam hocevar

11

트릭은 각도 (적어도 유클리드 공간에서)가 2 * pi만큼 주기적이라는 것을 기억하는 것입니다. 현재 각도와 목표 각도의 차이가 너무 큰 경우 (예 : 커서가 경계를 넘었을 경우) 2 * pi를 더하거나 빼서 현재 각도를 조정하십시오.

이 경우 다음을 시도해 볼 수 있습니다. (이전에 Javascript로 프로그래밍 한 적이 없으므로 코딩 스타일을 용서하십시오.)

  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;

편집 :이 구현에서 커서를 관절 중심 주위로 너무 빨리 움직이면 커서가 찌그러집니다. 조인트의 각속도는 항상에 비례하기 때문에 의도 된 동작 dtheta입니다. 이 동작이 바람직하지 않은 경우 조인트의 각가속도에 캡을 배치하여 문제를 쉽게 해결할 수 있습니다.

이를 위해서는 조인트의 속도를 추적하고 최대 가속을 적용해야합니다.

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

그런 다음 편의상 클리핑 기능을 소개합니다.

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

이제 우리 운동 코드는 다음과 같습니다. 먼저 필요에 따라 dtheta조정하여 이전과 같이 계산 joint.angle합니다.

  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;

그런 다음 조인트를 즉시 이동하는 대신 목표 속도를 계산하여 clip허용 범위 내에서 강제로 사용 합니다.

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

이렇게하면 방향을 전환 할 때에도 한 차원에서만 계산을 수행하면서 부드러운 동작이 생성됩니다. 또한 조인트의 속도와 가속도를 독립적으로 조정할 수 있습니다. 데모보기 : http://codepen.io/anon/pen/HGnDF/


이 방법은 실제로 가깝지만 마우스가 너무 빠르면 잘못된 방향으로 약간 점프하기 시작합니다. : 데모 여기, 내가 그 제대로 구현하지 않은 경우 알려 codepen.io/jackrugile/pen/db40aee91e1c0b693346e6cec4546e98
jackrugile

1
잘못된 길을 약간 뛰어 넘어 당신이 무슨 뜻인지 잘 모르겠습니다. 나를 위해 관절은 항상 올바른 방향으로 움직입니다. 물론 마우스를 중앙에서 너무 빨리 움직이면 관절을 벗어나고 한쪽 방향에서 다른 방향으로 움직일 때 흔들 리게됩니다. 회전 속도가 항상 비례하기 때문에 이것은 의도 된 동작 dtheta입니다. 조인트가 어느 정도의 모멘텀을 가지려고 했습니까?
David Zhang

조인트를 오버런하여 생성 된 흔들림을 제거하는 방법은 마지막 편집을 참조하십시오.
David Zhang

이봐, 당신의 데모 링크를 시도했지만 그것은 내 원래 링크를 가리키는 것 같습니다. 새로 구했습니까? 그것이 좋지 않다면, 오늘 밤에 이것을 구현하고 그것이 어떻게 수행되는지 볼 수 있어야합니다. 나는 당신이 당신의 편집에서 묘사 한 것이 내가 찾고있는 것이라 생각합니다. 감사합니다!
jackrugile

1
네 맞아요 미안, 내 실수 내가 고칠 게
David Zhang

3

나는 다른 대답을 좋아합니다. 매우 기술적 인!

원하는 경우이 작업을 수행하는 매우 간단한 방법이 있습니다. 우리는 이러한 예에 대한 각도를 가정합니다. 개념은 색상과 같은 다른 값 유형에 외삽 될 수 있습니다.

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;
}

방금 브라우저에서 이것을 만들었으며 테스트 한 적이 없습니다. 첫 번째 시도에서 논리를 올바르게 얻었기를 바랍니다.

[편집] 2017/06/02-논리가 조금 명확 해졌습니다.

distanceForward 및 distanceBackwards를 계산하여 시작하고 결과가 범위 (0-360)를 초과하도록 확장하십시오.

각도를 정규화하면 해당 값이 (0-360) 범위로 돌아갑니다. 이렇게하려면 값이 0보다 커질 때까지 360을 더하고 값이 360보다 큰 동안 360을 빼십시오. 결과 시작 / 종료 각도는 같습니다 (-285는 75와 같습니다).

다음으로 distanceForward 또는 distanceBackward 중 가장 작은 정규화 된 각도를 찾습니다. 예제에서 distanceForward는 75가되며 이는 distanceBackward (300)의 정규화 된 값보다 작습니다.

distanceForward가 가장 작은 AND endAngle <startAngle 인 경우 360을 추가하여 endAngle을 360 이상으로 확장하십시오 (이 예에서는 375가 됨).

distanceBackward가 가장 작은 AND endAngle> startAngle 인 경우 360을 빼서 endAngle을 0 이하로 확장하십시오.

이제 startAngle (300)에서 새로운 endAngle (375)로 이동합니다. 엔진에서 360을 빼서 360보다 큰 값을 자동으로 조정해야합니다. 그렇지 않으면 엔진이 값을 정규화하지 않으면 300에서 360으로 뛰고 0에서 15로 뛰어야합니다.


atan2기본 아이디어는 간단 하지만 공간과 약간의 성능을 절약 할 수 있습니다 (x와 y를 저장하거나 sin, cos 및 atan2를 너무 자주 계산할 필요가 없음). 이 솔루션이 왜 올바른지에 대해 조금 확장 할 가치가 있다고 생각합니다 (즉, SLERP가 쿼터니언에 대해하는 것처럼 원 또는 구에서 가장 짧은 경로를 선택하는 것). 이 질문과 답변은 대부분의 게임 플레이 프로그래머가 직면하는 매우 일반적인 문제이므로 커뮤니티 위키에 입력해야합니다.
teodron

좋은 대답입니다. @David Zhang의 다른 답변도 좋아합니다. 그러나 나는 빠른 회전을 할 때 항상 편집 된 솔루션을 사용하여 이상한 지터를 얻습니다. 그러나 당신의 대답은 내 게임에 완벽하게 들어 맞습니다. 나는 당신의 대답 뒤에 수학 이론에 관심이 있습니다. 단순 해 보이지만 왜 다른 방향의 정규화 된 각도 거리를 비교해야하는지 명확하지 않습니다.
newguy

내 코드가 작동해서 다행입니다. 언급했듯이 이것은 실제 프로젝트에서 테스트되지 않고 브라우저에 입력되었습니다. 나는이 질문에 답하는 것을 기억할 수 없다 (3 년 전)! 그러나 그것을 살펴보면, 전체도 차이를 비교하고 가장 짧은 차이를 취하기 위해 테스트 값 이이 범위를 초과 / 초과 할 수 있도록 범위 (0-360)를 확장 한 것처럼 보입니다. 정규화는 해당 값을 다시 (0-360) 범위로 가져옵니다. 따라서 distanceForward는 75 (-285 + 360)가되어 distanceBackward (285)보다 작으므로 가장 짧은 거리입니다.
Doug.McFarlane

distanceForward는 최단 거리이므로 IF 절의 첫 부분이 사용됩니다. endAngle (15)이 startAngle (300)보다 작으므로 360을 추가하여 375를 얻습니다. 따라서 startAngle (300)에서 새로운 endAngle (375)로 이동합니다. 엔진에서 360을 빼서 360보다 큰 값을 자동으로 조정해야합니다.
Doug.McFarlane

명확성을 더하기 위해 답을 편집했습니다.
Doug.McFarlane
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.