मैं उस लूप (जैसे ह्यू या रोटेशन) के मूल्यों के बीच कैसे झांकता हूं?


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

मैं इसे सबसे छोटी दूरी, यहां तक ​​कि "गैप के पार" कैसे घुमा सकता हूं?


मोडुलो का उपयोग करें। : D en.wikipedia.org/wiki/Modular_arithmetic
वॉन हिल्ट्स

1
@VaughanHilts मुझे यकीन नहीं है कि मैं अपनी स्थिति का उपयोग कैसे करूँगा। क्या आप आगे बता सकते हैं?
जैक्रगाइल

जवाबों:


11

यह हमेशा सबसे अच्छा तरीका नहीं है, और यह अधिक कम्प्यूटेशनल रूप से महंगा हो सकता है (हालांकि यह अंततः इस बात पर निर्भर करता है कि आप अपने डेटा को कैसे स्टोर करते हैं), लेकिन मैं यह तर्क दूंगा कि 2 डी मानों को मरोड़ना अधिकांश मामलों में उचित रूप से अच्छी तरह से काम करता है। एक वांछित कोण को फिर से चलाने के बजाय, आप वांछित सामान्यीकृत दिशा वेक्टर को लेप कर सकते हैं ।

इस विधि का एक फायदा "एंगल से सबसे छोटा रास्ता चुनें" विधि यह है कि यह तब काम करता है जब आपको दो से अधिक मानों के बीच इंटरपोल करने की आवश्यकता होती है।

जब ह्यू मूल्यों को लेरिंग करते हैं, तो आप 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 की लाइन 5 पर थोड़ा और विस्तृत कर सकते हैं? यकीन नहीं हो रहा है कि वास्तव में वहाँ क्या हो रहा है dx /= len...
jackrugile

1
एक वेक्टर को उसकी लंबाई से विभाजित करना सामान्यीकरण कहलाता है । यह सुनिश्चित करता है कि इसकी लंबाई 1. len ? len : 1.0भाग सिर्फ शून्य से एक विभाजन से बचता है, दुर्लभ स्थिति में कि माउस को संयुक्त स्थिति में बिल्कुल रखा गया है। यह लिखा जा सकता था if (len != 0) dx /= len;:।
सैम होसेवर

-1। यह जवाब ज्यादातर मामलों में इष्टतम से दूर है। यदि आप और के बीच में अंतर कर रहे हैं तो क्या होगा 180°? वेक्टर रूप में: [1, 0]और [-1, 0]। Interpolating वैक्टर आप या तो दे देंगे , 180°या, के मामले में 0 द्वारा विभाजन त्रुटि, t=0.5
गुस्तावो मैकिएल

@GustavoMaciel "अधिकांश मामले" नहीं हैं, यह एक बहुत ही विशिष्ट कोने का मामला है जो व्यवहार में कभी नहीं होता है। इसके अलावा, शून्य से कोई विभाजन नहीं है, कोड की जांच करें।
सैम होसेवर

@GustavoMaciel ने फिर से कोड की जाँच की, यह वास्तव में बेहद सुरक्षित है और ठीक उसी तरह काम करता है जैसा कि आपके द्वारा बताए गए कोने के मामले में भी होना चाहिए।
सैम होसेवर

11

चाल को याद रखना है कि कोण (कम से कम यूक्लिडियन स्थान में) 2 * पीआई द्वारा आवधिक हैं। यदि वर्तमान कोण और लक्ष्य कोण के बीच अंतर बहुत बड़ा है (अर्थात कर्सर सीमा पार कर गया है), तो वर्तमान कोण को तदनुसार 2 * pi जोड़कर या घटाकर समायोजित करें।

इस मामले में, आप निम्नलिखित प्रयास कर सकते हैं: (मैंने पहले कभी जावास्क्रिप्ट में प्रोग्राम नहीं किया है, इसलिए मेरी कोडिंग शैली को माफ़ करें।)

  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। क्या आप कुछ गति के लिए संयुक्त के लिए इच्छुक थे?
डेविड झांग

संयुक्त outrunning द्वारा बनाई गई झटकेदार गति को समाप्त करने के तरीके के लिए मेरा अंतिम संपादन देखें।
डेविड जांग

अरे, अपने डेमो लिंक की कोशिश की, लेकिन ऐसा लगता है कि यह सिर्फ मेरे मूल की ओर इशारा कर रहा है। क्या आपने एक नया बचाया? यदि यह ठीक नहीं है, तो मुझे आज रात को इसे लागू करने में सक्षम होना चाहिए और देखना चाहिए कि यह कैसा प्रदर्शन करता है। मुझे लगता है कि आपने अपने संपादन में जो वर्णन किया है, वह वही है जो मैं देख रहा था। आपको वापस मिल जाएगा, धन्यवाद!
जैक्रगाइल

1
ओह, हाँ, तुम सही हो। माफ कीजिएगा यह मेरी गलती है। मैं ठीक कर दूँगा।
डेविड जांग

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 - तर्क को थोड़ा स्पष्ट किया।

दूरी की दूरी और दूरी की गणना करके शुरू करें, और परिणाम सीमा (0-360) से आगे बढ़ाने की अनुमति दें।

सामान्यीकरण कोण उन मूल्यों को (0-360) की सीमा में वापस लाता है। ऐसा करने के लिए, आप 360 को जोड़ते हैं जब तक कि मूल्य शून्य से ऊपर नहीं है, और 360 को घटाएं जबकि मूल्य 360 से ऊपर है। परिणामस्वरूप प्रारंभ / अंत कोण समतुल्य होगा (-285 75 के समान है)।

आप आगे दूरी या दूरी के सबसे छोटे सामान्यीकृत कोण को पाते हैं। दूरी में उदाहरण के लिए 75 बन जाता है, जो दूरी के सामान्यीकृत मूल्य से छोटा होता है (300)।

यदि दूरस्थ रूप से सबसे छोटा और एंडएंगल <startAngle है, तो एंडएंगल को 360 से आगे बढ़ाकर 360 जोड़ें। (उदाहरण में यह 375 हो जाता है)।

यदि दूरीबेकवर्ड सबसे छोटा और अंतिम है> स्टार्टअंगल, 360 तक घटाकर एंडएंगल को 0 से नीचे तक बढ़ाएं।

अब आप startAngle (300) से नए endAngle (375) तक लेप करेंगे। इंजन को स्वचालित रूप से आपके लिए 360 घटाकर 360 से ऊपर के मूल्यों को समायोजित करना चाहिए। अन्यथा यदि आपको इंजन आपके लिए मानों को सामान्य नहीं करता है, तो आपको 0 से 15 तक 300 से 360 तक लेरप करना होगा।


खैर, हालांकि यह atan2विचार सरल है, यह कुछ जगह और प्रदर्शन को थोड़ा बचा सकता है (एक्स और वाई को स्टोर करने की आवश्यकता नहीं है, न ही पाप, कॉस और एटैन 2 की गणना करने के लिए अक्सर)। मुझे लगता है कि यह इस समाधान के सही होने पर थोड़ा विस्तार करने लायक है (यानी किसी सर्कल या गोले पर सबसे छोटा रास्ता चुनना, ठीक उसी तरह जैसे SLERP बटेरियन के लिए करता है)। इस सवाल और जवाब को कम्युनिटी विकी में रखा जाना चाहिए क्योंकि यह वास्तव में एक सबसे आम समस्या है जो गेमप्ले प्रोग्रामर का सबसे बड़ा चेहरा है।
तेद्रोन

बहुत बढ़िया जवाब। मुझे @ डेविड जांग द्वारा दिया गया अन्य उत्तर भी पसंद है। हालाँकि मुझे हमेशा एक अजीब घबराहट मिलती है जब मैं एक स्विफ्ट टर्न बनाता हूं। लेकिन आपका जवाब मेरे खेल में पूरी तरह फिट बैठता है। मुझे आपके उत्तर के पीछे गणित के सिद्धांत में दिलचस्पी है। हालांकि यह सरल दिखता है, लेकिन यह स्पष्ट नहीं है कि हमें विभिन्न दिशाओं के सामान्यीकृत कोण दूरी की तुलना क्यों करनी चाहिए।
newguy

मुझे खुशी है कि मेरा कोड काम करता है। जैसा कि उल्लेख किया गया है, यह सिर्फ ब्राउज़र में टाइप किया गया था, वास्तविक परियोजना में परीक्षण नहीं किया गया था। मुझे इस प्रश्न का उत्तर याद नहीं आ रहा है (तीन साल पहले)! लेकिन, इसे देखते हुए, ऐसा लग रहा है कि मैं परीक्षण रेंज के मूल्यों को कुल डिग्री अंतर की तुलना करने के लिए, और सबसे कम अंतर लेने के लिए इस सीमा से पहले / सीमा तक जाने की अनुमति देने के लिए सीमा (0-360) का विस्तार कर रहा था। सामान्य करना उन मूल्यों को फिर से (0-360) की सीमा में लाता है। तो डिस्टॉर्फवर्ड 75 (-285 + 360) हो जाता है, जो कि दूरी की तुलना में छोटा है (285), इसलिए यह सबसे कम दूरी है।
डग.मार्फर्लेन

चूंकि डिफॉरवर्डवर्ड सबसे कम दूरी है, आईएफ क्लॉज के पहले भाग का उपयोग किया जाता है। चूंकि endAngle (15) startAngle (300) से कम है, इसलिए हम 375 प्राप्त करने के लिए 360 जोड़ते हैं। इसलिए आप startAngle (300) से नए endAngle (375) तक लेप करेंगे। इंजन को स्वचालित रूप से आपके लिए 360 घटाकर 360 से ऊपर के मूल्यों को समायोजित करना चाहिए।
डग .cFarlane

मैंने अधिक स्पष्टता जोड़ने के लिए उत्तर संपादित किया।
डग.मकरफलेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.