• js基于“合成大西瓜的”碰撞模型(一)


    在玩过“合成大西瓜”后,对其中的碰撞原理产生了兴趣,想着探究一下其中的原理,首先探究一下平面碰撞原理

    事先分析:物理里面的力学知识,数学计算坐标系位移。

    页面:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
            <meta name="apple-mobile-web-app-capable" content="yes" />
            <meta name="apple-mobile-web-app-status-bar-style" content="black" />
            <script src="js/jquery.min.js"></script>
            <title></title>
            <style>
                html,body{
                    height: 100%;
                }
                * {
                  box-sizing: border-box;
                  padding: 0;
                  margin: 0;
                }
                main {
                  width: 100vw;
                  height: 100vh;
                }
            </style>
        </head>
        <body>
            <div id="showBox" style="position: absolute;top: 0;left: -150px;z-index: 999;  200px;height: 100%;">
                <div id="leftTree" style=" 150px;height: 100%;padding:10px;float: left;">
                    <p>屏幕宽度:<label id="screenWidth"></label></p>
                    <p>屏幕高度:<label id="screenHeight"></label></p>
                    <p>圆心位置:</p>
                    <p style="text-indent: 2em;">X&nbsp;轴:<input id="circX" type="text" style=" 50px;margin-bottom: 10px;" value="100"/></p>
                    <p style="text-indent: 2em;">Y&nbsp;轴:<input id="circY" type="text" style=" 50px;" value="100"/></p>
                    <p>小求半径:</p>
                    <p style="text-indent: 2em;">半径:<input id="circR" type="text" style=" 50px;" value="60"/></p>
                    <p>击打点:</p>
                    <p style="text-indent: 2em;">X&nbsp;轴:<input id="pickX" type="text" style=" 50px;margin-bottom: 10px;" value="0"/></p>
                    <p style="text-indent: 2em;">Y&nbsp;轴:<input id="pickY" type="text" style=" 50px;" value="0"/></p>
                    <p>击打力:</p>
                    <p style="text-indent: 2em;">动能:<input id="mf" type="text" style=" 50px;" value="1000"/></p>
                    <p>小求质量:</p>
                    <p style="text-indent: 2em;">质量:<input id="mg" type="text" style=" 50px;" value="10"/></p>
                    <p>滚动摩擦系数:</p>
                    <p style="text-indent: 2em;">系数:<input id="mk" type="text" style=" 50px;" value="0.3"/></p>
                    <p>撞墙衰减系数:</p>
                    <p style="text-indent: 2em;">系数:<input id="me" type="text" style=" 50px;" value="0.3"/></p>
                    <button type="button" style=" 100%;height: 30px;margin-top: 10px;" onclick="start(this)">开始</button>
                </div>
                <button type="button" style=" 40px;height: 40px;margin-top: 10px; float: right;border-radius: 50%;opacity: 0.5;" onclick="showBox()">***</button>
            </div>
            <main>
              <canvas id="gameboard"></canvas>
            </main>
        </body>
    </html>
    <script src="js/module.js"></script>

    module.js:

    const canvas = document.getElementById("gameboard");
    const ctx = canvas.getContext("2d");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    let width = canvas.width;
    let height = canvas.height;
    //Web坐标系是以左上角为原点,向右为x轴正方向,向下为y轴正方向
    //为了符合数学习惯,将其转换为笛卡尔坐标系
    ctx.save();
    ctx.translate(0,height);
    ctx.rotate(Math.PI);
    ctx.scale(-1,1);
    //绘制初始圆
    ctx.fillStyle = "hsl(170, 100%, 50%)";
    ctx.beginPath();
    ctx.arc($("#circX").val(), $("#circY").val(), $("#circR").val(),0, 2 * Math.PI);
    ctx.fill();
    
    //滚动摩擦系数
    let mk=0;
    //动能衰减系数
    let me=0;
    //剩余动能系数
    let loseProp=0;
    //屏幕长宽
    const screenWidth=$(window).width(),screenHeight=$(window).height();
    $("#screenWidth").text(screenWidth);$("#screenHeight").text(screenHeight);
    //显示/隐藏信息栏
    let clickFlag=false;
    function showBox(){
        if(clickFlag){
            $("#showBox").animate({left:'-150px'});
            clickFlag=false;
        }
        else{
            $("#showBox").animate({left:'0'});
            clickFlag=true;
        }
    }
    //执行
    function start(obj){
        if($(obj).text()=="开始"){
            mk=parseFloat($("#mk").val());
            me=parseFloat($("#me").val());
            loseProp=1-me;
            const game = new Circle();
            $(obj).text("停止");
        }
        else{
            window.location.reload();
        }
    }
    //球类
    class Circle{
        /**
         * @param {Object} start 是否初始化
         * @param {Object} mf 附加初始动能
         * @param {Object} mg 小球重量
         * @param {Object} x 圆心x轴位置
         * @param {Object} y 圆心y轴位置
         * @param {Object} r 圆半径
         * @param {Object} vx 往x轴移动距离
         * @param {Object} vy 往y轴移动距离
         * @param {Object} vh 斜边移动距离
         * @param {Object} pickX 击打点x轴坐标
         * @param {Object} pickY 击打点y轴坐标
         * @param {Object} dirX 往x轴正方向移动
         * @param {Object} dirY 往y轴正方向移动
         * @param {Object} lastTime 上一帧耗时
         */
        constructor() {
          this.start=true;
          this.mf = parseFloat($("#mf").val());
          this.mg = parseFloat($("#mg").val());
          this.x = parseFloat($("#circX").val());
          this.y = parseFloat($("#circY").val());
          this.r = parseFloat($("#circR").val());
          this.vx = 0;
          this.vy = 0;
          this.vh = 0;
          this.pickX = parseFloat($("#pickX").val());
          this.pickY = parseFloat($("#pickY").val());
          this.dirX = null;
          this.dirY = null;
          this.lastTime = null;
          this.init();
        }
        // 初始化画布
        init() {
          this.circles = [
            this.draw(this.x, this.y, this.r, 0, 2 * Math.PI)
          ];
          
          window.requestAnimationFrame(this.Sport.bind(this));
        }
        draw(x,y,r,sAngle,eAngle){
            ctx.fillStyle = "hsl(170, 100%, 50%)";
            ctx.beginPath();
            ctx.arc(x, y, r,sAngle,eAngle);
            ctx.fill();
        }
        print(obj){
            let str="";
             $.each(obj,function(key,value){ 
                str+=key+":"+value+"。";
            })
            console.log(str);
        }
        //参考Web Api https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
        //window.requestAnimationFrame() 会把当前时间的毫秒数(即时间戳:00.000)传递给回调函数
        Sport(timestamp){
            let flag=true;
            //计算上一帧耗时秒数
            if (!this.lastTime) {
              this.lastTime = 0;
              flag=false;
            }
            let timeDiff=(timestamp-this.lastTime)/1000;
            this.lastTime=timestamp;
            //用为这里是调用方法执行,而非加载执行,所以第一帧时间会造成累计,排除掉第一帧
            if(flag) this.locationReset(timeDiff)
            window.requestAnimationFrame(this.Sport.bind(this));
        }
        locationReset(t){
            let obj={"合力":this.mf};
            this.print(obj);
            
            var s=this.mf*t**2/(this.mg+this.mg*mk);
            if(this.mf>this.mg+this.mg*mk){
                this.calcAngle(s*1400,this.r);
                this.mf-= this.mg*mk*s*10; 
            }
        }
        calcAngle(s,r){
            if(this.start){
                //true往x轴正方向移动,false往x轴负方向移动
                this.dirX=this.x-this.pickX>=0;
                //true往y轴正方向移动,false往y轴负方向移动
                this.dirY=this.y-this.pickY>=0
                //发力点到球心的x轴距离
                let calcX=Math.abs(this.x-this.pickX);
                //发力点到球心的y轴距离
                let calcY=Math.abs(this.y-this.pickY);
                //发力点到球心的斜边距离
                let calcH=Math.sqrt(calcX**2+calcY**2);
                let prop=s/calcH;
                
                let moveX=prop*calcX;
                let moveY=prop*calcY;
                let moveH=prop*calcH;
                
                this.vx=this.dirX?moveX:-moveX;
                this.vy=this.dirY?moveY:-moveY;
                this.vh=moveH;
                this.start=false;
            }
            else{
                //this.vh/s=this.vx/x=this.vy/y
                this.vx=s*this.vx/this.vh;
                this.vy=s*this.vy/this.vh;
                this.vh=s;
            }
            this.x=this.dirX?this.x+this.vx:this.x-this.vx;
            this.y=this.dirY?this.y+this.vy:this.y-this.vy;
            
            this.reboundPath(this.dirX,this.dirY,Math.abs(this.x/this.y));
        }
        reboundPath(dirX,dirY,prop){
            let thisScreenWidth=screenWidth-this.r;
            let thisScreenHeight=screenHeight-this.r;
            
            //圆心位置超出x轴正方向,必然发生碰撞
            if(this.x>thisScreenWidth){
                //debugger;
                let moreThanX=this.x-thisScreenWidth;
                let moreThanY=this.y>this.r?this.y-thisScreenHeight:this.r-this.y;
                //先碰右墙
                if(moreThanX/moreThanY>prop||this.y<thisScreenHeight){
                    this.x=thisScreenWidth-moreThanX*loseProp;
                    this.dirX=!this.dirX;
                    this.mf*=loseProp;
                }
                //先碰上下墙
                else if(moreThanX/moreThanY<prop){
                    this.y=this.dirY?thisScreenHeight-moreThanY*loseProp:this.r+moreThanY*loseProp;
                    this.dirY=!this.dirY;
                    this.mf*=loseProp;
                }
                //碰右上、右下角
                else{
                    this.x=thisScreenWidth-moreThanX*loseProp;
                    this.dirX=!this.dirX;
                    
                    this.y=this.dirY?thisScreenHeight-moreThanY*loseProp:this.r+moreThanY*loseProp;
                    this.dirY=!this.dirY;
                    this.mf*=(1-me);
                }
            }
            //圆心位置超出x轴负方向,必然发生碰撞
            else if(this.x<this.r){
                let moreThanX=this.r-this.x;
                let moreThanY=this.y>this.r?this.y-thisScreenHeight:this.r-this.y;
                //先碰左墙
                if(moreThanX/moreThanY>prop||this.y<thisScreenHeight){
                    this.x=this.r+moreThanX*loseProp;
                    this.dirX=!this.dirX;
                    this.mf*=loseProp;
                }
                //先碰上下墙
                else if(moreThanX/moreThanY<prop){
                    this.y=this.dirY?thisScreenHeight-moreThanY*loseProp:this.r+(this.r+this.y)*loseProp;
                    this.dirY=!this.dirY;
                    this.mf*=loseProp;
                }
                //碰左上、左下角
                else{
                    this.x=this.r+moreThanX*loseProp;
                    this.dirX=!this.dirX;
                    
                    this.y=this.dirY?thisScreenHeight-moreThanY*loseProp:this.r+(this.r+this.y)*loseProp;
                    this.dirY=!this.dirY;
                    this.mf*=loseProp;
                }
            }
            else if(this.y>=thisScreenHeight){
                let moreThanY=this.y-thisScreenHeight;
    
                this.y=thisScreenHeight-moreThanY*loseProp;
                this.dirY=!this.dirY;
                this.mf*=loseProp;
            }
            else if(this.y<=this.r){
                let moreThanY=this.r-this.y;
                
                this.y=this.r+moreThanY*loseProp;
                this.dirY=!this.dirY;
                this.mf*=loseProp;
            }
            
            if(this.x>thisScreenWidth||this.x<this.r||this.y>thisScreenHeight||this.y<this.r){
                return this.reboundPath(dirX,dirY,prop)
            }
            else{
                ctx.clearRect(0,0,screenWidth,screenHeight);
                this.draw(this.x, this.y, this.r, 0, 2 * Math.PI)
                return;
            }
        }
    }

    效果图:

  • 相关阅读:
    Ajax学习总结
    从零开始学Docker
    IBM Websphere MQ常用命令及常见错误
    Log4j学习总结
    Eclipse中各图标含义
    类加载机制与反射
    Feign【入门】
    Eureka【故障演练分析】
    Eureka【启用https】
    Eureka【开启http basic权限认证】
  • 原文地址:https://www.cnblogs.com/tenfly/p/14512798.html
Copyright © 2020-2023  润新知