Constrain MovieClip перетащите в круг - PullRequest
5 голосов
/ 28 июня 2011

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

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

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

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

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) и mouseX относительно вершины дуги, чтобы вычислить треугольник, и оттуда я могу вычислить требуемую позицию y. Проблема в том, что я читаю различные сайты по тригонометрии и до сих пор не знаю, с чего начать. Может ли кто-нибудь объяснить, что мне нужно делать, как если бы я разговаривал с ребенком, пожалуйста?

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

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

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

Ответы [ 4 ]

6 голосов
/ 28 июня 2011

На мой взгляд, проблема, которую вы решаете, это поиск ближайшей точки на окружности .У 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);

РЕДАКТИРОВАТЬ2 Некоторые ссылки

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

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

1 голос
/ 25 марта 2014

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;
    }
}
}
1 голос
/ 28 июня 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;

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

0 голосов
/ 28 июня 2011

Примерно так должно получиться:

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, очень хороший выбор. Я бы убрался, если бы продолжал;)

Хороший вопрос - ура

...