freehand drawing class for serpent line - actionscript-3

I am looking to build a drawing application and have been able to do regular lines and dashed line but I wanted a 'wavy' line also known as serpent line. Any thoughts on how to accomplish this in AS3?
Updated: The code replied does answer the question but was hoping to be more freehand serpent so on mouse move it would be able to draw something like below (although it doesn't need the line through the waves).

You can either do it using beizer curves, but the simplest way is probably to draw line segments. Here's a working example:
package
{
import flash.display.*;
import flash.events.Event;
import flash.geom.*;
public class Main extends Sprite {
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
this.graphics.lineStyle(2, 0x0, 0.5);
// step = 1 creates a high-quality wave
drawSerpentLine(this.graphics, new Point(50, 50), new Point(150, 250), 4, 20, 1);
// step = 8 and the wave is not as smooth
drawSerpentLine(this.graphics, new Point(300, 30), new Point(233, 150), 6, 10, 8);
}
// takes a graphics reference, and draws a serpent line between the two
// specified points using the g's current lineStyle
// frequency: determines how many waves
// amplitude: how "much" wave
// step: the quality (lower means smoother lines at the cost of speed)
private function drawSerpentLine(g : Graphics, from : Point, to : Point, frequency : int = 5, amplitude : int = 20, step : int = 2):void {
// the angle between the two points
var ang : Number = Math.atan2(to.y - from.y, to.x - from.x);
// the distance between the points
var dis : Number = Point.distance(from, to);
// a point which we use to store the current position to draw
var currPoint : Point = new Point(from.x, from.y);
for (var i:int = 0; i <= dis; i += step) {
// how far away (perpendicularly) from the straight lines the current points should be
var waveOffsetLength : Number = Math.sin((i / dis) * Math.PI * frequency) * amplitude;
// calculate the current point.
currPoint.x = from.x + Math.cos(ang) * i + Math.cos(ang - Math.PI/2)*waveOffsetLength;
currPoint.y = from.y + Math.sin(ang) * i + Math.sin(ang - Math.PI/2)*waveOffsetLength;
if (i > 0) {
this.graphics.lineTo(currPoint.x, currPoint.y);
} else {
this.graphics.moveTo(currPoint.x, currPoint.y);
}
}
// close the last line so we end up at the end point
this.graphics.lineTo(to.x, to.y);
}
}
}
Update:
Here's a modified version which draws freehand. The approach is similar: Each mouse move we draw serpent between two points. But the "phase" of the serpent needs to be stored in a variable so that the different drawSerpentLine() calls continue at the last call's phase. Also, we can't just draw a serpent between the last mouse coord and the current mouse coord because that would not create a smooth look. So we average the coordinates.
package
{
import flash.display.*;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.*;
public class Main extends Sprite {
private var m_mouseIsDown : Boolean;
private var m_lastPoint : Point = new Point();
private var m_currPoint : Point = new Point();
private var m_phase : Number = 0;
private var m_firstDrawAfterMouseDown : Boolean;
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
this.graphics.lineStyle(2, 0x0, 0.5);
this.stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
this.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
this.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
}
private function drawSerpentLine(g : Graphics, from : Point, to : Point, frequency : Number = 0.15, amplitude : int = 6, step : int = 1):void {
// the angle between the two points
var ang : Number = Math.atan2(to.y - from.y, to.x - from.x);
// the distance between the points
var dis : Number = Point.distance(from, to);
for (var i:int = 0; i <= dis; i += step) {
m_phase += frequency;
// how far away (perpendicularly) from the straight lines the current points should be
var waveOffsetLength : Number = Math.sin(m_phase) * amplitude;
// calculate the current point.
var x : Number = from.x + Math.cos(ang) * i + Math.cos(ang - Math.PI/2)*waveOffsetLength;
var y : Number = from.y + Math.sin(ang) * i + Math.sin(ang - Math.PI/2)*waveOffsetLength;
if (m_firstDrawAfterMouseDown) {
this.graphics.moveTo(x, y);
m_firstDrawAfterMouseDown = false;
} else {
this.graphics.lineTo(x, y);
}
}
}
private function onMouseEvent(event : MouseEvent):void {
switch(event.type) {
case MouseEvent.MOUSE_DOWN:
m_mouseIsDown = m_firstDrawAfterMouseDown = true;
m_currPoint.x = m_lastPoint.x = this.mouseX;
m_currPoint.y = m_lastPoint.y = this.mouseY;
break;
case MouseEvent.MOUSE_MOVE:
if (m_mouseIsDown) {
// to create a smoother look we average the mouse coords
// where only 10% of the new mouse position is used, 90% is the old position
// the lower the first percentage the smoother the look, but the more
// the serpent will lag behind the actual mouse position (until you release the mouse)
m_currPoint.x = this.mouseX * 0.1 + m_lastPoint.x * 0.9;
m_currPoint.y = this.mouseY * 0.1 + m_lastPoint.y * 0.9;
drawSerpentLine(this.graphics, m_lastPoint, m_currPoint);
m_lastPoint.x = m_currPoint.x;
m_lastPoint.y = m_currPoint.y;
}
break;
case MouseEvent.MOUSE_UP:
m_mouseIsDown = false;
// when the user releases, we complete the serpent
// by drawing the full distance to the current position
// (no percentages like in mouse move)
m_currPoint.x = this.mouseX;
m_currPoint.y = this.mouseY;
drawSerpentLine(this.graphics, m_lastPoint, m_currPoint);
break;
}
}
}
}

Related

AS3 random symbol movement loop with easing?

I have cobbled together a script for making a movie symbol randomly move around the stage. My aim is to make it slowly hover around in one spot.
Problem is that it's pretty buggy and the position of the symbol always starts in the top left of the stage. I would really like to find a way to add easing into the script too.
Any help would be greatly appreciated!
//Declare Globals
var currentFrameCount: int = 300;
var totalFrameCount: int = 300;
this.x = Math.round(Math.random() * 200);
this.y = Math.round(Math.random() * 200);
var destinationX: Number = this.x;
var destinationY: Number = this.y;
var initialX: Number;
var initialY: Number;
var distanceX: Number;
var distanceY: Number;
var xProg: Number;
var yProg: Number;
var countFrame: int = 0;
var delay: int = 0;
addEventListener(Event.ENTER_FRAME, callDelay);
function callDelay(e: Event) {
countFrame++;
delayEvent(delay);
}
//Code to move the object to a random location and give it a random target to move to.
function spawnObject() {
destinationX = Math.round(Math.random() * 200); //random destination
destinationY = Math.round(Math.random() * 200);
currentFrameCount = 30;
initialX = this.x;
initialY = this.y;
distanceX = destinationX - initialX; //distance between destination and initial
distanceY = destinationY - initialY;
yProg = distanceY / totalFrameCount;
}
function delayEvent(period) {
if ((countFrame) >= period) {
removeEventListener(Event.ENTER_FRAME, callDelay);
timedEvent();
countFrame = 0;
return;
}
}
function timedEvent(): void {
currentFrameCount = totalFrameCount;
this.x = destinationX;
this.y = destinationY;
spawnObject(); //move the object to a new location and give new destination
this.addEventListener(Event.ENTER_FRAME, moveTo); //add an event listener to move the object around
}
function moveTo(e: Event): void {
currentFrameCount++;
if (currentFrameCount < totalFrameCount) {
this.x += xProg; //incrase x by the x step value
this.y += yProg; //increase y by the y step value
} else {
this.removeEventListener(Event.ENTER_FRAME, moveTo); // remvoe this event listener
addEventListener(Event.ENTER_FRAME, callDelay);
}
}

Ball bounce issue in AS3

[edit]
I was really stupid, all is working fine now.
So forget about this dummy question!
The rotation of the main clip was a big mistake.
I've changed this by adding a var called _rota with getter and setters.
I had not to rotate the clip but just to place another Sprite in it, so I can place the sub-Sprite in the right direction by using a simple function.
So I avoid all those loops...
My mistake SRY.
I just added a Sprite which have the rotation of the Main Sprite.
Changing the rotation of the main Sprite was the reason of this issue...
So, thank you and forget about this unclear question!!! :)
private function drawLine():void{
if(!clip){
clip = new Sprite();
addChild(clip);
}
var g:Graphics = clip.graphics;
g.clear();
g.lineStyle(1,0xffffff,1);
g.beginFill(0xffffff,1);
g.drawCircle(Math.sin(rota)*this.width/4,Math.cos(rota)*this.height/4,3);
g.endFill();
}
I was changing the rotation property of the clip, so it was usefulness
Now I have a pretty good result.
Solved...
Sorry again...
As you can see the particles are now set in the right direction an I have no more hitTest issues...
Particles are now moving on the direction showed by the white points.
[/edit]
The first thing that pops out at me is you're potentially modifying the position of both x and y properties twice.
If you run the logic once, and store your directionality, then you should be able to update the position of your ball in one go.
Replace your moveBall function with the following...
private var h:int = 1;
private var v:int = 1;
public function moveBall(e:Event):void {
speedx = Math.sin(deg2rad(rotation+90))*speed;
speedy = Math.cos(deg2rad(rotation+90))*speed;
if (x + radius + (speedx * h) > this.loaderInfo.width || (x + (speedx * h) - radius < 0)) {
h *= -1;
}
if (y + radius + (speedy * v) > this.loaderInfo.height || (y + (speedx * v) - radius < 0)) {
v *= -1;
}
this.x += (speedx * h);
this.y += (speedy * v);
}
As I need to set a Sprite in the right direction when the Ball instance change it's "pseudo rotation" (I avoid here the hitTest features)...
I've now two classes...
Do you thing I'm searching in the bright side of code or is it totally unclear? ;)
This is just a test, and I didn't spent time to code since a few years.
So this test is just to revise some basics about trigonometry...
Don't hesitate, to be rude if I'm wrong!
My new class Main :
package com
{
import com.display.Ball;
import flash.display.Graphics;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
[SWF(width = "400", height = "300", frameRate = "60", backgroundColor = "#dddddd")]
public class Main extends MovieClip
{
private var b1:Ball;
private var b2:Ball;
private var b3:Ball;
private var b4:Ball;
private var b5:Ball;
private var testClip:Sprite;
private const ANGLE_TOP_LEFT:int=135;
private const ANGLE_BOTTOM_LEFT:int=-135;
private const ANGLE_TOP_RIGHT:int=45;
private const ANGLE_BOTTOM_RIGHT:int=-45;
public function Main()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
trace("stageSize = " + getStageSize() + ", fps = " + stage.frameRate);
drawlineGuides();
addBalls();
stage.addEventListener(Event.RESIZE,onStageResize);
}
private function addBalls():void{
b1 = new Ball(500/2,250/2,10);
addChild(b1);
b1.color = 0x6666cc;
b1.rota = 135;
b1.drawBall();
b1.move(5);
b2 = new Ball(100,100,10);
addChild(b2);
b2.color = 0xff9900;
b2.rota = -110;
b2.drawBall();
b2.move(4);
b3 = new Ball(50,80,10);
addChild(b3);
b3.color = 0xff0000;
b3.rota = 60;
b3.drawBall();
b3.move(3);
b4 = new Ball(75,20,10);
addChild(b4);
b4.color = 0x00aa00;
b4.rota = 10;
b4.drawBall();
b4.move(4);
b5 = new Ball(125,130,10);
addChild(b5);
b5.color = 0x8457a2;
b5.rota = -45;
b5.drawBall();
b5.move(4);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b1.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b2.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b3.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b4.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b5.pauseResume);
}
private function rotate(e:Event):void{
testClip.rotation = b2.rotation-45;
}
private function getStageSize():Point{
var p:Point= new Point(stage.stageWidth,stage.stageHeight);
return p;
}
private function drawlineGuides():void{
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1,0x000000,1);
g.moveTo(0,stage.stageHeight/2);
g.lineTo(stage.stageWidth,stage.stageHeight/2);
g.moveTo(stage.stageWidth/2,0);
g.lineTo(stage.stageWidth/2,stage.stageHeight);
}
private function onStageResize(e:Event):void{
drawlineGuides();
}
}
}
And here is my new class Ball :
package com.display
{
/* this import is optionnal
if you want to run this class without the BongSound instance
comment all lines where the var bSound is called
*/
//import com.media.sound.BongSound;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.media.Sound;
public class Ball extends Sprite
{
private var _radius:int;
private var _rotation:Number;
private var _color:int;
private var _g:Graphics;
private var _g2:Graphics;
private var _speed:Number;
private var speedx:Number;
private var speedy:Number;
public var rota:Number;
private var smallCircle:Sprite;
private var rendered:Boolean = false;
public var paused:Boolean = false;
private const ZERO:uint = 0;
//private var bSound:BongSound;
/**
* Ball(posx:Number,posy:Number,radius:uint)<br/>
* this constructor create an instance of a bouncing ball<br/>
* the posx and posy must be included in the range of the defined stageWidth and stageHeight!<br/>
* Otherwise, the ball will be placed in the stage range.
*/
public function Ball(posx:Number,posy:Number,radius:uint)
{
//bSound = new BongSound();
smallCircle = new Sprite();
this.addChild(smallCircle);
this._radius = radius;
this.x = posx;
this.y = posy;
_g = this.graphics;
_g2 = smallCircle.graphics;
}
private function checkStageSize():void{
if(this.x + radius + speedx >= this.stage.stageWidth){
this.x = this.stage.stageWidth - this.width;
}
if(this.y + radius + speedy >= this.stage.stageHeight){
this.y = this.stage.stageHeight - this.height;
}
if(this.x - radius + speedx <= ZERO){
this.x = this.width;
}
if(this.y - radius + speedy <= ZERO){
this.y = this.height;
}
}
public function get speed():Number
{
return _speed;
}
public function set speed(value:Number):void
{
_speed = value;
}
public function get color():int
{
return _color;
}
public function set color(value:int):void
{
_color = value;
}
public function get radius():int
{
return _radius;
}
public function set radius(value:int):void
{
_radius = value;
}
/**
* drawBall()<br/>
* this function draws the main Ball Object
*/
public function drawBall():void
{
_g.clear();
_g.lineStyle(1,0x666666,1);
_g.beginFill(_color,1);
_g.drawCircle(0,0,this._radius);
_g.endFill();
_g.lineStyle(1,0x666666,1);
_g.beginFill(0xffffff,1);
_g.endFill();
}
/**
* drawPoint()<br/>
* this function draws the Point Object wich is placed in the direction/rotation of the main Ball instance.
*/
public function drawPoint():void{
_g2.clear();
_g2.lineStyle(1,0x666666,1);
_g2.beginFill(0xffffff,1);
_g2.drawCircle(ZERO, ZERO, this._radius/2);
smallCircle.x = Math.sin(deg2rad(rota+90))*this.radius/2;
smallCircle.y = Math.cos(deg2rad(rota+90))*this.radius/2;
_g2.endFill();
}
/**
* move(speed:Number):void<br/>
* this function set the speed and makes the Ball move.<br/>
* The displace function is called when an ENTER_FRAME event is triggered.
*/
public function move(speed:Number):void{
this.speed = speed;
this.addEventListener(Event.ENTER_FRAME,displace);
}
/**
* getRota():Number<br/>
* this function returns the rotation of the Ball instance.<br/>
* the rotation is returned in degrees.
*/
public function getRota():Number{
return rad2deg(Math.atan2(speedy,speedx));
}
/**
* pauseResume(e:MouseEvent):void
* Pause or resume movement.
*/
public function pauseResume(e:MouseEvent):void{
switch(paused){
case false:
this.removeEventListener(Event.ENTER_FRAME,displace);
paused = true;
break;
case true:
this.addEventListener(Event.ENTER_FRAME,displace);
paused = false;
break;
}
}
/**
* checkBounds():void<br/>
* <p>
* this function plays a Sound when the Ball instance hit the bounds.<br/>
* the rota variable is updated (even if the rotation of the Ball instance don't change).<br/>
* If the stage is resized, a call to checkStageSize() set the positions x & y in the bounds of the Stage.
* </p>
* #see checkStageSize()
*/
private function checkBounds():void{
if(this.x + radius + speedx >= this.stage.stageWidth){
//bSound.play();
rota = rad2deg(Math.atan2(-speedy,-speedx));
}
if(this.y + radius + speedy >= this.stage.stageHeight){
//bSound.play();
rota = rad2deg(Math.atan2(speedy,speedx));
}
if(this.x - radius + speedx <= ZERO){
//bSound.play();
rota = rad2deg(Math.atan2(-speedy,-speedx));
}
if(this.y - radius + speedy <= ZERO){
//bSound.play();
rota = rad2deg(Math.atan2(speedy,speedx));
}
checkStageSize();
}
/**
* <p>
* displace(e:Event):void
* displace the ball and calls drawPoint to place the sub-Sprite depending of the "rotation" of the Ball instance.</p>
* #see #drawPoint()
* #see #checkBounds()
*/
private function displace(e:Event):void{
checkBounds();
speedx = Math.sin(deg2rad(rota+90))*speed;
speedy = Math.cos(deg2rad(rota+90))*speed;
this.x += speedx;
this.y += speedy;
drawPoint();
}
public function deg2rad(value:Number):Number{
return value/180*Math.PI;
}
public function rad2deg(value:Number):Number{
return value*180/Math.PI;
}
}
}
PrintScreens :
It is now possible to continue the moves even when the Stage is Resized avoiding the issues I had in the past...

Calculate a trajectory for an object, when given an angle and a velocity in flash

I am trying to launch a cannonball from a cannon and have it follow a realistic path. The angle of fire changes depending on the orientation of the cannon (automatically orientates to mouse pointer). So what I'm trying to figure out, is how to move a cannonball along a parabolic path, when given an angle, and a set velocity.
I've read that this can be done without complicated trigonometry (never listened to it in highschool), and can be calculated simply by adding gravity to the yVelocity every tick. However, at this moment, I don't know how to calculate the initial yVelocity (again, depending on cannon orientation).
You can see the current animation here: http://kate.ict.op.ac.nz/~welfajw1/portfolio/videos/task3-assignment2.swf
This is all done in AS3, and the code I have is as follows:
Main timeline code:
import flash.display.*;
import flash.events.*;
import flash.geom.*;
var cannonball:ball_mc;
var angleDegree;
myCannon.addEventListener(Event.ENTER_FRAME, cannonEnterFrame);
function cannonEnterFrame(pEvt)
{
var mc = myCannon;
var mg = myCannon.myGun;
//find angle for orientation
var angleRadian = Math.atan2(mouseY - mc.y, mouseX - mc.x);
//convert to degrees
angleDegree = angleRadian * 180 / Math.PI;
//limit rotation
if(angleDegree > -63 && angleDegree < 20)
mg.rotation = angleDegree;
}
stage.addEventListener(Event.ENTER_FRAME, stageRefresh);
function stageRefresh(pEvt)
{
if (cannonball)
{
//move every "tick"
cannonball.move();
}
}
stage.addEventListener(MouseEvent.CLICK, mouseClicked);
function mouseClicked(pEvt)
{
//starting position of the ball
cannonball = new ball_mc(100, 475);
//SEND IN INITIAL x, y VELOCITIES
cannonball.fire(20, angleDegree);
//add to stage
stage.addChild(cannonball);
}
ball_mc code:
package
{
import flash.display.MovieClip;
import flash.sensors.Accelerometer;
import flashx.textLayout.formats.Float;
public class ball_mc extends MovieClip
{
//constant gravity
public static const g:Number = 2;
//starting velocities
private var ux:Number;
private var uy:Number;
public function ball_mc(startX:int, startY:int)
{
x = startX;
y = startY;
}
public function fire(vx:Number, vy:Number):void
{
ux = vx;
uy = vy;
}
public function move():void
{
//distance moved in x dir
var sx:Number = ux;
//new velocity in y dir
var vy:Number = uy + g;
//distance moved in y dir
var sy:Number = uy + g/2;
//apply movement
x += sx;
y += sy;
//save new y velocity
uy = vy;
}
}
}
You need a little bit of physics.
Initial speed must be calculated by using some criteria that you add on your own. One example is to calculate initial speed by using the distance between the mouse and the cannon, at the time the mouse is pressed. If the distance is greater the projectile will have a bigger speed, and if the distance is smaller the projectile will have smaller speed.
The you add an Event Listener with type ENTER_FRAME.
I guess it's 2 dimensional animation so you have to find the current x and y at any point in time.
Here's a little bit of code:
var TimeperFrame:Number = 1/fps //fps is not a constant, here you should add a number, a value that you previously added in fla. document properties. I usualy use 60 fps
var Time:Number = 0;
addEventListener(ENTER_FRAME, movingCannonBall);
function movingCannonBall(e:Event):void
{
Time += TimeperFrame;
}
Now here's the equitation for trajectory of projectile.
x = xo + vxo·t
y = yo + vyo·t - 0.5·g·t^2
yo = initial height of your cannon ball
vyo = initial y velocity; vyo = vo·sin θ
t = time passed, we conrol that by upper code
g = acceleration (9,81 m/s^2) at Earth's surface
xo = initial distance for the start
vxo = initial x velocity; vxo = vo·cos θ
Now in the upper code we add these equitations and it should look like this:
var TimeperFrame:Number = 1/fps
var Time:Number = 0;
var initx: Number = cannonball.x;
var inity: Number = cannonball.y;
var initVelocity: Number = (you define initial Velocity by your criteria)
var G: Number = 9.81;
addEventListener(ENTER_FRAME, movingCannonBall);
function movingCannonBall(e:Event):void
{
Time += TimeperFrame;
cannonball.x = initx + Math.cos(angle) * initVelocity * Time;
cannonball.y = inity + Math.sin(angle) * initVelocity * Time - G * Time * Time * 0.5
}
This should work. I have use this code many times and it's effiecient and also it's simple.

Dealing with twip precision limit placed on DisplayObject positioning

I'm working on a project to render xps content in flash, and am running into the twip (1/20 of a unit) precision limit in flash. The twip limit is mentioned in the swf file format, but in the flash as3 documentation it's only mentioned in PrintJob.addPage method. However, when setting the DisplayObject x and y properties, any precision less than 0.05 is rounded. Regardless of any scaling. The code below demonstrates this issue.
package
{
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.GraphicsPathCommand;
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.StageScaleMode;
public class TWIPTest extends Sprite
{
// can change behaviour by switching between createPoint and drawPoint
public function TWIPTest()
{
this.stage.scaleMode = StageScaleMode.NO_SCALE;
// scale our unit square so we can see what is happening
scaleX = 400; scaleY = 400;
// draw grid
var grid : Sprite = new Sprite();
addChild(grid);
grid.graphics.lineStyle(TWIP, 0x888888);
drawGrid(1 / TWIP, 1 / TWIP, TWIP, TWIP, grid);
// center of unit square
var cx : Number = 0.5;
var cy : Number = 0.5;
var shape : Shape;
// GREEN in middle of unit square
this.addChild(createPoint(cx, cy, 0x00FF00));
//drawPoint(cx, cy, 0x00FF00, this.graphics);
// BLUE one TWIP away from GREEN
this.addChild(createPoint(cx + TWIP, cy + TWIP, 0x0000FF));
//drawPoint(cx + TWIP, cy + TWIP, 0x0000FF, this.graphics);
// RED half a TWIP away from GREEN (this does not work....)
this.addChild(createPoint(cx + (TWIP / 2), cy + (TWIP / 2), 0xFF0000, 0.5));
//drawPoint(cx + (TWIP / 2), cy + (TWIP / 2), 0xFF0000, this.graphics, 0.5);
// now insert new container to work around limit encontered with RED point
var container : Sprite = new Sprite();
this.addChild(container);
container.scaleX = 0.5; container.scaleY = 0.5;
container.addChild(createPoint(2 * cx + TWIP, 2 * cy + TWIP, 0xFF00FF, 1.0, 2 * TWIP));
//drawPoint(cx + (TWIP / 2), cy + (TWIP / 2), 0xFF00FF, container.graphics);
}
static private function createPoint(x:Number, y:Number,color:uint,alpha:Number=1.0,radius:Number=0.05):Shape
{
var shape : Shape = new Shape();
shape.x = x; shape.y = y;
shape.graphics.beginFill(color,alpha);
shape.graphics.drawCircle(0, 0, radius);
shape.graphics.endFill();
return shape;
}
static private function drawPoint(x:Number, y:Number,color:uint,target:Graphics,alpha:Number=1.0,radius:Number=0.05):void
{
target.beginFill(color,alpha);
target.drawCircle(x, y, radius);
target.endFill();
}
// drawGrid from #Feltope
private function drawGrid(numColumns:Number, numRows:Number, cellHeight:Number, cellWidth:Number, grid:Sprite):void
{
for (var col:Number = 0; col &lt numColumns + 1; col++)
{
for (var row:Number = 0; row &lt numRows + 1; row++)
{
grid.graphics.moveTo(col * cellWidth, 0);
grid.graphics.lineTo(col * cellWidth, cellHeight * numRows);
grid.graphics.moveTo(0, row * cellHeight);
grid.graphics.lineTo(cellWidth * numColumns, row * cellHeight);
}
}
}
static private const TWIP : Number = 0.05;
}
}
This posting also mentions this issue.
The problem is that in parsing xps files this can happen anytime, and many times, for example
&ltCanvas RenderTransform="96.201126,0,0,-95.787476,713.62598,207.05859"&gt
&ltPath Data="..." RenderTransform="0.010394889,0,0,-0.010439778,-7.4180626,2.1616458"&gt
Notice the dx in the Path element, which seems trivial, but the parent Canvas (and other parents above this) scale it up so the rounding of dx to 0 or 0.05 is very noticeable.
I could add Sprite containers whenever this happens, as in the code above, but that's going to bloat memory and slow down rendering.
My question (finally), has anyone dealt with this issue? Any ideas on a better way of handling this limit? Workarounds? Any help appreciated. cheers
what is the biggest problem here? How it looks on screen or that after parsing the precision is lost? You can't do with rendering but you could do modified DisplayObject that will store for example position then reading it will contain the precision kept. or am I missing the point?:)
Edit:
Following is an example of custom display objects (Shape and Sprite)
import flash.display.Shape;
import flash.display.Sprite;
internal class MySprite extends Sprite
{
protected var m_nX:Number;
protected var m_nY:Number;
override public function get x():Number
{
return m_nX;
}
override public function set x(value:Number):void
{
m_nX = value;
super.x = m_nX;
}
override public function get y():Number
{
return m_nY;
}
override public function set y(value:Number):void
{
m_nY = value;
super.y = m_nY;
}
}
internal class MyShape extends Shape
{
protected var m_nX:Number;
protected var m_nY:Number;
override public function get x():Number
{
return m_nX;
}
override public function set x(value:Number):void
{
m_nX = value;
super.x = m_nX;
}
override public function get y():Number
{
return m_nY;
}
override public function set y(value:Number):void
{
m_nY = value;
super.y = m_nY;
}
}
Add this to your example and replace any instantiation of Shape and use MyShape, same for Sprite, replace it by MySprite. There will be no visual difference but the x and y of them will return precise values.
This is a sample test for precision keeping:
addChild(createPoint(0.51000005, 0.510000049, 0x800000, 1, TWIP));
var disp :DisplayObject = getChildAt(numChildren - 1);
trace(disp.x, disp.y);
will trace 0.51000005, 0.510000049 but above test added to your example without MySprite and MyShape custom DisplayObjects will return 0.5 0.5.

Constrain MovieClip drag to a circle

...well, to an incomplete circle.
I have a draggable slider that looks like this:
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;
}
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/
Rather than trying to move the point along the partial path of the circle, why not fake it and use a knob/dial? Skin it to look like the dot is moving along the path.
Then just set the rotation of the knob to:
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;
You can test this by throwing it in an enter frame event, but you'll obviously want to put some logic in to control how the knob starts moving and when it should stop.
100% working code.
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;
}
}
}
Something like this ought to work out:
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;
}
Obviously, modify as needed, but you just need to send in a center point, radius and then a radian (you can obtain with angle * (Math.PI / 180)). You could easily hard code in the first two params if they don't change. What does change is the radian, and that is what you will need to change over time, as your mouse is dragging (defined by the mouseX distance to the center point - positive or negative).
Hopefully that helps get you started -
update
This was how I was working this out - tho its a tad buggy, in that the puck resets to 0 degrees when the sequence starts. That being said, I just saw that - #Nox got this right. I'll post what I was arriving at using my projectLocation function anyways ;)
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;
}
}
}
Main difference is that I'm obtaining the angle via horizontal (x) movement, and not checking against the cursors angle. Thos, trapping the values manually feels kinda hacky compared to #Nox very good soution. Would of cleaned up had I kept going;)
Nice question - Cheers