AS3 zooming in and out where mouse clicked not at registration - actionscript-3

I am trying to have a masked mouse panned image zoom in and out with a click and a double click mouse event. I got the image to zoom but it always zooms in on the left edge registration point, not where I click. I have absolutely no idea how to code this and have spent the whole day on the internet trying to figure it out with no luck. I am hoping someone can help me to figure this out!
import com.greensock.*;//Greensock Tweening Platform.
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
this.mask = mask_mc;
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
function mousemove(e:MouseEvent) {
if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) {
if (imgLoader.width>mask_mc.width) {//Avoids Scrolling if image is under mask area width
percX = mask_mc.mouseX/mask_mc.width;
}
if (imgLoader.height>mask_mc.height) {//Avoids Scrolling if image is under mask area height
percY = mask_mc.mouseY/mask_mc.height;
}
destX = -(imgLoader.width-mask_mc.width)*percX;
destY = -(imgLoader.height-mask_mc.height)*percY;
TweenMax.to(imgLoader,.5,{x:destX,y:destY});
}
}
//Add listeners for the imgLoader movie clip.
imgLoader.doubleClickEnabled = true;
imgLoader.addEventListener(MouseEvent.CLICK, increaseSize);
imgLoader.addEventListener(MouseEvent.DOUBLE_CLICK, decreaseSize);
//This function increases the scale of the image
function increaseSize(event:MouseEvent):void{
TweenLite.to(imgLoader, 1, {scaleX:2, scaleY:2});
}
//This function decreases the scale of the image
function decreaseSize(event:MouseEvent):void{
TweenLite.to(imgLoader, 1, {scaleX:1, scaleY:1});
}

This answer is derived from here
Add this function:
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number, bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
return TweenLite.to(objectToScale,1,{onComplete: onComplete, scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y);
}
Then update your sizing function to this:
function increaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(imgLoader, 2, null, resumeMouseMove);
}
function decreaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(imgLoader, 1, null, resumeMouseMove);
}
function stopMouseMove():void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
function resumeMouseMove():void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
I also added a bounds parameter to the function. This is useful if you never want the edges of you content to be visible within the mask. So if you could use it by passing the bounds of your mask to the function:
scaleAroundMouse(imgLoader, 1, myMask.getBounds(this));

This example uses zoom effect classes that may help achieve the zoom effect you are looking for http://graphics-geek.blogspot.com/2010/06/video-image-zoom-effect-in-flex-4.html.

var mat:Matrix=new Matrix();
mat.translate(-p.x,-p.y);
mat.scale(desiredScale,desiredScale);
mat.translate(p.x,p.y);
yourObject.transform.matrix=mat;
That is taken from a question I posted about a month ago. You can see it here. While I didn't end up going with that specific snippet (I actually went with a modified version of the script LondongDrugs_MediaServ posted), it will work and is much easier to understand and implement.

Related

Is canvas required?

I've been working with HTML5 drag and drop, and canvas. I'm trying to put the two together: I drag an element to a drop-area; in the drop-area I want to be able to move the dropped items around to position them as needed.
I know how to drop elements into a div, for ex, but:
does the drop-area have to be a canvas for (re)positioning?
is there a specific term for the moving/repositioning of elements in the drop-area/canvas. I've done a lot of searching and can't find a term for this specifically. simply 'dragging'??
sample drop-area.
No the canvas element is not required anyhow.
You can achieve the exact same result as the linked example you gave without using any canvas.
Here is an example code, among many others possible, certainly far from being perfect, but which does the same as your example, using only <div> elements, css and javascript, but it could also be made with svg.
// we will increment it to get the dragged element atop of everything
var zIndex = 0;
// our constructor
var newElement = function() {
var that = {};
// first get its dimension and position randomly
that.rad = Math.random() * 20 + 10;
// x and y are the center of our element
that.x = Math.random() * (500 - that.rad * 2) + that.rad;
that.y = Math.random() * (300 - that.rad * 2) + that.rad;
// define the element that will be appended to the doc
that.el = document.createElement('div');
// a shortcut to the style property of the element
// since we'll play with this to update our object's position
var s = that.el.style;
// don't forget we're talking in css
s.width = that.rad * 2 + 'px';
s.height = that.rad * 2 + 'px';
s.left = that.x - that.rad + 'px';
s.top = that.y - that.rad + 'px';
s.backgroundColor = get_random_color();
// needed to make be sure we're not in a corner of the circle shaped elements
that.isCircle = Math.random() > .5;
if (that.isCircle) {
that.el.className = 'circle';
}
// happens on mousedown
that.startDrag = function(x, y) {
that.lastX = x;
that.lastY = y;
s.zIndex = ++zIndex;
}
// happens on mousemove if we're the one being dragged
that.move = function(x, y) {
that.x += x - that.lastX;
that.y += y - that.lastY;
that.lastX = x;
that.lastY = y;
s.left = that.x - that.rad + 'px';
s.top = that.y - that.rad + 'px';
};
container.appendChild(that.el);
return that;
};
var elements = [];
for (var i = 0; i < (~~(Math.random() * 50)) + 15; i++) {
elements.push(newElement());
}
var dragged;
var mousedown = function(e) {
var rect = container.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
dragged = null;
// sort our elements, higher zIndex firsts
elements.sort(function(a, b) {
return (+b.el.style.zIndex) - (+a.el.style.zIndex);
});
elements.forEach(function(el) {
// we already found the One, no need to go further
// (no "break;" in forEach method...)
if (dragged) return;
// is our mouse over the rectangular bounds of this element
if (x >= el.x - el.rad && x <= el.x + el.rad &&
y >= el.y - el.rad && y <= el.y + el.rad) {
if (el.isCircle) {
// a little bit of Pythagorian
var a = el.x - x;
var b = el.y - y;
var dist = Math.sqrt(a * a + b * b);
// too far from the center, we were in the corner
if (dist > el.rad) {
return;
}
}
// we got through here,
// tell the whole app we've got the One
dragged = el;
el.startDrag(x, y);
}
});
};
var mousemove = function(e) {
// nobody is being dragged, so don't do anything
if (!dragged) return;
// otherwise, tell the browser we handle the event
e.preventDefault();
e.stopPropagation();
// get the coordinates of our container
var rect = container.getBoundingClientRect();
// get the relative coordinates of our event
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
// move the dragged element accordingly
dragged.move(x, y);
};
var mouseup = function() {
// we dropped it..
dragged = null;
};
container.onmousedown = mousedown;
container.onmousemove = mousemove;
container.onmouseup = mouseup;
function get_random_color() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.round(Math.random() * 15)];
}
return color;
};
body {
text-align: center;
}
#container {
width: 500px;
height: 300px;
background-color: black;
margin: 0 auto;
position: relative;
overflow: hidden;
}
#container>div {
position: absolute;
opacity: .7;
}
.circle {
border-radius: 50%;
}
<div id="container"></div>
As you can see, I myself used the word dragged to refer to the object that we do move while we move the mouse with the button down. Yes, dragging is how this action is called...
But, this has very little to do with the HTML drag and drop API where what is important is not the positioning of your elements, but their content.
You can drag and drop elements into others, but it was mainly developed to drag data (external files or text content) to the document.
For this particular example, that would make things a lot harder and while the name "dragging" still applies to the action of the end-user, this shall not be confused with the API and every events related.
A canvas element is required by your linked canvas demo, but you can alternatively use the drag/drop API built into html+JS. And you can "manually" move DOM objects using mouse events as described in Kaiido's answer.
The canvas is required for your linked demo to work.
That exact demo was coded using a canvas element's drawing capabilities and that exact demo will not work if you try to substitute a DIV.
Think of html5 canvas as a re-writable bitmap. You can't "move" or "drag" anything on the canvas. Instead, you completely erase the canvas surface and redraw your circles in their new positions.
In the case of dragging, you listen for mousemove events on the canvas and reposition the circle under the mouse by the distance the mouse has moved since the last mousemove event.

How to shade the circle in canvas

I am working with HTML5 with canvas. I already draw a 2D circle.Now i want to shade the circle with a color.but the shading look like a 3D circle.Is this possible with canvas?.Thank you.
Fake smoke and mirrors
To fake a light on a sphere. I am guessing it is a sphere as you say circle and you could mean a donut. This technique will work for a donut as well.
So to lighting.
Phong Shading
The most basic lighting model is Phong (from memory). It uses the angle between the incoming light ray and the surface normal (a line going out from the surface at 90 deg). The amount of reflected light is the cosine of that angle time the light intensity.
Spheres a easy
As the sphere is symmetrical this allows us to use a radial gradient to apply the value for each pixel on the sphere and for a sphere with the light directly overhead this produces a perfect phong shaded sphere with very little effort.
The code that does that. x,y are the center of the sphere and r is the radius. The angle between the light and the surface normal is easy to calculate as you move out from the center of the sphere. It starts at zero and ends at Math.PI/2 (90deg). So the reflected value is the cosine of that angle.
var grd = ctx.createRadialGradient(x,y,0,x,y,r);
var step = (Math.PI/2)/r;
for(var i = 0; i < (Math.PI/2); i += step){
var c = "" + Math.floor(Math.max(0,255 * Math.abs(Math.cos(i)));
grd.addColorStop(i/(Math.PI/2),"rgba("+c+","+c+","+c+","1)");
}
That code creates a gradient to fit the circle.
Mod for Homer food
To do for a donut you need to modify i. The donut has an inner and outer radius (r1, r2) so inside the for loop modify i
var ii = (i/(Math.PI/2)); // normalise i
ii *= r2; // scale to outer edge
ii = ((r1+r2)/2)-ii; // get distance from center line
ii = ii / ((r2-r1)/2); // normalise to half the width;
ii = ii * Math.PI * (1/2); // scale to get the surface norm on the donut.
// use ii as the surface normal to calculate refelected light
var c = "" + Math.floor(Math.max(0,255 * Math.abs(Math.cos(ii)));
Phong Shading Sucks
By phong shading sucks big time and will not do. This also does not allow for lights that are off center or even partly behind the sphere.
We need to add the ability for off centered light. Luck has it that the radial gradients can be offset
var grd = ctx.createRadialGradient(x,y,0,x,y,r);
The first 3 numbers are the start circle of the gradient and can be positioned anywhere. The problem is that when we move the start location the phong shading model falls apart. To fix that there is a little smoke and mirrors stuff that can make the eye believe what the brain wants.
We adjust the fall off, the brightness, the spread, and the angle for each colour stop on the radial gradient depending on how far the light is from the center.
Specular highlights
This improves it a bit but still not the best. Another important component of lighting is specular reflections (the highlight). This is dependent on the angle between the reflected light and the eye. As we do not want to do all that (javascript is slow) we will cludge it via a slight modification of the phong shading. We simply multiply the surface normal by a value greater than 1. Though not perfect it works well.
Surface properties and environment
Next light is coloured, the sphere has reflective qualities that depend on frequency and there is ambient light as well. We don't want to model all this stuff so we need a way to fake it.
This can be done via compositing (Used for almost all 3D movie production). We build up the lighting one layer at a time. The 2D API provides compositing operations for us so we can create several gradients and layer them.
There is a lot more math involved but I have tried to keep it as simple as possible.
A demo
The following demo does a real time shading of a sphere (will work on all radially symmetrical objects) Apart from some setup code for canvas and mouse the demo has two parts the main loop does the compositing by layering the lights and the function createGradient creates the gradient.
The lights used can be found in the object lights and have various properties to control the layer. The first layer should use comp = source-in and lum = 1 or you will end up with the background showing through. All other layer lights can be what every you want.
The flag spec tells the shader that the light is specular and must include the specPower > 1 as I do not vet its existence.
The colours of the light is in the array col and represent Red, green and blue. The values can be greater the 256 and less than 0 as light in the natural world has a huge dynamic range and some effect need you to ramp up the incoming light way above the 255 limit of the RGB pixel.
I add a final "multiply" to the layered result. This is the magic touch in the smoke and mirror method.
If you like the code play with the values and layers. Move the mouse to change the light source location.
This is not real lighting it is fake, but who cares as long as it looks OK. lol
UPDATE
Found a bug so fixed it and while I was here, changed the code to randomize the lights when you click the left mouse button. This is so you can see the range of lighting that can be achieved when using the ctx.globalCompositeOperation in combination with gradients.
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** MouseFull.js begin **/
if(typeof mouse !== "undefined"){ // if the mouse exists
if( mouse.removeMouse !== undefined){
mouse.removeMouse(); // remove prviouse events
}
}else{
var mouse;
}
var canvasMouseCallBack = undefined; // if needed
mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
startMouse:undefined,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true;
} else if (t === "mousewheel") { m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") { m.w = -e.detail;}
if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
e.preventDefault();
}
function startMouse(element){
if(element === undefined){
element = document;
}
mouse.element = element;
mouse.mouseEvents.forEach(
function(n){
element.addEventListener(n, mouseMove);
}
);
element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
}
mouse.removeMouse = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(
function(n){
mouse.element.removeEventListener(n, mouseMove);
}
);
canvasMouseCallBack = undefined;
}
}
mouse.mouseStart = startMouse;
return mouse;
})();
if(typeof canvas !== "undefined"){
mouse.mouseStart(canvas);
}else{
mouse.mouseStart();
}
/** MouseFull.js end **/
// draws the circle
function drawCircle(c){
ctx.beginPath();
ctx.arc(c.x,c.y,c.r,0,Math.PI*2);
ctx.fill();
}
function drawCircle1(c){
ctx.beginPath();
var x = c.x;
var y = c.y;
var r = c.r * 0.95;
ctx.moveTo(x,y - r);
ctx.quadraticCurveTo(x + r * 0.8, y - r , x + r *1, y - r / 10);
ctx.quadraticCurveTo(x + r , y + r/3 , x , y + r/3);
ctx.quadraticCurveTo(x - r , y + r/3 , x - r , y - r /10 );
ctx.quadraticCurveTo(x - r * 0.8, y - r , x , y- r );
ctx.fill();
}
function drawShadowShadow(circle,light){
var x = light.x; // get the light position as we will modify it
var y = light.y;
var r = circle.r * 1.1;
var vX = x - circle.x; // get the vector to the light source
var vY = y - circle.y;
var dist = -Math.sqrt(vX*vX+vY*vY)*0.3;
var dir = Math.atan2(vY,vX);
lx = Math.cos(dir) * dist + circle.x; // light canb not go past radius
ly = Math.sin(dir) * dist + circle.y;
var grd = ctx.createRadialGradient(lx,ly,r * 1/4 ,lx,ly,r);
grd.addColorStop(0,"rgba(0,0,0,1)");
grd.addColorStop(1,"rgba(0,0,0,0)");
ctx.fillStyle = grd;
drawCircle({x:lx,y:ly,r:r})
}
// 2D light simulation. This is just an approximation and does not match real world stuff
// based on Phong shading.
// x,y,r descript the imagined sphere
// light is the light source
// ambient is the ambient lighting
// amount is the amount of this layers effect has on the finnal result
function createGradient(circle,light,ambient,amount){
var r,g,b; // colour channels
var x = circle.x; // get lazy coder values
var y = circle.y;
var r = circle.r;
var lx = light.x; // get the light position as we will modify it
var ly = light.y;
var vX = light.x - x; // get the vector to the light source
var vY = light.y - y;
// get the distance to the light source
var dist = Math.sqrt(vX*vX+vY*vY);
// id the light is a specular source then move it to half its position away
dist *= light.spec ? 0.5 : 1;
// get the direction of the light source.
var dir = Math.atan2(vY,vX);
// fix light position
lx = Math.cos(dir)*dist+x; // light canb not go past radius
ly = Math.sin(dir)*dist+y;
// add some dimming so that the light does not wash out.
dim = 1 - Math.min(1,(dist / (r*4)));
// add a bit of pretend rotation on the z axis. This will bring in a little backlighting
var lightRotate = (1-dim) * (Math.PI/2);
// spread the light a bit when near the edges. Reduce a bit for spec light
var spread = Math.sin(lightRotate) * r * (light.spec ? 0.5 : 1);
// create a gradient
var grd = ctx.createRadialGradient(lx,ly,spread,x,y,r + dist);
// use the radius to workout what step will cover a pixel (approx)
var step = (Math.PI/2)/r;
// for each pixel going out on the radius add the caclualte light value
for(var i = 0; i < (Math.PI/2); i += step){
if(light.spec){
// fake spec light reduces dim fall off
// light reflected has sharper falloff
// do not include back light via Math.abs
r = Math.max(0,light.col[0] * Math.cos((i + lightRotate)*light.specPower) * 1-(dim * (1/3)) );
g = Math.max(0,light.col[1] * Math.cos((i + lightRotate)*light.specPower) * 1-(dim * (1/3)) );
b = Math.max(0,light.col[2] * Math.cos((i + lightRotate)*light.specPower) * 1-(dim * (1/3)) );
}else{
// light value is the source lum * the cos of the angle to the light
// Using the abs value of the refelected light to give fake back light.
// add a bit of rotation with (lightRotate)
// dim to stop washing out
// then clamp so does not go below zero
r = Math.max(0,light.col[0] * Math.abs(Math.cos(i + lightRotate)) * dim );
g = Math.max(0,light.col[1] * Math.abs(Math.cos(i + lightRotate)) * dim );
b = Math.max(0,light.col[2] * Math.abs(Math.cos(i + lightRotate)) * dim );
}
// add ambient light
if(light.useAmbient){
r += ambient[0];
g += ambient[1];
b += ambient[2];
}
// add the colour stop with the amount of the effect we want.
grd.addColorStop(i/(Math.PI/2),"rgba("+Math.floor(r)+","+Math.floor(g)+","+Math.floor(b)+","+amount+")");
}
//return the gradient;
return grd;
}
// define the circles
var circles = [
{
x: canvas.width * (1/2),
y: canvas.height * (1/2),
r: canvas.width * (1/8),
}
]
function R(val){
return val * Math.random();
}
var lights;
function getLights(){
return {
ambient : [10,30,50],
sources : [
{
x: 0, // position of light
y: 0,
col : [R(255),R(255),R(255)], // RGB intensities can be any value
lum : 1, // total lumanance for this light
comp : "source-over", // composite opperation
spec : false, // if true then use a pretend specular falloff
draw : drawCircle,
useAmbient : true,
},{ // this light is for a little accent and is at 180 degree from the light
x: 0,
y: 0,
col : [R(255),R(255),R(255)],
lum : R(1),
comp : "lighter",
spec : true, // if true then you MUST inclue spec power
specPower : R(3.2),
draw : drawCircle,
useAmbient : false,
},{
x: canvas.width,
y: canvas.height,
col : [R(1255),R(1255),R(1255)],
lum : R(0.5),
comp : "lighter",
spec : false,
draw : drawCircle,
useAmbient : false,
},{
x: canvas.width/2,
y: canvas.height/2 + canvas.width /4,
col : [R(155),R(155),R(155)],
lum : R(1),
comp : "lighter",
spec : true, // if true then you MUST inclue spec power
specPower : 2.32,
draw : drawCircle,
useAmbient : false,
},{
x: canvas.width/3,
y: canvas.height/3,
col : [R(1255),R(1255),R(1255)],
lum : R(0.2),
comp : "multiply",
spec : false,
draw : drawCircle,
useAmbient : false,
},{
x: canvas.width/2,
y: -100,
col : [R(2255),R(2555),R(2255)],
lum : R(0.3),
comp : "lighter",
spec : false,
draw : drawCircle1,
useAmbient : false,
}
]
}
}
lights = getLights();
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
ctx.font = "20px Arial";
ctx.textAlign = "center";
function update(){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle = "#A74"
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "black";
ctx.fillText("Left click to change lights", canvas.width / 2, 20)
// set the moving light source to that of the mouse
if(mouse.buttonRaw === 1){
mouse.buttonRaw = 0;
lights = getLights();
}
lights.sources[0].x = mouse.x;
lights.sources[0].y = mouse.y;
if(lights.sources.length > 1){
lights.sources[1].x = mouse.x;
lights.sources[1].y = mouse.y;
}
drawShadowShadow(circles[0],lights.sources[0])
//do each sphere
for(var i = 0; i < circles.length; i ++){
// for each sphere do the each light
var cir = circles[i];
for(var j = 0; j < lights.sources.length; j ++){
var light = lights.sources[j];
ctx.fillStyle = createGradient(cir,light,lights.ambient,light.lum);
ctx.globalCompositeOperation = light.comp;
light.draw(circles[i]);
}
}
ctx.globalCompositeOperation = "source-over";
if(!STOP && (mouse.buttonRaw & 4)!== 4){
requestAnimationFrame(update);
}else{
if(typeof log === "function" ){
log("DONE!")
}
STOP = false;
var can = document.getElementById("canv");
if(can !== null){
document.body.removeChild(can);
}
}
}
if(typeof clearLog === "function" ){
clearLog();
}
update();
}
var STOP = false; // flag to tell demo app to stop
function resizeEvent(){
var waitForStopped = function(){
if(!STOP){ // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped,200);
}
STOP = true;
setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
demo();
/** FrameUpdate.js end **/
As #danday74 says, you can use a gradient to add depth to your circle.
You can also use shadowing to add depth to your circle.
Here's a proof-of-concept illustrating a 3d donut:
I leave it to you to design your desired circle
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var PI=Math.PI;
drawShadow(150,150,120,50);
function drawShadow(cx,cy,r,strokewidth){
ctx.save();
ctx.strokeStyle='white';
ctx.lineWidth=5;
ctx.shadowColor='black';
ctx.shadowBlur=15;
//
ctx.beginPath();
ctx.arc(cx,cy,r-5,0,PI*2);
ctx.clip();
//
ctx.beginPath();
ctx.arc(cx,cy,r,0,PI*2);
ctx.stroke();
//
ctx.beginPath();
ctx.arc(cx,cy,r-strokewidth,0,PI*2);
ctx.stroke();
ctx.shadowColor='rgba(0,0,0,0)';
//
ctx.beginPath();
ctx.arc(cx,cy,r-strokewidth,0,PI*2);
ctx.fillStyle='white'
ctx.fill();
//
ctx.restore();
}
body{ background-color: white; }
canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
Various thoughts which you can investigate ...
1 use an image as the texture for the circle
2 use a gradient to fill the circle, probably a radial gradient
3 consider using an image mask, a black / white mask which defines transparency ( prob not the right solution here )

Working with canvas to display only the drawn image and not whole canvas

With the plugin i found earlier on stackoverflow. Drawing has become smooth and nice. What i want is to only get the image part which i draw cropped from the canvas as an output and not the complete canvas. Can somebody help.
This is the code i am using for my canvas now: http://jsfiddle.net/sVsZL/1/
function canvasDisplay() {
var c=document.getElementById("canvas");
canvasImage=c.toDataURL("image/png");
document.getElementById("SSMySelectedImage").src=canvasImage;
}
Adding another answer because the other one was completely off.
Live Demo
What you need essentially is to keep track of a bounding box. What I do is create an object that holds the min values and max values of where you've drawn. This enables you to keep track of how big the image is and where it begins/ends.
this.dim = {minX : 9999, minY : 9999, maxX : 0, maxY : 0};
Then I created a function that checks the bounds.
this.setDimensions = function(x,y){
if(x < this.dim.minX){
this.dim.minX = x;
}
if(y < this.dim.minY){
this.dim.minY = y;
}
if(x > this.dim.maxX){
this.dim.maxX= x;
}
if(y > this.dim.maxY){
this.dim.maxY = y;
}
}
Make sure to check during clicking or moving.
this.mousedown = function(ev) {
tool.setDimensions(ev._x,ev._y);
};
this.mousemove = function(ev) {
tool.setDimensions(ev._x,ev._y);
};
And this is just a sample function that draws the portion to a new canvas that you could then save with toDataUrl
var button = document.getElementsByTagName("input")[0];
button.addEventListener("click", function(){
var savedCanvas = document.createElement("canvas"),
savedCtx = savedCanvas.getContext("2d"),
minX = PEN.dim.minX,
minY = PEN.dim.minY,
maxX = PEN.dim.maxX,
maxY = PEN.dim.maxY,
width = maxX - minX,
height = maxY - minY;
savedCanvas.width = width;
savedCanvas.height = height;
document.body.appendChild(savedCanvas);
savedCtx.drawImage(canvas,minX,minY,width,height,0,0,width,height);
});

Drag panning with zoom on mouseclick location AS3

So, another post on here REALLY helped me prior from this link!
It got me to zoom in neatly, but what I'm really looking for in my panning is a mouse drag (I will be working with a touch screen, so dragging to move is important) No need to use Touch classes, I have done dragging events with Mouse classes before and find it easier to me.
My problem here is that my neat zoom is linked to this MOUSE_MOVE panning, and I want to know a clean way to change my panning to a drag event, and still not have my panning go past my image constraints.
bg_mc- is my background image to move and zoom.
My code:
import com.greensock.*;
bg_mc.doubleClickEnabled = true;
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
this.mask = mask_mc;
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
function mousemove(e:MouseEvent) {
if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) {
if (bg_mc.width>mask_mc.width) {//Avoids Scrolling if image is under mask area width
percX = mask_mc.mouseX/mask_mc.width;
}
if (bg_mc.height>mask_mc.height) {
//Avoids Scrolling if image is under mask area height
percY = mask_mc.mouseY/mask_mc.height;
}
destX = -(bg_mc.width-mask_mc.width)*percX;
destY = -(bg_mc.height-mask_mc.height)*percY;
TweenMax.to(bg_mc,.5,{x:destX,y:destY});
}
}
//Add listeners for the imgLoader movie clip.
bg_mc.doubleClickEnabled = true;
bg_mc.addEventListener(MouseEvent.DOUBLE_CLICK, increaseSize);
bg_mc.addEventListener(MouseEvent.CLICK, decreaseSize);
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number,
bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new
Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
return TweenLite.to(objectToScale,1,{onComplete: onComplete,
scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y});
}
function increaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(bg_mc, 4, null, resumeMouseMove);
}
function decreaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(bg_mc, 1, null, resumeMouseMove);
}
function stopMouseMove():void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
function resumeMouseMove():void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
Any help I can get would be great!
On a mousedown or whatever listener you want to start dragging, use the MovieClip.StartDrag() function to allow dragging, and the StopDrag() to stop it.
While you could use the built in startDrag (or startTouchDrag) and stopDrag methods, it will not play nice with your zoom in/out function.
bg_mc.addEventListener(MouseEvent.MOUSE_DOWN,mouseDown); //add the listener to the bg directly
function mouseDown(e:MouseEvent) {
// if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) { //this isn't needed if you add the listener directly to bg_mc
stage.addEventListener(MouseEvent.MOUSE_UP,mouseUp);
bg_mc.startDrag(false, getBoundsRect()); //see the code below for the getBoundsRect() function
}
function mouseUp(e:MouseEvent):void {
bg_mc.stopDrag();
stage.removeEventListener(MouseEvent.MOUSE_UP,mouseUp);
}
You'd be better served to have a custom drag function, like below:
import com.greensock.*;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.events.Event;
import flash.geom.Point;
bg_mc.doubleClickEnabled = true;
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
bg_mc.mask = mask_mc;
//this function generates a bounds rectangle that would keep bg_mc edges from going outside of mask_mc area
function getBoundsRect():Rectangle {
return new Rectangle((mask_mc.x + mask_mc.width) - bg_mc.width, (mask_mc.y + mask_mc.height) - bg_mc.height, bg_mc.width - mask_mc.width, bg_mc.height - mask_mc.height);
}
var isZoomed:Boolean = false; //a var to keep track of whether your zoomed in or out
var isDragging:Boolean = false; //a var to keep track of whether the bg is being dragged
var tmpMousePoint:Point = new Point(); //this stores the mouse coordinates on the mouse down, to compare later on the mouse up to see if the mouse moved
var decay:Number = .27; //make this lower for slower drag, make it 1 to turn off a smooth drag all together
var tmpMouseMoved:Boolean = false; //to keep track on mouse up whether the action was a drag or a click
var mouseMoveSensitivity:Number = 15; //how far does mouse need to move before you cancel the click event
var offset:Point = new Point(); //the offset of the initial mouse click relative to bg_mc's 0,0
bg_mc.addEventListener(MouseEvent.MOUSE_DOWN,mouseDown);
bg_mc.addEventListener(MouseEvent.CLICK,mouseClick, false, 99999); //listen with a higher priority than any other click listeners on bg_mc - this way if it's a drag, you can cancel the click event
function mouseDown(e:Event):void {
//reset these to default
isDragging = true;
tmpMouseMoved = false;
tmpMousePoint.x = mouseX; //capture the current mouse to check later if it moved (so you know the user intended to drag not click)
tmpMousePoint.y = mouseY;
offset.x = bg_mc.mouseX;
offset.y = bg_mc.mouseY;
bg_mc.addEventListener(Event.ENTER_FRAME,bgEnterFrame); //listen every frame until the mouse is released
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
}
function bgEnterFrame(e:Event):void {
bg_mc.x += decay * (mouseX - offset.x - bg_mc.x);
bg_mc.y += decay * (mouseY - offset.y - bg_mc.y);
var bounds:Rectangle = getBoundsRect();
switch(true){
case (bg_mc.x < bounds.x):
bg_mc.x = bounds.x;
break;
case (bg_mc.x > bounds.x + bounds.width):
bg_mc.x = bounds.x + bounds.width;
}
switch(true){
case (bg_mc.y < bounds.y):
bg_mc.y = bounds.y;
break;
case (bg_mc.y > bounds.y + bounds.height):
bg_mc.y = bounds.y + bounds.height;
}
if(Math.abs(tmpMousePoint.x - mouseX) > mouseMoveSensitivity || Math.abs(tmpMousePoint.y - mouseY) > mouseMoveSensitivity){
tmpMouseMoved = true;
}
}
function mouseUp(e:Event):void {
isDragging = false;
//remove listeners
bg_mc.removeEventListener(Event.ENTER_FRAME,bgEnterFrame);
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUp);
}
function mouseClick(e:MouseEvent):void {
trace("CLICK cap");
if(tmpMouseMoved){
trace("Kill");
e.stopImmediatePropagation(); //cancel the mouse event
}
}
//Add listeners for the imgLoader movie clip.
bg_mc.doubleClickEnabled = true;
bg_mc.addEventListener(MouseEvent.DOUBLE_CLICK, increaseSize);
bg_mc.addEventListener(MouseEvent.CLICK, decreaseSize,false,0,true);
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number,
bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new
Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
TweenLite.killTweensOf(objectToScale); //need to add this so the click/double click don't compete with each other
return TweenLite.to(objectToScale,1,{onComplete: onComplete,
scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y});
}
function increaseSize(event:MouseEvent):void{
if(isZoomed){
scaleAroundMouse(bg_mc, 4, getBoundsRect());
isZoomed = false;
}
}
function decreaseSize(event:MouseEvent):void{
if(!isZoomed){
scaleAroundMouse(bg_mc, 1, getBoundsRect());
isZoomed = true;
}
}

how to move projectile in an arc to mouse x,y with AS3?

I've read a number of similar questions to this on here, but unfortunately none of them seem to give the exact answer I'm after, or they might but the maths is beyond me!
I'm creating a game where you have a cannon at the left edge of the screen. I want to be able to fire a cannonball from the cannon in an arc so that it intersects where the mouse pointer is on the screen.
I've seen a few examples that move a projectile in an arc from point a to point b, but what I need is for the cannonball to first move along the axis of the cannon itself, it's no good if the ball leaves the end of the cannon at a different angle to which the cannon is pointing.
The only force acting on the ball will be gravity and it's starting velocity.
Also to complicate matters, I need the cannons angle to change according to how far away the mouse pointer is from the end of the cannon, so if the pointer is far away than the cannon will point upwards say at an angle of 45 degrees, but if the pointer is very close to the end of the cannon then the cannon will point directly at the pointer, this I've more or less already got working by just getting the distance between them and then dividing it by a number and subtracting it from the rotation value of the cannon, but it's a bit of a rough way of doing it.
EDIT
Using the code below I've managed to the line in the screen shot below. But as you can see it's not the trajectory I need, I need something more like the red line I've put in.
And here's how I've implemented the code (probably wrongly)
public class GameTurretLine2
{
var rt:Object = null;
var lineMc:MovieClip = new MovieClip();
var myTurret:GameMainGun = null;
var pta:Point = new Point(0,0);
var ptb:Point = new Point(0,0);
var ptc:Point = new Point(0,0);
var ptd:Point = new Point(0,0);
public function GameTurretLine2(rt2,turret)
{
rt = rt2;
myTurret = turret;
lineMc.graphics.lineStyle(2, 0x55aa00);
mainLoop();
rt.rt.GameLayers.turretLineMc.addChild(lineMc);
}
function mainLoop()
{
lineMc.graphics.clear();
//get points
var turretEnd:Object = myTurret.rt.Useful.localToGlobalXY(myTurret.mC.turret.firePoint);
var turretStart:Object = myTurret.rt.Useful.localToGlobalXY(myTurret.mC.turret);
var mousePos:Point = new Point(myTurret.rt.rt.mouseX,myTurret.rt.rt.mouseY);
var inbetween:Point = new Point(0,0);
//start
pta.x = turretStart.newX;
pta.y = turretStart.newY;
//mouse end
ptd.x = mousePos.x;
ptd.y = mousePos.y;
// The cannon's angle:
// make the cannon's angle some inverse factor
// of the distance between the mouse and cannon tip
var dist:Number = myTurret.rt.Useful.getDistance(turretEnd.newX, turretEnd.newY, mousePos.x, mousePos.y);
var cAng:Number = dist * (180/Math.PI);
var ptbc:Point = new Point((ptd.x - pta.x) *.5,0);
ptbc.y = Math.tan(cAng) * ptbc.x;
//ptb = new Point(ptbc.x - ptbc.x * .15, ptbc.y);
ptb = new Point(turretEnd.newX, turretEnd.newY);
ptc = new Point(ptbc.x + ptbc.x * .5, ptbc.y);
// create the Bezier:
var bz:BezierSegment = new BezierSegment(pta,ptb,ptc,ptd);
trace(bz);
// define the distance between points that you want to draw
// has to be between 0 and 1.
var stepVal:Number = .1;
var curPt:Point = pta;
//draw circles
lineMc.graphics.drawCircle(pta.x, pta.y, 4);
lineMc.graphics.drawCircle(ptb.x, ptb.y, 4);
lineMc.graphics.drawCircle(ptc.x, ptc.y, 4);
lineMc.graphics.drawCircle(ptd.x, ptd.y, 4);
lineMc.graphics.lineStyle(2, 0x0000ff);
//step along the curve to draw it
for(var t:Number = 0;t < 1;t+=stepVal){
lineMc.graphics.moveTo(curPt.x, curPt.y);
curPt = bz.getValue(t);
trace("curPt = " + curPt.x + "," + curPt.y);
lineMc.graphics.lineTo(curPt.x, curPt.y);
}
trace("pta = " + pta.x + "," + pta.y);
trace("ptb = " + ptb.x + "," + ptb.y);
trace("ptc = " + ptc.x + "," + ptc.y);
trace("ptd = " + ptd.x + "," + ptd.y);
}
}
Also for some strange reason, the line created by the code flips, from how it is in the screen shot to an indented code (y flipped) just by moving the mouse a tiny amount, so as you move the mouse the line jumps everywhere.
One method is to create a Bezier curve.
This sounds like a workable solution because you essentially want the curve to always fit under some triangle. If this triangle defines the control points for a Bezier curve, you can make that match pretty closely the arc of a cannonball under gravity (it's not a perfect representation of gravity). One side-effect of this method is that the (inversed) height can define the force of the cannonball.
You can use the fl.motion.BezierSegment to create a curve and step along it. Paste this code into an FLA:
import fl.motion.BezierSegment;
var mySprite:Sprite = new Sprite();
addChild(mySprite);
mySprite.graphics.lineStyle(2, 0x55aa00);
// End point of the cannon:
var pta:Point = new Point(0, 100);
mySprite.graphics.drawCircle(pta.x, pta.y, 4);
trace("pta = " + pta.x + "," + pta.y);
// mouse point
// var ptd:Point = new Point(mouseX, mouseY);
// for testing:
var ptd:Point = new Point(200,100);
mySprite.graphics.drawCircle(ptd.x, ptd.y, 4);
trace("ptd = " + ptd.x + "," + ptd.y);
// The cannon's angle:
// make the cannon's angle some inverse factor
// of the distance between the mouse and cannon tip
// var dx:Number = ptd.x-pta.x;
// var dy:Number = ptd.y-pta.y;
// var dist:Number = Math.sqrt(dx * dx + dy * dy);
var cAng:Number = 30 * /(180/Math.PI);
// point the cannon in the correct direction here, however you are intending to do that.
// triangulate the cannon pt and mouse pt assuming the cannon's angle for both:
// *** NOTE: for simplicity, this assumes a straight line on the x-plane. ***
var ptbc:Point = new Point((ptd.x - pta.x) *.5,0);
ptbc.y = Math.tan(cAng) * ptbc.x;
trace("ptbc = " + ptbc.x + "," + ptbc.y);
// to adjust the curve:
var ptb:Point = new Point(ptbc.x - ptbc.x * .15, ptbc.y);
var ptc:Point = new Point(ptbc.x + ptbc.x * .5, ptbc.y);
mySprite.graphics.drawCircle(ptb.x, ptb.y, 4);
mySprite.graphics.drawCircle(ptc.x, ptc.y, 4);
// create the Bezier:
var bz:BezierSegment = new BezierSegment(pta,ptb,ptc,ptd);
trace(bz);
// define the distance between points that you want to draw
// has to be between 0 and 1.
var stepVal:Number = .1;
var curPt:Point = pta;
mySprite.graphics.lineStyle(2, 0x0000ff);
//step along the curve to draw it
for(var t:Number = 0;t < 1;t+=stepVal){
mySprite.graphics.moveTo(curPt.x, curPt.y);
curPt = bz.getValue(t);
trace("curPt = " + curPt.x + "," + curPt.y);
mySprite.graphics.lineTo(curPt.x, curPt.y);
}
mySprite.x = stage.stageWidth/2-mySprite.width/2;
mySprite.y = stage.stageHeight/2-mySprite.height/2;
As is, this code is not attached directly to the mouse, so you will have to use your own MouseEvent and AdjustCannonEvent to run this code. (Also, make sure to see the note in the code.)