Ограничение перетаскивания MovieClip в круг

...ну, до неполного круга.

У меня есть перетаскиваемый ползунок, который выглядит так: Arc Slider

Синяя полоса имеет имя экземпляра track, а розовая точка — имя экземпляра puck.

Мне нужно, чтобы шайба всегда была ограничена синей зоной, и здесь мои математические ошибки работают против меня! Пока у меня шайба движется по оси x только так:

private function init():void
{
    zeroPoint = track.x + (track.width/2);
    puck.x = zeroPoint-(puck.width/2);
    puck.buttonMode = true;
    puck.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
}

private function onMouseDown(evt:MouseEvent):void
{
    this.stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
    this.stage.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
}

private function onMouseUp(evt:MouseEvent):void
{
    this.stage.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
}

private function onMouseMove(evt:MouseEvent):void
{
    puck.x = mouseX-(puck.width/2);
    //need to plot puck.y using trig magic...
}

В настоящее время я думаю, что я могу использовать радиус неполного круга (50) и мышь X относительно вершины дуги для вычисления треугольника, а оттуда я могу вычислить требуемую позицию y. Проблема в том, что я читаю различные сайты по тригонометрии и до сих пор не знаю, с чего начать. Может ли кто-нибудь объяснить, что мне нужно делать, как если бы я разговаривал с ребенком, пожалуйста?

Редактировать: Тот факт, что круг разорван, не должен быть проблемой, я могу легко ограничить движение до определенного количества градусов в каждом направлении, это в первую очередь получение градусов, которые я могу не получить мою голову вокруг!

Edit2: я пытаюсь следовать ответу Bosworth99, и это функция, которую я придумал для вычисления радиана, чтобы поместить ее в его функцию:

private function getRadian():Number
{
    var a:Number = mouseX - zeroPoint;
    var b:Number = 50;
    var c:Number = Math.sqrt((a^2)+(b^2));
    return c;
}

person shanethehat    schedule 28.06.2011    source источник
comment
Я уверен, что кто-то с сильными математическими знаниями даст вам хороший ответ. Просто хочу отметить, что почти во всех случаях, когда мне нужно работать с чем-то кругом, например, я использую y=sin(x)+cos(x); ies.co.jp/math/java/trig/graphSinCosX/ graphSinCosX.html   -  person Igor Milla    schedule 28.06.2011
comment
Вот такую ​​ссылку я смотрел. Красивые круговые анимации, кажется, меня смущают!   -  person shanethehat    schedule 28.06.2011


Ответы (4)


Насколько я понимаю, проблема, которую вы решаете, заключается в нахождении ближайшей точки на окружности. У Google есть много предложений по этому вопросу.

Вы можете оптимизировать его, сначала обнаружив угол между положением мыши и центром круга. Используйте для этого Math.atan2(). Если угол находится в диапазоне зазоров, просто выберите ближайшую конечную точку: левую или правую.

EDIT1 Вот полный пример этой стратегии.

Надеюсь, это поможет.

import flash.geom.Point;
import flash.events.Event;
import flash.display.Sprite;

var center:Point = new Point(200, 200);
var radius:uint = 100;

var degreesToRad:Number = Math.PI/180;

// gap angles. degrees are used here just for the sake of simplicity.
// what we use here are stage angles, not the trigonometric ones.
var gapFrom:Number = 45; // degrees
var gapTo:Number = 135; // degrees

// calculate endpoints only once

var endPointFrom:Point = new Point();
endPointFrom.x = center.x+Math.cos(gapFrom*degreesToRad)*radius;
endPointFrom.y = center.y+Math.sin(gapFrom*degreesToRad)*radius;

var endPointTo:Point = new Point();
endPointTo.x = center.x+Math.cos(gapTo*degreesToRad)*radius;
endPointTo.y = center.y+Math.sin(gapTo*degreesToRad)*radius;

// just some drawing
graphics.beginFill(0);
graphics.drawCircle(center.x, center.y, radius);
graphics.moveTo(center.x, center.y);
graphics.lineTo(endPointFrom.x, endPointFrom.y);
graphics.lineTo(endPointTo.x, endPointTo.y);
graphics.lineTo(center.x, center.y);
graphics.endFill();

// something to mark the closest point
var marker:Sprite = new Sprite();
marker.graphics.lineStyle(20, 0xFF0000);
marker.graphics.lineTo(0, 1);
addChild(marker);

var onEnterFrame:Function = function (event:Event) : void
{
    // circle intersection goes here
    var mx:int = stage.mouseX;
    var my:int = stage.mouseY;

    var angle:Number = Math.atan2(center.y-my, center.x-mx);
    // NOTE: in flash rotation is increasing clockwise, 
    // while in trigonometry angles increase counter clockwise
    // so we handle this difference
    angle += Math.PI;

    // calculate the stage angle in degrees
    var clientAngle:Number = angle/Math.PI*180

    // check if we are in a gap
    if (clientAngle >= gapFrom && clientAngle <= gapTo) {
        // we are in a gap, no sines or cosines needed
        if (clientAngle-gapFrom < (gapTo-gapFrom)/2) {        
            marker.x = endPointFrom.x;
            marker.y = endPointFrom.y;
        } else {
            marker.x = endPointTo.x;
            marker.y = endPointTo.y;
        }
        // we are done here
        return;
    }

    // we are not in a gp, calculate closest position on a circle
    marker.x = center.x + Math.cos(angle)*radius;
    marker.y = center.y + Math.sin(angle)*radius;
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);

EDIT2 Некоторые ссылки

Вот несколько распространенных проблем, объясненных и решенных в блестяще ясной и лаконичной манере: http://paulbourke.net/geometry/ Этот ресурс помог мне много дней назад.

Пересечение линии и окружности здесь немного излишне, но вот оно: http://paulbourke.net/geometry/sphereline/

person Michael Antipin    schedule 28.06.2011
comment
Извините, у меня ужасный мозг для такой математики, а вы меня уже потеряли. При вычислении первого угла я могу использовать расстояние по оси X от вершины дуги до mouseX и центра окружности, но должна ли третья точка быть вершиной дуги? - person shanethehat; 28.06.2011
comment
Хорошо, я отредактировал приведенное выше с полным примером. Просто скопируйте и вставьте его, скажем, во фрейм нового документа Flash IDE. - person Michael Antipin; 28.06.2011
comment
Вау, это огромный ответ. Я изучу это; он, безусловно, делает именно то, что мне нужно в среде IDE, и перенос его на мой компонент должен быть в пределах моих возможностей. Большое спасибо. - person shanethehat; 28.06.2011
comment
Браво. Несколько настроек, позволяющих зарегистрировать шайбу в верхнем левом углу, отрегулировать зазор и сделать координаты мыши локальными для компонента, и все работает отлично. Еще раз спасибо, что нашли время, чтобы предоставить такой полный ответ. - person shanethehat; 28.06.2011
comment
= palbourke выглядит как отличный ресурс. надо закопаться. - person Bosworth99; 28.06.2011

Вместо того, чтобы пытаться перемещать точку по частичному пути круга, почему бы не сымитировать это и использовать ручку/циферблат? Скиньте его так, чтобы точка двигалась по траектории.

Затем просто установите вращение ручки на:

var deg:Number = Math.atan2(stage.mouseY - knob.y,stage.mouseX - knob.x) / (Math.PI/180);
// code to put upper/lower bounds on degrees    
knob.rotation = deg;

Вы можете проверить это, включив его в событие ввода кадра, но вы, очевидно, захотите добавить некоторую логику, чтобы контролировать, как ручка начинает двигаться и когда она должна остановиться.

person Alex Jillard    schedule 28.06.2011
comment
Это была моя первая мысль, но готовая графика имеет различные тени и фаски, которые нельзя поворачивать, поэтому я решил стиснуть зубы и положиться на коллективный разум SO! - person shanethehat; 28.06.2011
comment
Дорожка, по которой скользит точка, может находиться под самой ручкой. Ручка не может состоять ни из чего, кроме точки. Это совершенно не должно мешать дизайну. Если к точке применены специальные стили, которые не следует поворачивать, вам придется повернуть точку в противоположном направлении, что уменьшит простоту этого ответа и кода, который вам придется написать. - person Alex Jillard; 28.06.2011
comment
Собственно, это тоже была моя первая мысль. Вы можете повернуть что-то и получить положение ручки в другом координатном пространстве с помощью localToGlobal() и globalToLocal(). Или вы можете вращать контейнер ручки, вращая саму ручку в противоположном направлении. Однако математика, используемая для расчета правильного положения, не так уж сложна. И я предпочитаю решение, которое отделено от каких-либо трюков, специфичных для flash, когда оно никоим образом не повредит вам. Для меня это звучит чисто. :) - person Michael Antipin; 28.06.2011

100% рабочий код.

enter code here

const length:int = 100;

var dragging:Boolean = false;
var tx:int;
var ty:int;



var p1:Sprite = new Sprite();
var p2:Sprite = new Sprite();

p1.graphics.beginFill(0);
p1.graphics.drawCircle(0, 0, 10);
p1.graphics.endFill();

p2.graphics.copyFrom(p1.graphics);

p1.x = stage.stageWidth / 2;
p1.y = stage.stageHeight / 2;

p2.x = p1.x + length;
p2.y = p1.y;

addChild(p1);
addChild(p2);

p2.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);

function mouseDown(event:MouseEvent):void
{
    dragging = true;
}

function mouseUp(event:MouseEvent):void
{
    dragging = false;
}

function mouseMove(event:MouseEvent):void
{
if (dragging)
{
    tx = event.stageX - p1.x;
    ty = event.stageY - p1.y;
    if (tx * tx + ty * ty > length * length)
    {
        p2.x = p1.x + tx / Math.sqrt(tx * tx + ty * ty) * length;
        p2.y = p1.y + ty / Math.sqrt(tx * tx + ty * ty) * length;
    }
    else
    {
        p2.x = event.stageX;
        p2.y = event.stageY;
    }
}
}
person Nikhil Mahirrao    schedule 25.03.2014

Что-то вроде этого должно получиться:

private function projectLocation(center:point, radius:uint, radian:Number):Point 
    {
        var result:Point = new Point();

        //obtain X
        result.x = center.x + radius * Math.cos(radian));

        //obtain Y
        result.y = center.y + radius * Math.sin(radian));

        return result;
    }

Очевидно, измените по мере необходимости, но вам просто нужно отправить центральную точку, радиус, а затем радиан (вы можете получить с помощью angle * (Math.PI / 180)). Вы можете легко жестко закодировать первые два параметра, если они не меняются. Что действительно меняется, так это радианы, и это то, что вам нужно будет изменить с течением времени, когда ваша мышь перетаскивает (определяется расстоянием mouseX до центральной точки - положительным или отрицательным).

Надеюсь, это поможет вам начать -

обновить

Вот как я работал над этим - хотя это немного глючит, поскольку шайба сбрасывается до 0 градусов, когда начинается последовательность. При этом я только что увидел это - @Nox понял это правильно. В любом случае я опубликую то, к чему я пришел, используя функцию projectLocation;)

package com.b99.testBed.knob 
{
    import com.b99.testBed.Main;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;

    /**
     * ...
     * @author bosworth99
     */
    public class Knob extends Sprite
    {

        private var _puck       :Sprite;
        private var _track      :Sprite;
        private const DIAMETER  :uint = 100;
        private const RADIUS    :uint = DIAMETER / 2;

        public function Knob() 
        {
            super();
            init();
        }

        private function init():void
        {
            assembleDisplayObjects();
            addEventHandlers();
        }

        private function assembleDisplayObjects():void
        {
            _track = new Sprite();
            with (_track) 
            {
                graphics.beginFill(0xffffff, 1);
                graphics.lineStyle(1, 0x000000);
                graphics.drawEllipse(-RADIUS, -RADIUS, DIAMETER, DIAMETER);
                graphics.endFill();
            }
            this.addChild(_track);
            _track.x = Main.stage.stageWidth / 2;
            _track.y = Main.stage.stageHeight / 2;

            _puck = new Sprite();
            with (_puck) 
            {
                graphics.beginFill(0x2DFE07, 1);
                graphics.drawEllipse(-8, -8, 16, 16);
                graphics.endFill();
                x   = _track.x;
                y   = _track.y - _track.width / 2;
                buttonMode = true;
            }
            this.addChild(_puck);
        }

        private function addEventHandlers():void
        {
            Main.stage.addEventListener(MouseEvent.MOUSE_DOWN, activate);
            Main.stage.addEventListener(MouseEvent.MOUSE_UP, deactivate);
        }

        private function deactivate(e:MouseEvent):void 
        {
            Main.stage.removeEventListener(MouseEvent.MOUSE_MOVE, update);
        }

        private var _origin:uint;
        private function activate(e:MouseEvent):void 
        {
            Main.stage.addEventListener(MouseEvent.MOUSE_MOVE, update);
            _origin = mouseX;
        }

        private function update(e:MouseEvent):void 
        {
            var distance:Number;
            (mouseX < _origin)? distance = -(_origin - mouseX) : distance = mouseX - _origin;
            if(distance > 40){distance = 40};
            if(distance < -220){distance = -220};

            var angle:Number  = distance;  //modify?
            var radian:Number = angle * (Math.PI / 180);
            var center:Point  = new Point(_track.x, _track.y);
            var loc:Point     = projectLocation(center, RADIUS, radian);

            _puck.x = loc.x;
            _puck.y = loc.y;
        }

        private function projectLocation(center:Point, radius:uint, radian:Number):Point 
        {
            var result:Point = new Point();

            //obtain X
            result.x = center.x + radius * Math.cos(radian);

            //obtain Y
            result.y = center.y + radius * Math.sin(radian);

            return result;
        }

    }

}

Основное отличие состоит в том, что я получаю угол посредством горизонтального (x) движения, а не проверяю угол курсора. Тем не менее, захват значений вручную кажется довольно хакерским по сравнению с @Nox, очень хорошим решением. Если бы я продолжал идти, я бы почистился;)

Хороший вопрос - здравствуйте

person Bosworth99    schedule 28.06.2011
comment
Это помогло, поскольку моя шайба двигалась по кругу. К сожалению, мне нужен гораздо больший круг, и я подозреваю, что это связано с моим расчетом свойства радиана. Я добавлю функцию, которую я написал, к вопросу, так как форматирование комментариев плохое. - person shanethehat; 28.06.2011