/*其算法如下:
从当前位置画一条与物体的速度向量相重合的线1号,然后从另一个待测物体出发,绘制第二条平行的线2号,这样一帧一帧的重复画,比如把一个小球扔进一个桶中,当
1号线和2号线的交点在桶口的左右边沿之间
小球位于2号线的下方。即发生的碰撞
当一号线为水平或垂直时不应该判断,因为一种斜率为0,另一种斜率为无穷大
*/
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
scoreboard = document.getElementById('scoreboard'),
launchVelocityOutput = document.getElementById('launchVelocityOutput'),
launchAngleOutput = document.getElementById('launchAngleOutput'),
elapsedTime = undefined,
launchTime = undefined,
score = 0,
lastScore = 0,
lastMouse = { left: 0, top: 0 },
threePointer = false,
needInstructions = true,
LAUNCHPAD_X = 50,
LAUNCHPAD_Y = context.canvas.height-50,
LAUNCHPAD_WIDTH = 50,
LAUNCHPAD_HEIGHT = 12,
ARENA_LENGTH_IN_METERS = 10,
INITIAL_LAUNCH_ANGLE = Math.PI/4,
launchAngle = INITIAL_LAUNCH_ANGLE,
pixelsPerMeter = canvas.width / ARENA_LENGTH_IN_METERS,
// LaunchPad.................................................
launchPadPainter = {
LAUNCHPAD_FILL_STYLE: 'rgb(100,140,230)',
paint: function (ledge, context) {
context.save();
context.fillStyle = this.LAUNCHPAD_FILL_STYLE;
context.fillRect(LAUNCHPAD_X, LAUNCHPAD_Y,
LAUNCHPAD_WIDTH, LAUNCHPAD_HEIGHT);
context.restore();
}
},
launchPad = new Sprite('launchPad', launchPadPainter),
// Ball......................................................
BALL_RADIUS = 8,
lastBallPosition = { left: 0, top: 0 },
ballPainter = {
BALL_FILL_STYLE: 'rgb(255,255,0)',
BALL_STROKE_STYLE: 'rgb(0,0,0,0.4)',
paint: function (ball, context) {
context.save();
context.shadowColor = undefined;
context.lineWidth = 2;
context.fillStyle = this.BALL_FILL_STYLE;
context.strokeStyle = this.BALL_STROKE_STYLE;
context.beginPath();
context.arc(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS,
ball.radius, 0, Math.PI*2, false);
context.clip();
context.fill();
context.stroke();
context.restore();
}
},
// Lob behavior..............................................
lob = {
lastTime: 0,
GRAVITY_FORCE: 9.81, // m/s/s
applyGravity: function (elapsed) {
ball.velocityY = (this.GRAVITY_FORCE * elapsed) -
(launchVelocity * Math.sin(launchAngle));
},
updateBallPosition: function (updateDelta) {
lastBallPosition.left = ball.left;
lastBallPosition.top = ball.top;
ball.left += ball.velocityX * (updateDelta) * pixelsPerMeter;
ball.top += ball.velocityY * (updateDelta) * pixelsPerMeter;
},
checkForThreePointer: function () {
if (ball.top < 0) {
threePointer = true;
}
},
checkBallBounds: function () {
if (ball.top > canvas.height || ball.left > canvas.width) {
reset();
}
},
execute: function (ball, context, time) {
var updateDelta,
elapsedFlightTime;
if (ballInFlight) {
elapsedFrameTime = (time - this.lastTime)/1000,
elapsedFlightTime = (time - launchTime)/1000;
this.applyGravity(elapsedFlightTime);
this.updateBallPosition(elapsedFrameTime);
this.checkForThreePointer();
this.checkBallBounds();
}
this.lastTime = time;
}
},
ball = new Sprite('ball', ballPainter, [ lob ]),
ballInFlight = false,
// Bucket....................................................
BUCKET_LEFT = 668,
BUCKET_TOP = canvas.height - 100,
BUCKET_WIDTH = 83,
BUCKET_HEIGHT = 62,
bucketHitCenter = { x: BUCKET_LEFT + 2*this.BUCKET_WIDTH/3,
y: BUCKET_TOP + BUCKET_HEIGHT/8
},
bucketHitRadius = BUCKET_WIDTH/8,
bucketImage = new Image(),
catchBall = {
intersectionPoint: { x: 0, y: 0 },
isBallInBucket: function() { // a posteriori光线投射算法
if (lastBallPosition.left === ball.left ||
lastBallPosition.top === ball.top) {
return;
}
var x1 = lastBallPosition.left,
y1 = lastBallPosition.top,
x2 = ball.left,
y2 = ball.top,
x3 = BUCKET_LEFT + BUCKET_WIDTH/4,
y3 = BUCKET_TOP,
x4 = BUCKET_LEFT + BUCKET_WIDTH,
y4 = y3,
m1 = (ball.top - lastBallPosition.top) / (ball.left - lastBallPosition.left),
m2 = (y4 - y3) / (x4 - x3), // zero, but calculate anyway for illustration
b1 = y1 - m1*x1,
b2 = y3 - m2*x3;
this.intersectionPoint.x = (b2 - b1) / (m1 - m2);
this.intersectionPoint.y = m1*this.intersectionPoint.x + b1;
return this.intersectionPoint.x > x3 &&
this.intersectionPoint.x < x4 &&
ball.top + ball.height > y3 &&
ball.left + ball.width < x4;
},
adjustScore: function() {
if (threePointer) lastScore = 3;
else lastScore = 2;
score += lastScore;
scoreboard.innerText = score;
},
drawRay: function() {
context.beginPath();
context.save();
context.lineWidth = 1;
context.strokeStyle = 'blue';
context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS);
context.lineTo(this.intersectionPoint.x, this.intersectionPoint.y);
context.stroke();
context.restore();
},
execute: function (bucket, context, time) {
if (ballInFlight) {
this.drawRay();
if (this.isBallInBucket()) {
reset();
this.adjustScore();
}
}
}
},
bucket = new Sprite('bucket', {
paint: function (sprite, context) {
context.drawImage(bucketImage, BUCKET_LEFT, BUCKET_TOP);
}
},
[ catchBall ]
);
// Functions.....................................................
function freeze() {
ball.velocityX = 0;
ball.velocityY = 0;
ballInFlight = false;
needInstructions = false;
}
function reset() {
lastBallPosition.left = ball.left;
lastBallPosition.top = ball.top;
ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2 - BALL_RADIUS;
ball.top = LAUNCHPAD_Y - ball.height/2 - BALL_RADIUS;
ball.velocityX = 0;
ball.velocityY = 0;
ballInFlight = false;
needInstructions = false;
lastScore = 0;
}
function showText(text) {
var metrics;
context.font = '42px Helvetica';
metrics = context.measureText(text);
context.save();
context.shadowColor = undefined;
context.strokeStyle = 'rgb(80,120,210)';
context.fillStyle = 'rgba(100,140,230,0.5)';
context.fillText(text,
canvas.width/2 - metrics.width/2,
canvas.height/2);
context.strokeText(text,
canvas.width/2 - metrics.width/2,
canvas.height/2);
context.restore();
}
function drawRubberband() {
context.beginPath();
context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS);
context.lineTo(bucketHitCenter.x, bucketHitCenter.y);
context.stroke();
};
function drawGuidewire() {
context.moveTo(ball.left + BALL_RADIUS, ball.top + BALL_RADIUS);
context.lineTo(lastMouse.left, lastMouse.top);
context.stroke();
};
function updateBackgroundText() {
if (lastScore == 3) showText('Three pointer!');
else if (lastScore == 2) showText('Nice shot!');
else if (needInstructions) showText('Click to launch ball');
};
function resetScoreLater() {
setTimeout(function () {
lastScore = 0;
}, 1000);
};
function updateSprites(time) {
bucket.update(context, time);
launchPad.update(context, time);
ball.update(context, time);
}
function paintSprites() {
launchPad.paint(context);
bucket.paint(context);
ball.paint(context);
}
function mouseToCanvas(e) {
var rect = canvas.getBoundingClientRect(),
loc = { x: e.x || e.clientX,
y: e.y || e.clientY
};
loc.x -= rect.left;
loc.y -= rect.top;
return loc;
}
// Event handlers................................................
canvas.onmousedown = function(e) {
var rect;
e.preventDefault();
if ( ! ballInFlight) {
ball.velocityX = launchVelocity * Math.cos(launchAngle);
ball.velocityY = launchVelocity * Math.sin(launchAngle);
ballInFlight = true;
threePointer = false;
launchTime = +new Date();
}
};
canvas.onmousemove = function (e) {
var rect;
e.preventDefault();
if ( ! ballInFlight) {
loc = mouseToCanvas(e);
lastMouse.left = loc.x;
lastMouse.top = loc.y;
deltaX = Math.abs(lastMouse.left - ball.left);
deltaY = Math.abs(lastMouse.top - ball.top);
launchAngle = Math.atan(parseFloat(deltaY) / parseFloat(deltaX));
launchVelocity = 4 * deltaY / Math.sin(launchAngle) / pixelsPerMeter;
launchVelocityOutput.innerText = launchVelocity.toFixed(2);
launchAngleOutput.innerText = (launchAngle * 180/Math.PI).toFixed(2);
}
};
// Animation Loop................................................
function animate(time) {
elapsedTime = (time - launchTime) / 1000;
context.clearRect(0,0,canvas.width,canvas.height);
if (!ballInFlight) {
drawGuidewire();
updateBackgroundText();
if (lastScore !== 0) { // just scored
resetScoreLater();
}
}
paintSprites();
updateSprites(time);
//drawRubberband();
context.save();
context.beginPath();
context.lineWidth = 0.5;
context.strokeStyle = 'red';
context.moveTo(0, BUCKET_TOP+0.5);
context.lineTo(canvas.width, BUCKET_TOP);
context.stroke();
context.restore();
window.requestNextAnimationFrame(animate);
}
// Initialization................................................
ball.width = BALL_RADIUS*2;
ball.height = ball.width;
ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2 - BALL_RADIUS;
ball.top = LAUNCHPAD_Y - ball.height/2 - BALL_RADIUS;
lastBallPosition.left = ball.left;
lastBallPosition.top = ball.top;
ball.radius = BALL_RADIUS;
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0,0,0,0.5)';
context.shadowColor = 'rgba(0,0,0,0.5)';
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.shadowBlur = 4;
context.stroke();
bucketImage.src = '../../shared/images/bucket.png';
bucketImage.onload = function (e) {
bucket.left = BUCKET_LEFT;
bucket.top = BUCKET_TOP;
bucket.width = BUCKET_WIDTH;
bucket.height = BUCKET_HEIGHT;
};
animate(+new Date());
//html...............................................
<body>
<canvas id='canvas' width='800' height='450'>
Canvas not supported
</canvas>
<div id='scoreboard' class='floatingControls'>0</div>
<div id='controls' class='floatingControls'>
Launch velocity (m/s): <output id='launchVelocityOutput'></output> m/s<br/>
Launch angle (degrees): <output id='launchAngleOutput'></output> degrees<br/>
</div>
<script src = '../../shared/js/requestNextAnimationFrame.js'></script>
<script src = '../../shared/js/sprites.js'></script>
<script src = 'example.js'></script>
</body>
光线投射法37
原创
©著作权归作者所有:来自51CTO博客作者生而为人我很遗憾的原创作品,请联系作者获取转载授权,否则将追究法律责任
上一篇:外接图形法-矩形&圆形36
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
51c大模型~合集37大模型
-
51c自动驾驶~合集37
自动驾驶
自动驾驶 -
Open3D Ray Casting 光线投射
open3d 光线投射
python 开发语言 3d 光线投射 初始化