The engine behind Splitter Flash game : working code
2009-11-12 00:00:00 来源:WEB开发网I think we finally have a working slicing engine. It started with The engine behind Splitter Flash game, then come The engine behind Splitter Flash game – first AS3 prototype and The engine behind Splitter Flash game – new AS3 prototypes.
Now Guillaume Pommey aka pompom sent us a working version.
I revised my prototype of slicing engine two days ago and I corrected a lot of errors. After an afternoon on my code, I resolved almost all my problems.
I am proud ^^, now the slicing engine works.
I joint the Main.as and the .fla cause I use radio button to change the cutter style, I let you discover it :) .
Ps : Don’t forget that I use the Raycast function so speak about this on your blog.
For more information about Raycast function check Box2D Raycasts post.
The engine works perfectly, and has two ways of cutting objects: the laser mode cuts objects when you press C while the cutter one lets you cut by drawing a line.
Here it is the source code:
/*
* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*
*This a port in AS3 by Guillaume Pommey of a slicing engine in C
*Thanks to Emanuele Feronato for his help : http://www.emanueleferonato.com/category/box2d/
*Thanks to Boris The Brave for his Raycast function : http://personal.boristhebrave.com/
*I hope you will enjoy this, have fun !
*/
package{
import Box2D.Dynamics.*
import Box2D.Collision.*
import Box2D.Collision.Shapes.*
import Box2D.Dynamics.Joints.*
import Box2D.Dynamics.Contacts.*
import Box2D.Common.Math.*
import Box2D.Common.*
import General.*
import flash.display.*;
import flash.events.*;
import flash.utils.getTimer
import flash.display.MovieClip;
public class Main extends MovieClip{
public function Main(){
m_fpsCounter.x = 7;
m_fpsCounter.y = 5;
addChildAt(m_fpsCounter, 0);
m_sprite = new Sprite();
addChild(m_sprite);
m_input = new Input(m_sprite);
addEventListener(Event.ENTER_FRAME, update, false, 0, true);
stage.addEventListener(KeyboardEvent.KEY_DOWN, key_pressed);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpEvent);
//Create a b2World
var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set(-1000.0, -1000.0);
worldAABB.upperBound.Set(1000.0, 1000.0);
// Define the gravity vector
var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
// Allow bodies to sleep
var doSleep:Boolean = true;
// Construct a world object
m_world = new b2World(worldAABB, gravity, doSleep);
dbgDraw = new b2DebugDraw();
dbgDraw.m_sprite = m_sprite;
dbgDraw.m_drawScale = 15.0;
dbgDraw.m_fillAlpha = 0.3;
dbgDraw.m_lineThickness = 1.0;
dbgDraw.m_drawFlags = b2DebugDraw.e_shapeBit;
m_world.SetDebugDraw(dbgDraw);
//Create some nice shapes
CutterTest();
}
public function CutterTest(){
var ground:b2Body = null;
//Top
var bd:b2BodyDef = new b2BodyDef();
bd.position.Set(0.0, -6.0);
ground = m_world.CreateBody(bd);
var sd:b2PolygonDef = new b2PolygonDef();
sd.SetAsBox(50.0, 10.0);
ground.CreateShape(sd);
//Bottom
bd.position.Set(0.0, 45);
ground = m_world.CreateBody(bd);
sd.SetAsBox(50.0, 10.0);
ground.CreateShape(sd);
//Laser body
bd.position.Set(6.0, 15.0);
laserBody = m_world.CreateBody(bd);
sd.SetAsBox(5.0, 1.0);
sd.density = 4.0;
laserBody.CreateShape(sd);
laserBody.SetMassFromShapes();
//Boxes
sd.SetAsBox(3.0, 3.0);
sd.density = 5.0;
bd.userData = 1;
bd.position.Set(25.0, 15.0);
var body1:b2Body = m_world.CreateBody(bd);
body1.CreateShape(sd);
body1.SetMassFromShapes();
sd.SetAsBox(3.0, 3.0);
sd.density = 5.0;
bd.userData = 1;
bd.position.Set(25.0, 15.0);
body1 = m_world.CreateBody(bd);
body1.CreateShape(sd);
body1.SetMassFromShapes();
}
/***************************************************/
/*********************ETAPE 1***********************/
/***************************************************/
public function CheckPolyShape(poly:b2PolygonDef)
{
if (!(3 <= poly.vertexCount && poly.vertexCount <= b2_maxPolygonVertices)){
return -1;
}
var m_normals:Array = new Array(poly.vertexCount);
// Compute normals. Ensure the edges have non-zero length.
for (var i=0; i < poly.vertexCount; i++)
{
var i1 = i;
var i2 = 0;
if((i + 1) < poly.vertexCount){
i2 = i + 1;
} else {
i2 = 0;
}
var edge:b2Vec2 = poly.vertices[i2].Copy();
edge.Subtract(poly.vertices[i1]);
if (!(edge.LengthSquared()> Number.MIN_VALUE * Number.MIN_VALUE)){//Peut être une erreur
return -1;
}
m_normals[i] = b2Math.b2CrossVF(edge, 1.0);
m_normals[i].Normalize();
}
// Ensure the polygon is convex.
for (i=0; i <poly.vertexCount; i++)
{
for (var j=0; j <poly.vertexCount; j++)
{
// Don't check vertices on the current edge.
if (j == i || j == (i + 1) % poly.vertexCount)
{
continue;
}
// Your polygon is non-convex (it has an indentation).
// Or your polygon is too skinny.
var vecPlus:b2Vec2 = b2Math.SubtractVV(poly.vertices[j], poly.vertices[i]);
var s = b2Math.b2Dot(m_normals[i], vecPlus);//Problème ??
if (!(s < -b2Settings.b2_linearSlop)){//Idem
return -1;
}
}
}
// Ensure the polygon is counter-clockwise.
for (i=1; i <poly.vertexCount; i++)
{
var cross:Number = b2Math.b2CrossVV(m_normals[i-1], m_normals[i]);
// Keep asinf happy.
cross = b2Math.b2Clamp(cross, -1, 1);
// You have consecutive edges that are almost parallel on your polygon.
var angle:Number = Math.asin(cross);
if (!(angle> b2Settings.b2_angularSlop))
return -1;
}
// Compute the polygon centroid.
var m_centroid:b2Vec2 = new b2Vec2();
var area:Number = 0;
var pRef:b2Vec2 = new b2Vec2(0.0, 0.0);
var inv3:Number = 1 / 3;
for (i=0; i <poly.vertexCount; i++)
{
// Triangle vertices.
var p1:b2Vec2 = pRef;
var p2:b2Vec2 = poly.vertices[i];
var p3 = 0;
if((i + 1) < poly.vertexCount){
p3 = poly.vertices[i+1];
} else {
p3 = poly.vertices[0];
}
var e1:b2Vec2 = b2Math.SubtractVV(p2, p1);
var e2:b2Vec2 = b2Math.SubtractVV(p3, p1);
var D:Number = b2Math.b2CrossVV(e1, e2);
var triangleArea:Number = 0.5 * D;
area += triangleArea;
// Area weighted centroid
var p123:b2Vec2 = p1.Copy();
p123.Add(p1);
p123.Add(p2);
var trInv3:Number = triangleArea * inv3;
var p123Mul:b2Vec2 = b2Math.MulFV(trInv3, p123);
m_centroid.Add(p123);
}
// Centroid
if (!(area> Number.MIN_VALUE)){
return -1;
}
b2Math.MulFV((1.0 / area), m_centroid); // ??????
// Compute the oriented bounding box.
//ComputeOBB(&m_obb, m_vertices, m_vertexCount);
// Create core polygon shape by shifting edges inward.
// Also compute the min/max radius for CCD.
for (i=0; i <poly.vertexCount; i++)
{
if((i - 1) >= 0){
i1 = i - 1;
}else {
i1 = (poly.vertexCount - 1)
}
i2 = i;
var n1:b2Vec2 = m_normals[i1];
var n2:b2Vec2 = m_normals[i2];
var v:b2Vec2 = b2Math.SubtractVV(poly.vertices[i], m_centroid); // ?????
var d:b2Vec2 = new b2Vec2();
d.x = b2Math.b2Dot(n1, v) - b2Settings.b2_toiSlop;
d.y = b2Math.b2Dot(n2, v) - b2Settings.b2_toiSlop;
// Shifting the edge inward by b2_toiSlop should
// not cause the plane to pass the centroid.
//Those conditions are very annoying !!
// Your shape has a radius/extent less than b2_toiSlop.
/*if (!(d.x>= 0)){
return -1;
}
if (!(d.y>= 0)){
return -1;
}*/
}
return 0;
}
/// Split a shape trough a segment
/// @return
/// -1 - Error on split
/// 0 - Normal result is two new shape definitions.
public function SplitShape(shape:b2PolygonShape, segment, splitSize, newPolygon:Array)// ????
{
/*assert(shape != NULL);
assert(newPolygon != NULL);
assert(splitSize>= 0);*/
if(shape == null)
return -1;
if(newPolygon == null)
return -1;
if(splitSize <= 0)
return -1;
var lambda:Array = [1];
var normal:b2Vec2 = new b2Vec2();
var b:b2Body = shape.GetBody();
var xf:b2XForm = b.GetXForm();
if (shape.TestSegment(xf, lambda, normal, segment, 1) != b2Shape.e_hitCollide){
return -1;
}
var entryPoint:b2Vec2 = segment.p1.Copy();
entryPoint.Multiply(1 - lambda[0]);
var tmp:b2Vec2 = segment.p2.Copy();
tmp.Multiply(lambda[0]);
entryPoint.Add(tmp);
var reverseSegment:b2Segment= new b2Segment;
reverseSegment.p1 = segment.p2;
reverseSegment.p2 = segment.p1;
if (shape.TestSegment(xf, lambda, normal, reverseSegment, 1.0) != e_hitCollide){
return -1;
}
var exitPoint:b2Vec2 = reverseSegment.p1.Copy();
exitPoint.Multiply(1 - lambda[0]);
tmp = reverseSegment.p2.Copy();
tmp.Multiply(lambda[0]);
exitPoint.Add(tmp);
var localEntryPoint:b2Vec2 = b.GetLocalPoint(entryPoint);
var localExitPoint:b2Vec2 = b.GetLocalPoint(exitPoint);
var vertices:Array = shape.GetVertices();
var cutAdded:Array = [-1,-1];
var lastA = -1;
for(var i = 0; i<shape.GetVertexCount(); i++)
{
var n;
//Find out if this vertex is on the old or new shape.
if (b2Math.b2Dot(b2Math.b2CrossVF(b2Math.SubtractVV(localExitPoint,localEntryPoint), 1), b2Math.SubtractVV(vertices[i], localEntryPoint))> 0) //Clash ??
n = 0;
else
n = 1;
if (lastA != n)
{
//If we switch from one shape to the other add the cut vertices.
if (lastA == 0)
{
//assert(cutAdded[0] == -1); Couiiic
if(cutAdded[0] != -1){
return -1;
}
cutAdded[0] = newPolygon[lastA].vertexCount;
newPolygon[lastA].vertices[newPolygon[lastA].vertexCount] = localExitPoint;
newPolygon[lastA].vertexCount++;
newPolygon[lastA].vertices[newPolygon[lastA].vertexCount] = localEntryPoint;
newPolygon[lastA].vertexCount++;
}
if (lastA == 1)
{
//assert(cutAdded[lastA] == -1); Recouiiic
if(cutAdded[lastA] != -1){
return -1;
}
cutAdded[lastA] = newPolygon[lastA].vertexCount;
newPolygon[lastA].vertices[newPolygon[lastA].vertexCount] = localEntryPoint;
newPolygon[lastA].vertexCount++;
newPolygon[lastA].vertices[newPolygon[lastA].vertexCount] = localExitPoint;
newPolygon[lastA].vertexCount++;
}
}
newPolygon[n].vertices[newPolygon[n].vertexCount] = vertices[i];
newPolygon[n].vertexCount++;
lastA = n;
}
//Add the cut in case it has not been added yet.
if (cutAdded[0] == -1)
{
cutAdded[0] = newPolygon[0].vertexCount;
newPolygon[0].vertices[newPolygon[0].vertexCount] = localExitPoint;
newPolygon[0].vertexCount++;
newPolygon[0].vertices[newPolygon[0].vertexCount] = localEntryPoint;
newPolygon[0].vertexCount++;
}
if (cutAdded[1] == -1)
{
cutAdded[1] = newPolygon[1].vertexCount;
newPolygon[1].vertices[newPolygon[1].vertexCount] = localEntryPoint;
newPolygon[1].vertexCount++;
newPolygon[1].vertices[newPolygon[1].vertexCount] = localExitPoint;
newPolygon[1].vertexCount++;
}
for(n = 0; n<2 ; n++)
{
var offset:b2Vec2;
if (cutAdded[n]> 0)
{
offset = b2Math.SubtractVV(newPolygon[n].vertices[cutAdded[n]-1], newPolygon[n].vertices[cutAdded[n]])//Substitution
}else{
//Substitution
offset = b2Math.SubtractVV(newPolygon[n].vertices[newPolygon[n].vertexCount-1], newPolygon[n].vertices[0]);
}
offset.Normalize();
var aNewVec:b2Vec2 = b2Math.MulFV(splitSize, offset);
newPolygon[n].vertices[cutAdded[n]] = b2Math.AddVV(newPolygon[n].vertices[cutAdded[n]], aNewVec);
if (cutAdded[n] <newPolygon[n].vertexCount-2)
{
offset = b2Math.SubtractVV(newPolygon[n].vertices[cutAdded[n]+2], newPolygon[n].vertices[cutAdded[n]+1]);
}else{
offset = b2Math.SubtractVV(newPolygon[n].vertices[0], newPolygon[n].vertices[newPolygon[n].vertexCount-1]);
}
offset.Normalize();
aNewVec = b2Math.MulFV(splitSize, offset);
newPolygon[n].vertices[cutAdded[n]+1] = b2Math.AddVV(newPolygon[n].vertices[cutAdded[n]+1], aNewVec);
}
//Check if the new shapes are not too tiny. (TODO: still generates shapes which fail assert checks)
for(n=0;n<2;n++)
for(i=0;i<newPolygon[n].vertexCount;i++)
for(var j=0;j<newPolygon[n].vertexCount;j++)
if (i != j && (b2Math.SubtractVV(newPolygon[n].vertices[i], newPolygon[n].vertices[j])).Length() <0.1)
return -1;
for(n=0;n<2;n++){
if (CheckPolyShape(newPolygon[n])){
return -1;
}
return 0;
}
}
public function Cut():void
{
/*laserStart = new b2Vec2(5.0-0.1, 0.0);
laserDir = new b2Vec2(segmentLength, 0.0);
segment.p1 = laserBody.GetWorldPoint(laserStart);
segment.p2 = b2Math.AddVV(segment.p1, laserBody.GetWorldVector(laserDir));*/
var shapes:Array = new Array();
max_shapes = 64;
var count = 1;
count = m_world.Raycast(segment, shapes, max_shapes, false, null);
for(var i=0;i<count;i++)
{
//Make sure it's a polygon, we cannot cut circles.
if(shapes[i].GetType() != b2Shape.e_polygonShape){
continue;
}
var b:b2Body;
b = shapes[i].GetBody();
//Custom check to make sure we don't cut stuff we don't want to cut.
if (b.GetUserData() != 1){ //Ouille !
continue;//return if we cannot pass trough uncutable shapes.
}
var polyShape:b2PolygonShape;
polyShape = shapes[i];
var pd:Array = new Array(1);
pd[0] = new b2PolygonDef();
pd[1] = new b2PolygonDef();
pd[0].density = 5.0;
pd[1].density = 5.0;
if (SplitShape(polyShape, segment, 0.1, pd) == 0)
{
b.DestroyShape(shapes[i]);
b.CreateShape(pd[0]);
b.SetMassFromShapes();
b.WakeUp();
var bd:b2BodyDef = new b2BodyDef();
bd.userData = 1;
bd.position = b.GetPosition();
bd.angle = b.GetAngle();
var newBody:b2Body;
newBody = m_world.CreateBody(bd);
newBody.CreateShape(pd[1]);
newBody.SetMassFromShapes();
newBody.SetAngularVelocity(b.GetAngularVelocity());
newBody.SetLinearVelocity(b.GetLinearVelocity());
var oldBody:b2Body;
oldBody = m_world.CreateBody(bd);
oldBody.CreateShape(pd[0]);
oldBody.SetMassFromShapes();
m_world.DestroyBody(b);
bd = new b2BodyDef();
bd.position.Set(15, 5);
}
}
}
public function Step(ite, ate)
{
if(tool == 1){
laserStart = new b2Vec2(5.0-0.1, 0.0);
laserDir = new b2Vec2(segmentLength,0.0);
segment.p1 = laserBody.GetWorldPoint(laserStart);
segment.p2 = laserBody.GetWorldVector(laserDir);
segment.p2 = b2Math.AddVV(segment.p2, segment.p1);
laserColor = new b2Color(0,0,1);
dbgDraw.DrawSegment(segment.p1, segment.p2, laserColor);
} else {
if(Input.mouseDown){
if(!firstClic){
depX = (Input.mouseX / m_physScale);
depY = (Input.mouseY / m_physScale);
firstClic = true;
}
arrX = (Input.mouseX / m_physScale);
arrY = (Input.mouseY / m_physScale);
laserStart = new b2Vec2(depX, depY);
laserDir = new b2Vec2((arrX - depX), (arrY - depY));
segment.p1 = laserStart;
segment.p2 = laserDir;
segment.p2 = b2Math.AddVV(segment.p2, segment.p1);
laserColor = new b2Color(0,0,1);
dbgDraw.DrawSegment(segment.p1, segment.p2, laserColor);
m_cut = 1;
}
if(!(Input.mouseDown)){
firstClic = false;
}
}
}
public function mouseUpEvent(e:MouseEvent){
if(m_cut){
Cut();
m_cut = null;
}
}
/*********MEMBER DATA*********/
//World
public var m_world:b2World;
public var m_physScale:Number = 15;
public var m_iterations:int = 10;
public var m_timeStep:Number = 1/30;
public var m_input:Input;
public var m_sprite:Sprite;
public var dbgDraw:b2DebugDraw;
static public var m_fpsCounter:FpsCounter = new FpsCounter();
//Basics
public const b2_maxPolygonVertices:int = 8;
public var e_hitCollide = 1;
//Laser
public var segment:b2Segment = new b2Segment();
public var laserBody:b2Body;
public var segmentLength:Number = 30.0;
public var laserStart:b2Vec2;
public var laserDir:b2Vec2;
public var laserColor:b2Color;
public var max_shapes:Number = 64;
//Radio Button
public var tool:Number = 1;
public var firstClic:Boolean = false;
public var depX:Number;
public var depY:Number;
public var arrX:Number;
public var arrY:Number;
public var m_cut = null;
public function update(e:Event):void{
m_sprite.graphics.clear();
// For the mouse
if(tool == 1){
UpdateMouseWorld()
MouseDestroy();
MouseDrag();
}
var physStart:uint = getTimer();
m_world.Step(m_timeStep, m_iterations);
m_fpsCounter.updatePhys(physStart);
Step(m_timeStep, m_iterations);
//Update text
updateText();
// Update counter and limit framerate
m_fpsCounter.update();
FRateLimiter.limitFrame(40);
}
function key_pressed(e:KeyboardEvent):void {
switch (e.keyCode) {
case 67 :
if(tool == 1){
Cut();
}
break;
}
}
public function updateText():void{
if(tool ==1)
var a:String = "On";
else
a = "Off";
mouseS.text = 'Select object with mouse : '+a;
var shapes:Array = new Array();
var count = m_world.Raycast(segment, shapes, 64, false, null);
shapesR.text = 'Number off shapes crossed by laser : '+count;
bodyCount.text = 'Bodies enable to cut : '+(m_world.GetBodyCount() - 4); //Cause we have the top, the bottom, the laser...
}
//The mouse
public var m_mouseJoint:b2MouseJoint;
static public var mouseXWorldPhys:Number;
static public var mouseYWorldPhys:Number;
static public var mouseXWorld:Number;
static public var mouseYWorld:Number;
public function UpdateMouseWorld():void{
mouseXWorldPhys = ((Input.mouseX)/m_physScale);
mouseYWorldPhys = ((Input.mouseY)/m_physScale);
mouseXWorld = (Input.mouseX);
mouseYWorld = (Input.mouseY);
}
public function MouseDrag():void{
// mouse press
if (Input.mouseDown && !m_mouseJoint){
var body:b2Body = GetBodyAtMouse();
if (body)
{
var md:b2MouseJointDef = new b2MouseJointDef();
md.body1 = m_world.GetGroundBody();
md.body2 = body;
md.target.Set(mouseXWorldPhys, mouseYWorldPhys);
md.maxForce = 500.0 * body.GetMass();
md.timeStep = m_timeStep;
m_mouseJoint = m_world.CreateJoint(md) as b2MouseJoint;
body.WakeUp();
}
}
// mouse release
if (!Input.mouseDown){
if (m_mouseJoint)
{
m_world.DestroyJoint(m_mouseJoint);
m_mouseJoint = null;
}
}
// mouse move
if (m_mouseJoint)
{
var p2:b2Vec2 = new b2Vec2(mouseXWorldPhys, mouseYWorldPhys);
m_mouseJoint.SetTarget(p2);
}
}
//======================
// Mouse Destroy
//======================
public function MouseDestroy():void{
// mouse press
if (!Input.mouseDown && Input.isKeyPressed(68/*D*/)){
var body:b2Body = GetBodyAtMouse(true);
if (body)
{
m_world.DestroyBody(body);
return;
}
}
}
//======================
// GetBodyAtMouse
//======================
private var mousePVec:b2Vec2 = new b2Vec2();
public function GetBodyAtMouse(includeStatic:Boolean=false):b2Body{
// Make a small box.
mousePVec.Set(mouseXWorldPhys, mouseYWorldPhys);
var aabb:b2AABB = new b2AABB();
aabb.lowerBound.Set(mouseXWorldPhys - 0.001, mouseYWorldPhys - 0.001);
aabb.upperBound.Set(mouseXWorldPhys + 0.001, mouseYWorldPhys + 0.001);
// Query the world for overlapping shapes.
var k_maxCount:int = 10;
var shapes:Array = new Array();
var count:int = m_world.Query(aabb, shapes, k_maxCount);
var body:b2Body = null;
for (var i:int = 0; i < count; ++i)
{
if (shapes[i].GetBody().IsStatic() == false || includeStatic)
{
var tShape:b2Shape = shapes[i] as b2Shape;
var inside:Boolean = tShape.TestPoint(tShape.GetBody().GetXForm(), mousePVec);
if (inside)
{
body = tShape.GetBody();
break;
}
}
}
return body;
}
}
}
- ››TheServerSide网站2009年最热文章
- ››The Netron Project For vb.net
- ››The engine behind Splitter Flash game : workin...
- ››The engine behind Splitter Flash game – first...
- ››The engine behind Splitter Flash game – new A...
- ››The Standard C Library for Linux
- ››The File System(文件系统)
- ››The Standard C Library for Linux:ctype.h
- ››The Standard C Library for Linux:stdlib.h
- ››The Model-View-Controller Architecture
- ››The Three Faces of SOA
- ››The Alloy Look And Feel 1.4.4破解手记
赞助商链接