• 【CSON原创】 基于HTML5的小球物理测试系统


    效果预览:
     

    功能说明:

      一个基于HTML5 canvas的小球物理测试系统,用户可以手动为新的小球设置不同的属性值(颜色,半径,速度等),从而在canvas中发射小球,小球在运动过程中会收到重力,弹性以及摩擦力的影响。

    实现原理:

    在小球飞行过程中,以初始速度,入射角以及重力系数作为依据,正交分解得出小球X轴和Y轴上的分速度,通过定时器不断刷新canvas,显示出小球飞行的动画。当小球和墙壁产生碰撞时,以小球弹性为依据计算能量损耗,当小球在墙壁滚动时,以墙壁摩擦系数为依据计算其能量损耗。

    代码分析:

    var bounceWall = (function() {


    returnfunction(canvasId,backgroundColor) {
    this.init(canvasId, backgroundColor);

    }
    })();

    构造函数,其中调用了prototype中的init方法进行初始化。需要传入的参数是canvas的ID,和canvas的背景色,如果不传入backgroundColor参数,背景色默认为黑色。

    bounceWall.prototype = (function() {

    var CanvasSupport = Modernizr.canvas; //检查浏览器是否支持canvas
    var newBall =function(radius, color, speed, direction, currentX, currentY, elasticity) {

    this.radius = parseFloat(radius); //半径
    this.color = color; //颜色
    this.speed = parseFloat(speed); //速度
    this.elasticity = parseFloat(elasticity); //弹性
    this.direction = parseFloat(direction); //入射角
    this.currentX = parseFloat(currentX); //初始X坐标
    this.currentY = parseFloat(currentY); //初始Y坐标
    this.dx = speed * Math.cos(this.direction * Math.PI /180); //计算其X轴方向的初始速度
    this.dy = speed * Math.sin(this.direction * Math.PI /180); //计算其Y轴方向的初始速度
    this.nextX =this.currentX +this.dx; //根据速度和初速度得出其下次移动到的X坐标
    this.nextY =this.currentY +this.dy; //根据速度和初速度得出其下次移动到的Y坐标


    };

          开始进入到bounce wall的prototype,首先使用Modernizr检测是否可以使用canvas。Modernizr是一个可以用于检测浏览器是否支持一些新功能的js库,可以下载直接使用。

         之后出现的是小球的构造函数newBall,用户需要传入一系列的特性对其进行初始化,具体已经在注释中标出。需要特别注意的是其中的nextX和nextY记录的是小球下一次出现位置的坐标,它根据现在的位置(currentX和currentY)以及小球X轴和Y轴上的分速度(dx和dy)计算得出。nextX和nextY属性的用处主要是保证小球能和墙壁发生完全的碰撞,会在后面的代码分析。

    /* 绘制canvas的背景 */
    var drawBackground =function(contextObj, backgroundColor, canvasWidth, canvasHeight) {

    contextObj.fillStyle
    = backgroundColor;
    contextObj.fillRect(
    0, 0, parseInt(canvasWidth), parseInt(canvasHeight));

    };

    之后的函数是用户绘制canvas的背景,依据的属性是用户设定的背景色,canvas的宽度和高度。

    /* 更新小球状态 */

    var updateBallState =function(ballObj, canvasWidth, canvasHeight, gravityValue, friction) {

    ballObj.currentX
    = ballObj.nextX;
    ballObj.currentY
    = ballObj.nextY;
    ballObj.nextX
    = ballObj.currentX + ballObj.dx;
    ballObj.nextY
    = ballObj.currentY + ballObj.dy;

    /* 测试对墙壁产生是否X轴碰撞 */

    if (ballObj.nextX < ballObj.radius) {
    ballObj.nextX
    = ballObj.radius;
    ballObj.dx
    = Math.max(0, (ballObj.dx + ((1- ballObj.elasticity) * Math.abs(ballObj.dx))) *-1-1);
    }
    elseif ((ballObj.nextX + ballObj.radius) > parseInt(canvasWidth)) {
    ballObj.nextX
    = parseInt(canvasWidth) - ballObj.radius;
    ballObj.dx
    = Math.min(0, (ballObj.dx - ((1- ballObj.elasticity) * Math.abs(ballObj.dx))) *-1+1);
    }

    /* 水平运动时受摩擦力影响 */
    elseif (ballObj.currentY >= (parseInt(canvasHeight) - ballObj.radius)) {

    if (ballObj.dx >0) {
    ballObj.dx
    = Math.max(0, ballObj.dx - (ballObj.dx * friction) -0.01);
    }
    elseif (ballObj.dx <0) {
    ballObj.dx
    = Math.min(0, ballObj.dx - (ballObj.dx * friction) +0.01);
    }

    }


    /* 测试对墙壁产生是否Y轴碰撞 */
    if (ballObj.nextY < ballObj.radius) {
    ballObj.nextY
    = ballObj.radius;
    ballObj.dy
    = Math.max(0, (ballObj.dy + ((1- ballObj.elasticity) * Math.abs(ballObj.dy))) *-1-1);
    }
    elseif ((ballObj.nextY + ballObj.radius) > parseInt(canvasHeight)) {
    ballObj.nextY
    = parseInt(canvasHeight) - ballObj.radius;
    ballObj.dy
    = Math.min(0, (ballObj.dy - ((1- ballObj.elasticity) * Math.abs(ballObj.dy))) *-1+1);

    }
    else {
    ballObj.dy
    = ballObj.dy + gravityValue;
    }


    };

          接着是核心函数,updateBallState。该函数的作用是通过小球半径,canvas宽高等参数,判断小球是否和墙壁产生碰撞。并根据对四个不同墙壁的碰撞分别使用不同的处理方法。具体过程如下:

    1.首先把上次记录的nextX和nextY设置为现在的currentX和currentY,更新了现在小球所处的位置。

    2.计算小球的nextX和nextY,这两个参数将会决定小球下次所处的位置。

    3.如果ballObj.nextX < ballObj.radius,册小球于左边的墙壁碰撞,此时把nextX设置为小球半径的值,是为了让小球在完全和墙壁发生碰撞(和墙壁距离为0)之后,再反弹。

          之后改变小球的速度:

    ballObj.dx = Math.max(0, (ballObj.dx + ((1 - ballObj.elasticity) * Math.abs(ballObj.dx))) * -1 - 1);

          上面这个式子比较长,其中首先通过1 - ballObj.elasticity根据小球的弹性获取一个系数,该系数越大,则小球速度减小得越多。(1 - ballObj.elasticity) * Math.abs(ballObj.dx))) 则是在获取到刚刚的系数之后,再和小球现在的速度相乘,得到小球碰撞后减少的速度值。由此可以发现,小球弹性越好(ballObj.elasticity越大),则所减少的速度越少,每次碰撞性能损耗越少。之后再通过乘以-1把速度方向取反。可以发现最后还需要把得出的值减1,和0比较取其较大的值,这是因为小球在不断和墙壁碰撞的过程中,由于每次都是以现有速度的百分比作为减少的速度,所以永远都不会为0,小球永远都不会停下。所以这里通过减1,使小球的速度小于1时,速度会自动变成0,使小球最终停下。

    4.如果不是和左边墙壁发生碰撞,再同理判断是否和其它墙壁碰撞,若产生碰撞,照例需要改变nextX和nextY,dx或dy的值。

    /* 把小球绘制在canvas中 */
    var drawBallsToCanvas =function(ballArray, contextObj, backgroundColor, canvasWidth, canvasHeight, gravityValue, friction) {

    returnfunction() {

    drawBackground(contextObj, backgroundColor, canvasWidth, canvasHeight);

    for (var i =0, len = ballArray.length; i < len; i++) {
    contextObj.beginPath();
    debugger;
    contextObj.fillStyle
    = ballArray[i].color;
    contextObj.arc(ballArray[i].currentX, ballArray[i].currentY, ballArray[i].radius,
    0, 2* Math.PI, false);

    contextObj.fill();
    contextObj.closePath();

    updateBallState(ballArray[i], canvasWidth, canvasHeight, gravityValue, friction);


    }
    }
    };

          这个方法主要功能是每次定时器触发的时候,重新把所有小球绘制到canvas上。具体操作是首先重新画canvas背景(否则在旧位置的小球会保留在canvas上并显示出来),然后遍历保存所有小球的数组,根据每个小球的属性,在canvas上画出具有新位置的小球,并调用之前的updateBallState方法,更新小球的属性,为下次的移动作准备。

    return {
    /* 初始化函数 */
    init:
    function(canvasId, backgroundColor, friction, gravityValue) {
    if (!CanvasSupport)
    return;

    this.backgroundColor = backgroundColor ||"#000"; //背景色,默认为黑色
    this.friction = friction ||0.1; //墙壁摩擦系数,默认为0.1
    this.gravityValue = gravityValue ||0.2; //重力系数,默认为0.2
    this.canvasObj = util.$(canvasId); //canvas对象
    this.canvasWidth = util.getComputedStyle(this.canvasObj).width; //canvas高度
    this.canvasHeight = util.getComputedStyle(this.canvasObj).height;//canvas宽度
    this.contextObj =this.canvasObj.getContext('2d'); //2d的context对象
    this.ballArray = []; //保存所有小球的数组


    drawBackground(
    this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight);
    setInterval(drawBallsToCanvas(
    this.ballArray, this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight, this.gravityValue, this.friction =0.1), 33);

    },

    最后进入到返回的object,它作为bounceWall的prototype。其中有init的方法,如果浏览器不支持canvas则返回,否则初始化一切初始属性,并启动setInternal定时器定时更新canvas的状态。

    /* 添加小球 */
    createBall:
    function(radius, color, speed, direction, currentX, currentY, elasticity) {// 小球半径 颜色 速度 方向

    var ball =new newBall(radius, color, speed, direction, currentX, currentY, elasticity);


    this.ballArray.push(ball);

    }

    }

    })();

    添加小球的方法,demo中,该方法在“添加小球”的事件处理程序中被调用,根据用户输入数据进行新小球的添加。

    var wall =new bounceWall('wallCanvas', '#000'); //创建bounce wall

    var radiusInput = util.$('radiusInput'); //半径输入文本框
    var color_Input = util.$('color_Input'); //颜色输入文本框
    var speed_Input = util.$('speed_Input'); //速度输入文本框
    var elaticity_Input = util.$('elaticity_Input'); //弹性输入文本框
    var inAngle_Input = util.$('inAngle_Input'); //入射角输入文本框
    var X_Input = util.$('X_Input'); //初始X坐标输入文本框
    var Y_Input = util.$('Y_Input'); //初始Y坐标输入文本框


    var btn = util.$('add_btn'); //添加小球按钮
    btn.onclick =function() { //绑定事件处理程序,调用creatBall方法创建新小球
    wall.createBall(radiusInput.value, color_Input.value, speed_Input.value, inAngle_Input.value, X_Input.value, Y_Input.value, elaticity_Input.value);

    }

    最后是demo中调用的代码。

    完整demo代码:

    View Code
    *{margin:0; padding:0;}
    body
    {text-align:center;}

    ul
    {list-style-type:none;}
    #inputTable
    {width:200px; height:200px; margin:0 auto;}
    #inputTable input
    {width:50px;}
    View Code
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Bounce Wall</title>
    <link href="bounceWall.css" rel="stylesheet" type="text/css"/>
    </head>
    <body>

    <canvas id="wallCanvas" width="400px" height="400px">
    Your browser does not support HTML5 Canvas.
    </canvas>
    <table id="inputTable">
    <tr><td><label>半径:</label></td><td><input id="radiusInput" type="text"/></td></tr>

    <tr><td><label>颜色:</label></td><td><input id="color_Input" type="text"/></td></tr>
    <tr><td><label>速度:</label></td><td><input id="speed_Input" type="text"/></td></tr>
    <tr><td><label>弹性(0-1):</label></td><td><input id="elaticity_Input" type="text"/></td></tr>
    <tr><td><label>入射角(0-360):</label></td><td><input id="inAngle_Input" type="text"/></td></tr>
    <tr><td><label>起始X坐标(0-600):</label></td><td><input id="X_Input" type="text"/></td></tr>
    <tr><td><label>起始Y坐标(0-600):</label></td><td><input id="Y_Input" type="text"/></td></tr>

    </table>
    <input id="add_btn" type="button" value="添加小球"/>
    </body>

    <script src="util.js" type="text/javascript"></script>
    <script src="modernizr.js" type="text/javascript"></script>
    <script src="bounceWall.js" type="text/javascript"></script>
    <script>
    var wall =new bounceWall('wallCanvas', '#000'); //创建bounce wall

    var radiusInput = util.$('radiusInput'); //半径输入文本框
    var color_Input = util.$('color_Input'); //颜色输入文本框
    var speed_Input = util.$('speed_Input'); //速度输入文本框
    var elaticity_Input = util.$('elaticity_Input'); //弹性输入文本框
    var inAngle_Input = util.$('inAngle_Input'); //入射角输入文本框
    var X_Input = util.$('X_Input'); //初始X坐标输入文本框
    var Y_Input = util.$('Y_Input'); //初始Y坐标输入文本框


    var btn = util.$('add_btn'); //添加小球按钮
    btn.onclick =function() { //绑定事件处理程序,调用creatBall方法创建新小球
    wall.createBall(radiusInput.value, color_Input.value, speed_Input.value, inAngle_Input.value, X_Input.value, Y_Input.value, elaticity_Input.value);

    }

    </script>
    </html>
    View Code
    var bounceWall = (function() {


    returnfunction(canvasId,backgroundColor) {
    this.init(canvasId, backgroundColor);

    }
    })();

    bounceWall.prototype
    = (function() {

    var CanvasSupport = Modernizr.canvas; //检查浏览器是否支持canvas
    var newBall =function(radius, color, speed, direction, currentX, currentY, elasticity) {

    this.radius = parseFloat(radius); //半径
    this.color = color; //颜色
    this.speed = parseFloat(speed); //速度
    this.elasticity = parseFloat(elasticity); //弹性
    this.direction = parseFloat(direction); //入射角
    this.currentX = parseFloat(currentX); //初始X坐标
    this.currentY = parseFloat(currentY); //初始Y坐标
    this.dx = speed * Math.cos(this.direction * Math.PI /180); //计算其X轴方向的初始速度
    this.dy = speed * Math.sin(this.direction * Math.PI /180); //计算其Y轴方向的初始速度
    this.nextX =this.currentX +this.dx; //根据速度和初速度得出其下次移动到的X坐标
    this.nextY =this.currentY +this.dy; //根据速度和初速度得出其下次移动到的Y坐标


    };


    /* 绘制canvas的背景 */
    var drawBackground =function(contextObj, backgroundColor, canvasWidth, canvasHeight) {

    contextObj.fillStyle
    = backgroundColor;
    contextObj.fillRect(
    0, 0, parseInt(canvasWidth), parseInt(canvasHeight));

    };


    /* 更新小球状态 */

    var updateBallState =function(ballObj, canvasWidth, canvasHeight, gravityValue, friction) {

    ballObj.currentX
    = ballObj.nextX;
    ballObj.currentY
    = ballObj.nextY;
    ballObj.nextX
    = ballObj.currentX + ballObj.dx;
    ballObj.nextY
    = ballObj.currentY + ballObj.dy;

    /* 测试对墙壁产生是否X轴碰撞 */

    if (ballObj.nextX < ballObj.radius) {
    ballObj.nextX
    = ballObj.radius;
    ballObj.dx
    = Math.max(0, (ballObj.dx + ((1- ballObj.elasticity) * Math.abs(ballObj.dx))) *-1-1);
    }
    elseif ((ballObj.nextX + ballObj.radius) > parseInt(canvasWidth)) {
    ballObj.nextX
    = parseInt(canvasWidth) - ballObj.radius;
    ballObj.dx
    = Math.min(0, (ballObj.dx - ((1- ballObj.elasticity) * Math.abs(ballObj.dx))) *-1+1);
    }

    /* 水平运动时受摩擦力影响 */
    elseif (ballObj.currentY >= (parseInt(canvasHeight) - ballObj.radius)) {

    if (ballObj.dx >0) {
    ballObj.dx
    = Math.max(0, ballObj.dx - (ballObj.dx * friction) -0.01);
    }
    elseif (ballObj.dx <0) {
    ballObj.dx
    = Math.min(0, ballObj.dx - (ballObj.dx * friction) +0.01);
    }

    }


    /* 测试对墙壁产生是否Y轴碰撞 */
    if (ballObj.nextY < ballObj.radius) {
    ballObj.nextY
    = ballObj.radius;
    ballObj.dy
    = Math.max(0, (ballObj.dy + ((1- ballObj.elasticity) * Math.abs(ballObj.dy))) *-1-1);
    }
    elseif ((ballObj.nextY + ballObj.radius) > parseInt(canvasHeight)) {
    ballObj.nextY
    = parseInt(canvasHeight) - ballObj.radius;
    ballObj.dy
    = Math.min(0, (ballObj.dy - ((1- ballObj.elasticity) * Math.abs(ballObj.dy))) *-1+1);

    }
    else {
    ballObj.dy
    = ballObj.dy + gravityValue;
    }


    };




    /* 把小球绘制在canvas中 */
    var drawBallsToCanvas =function(ballArray, contextObj, backgroundColor, canvasWidth, canvasHeight, gravityValue, friction) {

    returnfunction() {

    drawBackground(contextObj, backgroundColor, canvasWidth, canvasHeight);

    for (var i =0, len = ballArray.length; i < len; i++) {
    contextObj.beginPath();
    debugger;
    contextObj.fillStyle
    = ballArray[i].color;
    contextObj.arc(ballArray[i].currentX, ballArray[i].currentY, ballArray[i].radius,
    0, 2* Math.PI, false);

    contextObj.fill();
    contextObj.closePath();

    updateBallState(ballArray[i], canvasWidth, canvasHeight, gravityValue, friction);


    }
    }
    };




    return {
    /* 初始化函数 */
    init:
    function(canvasId, backgroundColor, friction, gravityValue) {
    if (!CanvasSupport)
    return;

    this.backgroundColor = backgroundColor ||"#000"; //背景色,默认为黑色
    this.friction = friction ||0.1; //墙壁摩擦系数,默认为0.1
    this.gravityValue = gravityValue ||0.2; //重力系数,默认为0.2
    this.canvasObj = util.$(canvasId); //canvas对象
    this.canvasWidth = util.getComputedStyle(this.canvasObj).width; //canvas高度
    this.canvasHeight = util.getComputedStyle(this.canvasObj).height;//canvas宽度
    this.contextObj =this.canvasObj.getContext('2d'); //2d的context对象
    this.ballArray = []; //保存所有小球的数组


    drawBackground(
    this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight);
    setInterval(drawBallsToCanvas(
    this.ballArray, this.contextObj, this.backgroundColor, this.canvasWidth, this.canvasHeight, this.gravityValue, this.friction =0.1), 33);

    },
    /* 添加小球 */
    createBall:
    function(radius, color, speed, direction, currentX, currentY, elasticity) {// 小球半径 颜色 速度 方向

    var ball =new newBall(radius, color, speed, direction, currentX, currentY, elasticity);


    this.ballArray.push(ball);

    }

    }

    })();

    欢迎转载,请标明出处:http://www.cnblogs.com/Cson/archive/2011/06/30/2094752.html

  • 相关阅读:
    C# 根据主窗体的位置弹窗信息窗体一直保持在主窗体中间
    c# winForm父子窗口 通过委托进行信息传递
    使用devexpress插件 消除运行时弹窗
    C# 获取当前时间戳
    WinForm实现Loading等待界面
    转载 C#设置控件 Enabled 为 false 时背景色不改变
    DEV gridView中加入加载条显示进度,必须为圆角型
    winfrom 圆角化
    列表元素的反转、排序——python
    使用for循环和while循环打印九九乘法表——python
  • 原文地址:https://www.cnblogs.com/Cson/p/HTML5.html
Copyright © 2020-2023  润新知