Define rectangle how to - actionscript-3

I 'm trying to develop a game.In my project i have a movieclip which is the character for the game.I also have 5 buttons(left right down up and stop) so the user can move.The game is for android.In my library i have a square with dimnesions 50 x 50.I have filled the stage with copies of this square with different instances so the character can move on them.The point of the game is that the user moves the character and with HitTestObject() function he removes the square that he is walking on.I used a timer so if 5 seconds are over and the player hasn't completed a rectangle of missing squares the squares appear again.But if he makes a rectangle(lets say that he makes it by removing 8 squares) the squares that are inside this rectangle must dissapear also.I need to find a way to see when the player completes this rectangle.I also think that my aproach to what i want to do is draft and there is a much better one.
Thanks in advance!
i think i may haven't explained my problem well..sorry for this its my mistake!everytime the character touches on a square it disapears.if in 5 seconds the character hasn't completed a "rectangle" of missing squares they appear again.When i say rectangle i mean rectangle of squares.For example if 4 squares missing(not in a row) its a rectangle,or if 8 squares or 12 etc...
import flash.utils.Timer;
stop();
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
var hunterxspeed:Number=0;
var hunteryspeed:Number=0;
var timerformove:Timer=new Timer(30);
var timerforblocks:Number=0;
var simpletimer:Number=0;
var rectanglearray:Array = new Array();
var sumvertical:int=0;
var sumhorizontal:int=0;
var i:int=0;
moveup.addEventListener(TouchEvent.TOUCH_TAP, fl_TapHandler_2);
function fl_TapHandler_2(event:TouchEvent):void
{
hunteryspeed = -4;
hunterxspeed = 0;
rectanglearray[0] = 0;
rectanglearray[1] = -1;
sumvertical+=rectanglearray[0]+rectanglearray[1];
i++;
}
movedown.addEventListener(TouchEvent.TOUCH_TAP, fl_TapHandler_3);
function fl_TapHandler_3(event:TouchEvent):void
{
hunteryspeed = 4;
hunterxspeed = 0;
rectanglearray[0] = 0;
rectanglearray[1] = 1;
sumvertical+=rectanglearray[0]+rectanglearray[1];
i++;
}
moveright.addEventListener(TouchEvent.TOUCH_TAP, fl_TapHandler_4);
function fl_TapHandler_4(event:TouchEvent):void
{
hunterxspeed = 4;
hunteryspeed = 0;
hunter.scaleX = 2/4;
rectanglearray[2] = 1;
rectanglearray[3] = 0;
sumhorizontal+= rectanglearray[2] +rectanglearray[3];
i++;
}
moveleft.addEventListener(TouchEvent.TOUCH_TAP, fl_TapHandler_5);
function fl_TapHandler_5(event:TouchEvent):void
{
hunterxspeed = -4;
hunteryspeed = 0;
hunter.scaleX = -(2/4);
rectanglearray[2] = -1;
rectanglearray[3] = 0;
sumhorizontal+= rectanglearray[2] + rectanglearray[3];
i++;
}
stopmove.addEventListener(TouchEvent.TOUCH_TAP, fl_TapHandler_6);
function fl_TapHandler_6(event:TouchEvent):void
{
hunterxspeed = 0;
hunteryspeed = 0;
}
timerformove.addEventListener(TimerEvent.TIMER,huntermove);
function huntermove(e:TimerEvent) :void
{
if (hunter.y > stage.stageHeight-160)
{
hunter.y = stage.stageHeight-161;
hunteryspeed = 0;
}
if (hunter.y < 20)
{
hunter.y = 21;
hunteryspeed = 0;
}
if (hunter.x > stage.stageWidth-30)
{
hunter.x = stage.stageWidth-31;
hunterxspeed = 0;
}
if (hunter.x < 30)
{
hunter.x = 31;
hunterxspeed = 0;
}
hunterlegs.y = hunter.y+20;
hunterlegs.x = hunter.x;
hunter.y += hunteryspeed;
hunter.x += hunterxspeed;
if(timerforblocks==5000)
{
simpletimer+=50;
}
if(simpletimer>timerforblocks)
{
simpletimer=0;
timerforblocks=0;
}
if(hunterlegs.hitTestObject(grass))
{
timerforblocks=5000;
grass.x=grass.x-100;
}
if(hunterlegs.hitTestObject(grass1))
{
timerforblocks=5000;
grass1.x=grass1.x-100;
}
if(hunterlegs.hitTestObject(grass2))
{
timerforblocks=5000;
grass2.x=grass2.x-200;
}
if(hunterlegs.hitTestObject(grass12))
{
timerforblocks=5000;
grass12.x=grass12.x-300;
}
if(hunterlegs.hitTestObject(grass22))
{
timerforblocks=5000;
grass22.x=grass22.x-300;
}
if(hunterlegs.hitTestObject(grass21))
{
timerforblocks=5000;
grass21.x=grass21.x-300;
}
if(hunterlegs.hitTestObject(grass20))
{
timerforblocks=5000;
grass20.x=grass20.x-300;
}
if(hunterlegs.hitTestObject(grass10))
{
timerforblocks=5000;
grass10.x=grass10.x-300;
}
if(sumvertical==0 && sumhorizontal==0 && i==4)
{
grass.x=2000;
grass1.x=2000;
grass2.x=2000;
grass12.x=2000;
grass22.x=2000;
grass21.x=2000;
grass20.x=2000;
grass10.x=2000;
}
}
timerformove.start();
All objects are in the first frame.
I named grass the first square and i copied it to 100 more or less with different instances.
Hunter is my character.

A rectangle has 4 corners, and the character ends up at the origin square.
What you could try is to keep an array where you store the movement of the character. For instance, if the character moves upward, store {x: 0, y: -1} and if it moves left, store {x: 1, y: 0}.
Then, when it's time to check for valid rectangles, step through the list of movements and keep track of corners (where the direction changes) and check to see if the following criteria is fulfilled:
The sum of all horizontal movements is 0
The sum of all vertical movements is 0
The sum of corners is 4
This will check to see if the character ended up in the same position it originally started in and if it's movements formed a valid rectangle.
If the criteria isn't met, do the same check of the array, but start on position number 2. Etc.
This way, you will check if a valid rectangle has been formed at any stage during the character's movements (even if a rectangle was only part of the character's total moves). It will also validate for a rectangle of any size.
Here's an illustration that could perhaps clear things up:
In the first case, when the horizontal and vertical counters both sums 0, the corner count is 4. This is a valid rectangle.
In the second case, when the horizontal and vertical counters both sums 0, the corner count is 6. This is not a rectangle.
Edit to respond to follow-up question:
It's a bit tricky since it appears like the character is able to move around freely. The system described above becomes trivial if you have a game where the character moves across the grid one step at a time.
The system still applies though, but you need to look at the order of which the "squares" are being eaten. You can still make the same array of movement by keeping track of the last square to be hit and what place it had in the grid.
For example, if we follow the pattern of the first example the squares are eaten in the following order:
Square 1: [2, 4]
Square 2: [1, 4] -> subtract from last position: [1-2, 4-4] = [-1, 0] <- add this to the movement array
Square 3: [1, 3] -> subtract from last position: [1-1, 3-4] = [0, -1] <- add this to the movement array
Square 4: [1, 2] -> subtract from last position: [1-1, 2-3] = [0, -1] <- add this to the movement array
...
Square 12: [3, 4] -> subtract from last position: [3-4, 4-4] = [-1, 0] <- add this to the movement array
Square 13: [2, 4] -> subtract from last position: [2-3, 4-4] = [-1, 0] <- add this to the movement array
You will now have the same movement array as in the example above.

Related

Pairing a draggable object to a target object in AS3

I'm currently stuck with my approach below. I'm not entirely sure if using "hitTestObject" method is appropriate in pairing the pieces to their respective place. I was able to at least match the chess piece to their respective location (that's the best I can do and I feel i'm doing it wrong) but I'm now stuck in counting how many pieces are actually in their correct places. e.g. when I move the pawn to a different tile, it will still count as one, I also want to avoid duplicate counting, example, If pawn is already in the correct location, it will just count as 1, and if it was moved, then that count will be removed. Only count the pieces that are in the correct tile.
My goal here is to be able to make all the chess pieces draggable and determine if they're in their respective location. If ALL the chess pieces are in their location, it will trace or call a function.
Thank you!
import flash.events.Event;
import flash.display.MovieClip;
import flash.events.MouseEvent;
/* Declaring an X and Y variable to be used as a reset container */
var xPos: int, yPos: int;
/* Attaching event listeners for each chess piece */
addListeners(
king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8);
/* Getting the original x and y postion to be used as a reset */
function getPosition(currentTarget: Object): void {
xPos = currentTarget.x;
yPos = currentTarget.y;
}
/* Function to get the suffix value of an object. example, I need to get the value 4 from "pawn_4" */
function getLastCharInString($s: String, $pos: Number): String {
return $s.substr($s.length - $pos, $s.length);
}
/* A simple function that rotates the chess piece */
function lift(object: Object, rot: Number) {
object.rotation = rot;
}
function dragObject(e: MouseEvent): void {
getPosition(e.currentTarget);
lift(e.currentTarget, -10);
getChildByName(e.currentTarget.name + "_hs").alpha = 1;
e.currentTarget.startDrag();
}
/* This variable is supposed to hold the value of each piece that is correctly placed in each tile.
The total score should be 16 as there are 16 pieces. Only correcly placed piece should be added in the total score. */
var counter:int;
function stopDragObject(e: MouseEvent): void {
var curretTarget = e.currentTarget.name;
lift(e.currentTarget, 0);
/* Hide active hotspots */
getChildByName(e.currentTarget.name + "_hs").alpha = 0;
var multiplePieceSufix = Number(getLastCharInString(curretTarget, 1));
if (multiplePieceSufix >= 1) {
/* Boolean variables that checks whether the current piece is active*/
var isPawn: Boolean = false,
isBishop: Boolean = false,
isKnight: Boolean = false,
isRook: Boolean = false,
currentTargeName;
var widthDiff = getChildByName(e.currentTarget.name + "_hs").width - getChildByName(e.currentTarget.name).width / 2;
var heightDiff = getChildByName(e.currentTarget.name + "_hs").height - getChildByName(e.currentTarget.name).height / 2;
if (curretTarget.substr(0, 4) == "pawn") {
isPawn = true;
} else if (curretTarget.substr(0, 6) == "bishop") {
isBishop = true;
} else if (curretTarget.substr(0, 6) == "knight") {
isKnight = true;
} else if (curretTarget.substr(0, 4) == "rook") {
isRook = true;
}
if (isPawn == true) {
/* there are total of 8 pieces of pawn */
for (var w = 1; w < 9; w++) {
currentTargeName = this["pawn_" + w + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
/* For some reason the chess pieces are not aligning with their "_hs" version, I already checked their registry point and it seem to be normal.
so to fix, I had to manually add some hard coded values to adjust their location. */
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isBishop == true) {
for (var x = 1; x < 3; x++) {
currentTargeName = this["bishop_" + x + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 9;
e.currentTarget.y = currentTargeName.y + currentTargeName.height - 18;
}
}
} else if (isKnight == true) {
for (var y = 1; y < 3; y++) {
currentTargeName = this["knight_" + y + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}
}
} else if (isRook == true) {
for (var z = 1; z < 3; z++) {
currentTargeName = this["rook_" + z + "_hs"];
if (e.target.hitTestObject(currentTargeName)) {
e.currentTarget.x = currentTargeName.x - 8;
e.currentTarget.y = currentTargeName.y + 62;
}
}
}
} else {
if (e.target.hitTestObject(getChildByName(e.currentTarget.name + "_hs"))) {
/* Again, I'm not sure why the pieces are not aligning as intended.
modX and modY is a holder for the adjustment value. I'm not comfortable
seeing this approach myself, but I also run out of ideas how to fix it. */
var modX: Number, modY: Number;
if (e.currentTarget.name == "king") {
modX = 11;
modY = 53;
} else {
modX = 11;
modY = 29;
}
e.currentTarget.x = getChildByName(e.currentTarget.name + "_hs").x - modX;
e.currentTarget.y = getChildByName(e.currentTarget.name + "_hs").y + getChildByName(e.currentTarget.name + "_hs").height - modY;
}
}
/* This is supposed to add to the total score or count of how many pieces are placed correctly.
Thie problem with thi scounter, as it also counts any piece that is places to any "_hs" */
counter++;
trace(counter);
e.currentTarget.stopDrag();
}
function addListeners(...objects): void {
for (var i: int = 0; i < objects.length; i++) {
objects[i].addEventListener(MouseEvent.MOUSE_DOWN, dragObject);
objects[i].addEventListener(MouseEvent.MOUSE_UP, stopDragObject);
// hide hotspots
getChildByName( objects[i].name + "_hs" ).alpha = 0;
}
}
Source: Download the FLA here
--
Updates:
I have added comments in my code to clarify what I'm trying to accomplish.
I'm planning to do board game in flash which has similar function and behaviour to this. User can drag the object to a specified tile and check wether that object belongs there or not.
After reviewing your code, your question is quite broad. I'm going pair it down to what seems to be your main concern - the score / counting correctly moved pieces.
Right now, you do the following every time an object is dragged:
counter++;
This means that the counter will increment no matter where you drag the object, and no matter how times you drag the object. (so even if the piece was already in the correct spot, if you dragged it a second time it will still increment your counter).
What you need to do, is associate a flag with each object to indicate whether it is in the correct location or not, and set that flag to the appropriate value every time that object is done dragging.
Something like this:
//don't use target, use currentTarget
if (e.currentTarget.hitTestObject(currentTargeName)) {
e.currentTarget.correct = true; //since MovieClips are dynamic, you can just make up a property on them and assign a value to it.
//to fix your alignment:
e.currentTarget.x = currentTargeName.x + ((currentTargetName.width - e.currentTarget.width) * 0.5);
e.currentTarget.y = currentTargeName.y + currentTargeName.height;
}else{
//if the hit test is false, mark it as NOT correct
e.currentTarget.correct = false;
}
Then, later to know the current count, iterate over all the pieces and check their correct value. This would be much easier if all your pieces were in an array.
var allPieces:Array = [king, queen, bishop_1, bishop_2, knight_1, knight_2, rook_1, rook_2,
pawn_1, pawn_2, pawn_3, pawn_4, pawn_5, pawn_6, pawn_7, pawn_8];
function countCorrect():Boolean {
var ctr:int = 0;
for(var i:int=0;i<allPieces.length;i++){
if(allPieces[i].correct) ctr++;
}
return ctr;
}
trace(countCorrect() + " of " allPieces.length " are correct");
As an aside, this best way to do this would be with some custom class files. That would however require a complete refactoring of your code.
Also, you probably don't want to use hitTestObject, as even if a piece is mostly over a neighbor, it will still be true as long as 1 pixel of it's bound touch 1 pixel of the tile. Better would be to do a hitTestPoint on the tile, and pass in the center point of the piece (the the middle of the piece has to be touching the tile for it to count).
//a point that is the center of the events current target (the piece)
var point:Point = new Point();
point.x = e.currentTarget.x + (e.currentTarget.width * 0.5);
point.y = e.currentTarget.y - (e.currentTarget.height * 0.5);
if (currentTargetName.hitTestPoint(point)) {

How to draw dashed lines with using rectangles in AS3?

I'm using starling framework for my game project and it hasn't got any draw dashed line method. Because of this they suggest me to draw dashed lines with using small rectangles which is called quads.
My math is not enough for it, could you give a sample method for rectangles with dashed lines occurring.
Thanks..
This class by Andy Woodruff draws dashed lines
/*
DashedLine class
by Andy Woodruff (http://cartogrammar.com/blog || awoodruff#gmail.com)
May 2008
Still in progress; I'll get a more perfected version eventually. For now take it as is.
This is a Sprite with the capability to do basic lineTo drawing with dashed lines.
Example:
var dashy:DashedLine = new DashedLine(2,0x333333,new Array(3,3,10,3,5,8,7,13));
dashy.moveTo(120,120);
dashy.beginFill(0xcccccc);
dashy.lineTo(220,120);
dashy.lineTo(220,220);
dashy.lineTo(120,220);
dashy.lineTo(120,120);
dashy.endFill();
*/
package com.cartogrammar.drawing {
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Point;
import flash.display.CapsStyle;
public class DashedLine extends Sprite {
var lengthsArray:Array = new Array(); // array of dash and gap lengths (dash,gap,dash,gap....)
var lineColor:uint; // line color
var lineWeight:Number; // line weight
var lineAlpha:Number = 1; // line alpha
var curX:Number = 0; // stores current x as it changes with lineTo and moveTo calls
var curY:Number = 0; // same as above, but for y
var remainingDist:Number = 0; // stores distance between the end of the last full dash or gap and the end coordinates specified in lineTo
var curIndex = null; // current index in the length array, so we know which dash or gap to draw
var arraySum:Number = 0; // total length of the dashes and gaps... not currently being used for anything, but maybe useful?
var startIndex:int = 0; // array index (the particular dash or gap) to start with in a lineTo--based on the last dash or gap drawn in the previous lineTo (along with remainingDist, this is so our line can properly continue around corners!)
var fill:Shape = new Shape(); // shappe in the background to be used for fill (if any)
var stroke:Shape = new Shape(); // shape in the foreground to be used for the dashed line
public function DashedLine(weight:Number = 0, color:Number = 0, lengthsArray:Array = null){
if (lengthsArray != null){ // if lengths array was specified, use it
this.lengthsArray = lengthsArray;
} else { // if unspecified, use a default 5-5 line
this.lengthsArray = [5,5];
}
if (this.lengthsArray.length % 2 != 0){ // if array has more dashes than gaps (i.e. an odd number of values), add a 5 gap to the end
lengthsArray.push(5);
}
// sum the dash and gap lengths
for (var i:int in lengthsArray){
arraySum += lengthsArray[i];
}
// set line weight and color properties from constructor arguments
lineWeight = weight;
lineColor = color;
// set the lineStyle according to specified properties - beyond weight and color, we use the defaults EXCEPT no line caps, as they interfere with the desired gaps
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
// add fill and stroke shapes
addChild(fill);
addChild(stroke);
}
// basic moveTo method
public function moveTo(x:Number,y:Number):void{
stroke.graphics.moveTo(x,y); // move to specified x and y
fill.graphics.moveTo(x,y);
// keep track of x and y
curX = x;
curY = y;
// reset remainingDist and startIndex - if we are moving away from last line segment, the next one will start at the beginning of the dash-gap sequence
remainingDist = 0;
startIndex = 0;
}
// lineTo method
public function lineTo(x:Number,y:Number):void{
var slope:Number = (y - curY)/(x - curX); // get slope of segment to be drawn
// record beginning x and y
var startX:Number = curX;
var startY:Number = curY;
// positive or negative direction for each x and y?
var xDir:int = (x < startX) ? -1 : 1;
var yDir:int = (y < startY) ? -1 : 1;
// keep drawing dashes and gaps as long as either the current x or y is not beyond the destination x or y
outerLoop : while (Math.abs(startX-curX) < Math.abs(startX-x) || Math.abs(startY-curY) < Math.abs(startY-y)){
// loop through the array to draw the appropriate dash or gap, beginning with startIndex (either 0 or determined by the end of the last lineTo)
for (var i:int = startIndex; i < lengthsArray.length; i++){
var dist:Number = (remainingDist == 0) ? lengthsArray[i] : remainingDist; // distance to draw is either the dash/gap length from the array or remainingDist left over from the last lineTo if there is any
// get increments of x and y based on distance, slope, and direction - see getCoords()
var xInc:Number = getCoords(dist,slope).x * xDir;
var yInc:Number = getCoords(dist,slope).y * yDir;
// if the length of the dash or gap will not go beyond the destination x or y of the lineTo, draw the dash or gap
if (Math.abs(startX-curX) + Math.abs(xInc) < Math.abs(startX-x) || Math.abs(startY-curY) + Math.abs(yInc) < Math.abs(startY-y)){
if (i % 2 == 0){ // if even index in the array, it is a dash, hence lineTo
stroke.graphics.lineTo(curX + xInc,curY + yInc);
} else { // if odd, it's a gap, so moveTo
stroke.graphics.moveTo(curX + xInc,curY + yInc);
}
// keep track of the new x and y
curX += xInc;
curY += yInc;
curIndex = i; // store the current dash or gap (array index)
// reset startIndex and remainingDist, as these will only be non-zero for the first loop (through the array) of the lineTo
startIndex = 0;
remainingDist = 0;
} else { // if the dash or gap can't fit, break out of the loop
remainingDist = getDistance(curX,curY,x,y); // get the distance between the end of the last dash or gap and the destination x/y
curIndex = i; // store the current index
break outerLoop; // break out of the while loop
}
}
}
startIndex = curIndex; // for next time, the start index is the last index used in the loop
if (remainingDist != 0){ // if there is a remaining distance, line or move from current x/y to the destination x/y
if (curIndex % 2 == 0){ // even = dash
stroke.graphics.lineTo(x,y);
} else { // odd = gap
stroke.graphics.moveTo(x,y);
}
remainingDist = lengthsArray[curIndex] - remainingDist; // remaining distance (which will be used at the beginning of the next lineTo) is now however much is left in the current dash or gap after that final lineTo/moveTo above
} else { // if there is no remaining distance (i.e. the final dash or gap fits perfectly), we're done with the current dash or gap, so increment the start index for next time
if (startIndex == lengthsArray.length - 1){ // go to the beginning of the array if we're at the end
startIndex = 0;
} else {
startIndex++;
}
}
// at last, the current x and y are the destination x and y
curX = x;
curY = y;
fill.graphics.lineTo(x,y); // simple lineTo (invisible line) on the fill shape so that the fill (if one was started via beginFill below) follows along with the dashed line
}
// returns a point with the vertical and horizontal components of a diagonal given the distance and slope
private function getCoords(distance:Number,slope:Number):Point {
var angle:Number = Math.atan(slope); // get the angle from the slope
var vertical:Number = Math.abs(Math.sin(angle)*distance); // vertical from sine of angle and length of hypotenuse - using absolute value here and applying negative as needed in lineTo, because this number doesn't always turn out to be negative or positive exactly when I want it to (haven't thought through the math enough yet to figure out why)
var horizontal:Number = Math.abs(Math.cos(angle)*distance); // horizontal from cosine
return new Point(horizontal,vertical); // return the point
}
// basic Euclidean distance
private function getDistance(startX:Number,startY:Number,endX:Number,endY:Number):Number{
var distance:Number = Math.sqrt(Math.pow((endX-startX),2) + Math.pow((endY-startY),2));
return distance;
}
// clear everything and reset the lineStyle
public function clear():void{
stroke.graphics.clear();
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
fill.graphics.clear();
moveTo(0,0);
}
// set lineStyle with specified weight, color, and alpha
public function lineStyle(w:Number=0,c:Number=0,a:Number=1):void{
lineWeight = w;
lineColor = c;
lineAlpha = a;
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
}
// basic beginFill
public function beginFill(c:uint,a:Number=1):void{
fill.graphics.beginFill(c,a);
}
// basic endFill
public function endFill():void{
fill.graphics.endFill();
}
}
}
Thanks For Your Replies, I Wrote My Own Class Which Draws Rectangles To Create A Dashed Lines.
Thanks..

Does Canvas redraw itself every time anything changes?

I have done some research on how canvas works. It is supposed to be "immediate mode" means that it does not remember what its drawing looks like, only the bitmap remains everytime anything changes.
This seems to suggest that canvas does not redraw itself on change.
However, when I tested canvas on iPad (basically I keep drawing parallel lines on the canvas), the frame rate degrades rapidly when there are more lines on the canvas. Lines are drawn more slowly and in a more jumpy way.
Does this mean canvas actually have to draw the whole thing on change? Or there is other reason for this change in performance?
The HTML canvas remembers the final state of pixels after each stroke/fill call is made. It never redraws itself. (The web browser may need to re-blit portions of the final image to the screen, for example if another HTML object is moved over the canvas and then away again, but this is not the same as re-issuing the drawing commands.
The context always remembers its current state, including any path that you have been accumulating. It is probable that you are (accidentally) not clearing your path between 'refreshes', and so on the first frame you are drawing one line, on the second frame two lines, on the third frame three lines, and so forth. (Are you calling ctx.closePath() and ctx.beginPath()? Are you clearing the canvas between drawings?)
Here's an example showing that the canvas does not redraw itself. Even at tens of thousands of lines I see the same frame rate as with hundreds of lines (capped at 200fps on Chrome, ~240fps on Firefox 8.0, when drawing 10 lines per frame).
var lastFrame = new Date, avgFrameMS=5, lines=0;
function drawLine(){
ctx.beginPath();
ctx.moveTo(Math.random()*w,Math.random()*h);
ctx.lineTo(Math.random()*w,Math.random()*h);
ctx.closePath();
ctx.stroke();
var now = new Date;
var frameTime = now - lastFrame;
avgFrameMS += (frameTime-avgFrameMS)/20;
lastFrame = now;
setTimeout(drawLine,1);
lines++;
}
drawLine();
// Show the stats infrequently
setInterval(function(){
fps.innerHTML = (1000/avgFrameMS).toFixed(1);
l.innerHTML = lines;
},1000);
Seen in action: http://phrogz.net/tmp/canvas_refresh_rate.html
For more feedback on what your code is actually doing versus what you suspect it is doing, share your test case with us.
Adding this answer to be more general.
It really depends on what the change is. If the change is simply to add another path to the previously drawn context, then the canvas does not have to be redrawn. Simply add the new path to the present context state. The previously selected answer reflects this with an excellent demo found here.
However, if the change is to translate or "move" an already drawn path to another part of the canvas, then yes, the whole canvas has to be redrawn. Imagine the same demo linked above accumulating lines while also rotating about the center of the canvas. For every rotation, the canvas would have to be redrawn, with all previously drawn lines redrawn at the new angle. This concept of redrawing on translation is fairly self-evident, as the canvas has no method of deleting from the present context. For simple translations, like a dot moving across the canvas, one could draw over the dot's present location and redraw the new dot at the new, translated location, all on the same context. This may or may not be more operationally complex than just redrawing the whole canvas with the new, translated dot, depending on how complex the previously drawn objects are.
Another demo to demonstrate this concept is when rendering an oscilloscope trace via the canvas. The below code implements a FIFO data structure as the oscilloscope's data, and then plots it on the canvas. Like a typical oscilloscope, once the trace spans the width of the canvas, the trace must translate left to make room for new data points on the right. To do this, the canvas must be redrawn every time a new data point is added.
function rand_int(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive
}
function Deque(max_len) {
this.max_len = max_len;
this.length = 0;
this.first = null;
this.last = null;
}
Deque.prototype.Node = function(val, next, prev) {
this.val = val;
this.next = next;
this.prev = prev;
};
Deque.prototype.push = function(val) {
if (this.length == this.max_len) {
this.pop();
}
const node_to_push = new this.Node(val, null, this.last);
if (this.last) {
this.last.next = node_to_push;
} else {
this.first = node_to_push;
}
this.last = node_to_push;
this.length++;
};
Deque.prototype.pop = function() {
if (this.length) {
let val = this.first.val;
this.first = this.first.next;
if (this.first) {
this.first.prev = null;
} else {
this.last = null;
}
this.length--;
return val;
} else {
return null;
}
};
Deque.prototype.to_string = function() {
if (this.length) {
var str = "[";
var present_node = this.first;
while (present_node) {
if (present_node.next) {
str += `${present_node.val}, `;
} else {
str += `${present_node.val}`
}
present_node = present_node.next;
}
str += "]";
return str
} else {
return "[]";
}
};
Deque.prototype.plot = function(canvas) {
const w = canvas.width;
const h = canvas.height;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, w, h);
//Draw vertical gridlines
ctx.beginPath();
ctx.setLineDash([2]);
ctx.strokeStyle = "rgb(124, 124, 124)";
for (var i = 1; i < 9; i++) {
ctx.moveTo(i * w / 9, 0);
ctx.lineTo(i * w / 9, h);
}
//Draw horizontal gridlines
for (var i = 1; i < 10; i++) {
ctx.moveTo(0, i * h / 10);
ctx.lineTo(w, i * h / 10);
}
ctx.stroke();
ctx.closePath();
if (this.length) {
var present_node = this.first;
var x = 0;
ctx.setLineDash([]);
ctx.strokeStyle = "rgb(255, 51, 51)";
ctx.beginPath();
ctx.moveTo(x, h - present_node.val * (h / 10));
while (present_node) {
ctx.lineTo(x * w / 9, h - present_node.val * (h / 10));
x++;
present_node = present_node.next;
}
ctx.stroke();
ctx.closePath();
}
};
const canvas = document.getElementById("canvas");
const deque_contents = document.getElementById("deque_contents");
const button = document.getElementById("push_to_deque");
const min = 0;
const max = 9;
const max_len = 10;
var deque = new Deque(max_len);
deque.plot(canvas);
button.addEventListener("click", function() {
push_to_deque();
});
function push_to_deque() {
deque.push(rand_int(0, 9));
deque_contents.innerHTML = deque.to_string();
deque.plot(canvas);
}
body {
font-family: Arial;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
<div class="centered">
<p>Implementation of a FIFO deque data structure in JavaScript to mimic oscilloscope functionality. Push the button to push random values to the deque object. After the maximum length is reached, the first item pushed in is popped out to make room for the next value. The values are plotted in the canvas. The canvas must be redrawn to translate the data, making room for the new data.
</p>
<div>
<button type="button" id="push_to_deque">push</button>
</div>
<div>
<h1 id="deque_contents">[]</h1>
</div>
<div>
<canvas id="canvas" width="800" height="500" style="border:2px solid #D3D3D3; margin: 10px;">
</canvas>
</div>
</div>

Algorithm to solve the points of a evenly-distributed / even-gaps spiral?

First, just to give a visual idea of what I'm after, here's the closest result (yet not exactly what I'm after) image that I've found:
Here's the entire site-reference: http://www.mathematische-basteleien.de/spiral.htm
BUT, it doesn't exactly solve the problem I'm after. I would like to store an array of points of a very specific spiral algorithm.
The points are evenly distributed
The 360 degree cycles have an even gap
If I'm not mistaken, the first two points would be:
point[ 0 ] = new Point(0,0);
point[ 1 ] = new Point(1,0);
But where to go from here?
The only arguments I'd like to provide are:
the quantity of points I wish to resolve (length of array).
the distance between each points (pixels gap).
the distance between cycles.
It almost sounds, to me, that I have to calculate the "spiral-circumference" (if there's such a term) in order to plot the evenly distributed points along the spiral.
Can 2*PI*radius be reliably used for this calculation you think?
If it's been done before, please show some code example!
Fun little problem :)
If you look at the diagram closer, the sequence is clearly stated:
There are probably many solutions to drawing these, maybe more elegant, but here's mine:
You know the hypotenuse is square root of the current segment count+1
and the opposite side of the triangle is always 1.
Also you know that Sine(Math.sin) of the angle is equal to the opposite side divided by the hypotenuse.
from the old mnenonic SOH(Sine,Opposite,Hypotenuse),-CAH-TOA.
Math.sin(angle) = opp/hyp
You know the value of the sine for the angle, you know the two sides, but you don't know the angle yet, but you can use the arc sine function(Math.asin) for that
angle = Math.asin(opp/hyp)
Now you know the angle for each segment, and notice it increments with each line.
Now that you have an angle and a radius(the hypotenuse) you can use for polar to cartesian formula to convert that angle,radius pair to a x,y pair.
x = Math.cos(angle) * radius;
y = Math.sin(angle) * radius;
Since you asked for an actionscript solution, there Point class already provides this function for you through the polar() method. You pass it a radius and angle and it returns your x and y in a Point object.
Here's a little snippet which plots the spiral. You can control the number of segments by moving the mouse on the Y axis.
var sw:Number = stage.stageWidth,sh:Number = stage.stageHeight;
this.addEventListener(Event.ENTER_FRAME,update);
function update(event:Event):void{
drawTheodorus(144*(mouseY/sh),sw*.5,sh*.5,20);
}
//draw points
function drawTheodorus(segments:int,x:Number,y:Number,scale:Number):void{
graphics.clear();
var points:Array = getTheodorus(segments,scale);
for(var i:int = 0 ; i < segments; i++){
points[i].offset(x,y);
graphics.lineStyle(1,0x990000,1.05-(.05+i/segments));
graphics.moveTo(x,y);//move to centre
graphics.lineTo(points[i].x,points[i].y);//draw hypotenuse
graphics.lineStyle(1+(i*(i/segments)*.05),0,(.05+i/segments));
if(i > 0) graphics.lineTo(points[i-1].x,points[i-1].y);//draw opposite
}
}
//calculate points
function getTheodorus(segments:int = 1,scale:Number = 10):Array{
var result = [];
var radius:Number = 0;
var angle:Number = 0;
for(var i:int = 0 ; i < segments ; i++){
radius = Math.sqrt(i+1);
angle += Math.asin(1/radius);//sin(angle) = opposite/hypothenuse => used asin to get angle
result[i] = Point.polar(radius*scale,angle);//same as new Point(Math.cos(angle)*radius.scale,Math.sin(angle)*radius.scale)
}
return result;
}
This could've been written in less lines, but I wanted to split this into two functions:
one that deals only with computing the numbers, and the other which deals with drawing the lines.
Here are some screenshots:
For fun I added a version of this using ProcessingJS here.
Runs a bit slow, so I would recommend Chromium/Chrome for this.
Now you can actually run this code right here (move the mouse up and down):
var totalSegments = 850,hw = 320,hh = 240,segments;
var len = 10;
points = [];
function setup(){
createCanvas(640,480);
smooth();
colorMode(HSB,255,100,100);
stroke(0);
noFill();
//println("move cursor vertically");
}
function draw(){
background(0);
translate(hw,hh);
segments = floor(totalSegments*(mouseY/height));
points = getTheodorus(segments,len);
for(var i = 0 ; i < segments ; i++){
strokeWeight(1);
stroke(255-((i/segments) * 255),100,100,260-((i/segments) * 255));
line(0,0,points[i].x,points[i].y);
// strokeWeight(1+(i*(i/segments)*.01));
strokeWeight(2);
stroke(0,0,100,(20+i/segments));
if(i > 0) line(points[i].x,points[i].y,points[i-1].x,points[i-1].y);
}
}
function getTheodorus(segments,len){
var result = [];
var radius = 0;
var angle = 0;
for(var i = 0 ; i < segments ; i++){
radius = sqrt(i+1);
angle += asin(1/radius);
result[i] = new p5.Vector(cos(angle) * radius*len,sin(angle) * radius*len);
}
return result;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.4/p5.min.js"></script>
George's answer was excellent! I was looking for the solution for quite a while.
Here's the same code adjusted for PHP, in case it helps someone. I use the script to draw dots (= cities) for a map with X, Y coordinates. X starts from left, Y starts from bottom left.
<?
/**
* Initialize variables
**/
// MAXIMUM width & height of canvas (X: 0->400, Y: 0->400)
$width = 400;
// For loop iteration amount, adjust this manually
$segments = 10000;
// Scale for radius
$radiusScale = 2;
// Draw dot (e.g. a city in a game) for every N'th drawn point
$cityForEveryNthDot = 14;
/**
* Private variables
**/
$radius = 0;
$angle = 0;
$centerPoint = $width/2;
/**
* Container print
**/
print("<div style=\"width: ${width}px; height: ${width}px; background: #cdcdcd; z-index: 1; position: absolute; left: 0; top: 0;\"></div>");
/**
* Looper
**/
for($i=0;$i<$segments;$i++) {
// calculate radius and angle
$radius = sqrt($i+1) * $radiusScale;
$angle += asin(1/$radius);
// skip this point, if city won't be created here
if($i % $cityForEveryNthDot != 0) {
continue;
}
// calculate X & Y (from top left) for this point
$x = cos($angle) * $radius;
$y = sin($angle) * $radius;
// print dot
print("<div style=\"width: 1px; height: 1px; background: black; position: absolute; z-index: 2; left: " . round($x+$centerPoint) . "; top: " . round($y+$centerPoint) . ";\"></div>");
// calculate rounded X & Y (from bottom left)
$xNew = round($x+$centerPoint);
$yNew = round($width - ($y+$centerPoint));
// just some internal checks
if($xNew > 1 && $yNew > 1 && $xNew < $width && $yNew < $width) {
/**
* do something (e.g. store to database). Use xNew and yNew
**/
}
}

Insane Graphics.lineStyle behavior

I'd like some help with a little project of mine.
Background:
i have a little hierarchy of Sprite derived classes (5 levels starting from the one, that is the root application class in Flex Builder). Width and Height properties are overriden so that my class always remembers it's requested size (not just bounding size around content) and also those properties explicitly set scaleX and scaleY to 1, so that no scaling would ever be involved. After storing those values, draw() method is called to redraw content.
Drawing:
Drawing is very straight forward. Only the deepest object (at 1-indexed level 5) draws something into this.graphics object like this:
var gr:Graphics = this.graphics;
gr.clear();
gr.lineStyle(0, this.borderColor, 1, true, LineScaleMode.NONE);
gr.beginFill(0x0000CC);
gr.drawRoundRectComplex(0, 0, this.width, this.height, 10, 10, 0, 0);
gr.endFill();
Further on:
There is also MouseEvent.MOUSE_WHEEL event attached to the parent of the object that draws. What handler does is simply resizes that drawing object.
Problem:
Screenshot
When resizing sometimes that hairline border line with LineScaleMode.NONE set gains thickness (quite often even >10 px) + it quite often leaves a trail of itself (as seen in the picture above and below blue box (notice that box itself has one px black border)). When i set lineStile thickness to NaN or alpha to 0, that trail is no more happening.
I've been coming back to this problem and dropping it for some other stuff for over a week now.
Any ideas anyone?
P.S. Grey background is that of Flash Player itself, not my own choise.. :D
As requested, a bit more:
Application is supposed to be a calendar-timeline with a zooming "feature" (project for a course at university). Thus i have these functions that have something to do with resizing:
public function performZoom():void
{
// Calculate new width:
var newDayWidth:Number = view.width / 7 * this.calModel.zoom;
if (newDayWidth < 1)
{
newDayWidth = 1;
}
var newWidth:int = int(newDayWidth * timeline.totalDays);
// Calculate day element Height/Width ratio:
var headerHeight:Number = this.timeline.headerAllDay;
var proportion:Number = 0;
if (this.view.width != 0 && this.view.height != 0)
{
proportion = 1 / (this.view.width / 7) * (this.view.height - this.timeline.headerAllDay);
}
// Calculate new height:
var newHeight:int = int(newDayWidth * proportion + this.timeline.headerAllDay);
// Calculate mouse position scale on X axis:
var xScale:Number = 0;
if (this.timeline.width != 0)
{
xScale = newWidth / this.timeline.width;
}
// Calculate mouse position scale on Y axis:
var yScale:Number = 0;
if (this.timeline.height - this.timeline.headerAllDay != 0)
{
yScale = (newHeight - this.timeline.headerAllDay) / (this.timeline.height - this.timeline.headerAllDay);
}
this.timeline.beginUpdate();
// Resize the timeline
this.timeline.resizeElement(newWidth, newHeight);
this.timeline.endUpdate();
// Move timeline:
this.centerElement(xScale, yScale);
// Reset timeline local mouse position:
this.centerMouse();
}
public function resizeElement(widthValue:Number, heightValue:Number):void
{
var prevWidth:Number = this.myWidth;
var prevHeight:Number = this.myHeight;
if (widthValue != prevWidth || heightValue != prevHeight)
{
super.width = widthValue;
super.height = heightValue;
this.scaleX = 1.0;
this.scaleY = 1.0;
this.myHeight = heightValue;
this.myWidth = widthValue;
if (!this.docking)
{
this.origHeight = heightValue;
this.origWidth = widthValue;
}
this.updateAnchorMargins();
onResizeInternal(prevWidth, prevHeight, widthValue, heightValue);
}
}
Yes. I know.. a lot of core, and a lot of properties, but in fact most of the stuff has been disabled at the end and the situation is as described at the top.
this didn't work:
gr.lineStyle(); // Reset line style
Can we see your resizing code?
Also try clearing your line style as well as your fill:
gr.lineStyle(0, this.borderColor, 1, true, LineScaleMode.NONE);
gr.beginFill(0x0000CC);
gr.drawRoundRectComplex(0, 0, this.width, this.height, 10, 10, 0, 0);
gr.endFill();
gr.lineStyle();//<---- add this line
I don't know whether it's flash bug or what it is, but i finally found the solution.
The thing is that in my case when resizing in a nutshell you get like this:
this.width = this.stage.stageWidth / 7 * 365;
When i switch to maximized window this.width gains value 86k+. When i added this piece of code to draw horizontal line, it fixed itself:
var recWidth:Number = this.width;
var i:int;
var newEnd:Number = 0;
for (i = 1; newEnd < recWidth; i++)
{
newEnd = 100 * i;
if (newEnd > recWidth)
{
newEnd = recWidth;
}
gr.lineTo(newEnd, 0);
}
I don't know what is going on.. This is inconvenient...