Get the upper, bottom, rightmost and leftmost point of a pixel-perfect BitmapData collision - actionscript-3

How can I get the upper, bottom, rightmost and leftmost point of a pixel-perfect BitmapData collision? This is my collision-detection code:
public static function checkCollision(object1:*, object2:*, debug:Boolean = false):Boolean{
var object1Rect:Rectangle = object1.getRect(stage);
var object2Rect:Rectangle = object2.getRect(stage);
var object1Point:Point = new Point(object1Rect.x, object1Rect.y);
var object2Point:Point = new Point(object2Rect.x, object2Rect.y);
var bitmapData1:BitmapData = new BitmapData(
object1Rect.width,
object1Rect.height,
true,
0
);
var bitmapData2:BitmapData = new BitmapData(
object2Rect.width,
object2Rect.height,
true,
0
);
var clr:ColorTransform = new ColorTransform();
if(debug)
clr.color = 0x00ff00;
bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1Rect.x, -object1Rect.y), clr);
bitmapData2.draw(object2, null, clr);
if(debug){
if(bmp1.stage)
stage.removeChild(bmp1);
bmp1 = new Bitmap(bitmapData1);
bmp1.x = object1Point.x;
bmp1.y = object1Point.y;
stage.addChild(bmp1);
if(bmp2.stage)
stage.removeChild(bmp2);
bmp2 = new Bitmap(bitmapData2);
bmp2.x = object2Point.x;
bmp2.y = object2Point.y;
stage.addChild(bmp2);
}
var bCollide:Boolean = bitmapData1.hitTest(
object1Point,
255,
bitmapData2,
object2Point,
255
);
if(!debug){
bitmapData1.dispose();
bitmapData2.dispose();
}
return bCollide;
}
And it works perfeclty fine. However, the code I use to detect the top hitpoint doesn't work properly. This is the code:
public static function getHitPoint(object1:*, object2:*):Point{
var point:Point = new Point();
var object1Rect:Rectangle = object1.getRect(stage);
var object2Rect:Rectangle = object2.getRect(stage);
var object1Point:Point = new Point(object1Rect.x, object1Rect.y);
var object2Point:Point = new Point(object2Rect.x, object2Rect.y);
var bitmapData1:BitmapData = new BitmapData(
object1.width,
object1.height,
true,
0
);
var bitmapData2:BitmapData = new BitmapData(
object2.width,
object2.height,
true,
0
);
bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1Rect.x, -object1Rect.y));
bitmapData2.draw(object2);
var bitmap1:Bitmap = new Bitmap(bitmapData1);
var bitmap2:Bitmap = new Bitmap(bitmapData2);
bitmap1.x = object1Point.x;
bitmap1.y = object1Point.y;
bitmap2.x = object2Point.x;
bitmap2.y = object2Point.y;
var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);
var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(
new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1Rect.width, object1Rect.height)
);
for(var i:String in overlappingPixels){
var index:uint = uint(i);
if(overlappingPixels[i] != 0){
point.x = (index % object1.width) + (bitmap2.x > bitmap1.x ? bitmap2.x : bitmap1.x);
point.y = (uint(index / bitmap1.height)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);
break;
}
}
return point;
}
I've got no idea why, but the getHitPoint() function sometimes returns the wrong coordinates. Can anyone please explain why that is? And how can I detect the bottommost, the leftmost and the rightmost hitpoint?
Edit
I now know why getHitPoint() sometimes returned a wrong value: point.y = (uint(index / bitmap1.height)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y); should be point.y = (uint(index/bitmap1.width)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);
Edit 2
I found out how to get the bottom hitpoint:
public static function getHitPoint(object1:*, object2:*, direction:int = 0):*{
var point:Point = new Point();
var object1Rect:Rectangle = object1.getRect(stage);
var object2Rect:Rectangle = object2.getRect(stage);
var object1Point:Point = new Point(object1Rect.x, object1Rect.y);
var object2Point:Point = new Point(object2Rect.x, object2Rect.y);
var bitmapData1:BitmapData = new BitmapData(
Math.round(object1Rect.width),
Math.round(object1Rect.height),
true,
0
);
var bitmapData2:BitmapData = new BitmapData(
Math.round(object2Rect.width),
Math.round(object2Rect.height),
true,
0
);
bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1Rect.x, -object1Rect.y));
bitmapData2.draw(object2);
var bitmap1:Bitmap = new Bitmap(bitmapData1);
var bitmap2:Bitmap = new Bitmap(bitmapData2);
bitmap1.x = object1Point.x;
bitmap1.y = object1Point.y;
bitmap2.x = object2Point.x;
bitmap2.y = object2Point.y;
var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);
var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(
new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1Rect.width, object1Rect.height)
);
switch(direction){
case 0: //top
for(var i:String in overlappingPixels){
var index:uint = uint(i);
if(overlappingPixels[i] != 0){
point.x = (index % bitmap1.width) + (bitmap2.x > bitmap1.x ? bitmap2.x : bitmap1.x);
point.y = (uint((index)/bitmap1.width)) + (bitmap2.y > bitmap1.y ? bitmap2.y : bitmap1.y);
return point;
}
}
case 1: //right
// I still need this
case 2: //bottom
overlappingPixels.reverse();
for(var i:String in overlappingPixels){
var index:uint = uint(i);
if(overlappingPixels[i] != 0){
point.x = bitmap1.width - (index % bitmap1.width) + (bitmap2.x > bitmap1.x ? bitmap2.x : bitmap1.x);
point.y = (bitmap2.y + bitmap2.height > bitmap1.y + bitmap1.height ? bitmap1.y + bitmap1.height : bitmap2.y + bitmap2.height) - (uint(index/bitmap1.width));
return point;
}
}
case 3: //left
// I still need this too
}
return false;
}
I still need a way to get the left and rightmost hitpoints though

You don't need to do it like you're doing there. You can do it all within a single function, which returns everything back correctly. I've added comments to the below. Please take note of what I've changed, as when you're trying to do it as you're doing now, with the code you changed, it is impossible.
This works for any shape, any direction. It'll give you the exact X and Y of the collision.
Please do not make this into a static function. Put it into a global class and use a Singleton to manage it instead. Things start to go very badly wrong when you being using static functions and reference the stage.
Also, if you're going to be working with pixel values of less than 1 (ie 99.75), the below will need a bit of adapting to cater for that. I've assumed you're using whole pixels, given your Math.round usage.
/**
*
* #param object1
* #param object2
* #return
*/
private function getHitPoint(object1:*, object2:*):*{
var point:Point;
// X and Y where we hit
// do NOT change this to a stage location or it does NOT work
var object1Point:Point = new Point(object1.x, object1.y);
var object2Point:Point = new Point(object2.x, object2.y);
var bitmapData1:BitmapData = new BitmapData(
Math.round(object1.width),
Math.round(object1.height),
true,
0
);
var bitmapData2:BitmapData = new BitmapData(
Math.round(object2.width),
Math.round(object2.height),
true,
0
);
// Draw
bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1.x, -object1.y));
bitmapData2.draw(object2);
// Create BMP's
var bitmap1:Bitmap = new Bitmap(bitmapData1);
var bitmap2:Bitmap = new Bitmap(bitmapData2);
// Set X and Y and BMP
bitmap1.x = object1Point.x;
bitmap1.y = object1Point.y;
bitmap2.x = object2Point.x;
bitmap2.y = object2Point.y;
// BMP origin is the object1 X and Y
var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
// Create a local version of the bitmap2 so we can see what is overlapping
var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);
// Create a rectangle from what we now know
var rect:Rectangle = new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1.width, object1.height);
// The overlapping pixels are within the rectangle, so get them all
var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(rect);
// Run through all the overlapping pixels until we find a colourful one
for (var i:uint = 0; i < overlappingPixels.length; i++ ) {
var index:uint = overlappingPixels[i];
// If the colour is not 0, we have found it
if(index != 0){
point = new Point();
// Basically, instead of using width and getting 100, we're working out how
// many pixles across the overlap is. The Vector doesn't tell us this, so we need to work it out
var overlappingWidth:uint = object1.width - Math.abs(bitmap2OriginLocal.x);
// The Y is object1.y, minus the local y, plus object1's width minus the X from the local
point.y = object1.y - bitmap2OriginLocal.y + uint(i / overlappingWidth);
// The X is the same as above, but % of the width
point.x = object1.x - bitmap2OriginLocal.x + (i % overlappingWidth);
// Found it, we're done
break;
}
}
// Only fires when you've got a collision that is less than 1 pixel from the width or height
// Just a fail safe
if (!point) {
point = new Point(object1.width, object1.height);
}
return point;
}
For context, my entire class is below which shows how I was using this function. You can copy/paste this class and it will work. It shows how you move sprites around the screen, once it finds a collision, then it works out where the collision took place.
This class is for absolute pixel perfect collision detection, including an example.
package kazo
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Matrix;
/**
* ...
* #author KM
*/
public class TestCases2 extends Sprite
{
private var rect :Sprite;
private var circle :Sprite;
/**
*
*/
public function TestCases2()
{
addEventListener(Event.ADDED_TO_STAGE, init);
}
public function init(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
trace('Starting test case');
// Rectangle
rect = new Sprite();
// Circle
circle = new Sprite();
// Draw the rectangle. Center point must be TOP LEFT
// If you're using Flash Professional, place everything at 0,0 inside the MC
rect.graphics.beginFill(0xff0000);
rect.graphics.drawRect(0, 0, 100, 100);
rect.graphics.endFill();
// Draw the circle. Center point is TOP LEFT, so the X and Y of the circle need to be equal to the radius
circle.graphics.beginFill(0xffff00);
circle.graphics.drawCircle(50, 50, 50);
circle.graphics.endFill();
// Add them
addChild(rect);
addChild(circle);
// Position
rect.x = 225;
rect.y = 75;
// Position
circle.x = 225;
circle.y = 225;
// Frame loop
addEventListener(Event.ENTER_FRAME, frameFunc);
}
/**
*
* #param e
*/
private function frameFunc(e:Event):void {
// move them around
circle.y -= 2;
circle.x += 0;
rect.y += 1;
rect.x += 0;
// Check for collisions. If found, stop. Pass 'true' as the final param if you want it to draw to the screen
if (checkCollision(rect, circle)) {
var ref:Point = getHitPoint(rect, circle);
// Draws where the collision hit
var loc:Sprite = new Sprite();
loc.graphics.beginFill(0x000000);
loc.graphics.drawRect(0, 0, 10, 10);
loc.graphics.endFill();
addChild(loc);
loc.x = ref.x;
loc.y = ref.y;
trace(ref);
removeEventListener(Event.ENTER_FRAME, frameFunc);
}
}
/**
*
* #param _obj1
* #param _obj2
* #param _debug
* #return
*/
private function checkCollision(_obj1:Sprite, _obj2:Sprite, _debug:Boolean = false):Boolean {
// Draw the first item to bitmapdata
var bmd1:BitmapData = new BitmapData(_obj1.width, _obj1.height, true, 0);
// ..and the second
var bmd2:BitmapData = new BitmapData(_obj2.width, _obj2.height, true, 0);
// Now draw them
bmd1.draw(_obj1);
bmd2.draw(_obj2);
// If we're in debug, also add the bitmap to the stage so we can see where we are
if (_debug) {
var bmp:Bitmap = new Bitmap(bmd1);
bmp.x = _obj1.x;
bmp.y = _obj1.y;
addChild(bmp);
var bmp2:Bitmap = new Bitmap(bmd2);
bmp2.x = _obj2.x;
bmp2.y = _obj2.y;
addChild(bmp2);
}
// Hit test including alpha channel. Obj1 X/Y, Obj2 X/Y, alpha channel
var rtn:Boolean = bmd1.hitTest(new Point(_obj1.x, _obj1.y), 255, bmd2, new Point(_obj2.x, _obj2.y), 255);
// Dispose the bitmap data, we dont need it anymore
if (!_debug) {
bmd1.dispose();
bmd2.dispose();
}
// Return the boolean
return rtn;
}
/**
*
* #param object1
* #param object2
* #return
*/
private function getHitPoint(object1:*, object2:*):*{
var point:Point;
// X and Y where we hit
// do NOT change this to a stage location or it does NOT work
var object1Point:Point = new Point(object1.x, object1.y);
var object2Point:Point = new Point(object2.x, object2.y);
var bitmapData1:BitmapData = new BitmapData(
Math.round(object1.width),
Math.round(object1.height),
true,
0
);
var bitmapData2:BitmapData = new BitmapData(
Math.round(object2.width),
Math.round(object2.height),
true,
0
);
// Draw
bitmapData1.draw(object1, new Matrix(1, 0, 0, 1, -object1.x, -object1.y));
bitmapData2.draw(object2);
// Create BMP's
var bitmap1:Bitmap = new Bitmap(bitmapData1);
var bitmap2:Bitmap = new Bitmap(bitmapData2);
// Set X and Y and BMP
bitmap1.x = object1Point.x;
bitmap1.y = object1Point.y;
bitmap2.x = object2Point.x;
bitmap2.y = object2Point.y;
// BMP origin is the object1 X and Y
var bitmapOrigin:Point = new Point(object1Point.x, object1Point.y);
// Create a local version of the bitmap2 so we can see what is overlapping
var bitmap2OriginLocal:Point = bitmap2.globalToLocal(bitmapOrigin);
// Create a rectangle from what we now know
var rect:Rectangle = new Rectangle(bitmap2OriginLocal.x, bitmap2OriginLocal.y, object1.width, object1.height);
// The overlapping pixels are within the rectangle, so get them all
var overlappingPixels:Vector.<uint> = bitmap2.bitmapData.getVector(rect);
// Run through all the overlapping pixels until we find a colourful one
for (var i:uint = 0; i < overlappingPixels.length; i++ ) {
var index:uint = overlappingPixels[i];
// If the colour is not 0, we have found it
if(index != 0){
point = new Point();
// Basically, instead of using width and getting 100, we're working out how
// many pixles across the overlap is. The Vector doesn't tell us this, so we need to work it out
var overlappingWidth:uint = object1.width - Math.abs(bitmap2OriginLocal.x);
// The Y is object1.y, minus the local y, plus object1's width minus the X from the local
point.y = object1.y - bitmap2OriginLocal.y + uint(i / overlappingWidth);
// The X is the same as above, but % of the width
point.x = object1.x - bitmap2OriginLocal.x + (i % overlappingWidth);
// Found it, we're done
break;
}
}
// Only fires when you've got a collision that is less than 1 pixel from the width or height
// Just a fail safe
if (!point) {
point = new Point(object1.width, object1.height);
}
return point;
}
}
}

Related

Detect Colour Using Action Script 3

So I have made a game in scratch which uses the following code block:
I am trying to remake this game in Adobe Animate using Action Script 3 (for a class project), is there a similar way to do this in animate?
It is possible. The trick to do it is to create a tiny-teeny BitmapData object and to draw a small portion of stage under the mouse pointer into that object so that you can obtain the pixel color value.
// BitmapData object and some service objects.
var BD:BitmapData = new BitmapData(3, 3, true);
var R:Rectangle = new Rectangle(0, 0, 3, 3);
var M:Matrix = new Matrix;
// Let's create a TextField so we can output the pixel color value.
var T:TextField = new TextField;
var TF:TextFormat = new TextFormat("_typewriter", 12, 0x000000, true, false, false, null, null, TextFormatAlign.CENTER);
T.x = 10;
T.y = 10;
T.width = 100;
T.height = 18;
T.border = true;
T.background = true;
T.selectable = false;
T.mouseEnabled = false;
T.defaultTextFormat = TF;
addChild(T);
// Lets add some semi-transparent color circles
// so we have colored things to point the mouse at.
for (var i:int = 0; i < 10; i++)
{
var aColor:uint = 0;
aColor |= int(128 + 128 * Math.random()) << 16; // RED
aColor |= int(128 + 128 * Math.random()) << 8; // GREEN
aColor |= int(128 + 128 * Math.random()); // BLUE
var anX:int = stage.stageWidth / 8 + Math.random() * stage.stageWidth * 3 / 4;
var anY:int = stage.stageHeight / 8 + Math.random() * stage.stageHeight * 3 / 4;
var aRadius:int = 50 + 100 * Math.random();
var anAlpha:Number = 0.5 + 0.5 * Math.random();
graphics.beginFill(aColor, anAlpha);
graphics.drawCircle(anX, anY, aRadius);
graphics.endFill();
}
// Now let's watch the mouse every frame.
addEventListener(Event.ENTER_FRAME, onFrame);
function onFrame(e:Event):void
{
// Get pixel color as an RRGGBB String and print it.
T.text = "#" + addZeroes(getColorUnderMouse());
}
function getColorUnderMouse():uint
{
// Adjust Matrix so that we draw the correct piece of screen.
M.tx = -root.mouseX + 1;
M.ty = -root.mouseY + 1;
// Clear the BitmapData and capture the 3x3 piece under the mouse pointer.
BD.fillRect(R, 0xFFFFFFFF);
BD.draw(root, M, null, null, R);
// Read the pixel color value at the center of 3x3 and return it.
return BD.getPixel(1, 1);
}
// This function fixes the hexabinary value with leading
// zeroes if the color value is too small (like 0 = black).
function addZeroes(value:uint, count:uint = 6):String
{
var result:String = value.toString(16).toUpperCase();
while (result.length < count)
{
result = "0" + result;
}
return result;
}

Merge or Use instance and Native render make no difference with Chrome plus Integrated Graphics Card [duplicate]

I am testing the FPS with my laptop using the Intel(R) Iris(R) Plus Graphics 655 card.
To test the threeJS example with Instance rendering and merge-drawcall rendering.
So I used both the QRCode_buffergeometry.json model and the suzanne_buffergeometry.json model.
for the QRCode_buffergeometry.json: vertex:12852, face: 4284
and for the suzanne_buffergeometry.json: vertex:1515 face: 967
Then the FPS for the suzanne_buffergeometry with 8000 count:
INSTANCE: 36
MERGED: 43
NATIVE: from 23 to 35 by rotation
for the QRCode_buffergeometry model with 8000 count:
INSTANCE: 9
MERGED: 15-17
NATIVE: 17-19
I am very confused with this performance.
1. As far as my understanding, with no matter if i use instance or merge-drawcall, the drawcall is fixed to be 1 and the total face number to draw is same, why merged-drawcall is better than instance? Since the face and vertex number are both same, I suppose what happened in the vertex shader for transform the vertex should be same too, so why merged is faster?
For the QRCode_buffergeometry model, native is almost same as merged, and better than instance, so I guess the CPU is not the bottle neck but the GPU is, however the final drawing data should be same, i mean eventually the face number to be draw should be same, why native is faster?, isn't that the instance is supposed to be the best way? I am pretty sure the camera's far and near is big enough, so there should not be any culling issue.
When I am trying to optimize some big scene, when should I pick merge? when to pick instance? and when maybe no doing anything is better?
Any help?
Thanks a lot~~~
Attached the code for the sample is here
body { margin: 0; }
<div id="container"></div>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.112.1/build/three.module.js';
import Stats from 'https://cdn.jsdelivr.net/npm/three#0.112.1/examples/jsm/libs/stats.module.js';
import {
GUI
} from 'https://cdn.jsdelivr.net/npm/three#0.112.1/examples/jsm/libs/dat.gui.module.js';
import {
OrbitControls
} from 'https://cdn.jsdelivr.net/npm/three#0.112.1/examples/jsm/controls/OrbitControls.js';
import {
BufferGeometryUtils
} from 'https://cdn.jsdelivr.net/npm/three#0.112.1/examples/jsm/utils/BufferGeometryUtils.js';
var container, stats, gui, guiStatsEl;
var camera, controls, scene, renderer, material;
// gui
var Method = {
INSTANCED: 'INSTANCED',
MERGED: 'MERGED',
NAIVE: 'NAIVE'
};
var api = {
method: Method.INSTANCED,
mesh_number: 1,
count_per_mesh: 1000
};
var modelName = 'suzanne_buffergeometry.json';
var modelScale = (modelName === 'suzanne_buffergeometry.json' ? 1 : 0.01);
var modelVertex = (modelName === 'suzanne_buffergeometry.json' ? 1515 : 12852);
var modelFace = (modelName === 'suzanne_buffergeometry.json' ? 967 : 4284);
//
init();
initMesh();
animate();
//
function clean() {
var meshes = [];
scene.traverse(function(object) {
if (object.isMesh) meshes.push(object);
});
for (var i = 0; i < meshes.length; i++) {
var mesh = meshes[i];
mesh.material.dispose();
mesh.geometry.dispose();
scene.remove(mesh);
}
}
var randomizeMatrix = function() {
var position = new THREE.Vector3();
var rotation = new THREE.Euler();
var quaternion = new THREE.Quaternion();
var scale = new THREE.Vector3();
return function(matrix) {
position.x = Math.random() * 40 - 20;
position.y = Math.random() * 40 - 20;
position.z = Math.random() * 40 - 20;
rotation.x = Math.random() * 2 * Math.PI;
rotation.y = Math.random() * 2 * Math.PI;
rotation.z = Math.random() * 2 * Math.PI;
quaternion.setFromEuler(rotation);
scale.x = scale.y = scale.z = Math.random() * modelScale;
matrix.compose(position, quaternion, scale);
};
}();
function initMesh() {
clean();
console.time(api.method + ' (build)');
for (var i = 0; i < api.mesh_number; i++) {
// make instances
new THREE.BufferGeometryLoader()
.setPath('https://threejs.org/examples/models/json/')
.load(modelName, function(geometry) {
material = new THREE.MeshNormalMaterial();
geometry.computeVertexNormals();
switch (api.method) {
case Method.INSTANCED:
makeInstanced(geometry);
break;
case Method.MERGED:
makeMerged(geometry);
break;
case Method.NAIVE:
makeNaive(geometry);
break;
}
});
}
console.timeEnd(api.method + ' (build)');
var drawCalls = 0;
switch (api.method) {
case Method.INSTANCED:
case Method.MERGED:
drawCalls = api.mesh_number;
break;
case Method.NAIVE:
drawCalls = api.mesh_number * api.count_per_mesh;
break;
}
guiStatsEl.innerHTML = [
'<i>GPU draw calls</i>: ' + drawCalls,
'<i>Face Number</i>: ' + (modelFace * api.mesh_number * api.count_per_mesh),
'<i>Vertex Number</i>: ' + (modelVertex * api.mesh_number * api.count_per_mesh)
].join('<br/>');
}
function makeInstanced(geometry, idx) {
var matrix = new THREE.Matrix4();
var mesh = new THREE.InstancedMesh(geometry, material, api.count_per_mesh);
for (var i = 0; i < api.count_per_mesh; i++) {
randomizeMatrix(matrix);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
}
function makeMerged(geometry, idx) {
var instanceGeometry;
var geometries = [];
var matrix = new THREE.Matrix4();
for (var i = 0; i < api.count_per_mesh; i++) {
randomizeMatrix(matrix);
var instanceGeometry = geometry.clone();
instanceGeometry.applyMatrix(matrix);
geometries.push(instanceGeometry);
}
var mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
scene.add(new THREE.Mesh(mergedGeometry, material));
}
function makeNaive(geometry, idx) {
var matrix = new THREE.Matrix4();
for (var i = 0; i < api.count_per_mesh; i++) {
randomizeMatrix(matrix);
var mesh = new THREE.Mesh(geometry, material);
mesh.applyMatrix(matrix);
scene.add(mesh);
}
}
function init() {
var width = window.innerWidth;
var height = window.innerHeight;
// camera
camera = new THREE.PerspectiveCamera(70, width / height, 1, 100);
camera.position.z = 30;
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.outputEncoding = THREE.sRGBEncoding;
container = document.getElementById('container');
container.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
// controls
controls = new OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
// stats
stats = new Stats();
container.appendChild(stats.dom);
// gui
gui = new GUI();
gui.add(api, 'method', Method).onChange(initMesh);
gui.add(api, 'count_per_mesh', 1, 20000).step(1).onChange(initMesh);
gui.add(api, 'mesh_number', 1, 200).step(1).onChange(initMesh);
var perfFolder = gui.addFolder('Performance');
guiStatsEl = document.createElement('li');
guiStatsEl.classList.add('gui-stats');
perfFolder.__ul.appendChild(guiStatsEl);
perfFolder.open();
// listeners
window.addEventListener('resize', onWindowResize, false);
Object.assign(window, {
scene
});
}
//
function onWindowResize() {
var width = window.innerWidth;
var height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
stats.update();
render();
}
function render() {
renderer.render(scene, camera);
}
//
function getGeometryByteLength(geometry) {
var total = 0;
if (geometry.index) total += geometry.index.array.byteLength;
for (var name in geometry.attributes) {
total += geometry.attributes[name].array.byteLength;
}
return total;
}
// Source: https://stackoverflow.com/a/18650828/1314762
function formatBytes(bytes, decimals) {
if (bytes === 0) return '0 bytes';
var k = 1024;
var dm = decimals < 0 ? 0 : decimals;
var sizes = ['bytes', 'KB', 'MB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
</script>
This is only guesses
Three.js by default culls if things are outside the frustum.
We can turn this off with mesh.frustumCulled = false. I didn't notice a difference and this should show up in the draw count.
Three.js by default sorts opaque objects back to front.
This means everything else being equal, sorted will run faster
than unsorted because of the depth test. If I set the depth test
to always
material.depthFunc = THREE.AlwaysDepth
Then I seem to get slightly faster rendering with instanced vs native. Of course
everything else is not equal.
An issue in Chrome.
If I run in Firefox or Safari I get the expected results. Merged > Instanced > Native
It could be a bug or it could be they're working around a driver or
security issue that the other browsers are not. You'd have to ask.

How to scale GraphicsPathCommand data?

Context: For a legacy Flex/Actionscript drawing app, I need to add scaling of simple symbols. The app uses the Graffiti lib for drawing and the resulting shape data is stored as GraphicsPathCommands serialized to XML using the Degrafa lib for save and reload. I need to enable the user to scale these graphics and then get updated path data which can be serialized. The symbols are simple but more complicated than simple geometry. For example:
Question: I converted the SVG data for this symbol to Actionscript GraphicsPathCommands and am able to draw it, and of course translation is easy – but I don't know how I would scale it, given a bounding box defined by a user dragging out a marquee rectangle in the app.
Does anyone know of either an Actionscript way of transforming the command data, or a Javascript snippet for scaling SVG which I can port to Actionscript?
For reference, an example of the Actionscript GraphicsPathCommands for drawing a star is below.
public function DrawPathExample()
{
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
graphics.beginFill(0x003366);
graphics.drawPath(star_commands, star_coord);
}
Solution
A full solution for interactively scaling GraphicsPathCommand data is below. The path data was derived from an SVG put through this SVGParser. It generates path drawing commands in the form of graphics.lineTo(28.4,16.8);. A couple of utility functions separate the data from the commands and store them in Vectors so the data can be serialized. I don't need to use arbitrary SWGs so I just hardcoded the data.
package classes
{
import flash.display.GraphicsPathCommand;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
public class DrawSVG extends Sprite
{
private var startPt:Point = new Point();
private var selectRect:Rectangle = new Rectangle();
private var viewBox:Rectangle = new Rectangle();
protected var commands:Vector.<int> = new Vector.<int>();
protected var drawingData:Vector.<Number> = new Vector.<Number>();
protected var sourceDrawingData:Vector.<Number> = new Vector.<Number>();
public function DrawSVG()
{
super();
this.addEventListener(Event.ADDED_TO_STAGE, setup);
setupWomanData();
}
private function setup(event:Event):void
{
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
private function onMouseDown(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
// offset so graphic draws centered on click point
startPt = new Point(event.stageX - (viewBox.width /2), event.stageY - (viewBox.height /2));
selectRect = new Rectangle(startPt.x, startPt.y, viewBox.width, viewBox.height);
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, selectRect.width, selectRect.height);
}
private function onMouseMove(event:MouseEvent):void
{
selectRect.width = Math.max(viewBox.width, Math.abs(event.stageX - startPt.x));
selectRect.height = Math.max(viewBox.height, Math.abs(event.stageY - startPt.y));
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
this.graphics.clear();
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, viewBox.width * scaleFactor, viewBox.height * scaleFactor);
}
private function onMouseUp(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
createSprite(commands, drawingData);
}
private function drawSymbol(toScale:Number):void
{
drawingData.length = 0;
for (var i:int = 0; i < sourceDrawingData.length; i++) {
drawingData[i] = Math.max(sourceDrawingData[i], sourceDrawingData[i] * toScale);
drawingData[i] += i % 2 == 0 ? startPt.x : startPt.y ;
}
this.graphics.clear();
this.graphics.lineStyle();
this.graphics.beginFill(0xff0000);
this.graphics.drawPath(commands, drawingData);
this.graphics.endFill();
}
private function createSprite(command:Vector.<int>, coord:Vector.<Number>):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0xff);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
return s;
}
private function setupWomanData():void
{
commands = new Vector.<int>();
drawingData = new Vector.<Number>();
viewBox= new Rectangle(0, 0, 24.629, 52.336);
addMoveToCmd(12.31,10.3);
addCurveToCmd(13.37,10.3,14.3,9.89);
addCurveToCmd(15.24,9.48,15.94,8.78);
addCurveToCmd(16.64,8.08,17.05,7.14);
addCurveToCmd(17.46,6.2,17.46,5.15);
addCurveToCmd(17.46,4.1,17.05,3.16);
addCurveToCmd(16.64,2.23,15.94,1.52);
addCurveToCmd(15.24,0.82,14.3,0.41);
addCurveToCmd(13.37,0,12.31,0);
addCurveToCmd(11.26,0,10.33,0.41);
addCurveToCmd(9.39,0.82,8.69,1.52);
addCurveToCmd(7.98,2.23,7.57,3.16);
addCurveToCmd(7.16,4.1,7.16,5.15);
addCurveToCmd(7.16,6.2,7.57,7.14);
addCurveToCmd(7.98,8.08,8.69,8.78);
addCurveToCmd(9.39,9.48,10.33,9.89);
addCurveToCmd(11.26,10.3,12.31,10.3);
addLineToCmd(12.314,10.304);
addMoveToCmd(24.6,26.36);
addLineToCmd(20.7,12.77);
addCurveToCmd(20.62,12.3,20.39,11.91);
addCurveToCmd(20.15,11.51,19.81,11.23);
addCurveToCmd(19.47,10.94,19.04,10.78);
addCurveToCmd(18.61,10.62,18.14,10.62);
addLineToCmd(6.49,10.62);
addCurveToCmd(6.02,10.62,5.59,10.78);
addCurveToCmd(5.16,10.94,4.82,11.23);
addCurveToCmd(4.48,11.51,4.24,11.91);
addCurveToCmd(4.01,12.3,3.93,12.77);
addLineToCmd(0.03,26.36);
addCurveToCmd(0.01,26.4,0.01,26.45);
addCurveToCmd(-0.01,26.5,-0.01,26.55);
addCurveToCmd(0.01,26.6,0.01,26.65);
addCurveToCmd(0.02,26.69,0.03,26.74);
addCurveToCmd(-0.15,27.95,0.55,28.69);
addCurveToCmd(1.25,29.44,2.2,29.6);
addCurveToCmd(3.15,29.77,4.05,29.3);
addCurveToCmd(4.95,28.84,5.17,27.63);
addLineToCmd(6.85,21.37);
addLineToCmd(4.07,34.88);
addCurveToCmd(3.81,35.51,3.91,36.15);
addCurveToCmd(4,36.78,4.35,37.3);
addCurveToCmd(4.7,37.81,5.26,38.13);
addCurveToCmd(5.81,38.45,6.49,38.45);
addLineToCmd(6.78,38.45);
addLineToCmd(6.78,49.72);
addCurveToCmd(6.78,50.99,7.59,51.62);
addCurveToCmd(8.41,52.25,9.39,52.25);
addCurveToCmd(10.37,52.25,11.19,51.62);
addCurveToCmd(12,50.99,12,49.72);
addLineToCmd(12,38.45);
addLineToCmd(12.63,38.45);
addLineToCmd(12.63,49.72);
addCurveToCmd(12.63,50.99,13.44,51.62);
addCurveToCmd(14.26,52.25,15.24,52.25);
addCurveToCmd(16.22,52.25,17.04,51.62);
addCurveToCmd(17.85,50.99,17.85,49.72);
addLineToCmd(17.85,38.45);
addLineToCmd(18.14,38.45);
addCurveToCmd(18.82,38.45,19.38,38.13);
addCurveToCmd(19.93,37.81,20.28,37.3);
addCurveToCmd(20.63,36.78,20.72,36.14);
addCurveToCmd(20.81,35.51,20.56,34.87);
addLineToCmd(17.78,21.37);
addLineToCmd(19.45,27.58);
addCurveToCmd(19.67,28.79,20.57,29.27);
addCurveToCmd(21.47,29.75,22.43,29.6);
addCurveToCmd(23.38,29.45,24.08,28.7);
addCurveToCmd(24.78,27.96,24.6,26.74);
addCurveToCmd(24.61,26.69,24.62,26.65);
addCurveToCmd(24.63,26.6,24.63,26.55);
addCurveToCmd(24.63,26.5,24.62,26.45);
addCurveToCmd(24.62,26.4,24.6,26.36);
addLineToCmd(24.601,26.356);
}
protected function addCurveToCmd(p1:Number, p2:Number, p3:Number, p4:Number):void
{
commands.push(GraphicsPathCommand.CURVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
sourceDrawingData.push(p3);
sourceDrawingData.push(p4);
}
protected function addMoveToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.MOVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
protected function addLineToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.LINE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
}
}
Seems like there is a pretty straightforward way to do this. It looks like the only thing to scale are the coordinates themselves, so you may just apply a scale factor.
Based on your example:
public function ASEntryPoint() {
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
//reference shape to detect initial size
var s:Shape = shapeInRect(star_commands, star_coord);
var bounds:Rectangle = s.getBounds(s);
s.graphics.lineStyle(1);
s.graphics.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
addChild(s);
//fit to target
var targetSize:Rectangle = new Rectangle(150, 100, 75, 60);
//detect lesser factor - assuming you need to preserve proportions
var kx:Number = targetSize.width / (bounds.width);
var ky:Number = targetSize.height / (bounds.height);
var toUse:Number = kx < ky ? kx : ky;
//apply to coords
for (var i:int = 0; i < star_coord.length; i++) {
//size
star_coord[i] *= toUse;
//fix initial offset
star_coord[i] -= i % 2 == 0 ? bounds.x * toUse : bounds.y * toUse;
}
//draw
addChild(shapeInRect(star_commands, star_coord, targetSize));
}
private function shapeInRect(command:Vector.<int>, coord:Vector.<Number>, rect:Rectangle = null):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0x003366);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
if (rect){
s.graphics.lineStyle(1);
s.graphics.drawRect(0, 0, rect.width, rect.height);
s.x = rect.x;
s.y = rect.y;
}
return s;
}

How do I rasterize a graphic that contains path data?

I am trying to rasterize some SVG data to a PNG and it is not working. Could someone please tell me what I am doing wrong?
This code does not seem to have any data in the BitmapData object.
var color:uint = Math.floor( (Math.random() * 0xFFFF00) + 0x0000FF);
var graphic:Graphic = new Graphic();
graphic.graphics.beginFill(color);
var pathData:String = "M 0 0 L 0 40 L 40 40 L 40 40 Z";
var path:Path = new Path();
path.data = pathData;
path.x =0;
path.y=0;
path.width = 40;
path.height = 40;
path.stroke=new SolidColorStroke(100);
path.fill=new SolidColor(100);
path.winding = GraphicsPathWinding.EVEN_ODD;
graphic.addElement(path);
graphic.width = 40;
graphic.height = 40;
graphic.validateNow();
var FillColor = 0x00000000;
var bitMapData:BitmapData = new BitmapData(graphic.width,graphic.height, true, FillColor);
bitMapData.draw(graphic);
But this code does:
var graphic:Graphic = new Graphic();
graphic.graphics.beginFill(color);
var width:Number = Math.floor(Math.random() * (MAXWIDTH-MINWIDTH)) + MINWIDTH;
var height:Number = Math.floor(Math.random() * (MAXHEIGHT-MINHIEGHT)) + MINHIEGHT;
var radius:Number = Math.floor( (Math.random()*(MAXRADIUS-MINRADIUS)))+MINRADIUS;
width = height = radius*2;
graphic.graphics.drawCircle(radius, radius,radius );
graphic.graphics.endFill();
var FillColor = 0x00000000;
var bitMapData:BitmapData = new BitmapData(graphic.width,graphic.height, true, FillColor);
bitMapData.draw(graphic);
if I do:
var temp:Graphic = new Graphic();
temp.graphics.beginFill(0x000000);
temp.graphics.drawRect(0,0,width/2, height/2);
temp.graphics.endFill();
sprite.graphics.drawRect(0,0,width, height);
sprite.addElement(temp);
both rectangles draw on canvas, but
BitMapData.draw(sprite);
only shows the toplevel sprite.
So I figured it out. Paths use BeforeDraw(), Draw(), and EndDraw(), which performs the fill and stroke operations. The problem is that these functions dont get called until the path gets rendered on the canvas. So, I extended my path class and over-rode the EndDraw() function. In this function I dispatched an event. Then, when I catch the event I can get the DisplayObject from the path (which is now filled in) and pass that object into BitmapData().

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