//HTML.........................................................................
<body>
<canvas id='canvas' width='500' height='300'>
Canvas not supported
</canvas>
<script src = 'shapes.js'></script>
<script src = '../../shared/js/requestNextAnimationFrame.js'></script>
<script src = 'example.js'></script>
</body>
//example.js.........................................................................
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
shapes = [],
polygonPoints = [
[ new Point(250, 150), new Point(250, 200),
new Point(300, 200) ],
[ new Point(150, 100), new Point(150, 150),
new Point(200, 150) ],
[ new Point(150, 250), new Point(150, 200),
new Point(200, 200) ],
[ new Point(100, 75), new Point(100, 100),
new Point(125, 100), new Point(125, 75) ],
[ new Point(300, 75), new Point(280, 125),
new Point(350, 125) ]
],
polygonStrokeStyles = [ 'blue', 'yellow', 'red', 'red', 'black'],
polygonFillStyles = [ 'rgba(255,255,0,0.7)',
'rgba(100,140,230,0.6)',
'rgba(255,255,255,0.6)',
'aqua',
'rgba(255,0,255,0.8)' ],
shapeMoving = undefined,
c1 = new Circle(150, 275, 10),
c2 = new Circle(350, 200, 15),
lastTime = undefined,
velocity = new Vector(new Point(350, 190)),
lastVelocity = { x: 350, y: 190 },
showInstructions = true;
// Functions.....................................................
function windowToCanvas(e) {
var x = e.x || e.clientX,
y = e.y || e.clientY,
bbox = canvas.getBoundingClientRect();
return { x: x - bbox.left * (canvas.width / bbox.width),
y: y - bbox.top * (canvas.height / bbox.height)
};
};
function drawShapes() {
shapes.forEach( function (shape) {
shape.stroke(context);
shape.fill(context);
});
}
function separate(mtv) {
var dx, dy, velocityMagnitude, point;
if (mtv.axis === undefined) {
point = new Point();
velocityMagnitude = Math.sqrt(Math.pow(velocity.x, 2) +
Math.pow(velocity.y, 2));
point.x = velocity.x / velocityMagnitude;
point.y = velocity.y / velocityMagnitude;
mtv.axis = new Vector(point);
}
dy = mtv.axis.y * mtv.overlap;
dx = mtv.axis.x * mtv.overlap
if ((dx < 0 && velocity.x < 0) ||
(dx > 0 && velocity.x > 0)) {
dx = -dx;
}
if ((dy < 0 && velocity.y < 0) ||
(dy > 0 && velocity.y > 0)) {
dy = -dy;
}
shapeMoving.move(dx, dy);
}
function checkMTVAxisDirection(mtv, collider, collidee) {
var centroid1, centroid2, centroidVector, centroidUnitVector;
if (mtv.axis === undefined)
return;
centroid1 = new Vector(collider.centroid()),
centroid2 = new Vector(collidee.centroid()),
centroidVector = centroid2.subtract(centroid1),
centroidUnitVector = (new Vector(centroidVector)).normalize();
if (centroidUnitVector.dotProduct(mtv.axis) > 0) {
mtv.axis.x = -mtv.axis.x;
mtv.axis.y = -mtv.axis.y;
}
};
function bounce(mtv, collider, collidee) {
var dotProductRatio, vdotl, ldotl, point,
velocityVector = new Vector(new Point(velocity.x, velocity.y)),
velocityUnitVector = velocityVector.normalize(),
velocityVectorMagnitude = velocityVector.getMagnitude(),
perpendicular;
if (shapeMoving) {
checkMTVAxisDirection(mtv, collider, collidee)
point = new Point();
if (mtv.axis !== undefined) {
perpendicular = mtv.axis.perpendicular();
}
else {
perpendicular = new Vector(new Point(-velocityUnitVector.y,
velocityUnitVector.x));
}
vdotl = velocityUnitVector.dotProduct(perpendicular);
ldotl = perpendicular.dotProduct(perpendicular);
dotProductRatio = vdotl / ldotl;
point.x = 2 * dotProductRatio * perpendicular.x - velocityUnitVector.x;
point.y = 2 * dotProductRatio * perpendicular.y - velocityUnitVector.y;
separate(mtv);
velocity.x = point.x * velocityVectorMagnitude;
velocity.y = point.y * velocityVectorMagnitude;
}
}
function collisionDetected(mtv) {
return mtv.axis != undefined || mtv.overlap !== 0;
};
function handleEdgeCollisions() {
var bbox = shapeMoving.boundingBox(),
right = bbox.left + bbox.width,
bottom = bbox.top + bbox.height;
if (right > canvas.width || bbox.left < 0) {
velocity.x = -velocity.x;
if (right > canvas.width)
shapeMoving.move(0-(right-canvas.width), 0);
if (bbox.left < 0)
shapeMoving.move(-bbox.left, 0);
}
if (bottom > canvas.height || bbox.top < 0) {
velocity.y = -velocity.y;
if (bottom > canvas.height)
shapeMoving.move(0, 0-(bottom-canvas.height));
if (bbox.top < 0)
shapeMoving.move(0, -bbox.top);
}
};
function handleShapeCollisions() {
var mtv;
shapes.forEach( function (shape) {
if (shape !== shapeMoving) {
mtv = shapeMoving.collidesWith(shape);
if (collisionDetected(mtv)) {
bounce(mtv, shapeMoving, shape);
}
}
});
};
function detectCollisions() {
if (shapeMoving) {
handleShapeCollisions();
handleEdgeCollisions();
}
};
// Event Handlers................................................
canvas.onmousedown = function (e) {
var location = windowToCanvas(e);
if (showInstructions)
showInstructions = false;
velocity.x = lastVelocity.x;
velocity.y = lastVelocity.y;
shapeMoving = undefined;
shapes.forEach( function (shape) {
if (shape.isPointInPath(context, location.x, location.y)) {
shapeMoving = shape;
}
});
};
canvas.onmouseup = function (e) {
lastVelocity.x = velocity.x;
lastVelocity.y = velocity.y;
};
// Animation.....................................................
function animate(time) {
var elapsedTime;
if (lastTime === 0) {
lastTime = time;
}
else {
context.clearRect(0,0,canvas.width,canvas.height);
drawGrid('lightgray', 10, 10);
if (shapeMoving !== undefined) {
elapsedTime = parseFloat(time - lastTime) / 1000;
shapeMoving.move(velocity.x * elapsedTime,
velocity.y * elapsedTime);
detectCollisions();
}
drawShapes();
lastTime = time;
if (showInstructions) {
context.fillStyle = 'cornflowerblue';
context.font = '24px Arial';
context.fillText('Click on a shape to animate it', 20, 40);
context.fillText('Click on the background to stop animating', 20, 65);
}
}
window.requestNextAnimationFrame(animate);
};
function drawGrid(color, stepx, stepy) {
context.save()
context.shadowColor = undefined;
context.shadowBlur = 0;;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.strokeStyle = color;
context.fillStyle = '#ffffff';
context.lineWidth = 0.5;
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
for (var i = stepx + 0.5; i < context.canvas.width; i += stepx) {
context.moveTo(i, 0);
context.lineTo(i, context.canvas.height);
}
context.stroke();
context.beginPath();
for (var i = stepy + 0.5; i < context.canvas.height; i += stepy) {
context.moveTo(0, i);
context.lineTo(context.canvas.width, i);
}
context.stroke();
context.restore();
}
// Initialization................................................
for (var i=0; i < polygonPoints.length; ++i) {
var polygon = new Polygon(),
points = polygonPoints[i];
polygon.strokeStyle = polygonStrokeStyles[i];
polygon.fillStyle = polygonFillStyles[i];
points.forEach( function (point) {
polygon.addPoint(point.x, point.y);
});
shapes.push(polygon);
}
c1.fillStyle = 'rgba(255,255,0,1.0)';
c2.strokeStyle = 'rgba(255,255,0,1.0)';
c2.fillStyle = 'rgba(0,0,255,0.6)';
shapes.push(c1);
shapes.push(c2);
if (navigator.userAgent.indexOf('Opera') === -1)
context.shadowColor = 'rgba(100,140,255,0.5)';
context.shadowBlur = 4;
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.font = '38px Arial';
drawGrid('lightgray', 10, 10);
window.requestNextAnimationFrame(animate);
//shape.js.........................................................................
function polygonCollidesWithPolygon (p1, p2, displacement) { // displacement for p1
var mtv1 = p1.minimumTranslationVector(p1.getAxes(), p2, displacement),
mtv2 = p1.minimumTranslationVector(p2.getAxes(), p2, displacement);
if (mtv1.overlap === 0 || mtv2.overlap === 0)
return { axis: undefined, overlap: 0 };
else
return mtv1.overlap < mtv2.overlap ? mtv1 : mtv2;
};
// ..............................................................
// Check to see if a circle collides with another circle
// ..............................................................
function circleCollidesWithCircle (c1, c2) {
var distance = Math.sqrt( Math.pow(c2.x - c1.x, 2) +
Math.pow(c2.y - c1.y, 2)),
overlap = Math.abs(c1.radius + c2.radius) - distance;
return overlap < 0 ?
new MinimumTranslationVector(undefined, 0) :
new MinimumTranslationVector(undefined, overlap);
};
// ..............................................................
// Get the polygon's point that's closest to the circle
// ..............................................................
function getPolygonPointClosestToCircle(polygon, circle) {
var min = BIG_NUMBER,
length,
testPoint,
closestPoint;
for (var i=0; i < polygon.points.length; ++i) {
testPoint = polygon.points[i];
length = Math.sqrt(Math.pow(testPoint.x - circle.x, 2),
Math.pow(testPoint.y - circle.y, 2));
if (length < min) {
min = length;
closestPoint = testPoint;
}
}
return closestPoint;
};
// ..............................................................
// Get the circle's axis (circle's don't have an axis, so this
// method manufactures one)
// ..............................................................
function getCircleAxis(circle, polygon, closestPoint) {
var v1 = new Vector(new Point(circle.x, circle.y)),
v2 = new Vector(new Point(closestPoint.x, closestPoint.y)),
surfaceVector = v1.subtract(v2);
return surfaceVector.normalize();
};
// ..............................................................
// Tests to see if a polygon collides with a circle
// ..............................................................
function polygonCollidesWithCircle (polygon, circle, displacement) {
var axes = polygon.getAxes(),
closestPoint = getPolygonPointClosestToCircle(polygon, circle);
axes.push(getCircleAxis(circle, polygon, closestPoint));
return polygon.minimumTranslationVector(axes, circle, displacement);
};
// ..............................................................
// Given two shapes, and a set of axes, returns the minimum
// translation vector.
// ..............................................................
function getMTV(shape1, shape2, displacement, axes) {
var minimumOverlap = BIG_NUMBER,
overlap,
axisWithSmallestOverlap,
mtv;
for (var i=0; i < axes.length; ++i) {
axis = axes[i];
projection1 = shape1.project(axis);
projection2 = shape2.project(axis);
overlap = projection1.getOverlap(projection2);
if (overlap === 0) {
return new MinimumTranslationVector(undefined, 0);
}
else {
if (overlap < minimumOverlap) {
minimumOverlap = overlap;
axisWithSmallestOverlap = axis;
}
}
}
mtv = new MinimumTranslationVector(axisWithSmallestOverlap,
minimumOverlap);
return mtv;
};
// Constants.....................................................
var BIG_NUMBER = 1000000;
// Points........................................................
var Point = function (x, y) {
this.x = x;
this.y = y;
};
Point.prototype = {
rotate: function (rotationPoint, angle) {
var tx, ty, rx, ry;
tx = this.x - rotationPoint.x; // tx = translated X
ty = this.y - rotationPoint.y; // ty = translated Y
rx = tx * Math.cos(-angle) - // rx = rotated X
ty * Math.sin(-angle);
ry = tx * Math.sin(-angle) + // ry = rotated Y
ty * Math.cos(-angle);
return new Point(rx + rotationPoint.x, ry + rotationPoint.y);
}
};
// Lines.........................................................
var Line = function(p1, p2) {
this.p1 = p1; // point 1
this.p2 = p2; // point 2
}
Line.prototype.intersectionPoint = function (line) {
var m1, m2, b1, b2, ip = new Point();
if (this.p1.x === this.p2.x) {
m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x);
b2 = line.p1.y - m2 * line.p1.x;
ip.x = this.p1.x;
ip.y = m2 * ip.x + b2;
}
else if(line.p1.x === line.p2.x) {
m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x);
b1 = this.p1.y - m1 * this.p1.x;
ip.x = line.p1.x;
ip.y = m1 * ip.x + b1;
}
else {
m1 = (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x);
m2 = (line.p2.y - line.p1.y) / (line.p2.x - line.p1.x);
b1 = this.p1.y - m1 * this.p1.x;
b2 = line.p1.y - m2 * line.p1.x;
ip.x = (b2 - b1) / (m1 - m2);
ip.y = m1 * ip.x + b1;
}
return ip;
};
// Bounding boxes................................................
var BoundingBox = function(left, top, width, height) {
this.left = left;
this.top = top;
this.width = width;
this.height = height;
};
// Vectors.......................................................
var Vector = function(point) {
if (point === undefined) {
this.x = 0;
this.y = 0;
}
else {
this.x = point.x;
this.y = point.y;
}
};
Vector.prototype = {
getMagnitude: function () {
return Math.sqrt(Math.pow(this.x, 2) +
Math.pow(this.y, 2));
},
setMagnitude: function (m) {
var uv = this.normalize();
this.x = uv.x * m;
this.y = uv.y * m;
},
dotProduct: function (vector) {
return this.x * vector.x +
this.y * vector.y;
},
add: function (vector) {
var v = new Vector();
v.x = this.x + vector.x;
v.y = this.y + vector.y;
return v;
},
subtract: function (vector) {
var v = new Vector();
v.x = this.x - vector.x;
v.y = this.y - vector.y;
return v;
},
normalize: function () {
var v = new Vector(),
m = this.getMagnitude();
v.x = this.x / m;
v.y = this.y / m;
return v;
},
perpendicular: function () {
var v = new Vector();
v.x = this.y;
v.y = 0-this.x;
return v;
},
reflect: function (axis) {
var dotProductRatio, vdotl, ldotl, v = new Vector(),
vdotl = this.dotProduct(axis),
ldotl = axis.dotProduct(axis),
dotProductRatio = vdotl / ldotl;
v.x = 2 * dotProductRatio * axis.x - this.x;
v.y = 2 * dotProductRatio * axis.y - this.y;
return v;
}
};
// Shapes........................................................
var Shape = function () {
this.fillStyle = 'rgba(255, 255, 0, 0.8)';
this.strokeStyle = 'white';
};
Shape.prototype = {
move: function (dx, dy) {
throw 'move(dx, dy) not implemented';
},
createPath: function (context) {
throw 'createPath(context) not implemented';
},
boundingBox: function () {
throw 'boundingBox() not implemented';
},
fill: function (context) {
context.save();
context.fillStyle = this.fillStyle;
this.createPath(context);
context.fill();
context.restore();
},
stroke: function (context) {
context.save();
context.strokeStyle = this.strokeStyle;
this.createPath(context);
context.stroke();
context.restore();
},
collidesWith: function (shape, displacement) {
throw 'collidesWith(shape, displacement) not implemented';
},
isPointInPath: function (context, x, y) {
this.createPath(context);
return context.isPointInPath(x, y);
},
project: function (axis) {
throw 'project(axis) not implemented';
},
minimumTranslationVector: function (axes, shape, displacement) {
return getMTV(this, shape, displacement, axes);
}
};
// Circles.......................................................
var Circle = function (x, y, radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.strokeStyle = 'blue';
this.fillStyle = 'yellow';
}
Circle.prototype = new Shape();
Circle.prototype.centroid = function () {
return new Point(this.x,this.y);
};
Circle.prototype.move = function (dx, dy) {
this.x += dx;
this.y += dy;
};
Circle.prototype.boundingBox = function (dx, dy) {
return new BoundingBox(this.x - this.radius,
this.y - this.radius,
2*this.radius,
2*this.radius);
};
Circle.prototype.createPath = function (context) {
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
};
Circle.prototype.project = function (axis) {
var scalars = [],
point = new Point(this.x, this.y);
dotProduct = new Vector(point).dotProduct(axis);
scalars.push(dotProduct);
scalars.push(dotProduct + this.radius);
scalars.push(dotProduct - this.radius);
return new Projection(Math.min.apply(Math, scalars),
Math.max.apply(Math, scalars));
};
Circle.prototype.collidesWith = function (shape, displacement) {
if (shape.radius === undefined) {
return polygonCollidesWithCircle(shape, this, displacement);
}
else {
return circleCollidesWithCircle(this, shape, displacement);
}
};
// Polygons......................................................
var Polygon = function () {
this.points = [];
this.strokeStyle = 'blue';
this.fillStyle = 'white';
};
Polygon.prototype = new Shape();
Polygon.prototype.getAxes = function () {
var v1, v2, surfaceVector, axes = [], pushAxis = true;
for (var i=0; i < this.points.length-1; i++) {
v1 = new Vector(this.points[i]);
v2 = new Vector(this.points[i+1]);
surfaceVector = v2.subtract(v1);
axes.push(surfaceVector.perpendicular().normalize());
}
return axes;
};
Polygon.prototype.project = function (axis) {
var scalars = [];
this.points.forEach( function (point) {
scalars.push(new Vector(point).dotProduct(axis));
});
return new Projection(Math.min.apply(Math, scalars),
Math.max.apply(Math, scalars));
};
Polygon.prototype.addPoint = function (x, y) {
this.points.push(new Point(x,y));
};
Polygon.prototype.createPath = function (context) {
if (this.points.length === 0)
return;
context.beginPath();
context.moveTo(this.points[0].x,
this.points[0].y);
for (var i=0; i < this.points.length; ++i) {
context.lineTo(this.points[i].x,
this.points[i].y);
}
context.closePath();
};
Polygon.prototype.move = function (dx, dy) {
var point, x;
for(var i=0; i < this.points.length; ++i) {
point = this.points[i];
x += dx;
y += dy;
}
};
Polygon.prototype.collidesWith = function (shape, displacement) {
if (shape.radius !== undefined) {
return polygonCollidesWithCircle(this, shape, displacement);
}
else {
return polygonCollidesWithPolygon(this, shape, displacement);
}
};
Polygon.prototype.move = function (dx, dy) {
for (var i=0, point; i < this.points.length; ++i) {
point = this.points[i];
point.x += dx;
point.y += dy;
}
};
Polygon.prototype.boundingBox = function (dx, dy) {
var minx = BIG_NUMBER,
miny = BIG_NUMBER,
maxx = -BIG_NUMBER,
maxy = -BIG_NUMBER,
point;
for (var i=0; i < this.points.length; ++i) {
point = this.points[i];
minx = Math.min(minx,point.x);
miny = Math.min(miny,point.y);
maxx = Math.max(maxx,point.x);
maxy = Math.max(maxy,point.y);
}
return new BoundingBox(minx, miny,
parseFloat(maxx - minx),
parseFloat(maxy - miny));
};
Polygon.prototype.centroid = function () {
var pointSum = new Point(0,0);
for (var i=0, point; i < this.points.length; ++i) {
point = this.points[i];
pointSum.x += point.x;
pointSum.y += point.y;
}
return new Point(pointSum.x / this.points.length,
pointSum.y / this.points.length);
}
// Projections...................................................
var Projection = function (min, max) {
this.min = min;
this.max = max;
};
Projection.prototype = {
overlaps: function (projection) {
return this.max > projection.min && projection.max > this.min;
},
getOverlap: function (projection) {
var overlap;
if (!this.overlaps(projection))
return 0;
if (this.max > projection.max) {
overlap = projection.max - this.min;
}
else {
overlap = this.max - projection.min;
}
return overlap;
}
};
// MinimumTranslationVector.........................................
var MinimumTranslationVector = function (axis, overlap) {
this.axis = axis;
this.overlap = overlap;
};
var ImageShape = function(imageSource, x, y, w, h) {
var self = this;
this.image = new Image();
this.imageLoaded = false;
this.points = [ new Point(x,y) ];
this.x = x;
this.y = y;
this.image.src = imageSource;
this.image.addEventListener('load', function (e) {
self.setPolygonPoints();
self.imageLoaded = true;
}, false);
}
ImageShape.prototype = new Polygon();
ImageShape.prototype.fill = function (context) {
};
ImageShape.prototype.setPolygonPoints = function() {
this.points.push(new Point(this.x + this.image.width, this.y));
this.points.push(new Point(this.x + this.image.width, this.y + this.image.height));
this.points.push(new Point(this.x, this.y + this.image.height));
this.points.push(new Point(this.x, this.y));
this.points.push(new Point(this.x + this.image.width, this.y));
};
ImageShape.prototype.drawImage = function (context) {
context.drawImage(this.image, this.points[0].x, this.points[0].y);
};
ImageShape.prototype.stroke = function (context) {
var self = this;
if (this.imageLoaded) {
context.drawImage(this.image, this.points[0].x, this.points[0].y);
}
else {
this.image.addEventListener('load', function (e) {
self.drawImage(context);
}, false);
}
};
var SpriteShape = function (sprite, x, y) {
this.sprite = sprite;
this.x = x;
this.y = y;
sprite.left = x;
sprite.top = y;
this.setPolygonPoints();
};
SpriteShape.prototype = new Polygon();
SpriteShape.prototype.move = function (dx, dy) {
var point, x;
for(var i=0; i < this.points.length; ++i) {
point = this.points[i];
point.x += dx;
point.y += dy;
}
this.sprite.left = this.points[0].x;
this.sprite.top = this.points[0].y;
};
SpriteShape.prototype.fill = function (context) {
};
SpriteShape.prototype.setPolygonPoints = function() {
this.points.push(new Point(this.x, this.y));
this.points.push(new Point(this.x, this.y + this.sprite.height));
this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height));
this.points.push(new Point(this.x + this.sprite.width, this.y));
this.points.push(new Point(this.x, this.y));
};
/*
SpriteShape.prototype.stroke = function (context) {
this.sprite.paint(context);
};
*/
利用MTV弹离物体44
原创
©著作权归作者所有:来自51CTO博客作者生而为人我很遗憾的原创作品,请联系作者获取转载授权,否则将追究法律责任
上一篇:利用MTV分开和粘合物体43
下一篇:自定义控件45

提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
51c大模型~合集44
大模型
大模型 -
利用python画图(第二弹)
内有大量美丽的图片哟
python 递归 调用函数