Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constrain MovieClip drag to a circle

...well, to an incomplete circle.

I have a draggable slider that looks like this: Arc Slider

The blue bar has the instance name track and the pink dot has the instance name puck.

I need the puck to be constrained within the blue area at all times, and this is where my maths failings work against me! So far I have the puck moving along the x axis only like this:

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

My thinking is currently that I can use the radius of the incomplete circle (50) and the mouseX relative to the top of the arc to calculate a triangle, and from there I can calculate the required y position. Problem is, I'm reading various trigonometry sites and still have no idea where to begin. Could someone explain what I need to do as if speaking to a child please?

Edit: The fact that the circle is broken shouldn't be an issue, I can cap the movement to a certain number of degrees in each direction easily, it's getting the degrees in the first place that I can't get my head around!

Edit2: I'm trying to follow Bosworth99's answer, and this is the function I've come up with for calculating a radian to put into his function:

private function getRadian():Number
{
    var a:Number = mouseX - zeroPoint;
    var b:Number = 50;
    var c:Number = Math.sqrt((a^2)+(b^2));
    return c;
}
like image 262
shanethehat Avatar asked Jun 28 '11 16:06

shanethehat


1 Answers

As I see it, the problem you solve is finding the closest point on a circle. Google have a lot of suggestions on this subject.

You can optimise it by first detecting an angle between mouse position and circle center. Use Math.atan2() for that. If the angle is in a gap range, just choose the closest endpoint: left or right.

EDIT1 Here is a complete example of this strategy.

Hope that helps.

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 Some links

Here are some common problems explained and solved in a brilliantly clear and concise manner: http://paulbourke.net/geometry/ This resource helped me a lot days ago.

Intersection of a line and a circle is a bit of an overkill here, but here it is: http://paulbourke.net/geometry/sphereline/

like image 141
Michael Antipin Avatar answered Sep 19 '22 18:09

Michael Antipin