数学类与图形类
数学类cc.js
//计算几何误差修正 Math.EPS=0.00000001; //判断x的符号 Math.cmp=function(x) { if(Math.abs(x)<Math.EPS)return 0; if(x>0){ return 1; }else{ return -1; } } //矩阵类 class Matrix { //将一个数组构建成一个矩阵,行Row、列Column constructor(data,Row,Column){ this.data=data||[]; this.Row=Row;//行 this.Column=Column;//竖 } //根据行、列返回矩阵元素 getItem(r,c){ return this.data[r*this.Column+c]||0; } //根据行、列设置矩阵元素 setItem(r,c,item){ this.data[r*this.Column+c]=item; } //换行 swapRow(r1,r2){ for(let c=0;c<this.Column;c++){ const cache=this.getItem(r1,c) this.setItem(r1,c,this.getItem(r2,c)) this.setItem(r2,c,cache); } } oneRowEach(r,callback){ for(let c=0;c<this.Column;c++){ callback(this.getItem(r,c),r,c) } } //按行遍历矩阵元素,返回元素item,行r,列c rowEach(callback){ for(let r=0;r<this.Row;r++){ for(let c=0;c<this.Column;c++){ callback(this.getItem(r,c),r,c) } } } //按竖遍历矩阵元素,返回元素item,行r,列c columnEach(callback){ for(let c=0;c<this.Column;c++){ for(let r=0;r<this.Row;r++){ callback(this.getItem(r,c),r,c) } } } //行循环 oneRowMap(r,callback){ this.oneRowEach(r,(item,r,c)=> { this.setItem(r,c,callback(item,r,c)); }) } //按行map矩阵元素 rowMap(callback){ this.rowEach((item,r,c)=> { this.setItem(r,c,callback(item,r,c)); }) } //相加 add(matrix){ if(matrix instanceof Matrix&& this.Row === matrix.Row && this.Column === matrix.Column){ const nMatrix=new Matrix([],this.Row,this.Column) this.rowEach(function (item,r,c) { nMatrix.setItem(r,c,item+matrix.getItem(r,c)) }) return nMatrix; }else{ throw '方法plus 参数错误'; } } //相减 sub(matrix){ if(matrix instanceof Matrix&& this.Row === matrix.Row && this.Column === matrix.Column){ const nMatrix=new Matrix([],this.Row,this.Column) this.rowEach(function (item,r,c) { nMatrix.setItem(r,c,item-matrix.getItem(r,c)) }) return nMatrix; }else{ throw '方法minus 参数错误'; } } //相乘 multiply(obj){ if(obj instanceof Matrix){ return this.multiplyMatrix(obj) }else if(typeof obj=='number'){ return this.multiplyNumber(obj) }else{ throw 'multiply 输入的参数类型错误'; } } //矩阵与数相乘,返回一个新的矩阵 multiplyNumber(number){ const nMatrix=new Matrix([],this.Row,this.Column) this.rowEach((item,r,c)=> { nMatrix.setItem(r,c,item*number) }) return nMatrix; } //矩阵与矩阵相乘 矩阵A的行必须与矩阵B的列数相等 multiplyMatrix(matrix){ if(this.Row!==matrix.Column){ throw '矩阵A的行必须与矩阵B的列数相等'; } const nMatrix=new Matrix([],this.Row,matrix.Column) for(let r=0;r<this.Row;r++){ for(let mc=0;mc<matrix.Column;mc++){ let num=0; for(let c=0;c<this.Column;c++){ num=num+this.getItem(r,c)*matrix.getItem(c,mc) } nMatrix.setItem(r,mc,num) } } return nMatrix; } //复制生成一个新的矩阵 clone(){ return new Matrix([].concat(this.data),this.Row,this.Column) } //转换成字符图形 toString(){ let str='['; for(let r=0;r<this.Row;r++){ str=str+'\n' for(let c=0;c<this.Column;c++){ if(r==this.Row-1&&c==this.Column-1){ str=str+this.getItem(r,c); }else{ str=str+this.getItem(r,c)+','; } } } str=str+'\n]' return str; } } /*Gauss 消元 传入一个矩阵,传出结果 */ function Gauss(matrix){ let l=[];//是否为自由元 let ans=[];//存储解 const n=matrix.Column-1;//解的个数 let res=0,r=0; for(let i=0;i<matrix.Column;i++){ for(let j=r;j<matrix.Row;j++){ if(Math.abs(matrix.getItem(j,i))>Math.EPS){ if(j!==r){ //行交换位置 for(let k=i;k<=n;k++){ const temp1=matrix.getItem(j,k) const temp2=matrix.getItem(r,k) matrix.setItem(j,k,temp2) matrix.setItem(r,k,temp1) } } break; } } // console.log(matrix.toString(),r,i) if(Math.abs(matrix.getItem(r,i)<Math.EPS)){ ++res; console.log('continue') continue; } //方程相减,消除元 for(let j=0;j<matrix.Row;j++){ if(j!==r&&Math.abs(matrix.getItem(j,i))>Math.EPS){ let tmp=matrix.getItem(j,i)/matrix.getItem(r,i); for(let k=i;k<=n;k++){ const item=matrix.getItem(j,k)-tmp*matrix.getItem(r,k) matrix.setItem(j,k,item) } } } l[i]=true; r++; } //输出答案 for(let i=0;i<n;i++){ if(l[i]){ for(let j=0;j<n;j++){ if(Math.abs(matrix.getItem(j,i))>0){ ans[i]=matrix.getItem(j,n)/a.getItem(j,i) } } } } return ans; } //将一个矩阵转换成上三角矩阵 function upperMatrix(oriMatrix) { const matrix=oriMatrix.clone(); let r=0; //生成上三角矩阵 for(let i=0;i<matrix.Row;i++){ //循环行 for(let j=r;j<matrix.Row;j++){ if(Math.abs(matrix.getItem(j,i))>Math.EPS){ if(j!==r){ //行交换位置 matrix.swapRow(j,r) } break; } } if(Math.abs(matrix.getItem(r,i)<Math.EPS)){ continue; } //方程相减,消除元 for(let j=0;j<matrix.Row;j++){ if(j!==r&&Math.abs(matrix.getItem(j,i))>Math.EPS){ let tmp=matrix.getItem(j,i)/matrix.getItem(r,i); for(let k=i;k<matrix.Column;k++){ const item=matrix.getItem(j,k)-tmp*matrix.getItem(r,k) matrix.setItem(j,k,item) } } } r++; } return matrix } //求矩阵的逆 function Inverse(matrix){ if(matrix.Row!==matrix.Column){ throw '矩阵的行与列需要相等'; } const N=matrix.Row; //方程矩阵A const A = new Matrix([],N,2*N); for(let r=0;r<N;r++){ for(let c=0;c<N;c++){ A.setItem(r,c,matrix.getItem(r,c)) } } for(let r=0;r<N;r++){ for(let c=N;c<N*2;c++){ if(r===c-N){ A.setItem(r,c,1) }else{ A.setItem(r,c,0) } } } //换成上三角矩阵 const B=upperMatrix(A) //左边转成单位矩阵 for(let i=0;i<N;i++){ if(Math.abs(B.getItem(i,i))!==1){ for(let k=N;k<2*N;k++){ B.setItem(i,k,B.getItem(i,k)/B.getItem(i,i)) } B.setItem(i,i,1) } } //输出结果 const C = new Matrix([],N,N); C.rowMap(function (item,r,c) { return B.getItem(r,c+N); }) return C; } //欧几里得算法 求两个数a、b的最大公约数 function gcd(a,b){ return b===0?a:gcd(b,a%b) } //分数类 分子,分母 class Fraction{ constructor(num=0,den=1){ if(den<0){ num=-num; den=-den; } if(den===0){ throw '分母不能为0' } let g=gcd(Math.abs(num),den) this.num=num/g; this.den=den/g; } //加 add(o){ return new Fraction(this.num*o.den+this.den*o.num,this.den*o.den) } //减 sub(o){ return new Fraction(this.num*o.den-this.den*o.num,this.den*o.den) } //乘 multiply(o){ return new Fraction(this.num*o.num,this.den*o.den); } //除 divide(o){ return new Fraction(this.num*o.den,this.den*o.num); } //小于 lessThan(o){ return this.num*o.den<this.den*o.num; } //等于 equal(o){ return this.num*o.den===this.den*o.num; } } //点类 function Point(x,y) { if(this instanceof Point){ if(Math.cmp(x.toFixed(2)-x)==0){ x=Number(x.toFixed(2)); } if(Math.cmp(y.toFixed(2)-y)==0){ y=Number(y.toFixed(2)); } this.x=x; this.y=y; }else{ return new Point(x,y) } } //向量的模长 Point.prototype.norm=function(){ return Math.sqrt(this.x*this.x+this.y*this.y); } // 加 Point.add=function(a,b){ return new Point(a.x+b.x,a.y+b.y) } // 减 Point.sub=function(a,b){ return new Point(a.x-b.x,a.y-b.y); } // 等于 Point.equals=function(a,b){ return Math.cmp(a.x-b.x)===0&&Math.cmp(a.y-b.y)===0; } //乘 向量与数字 Point.multiply=function(a,b){ if(a instanceof Point&&typeof b=='number'){ return Point(a.x*b,a.y*b) } if(b instanceof Point&&typeof a=='number'){ return Point(a*b.x,a*b.y) } } //除 向量与数字 Point.divide=function(a,b){ return Point(a.x/b,a.y/b) } //向量的叉积 Point.det=function (a,b) { return a.x*b.y-a.y*b.x; } //向量的点积 Point.dot=function (a,b) { return a.x*b.x+a.y*b.y; } //两个点的距离 Point.dist=function (a,b) { return Point.sub(a,b).norm() } //逆时针旋转,a为弧度 Point.rotate=function (p,A) { if(A===0){return p;} const tx=p.x; const ty=p.y; return Point(tx*Math.cos(A)-ty*Math.sin(A),tx*Math.sin(A)+ty*Math.cos(A)) } //计算几何线段类 function Line(a,b) { if(this instanceof Line){ this.a=a; this.b=b; }else{ if(a instanceof Point&&b instanceof Point){ return new Line(a,b) }else{ throw 'Line 参数错误' } } } //点p到线段s、t的距离 Line.dis_point_segment=function(p,s,t){ if(Math.cmp(Point.dot(Point.sub(p,s),Point.sub(t,s)))<0){ return Point.sub(p,s).norm() } if(Math.cmp(Point.dot(Point.sub(p,t),Point.sub(s,t)))<0){ return Point.sub(p,t).norm() } return Math.abs(Point.det(Point.sub(s,p),Point.sub(t,p))/Point.dist(s,t)) } //点到线段的垂足 Line.pointProjLine=function(p,s,t){ const r=Point.dot(Point.sub(t,s),Point.sub(p,s))/Point.dot(Point.sub(t,s),Point.sub(t,s)) return Point.add(s,Point.multiply(r,Point.sub(t,s))) } //点是否在线段上 Line.pointOnSegment=function(p,s,t){ return Math.cmp(Point.det(Point.sub(p,s),Point.sub(t,s)))===0&&Math.cmp(Point.det(Point.sub(p,s),Point.sub(p,t)))<=0 } //判断线段a、b是否平行,a、b 为line Line.parallel=function (a,b) { return !Math.cmp(Point.det(Point.sub(a.a,a.b),Point.sub(b.a,b.b))) } //判断线段a、b是否相交,返回交点 Line.lineMakePoint=function (a,b) { if(Line.parallel(a,b)){ return false; } const s1=Point.det(Point.sub(a.a,b.a),Point.sub(b.b,b.a)); const s2=Point.det(Point.sub(a.b,b.a),Point.sub(b.b,b.a)); return Point.divide(Point.sub(Point.multiply(s1,a.b),Point.multiply(s2,a.a)),s1-s2); } // 将直线a沿法向量方向平移距离len得到的直线 Line.moveD=function (a,len) { let d=Point.sub(a.b,a.a) d=Point.divide(d,d.norm()); d=Point.rotate(d,Math.PI/2); return Line(Point.add(a.a,Point.multiply(d,len)),Point.add(a.b,Point.multiply(d,len))) } /* * 流程控制器 * 作者:caoke * */ class Step{ //初始化 constructor(stepArr,callback){ this.stepArr=stepArr; this.curIndex=0; this.isPaused=false; this.nextMode=null; this.curStep=this.stepArr[this.curIndex]; this.hasRunTimes={}; this.stepArr.forEach( (step)=> { this.hasRunTimes[step]=0; }) this.callback=callback; } callback(){ this.go() } // 运行当前流程 run(){ this.curStep=this.stepArr[this.curIndex] if(this.curStep){ this.hasRunTimes[this.curStep]++ this.callback&&this.callback.apply(this,[this.curStep,this.hasRunTimes[this.curStep]]) } } // 跳转到某个流程 go(step){ this.clear() if(step){ this.curIndex=this.stepArr.indexOf(step) }else{ this.curIndex++ } this.run() } // 进入下一个流程 next(){ if(this.nextMode){ this.go(this.nextMode.nextStep) }else{ this.go() } } // 自动进入下一步 waitSecondAndGo(second,step){ if(!this.isPaused){ this.stopTimer() } const mode={ startTime:new Date().getTime(), allSecond:second, leftSecond:second*1000, nextStep:step, timer:null, } //获得下一步 if(this.nextMode==null||mode.leftSecond<this.nextMode.leftSecond){ this.nextMode=mode; } if(!this.isPaused){ this.startTimer() } } // 暂停 pause(){ if(!this.isPaused){ this.isPaused=true; this.stopTimer() } } // 继续 repause(){ if(this.isPaused){ this.isPaused=false; this.startTimer() } } stopTimer(){ if(this.nextMode&&this.nextMode.timer){ const duration=new Date().getTime()-this.nextMode.startTime; this.nextMode.leftSecond=this.nextMode.leftSecond-duration; clearTimeout(this.nextMode.timer); this.nextMode.timer=null; } } startTimer(){ if(this.nextMode&&this.nextMode.timer==null){ this.nextMode.startTime=new Date().getTime(); this.nextMode.timer=setTimeout(() => { this.go(this.nextMode.nextStep) },this.nextMode.leftSecond) } } // 销毁 clear(){ if(this.nextMode){ if(this.nextMode.timer){ clearTimeout(this.nextMode.timer); this.nextMode.timer=null } this.nextMode=null } } }
图形类test.js
const canvas=document.getElementById("myCanvas"); var cc={ canvas:canvas, ctx:canvas.getContext("2d"), translate(str){ if(/px/.test(str)){ return parseInt(str) } if(/vh/.test(str)){ return parseInt(str)*window.innerHeight/100 } if(/vw/.test(str)){ return parseInt(str)*window.innerWidth/100 } } } cc.width=canvas.width=cc.translate('100vw'); cc.height=canvas.height=cc.translate('100vh'); cc.rect=canvas.getBoundingClientRect(); class Node{ constructor(){ this.children=[] this.parent=null; this.root=null; this.isShow=true; this.angle=0; } getRoot(){ if(this instanceof World){ return this; }else{ return this.parent.getRoot() } } add(node){ if(!node.parent){ node.parent=this; node.root=node.getRoot() this.children.push(node); } } remove(node){ this.children=this.children.filter((nd)=>{ if(node===nd){ node.parent=null; node.root=null; } return node!==nd }) } render(){ if(!this.isShow){return false;} for(let i=0;i<this.children.length;i++){ this.children[i].render() } return true; } } class ccRect extends Node{ constructor(point,width,height){ super() this.point=point; this.width=width; this.height=height; } render(){ if(!super.render()){return;} const root=this.root; const ctx=cc.ctx; const arr=[ root.WorldToScreenPoint(Point.rotate(Point(this.point.x-this.width/2,this.point.y+this.height/2),this.angle)), root.WorldToScreenPoint(Point.rotate(Point(this.point.x+this.width/2,this.point.y+this.height/2),this.angle)), root.WorldToScreenPoint(Point.rotate(Point(this.point.x+this.width/2,this.point.y-this.height/2),this.angle)), root.WorldToScreenPoint(Point.rotate(Point(this.point.x-this.width/2,this.point.y-this.height/2),this.angle)) ] ctx.beginPath(); ctx.moveTo(arr[0].x,cc.height-arr[0].y); ctx.lineTo(arr[1].x,cc.height-arr[1].y); ctx.lineTo(arr[2].x,cc.height-arr[2].y); ctx.lineTo(arr[3].x,cc.height-arr[3].y); ctx.closePath(); ctx.strokeStyle="#72dd38"; ctx.stroke(); } } class ccLine extends Node{ constructor(a,b){ super() this.a=a; this.b=b; } render(){ if(!super.render()){return;} const root=this.root; const ctx=cc.ctx; const a=root.WorldToScreenPoint(Point.rotate(this.a,this.angle)); const b=root.WorldToScreenPoint(Point.rotate(this.b,this.angle)); ctx.beginPath(); ctx.moveTo(a.x,cc.height-a.y); ctx.lineTo(b.x,cc.height-b.y); ctx.strokeStyle=this.strokeStyle; ctx.stroke(); } } class ccText extends Node{ constructor(text,point){ super() this.text=text this.point=point } render(){ if(!super.render()){return;} const root=this.root; const ctx=cc.ctx; ctx.font="10px Arial"; ctx.textAlign=this.textAlign||"center"; ctx.textBaseline=this.textBaseline||"middle"; const point=root.WorldToScreenPoint(Point.rotate(this.point,this.angle)); ctx.fillText(this.text,point.x,cc.height-point.y); } } class ccCircle extends Node{ constructor(point,radius,startingAngle,endingAngle){ super() this.point=point; this.radius=radius; this.startingAngle=startingAngle||0; this.endingAngle=endingAngle||2*Math.PI; } render(){ if(!super.render()){return;} const root=this.root; const ctx=cc.ctx; const point=root.WorldToScreenPoint(Point.rotate(this.point,this.angle)); ctx.beginPath(); ctx.arc(point.x,cc.height-point.y,root.WorldToScreenGrid(this.radius),this.startingAngle,this.endingAngle); ctx.strokeStyle=this.strokeStyle; ctx.stroke(); } } class World extends Node{ constructor(config){ super(); this.point=Point(0,0); this.grid=1 Object.assign(this,config); } show(){ for(let x=0;x<=75;x++){ const line=new ccLine(Point(x,0),Point(x,133)) line.strokeStyle='#ddd' this.add(line) } for(let y=0;y<=133;y++){ const line=new ccLine(Point(0,y),Point(75,y)) line.strokeStyle='#ddd' this.add(line) } } WorldToScreenGrid(grid){ grid=this.grid*grid; if(this.parent instanceof World){ grid= this.parent.WorldToScreenGrid(grid) } return grid; } WorldToScreenPoint(pos){ pos=Point.add(Point.rotate(Point.multiply(pos,this.grid),this.angle),this.point) if(this.parent instanceof World){ pos= this.parent.WorldToScreenPoint(pos) } return pos; } ScreenToWorldPoint(pos){ if(this.parent instanceof World){ pos=this.parent.ScreenToWorldPoint(pos) } return Point.divide(Point.rotate(Point.sub(pos,this.point),-this.angle),this.grid); } } class Game extends World{ constructor(){ super() //交互 const handle=(e)=> { let type=e.type; if(type==='mousedown'){ type='touchstart'; } if(type==='mousemove'){ type='touchmove'; } if(type==='mouseup'){ type='touchend'; } if(type==='touchcancel'){ type='touchend'; } if(e.type==='mousedown'||e.type==='mousemove'||e.type==='mouseup'){ this[type]([Point( e.x-cc.rect.left, cc.height-(e.y-cc.rect.top) )]) }else if(e.type==='touchstart'||e.type==='touchmove'||e.type==='touchend'){ const arr=[] for(let i=0;i<e.touches.length;i++){ const item=e.touches[i]; arr.push(Point( item.pageX-cc.rect.left, cc.height-(item.pageY-cc.rect.top) )) } this[type](arr) } } document.addEventListener('mousedown',handle) document.addEventListener('mousemove',handle) document.addEventListener('mouseup',handle) document.addEventListener('touchstart',handle,false) document.addEventListener('touchmove',handle,false) document.addEventListener('touchend',handle,false) document.addEventListener('touchcancel',handle,false) this.world=new World({ point:Point(0,0), grid:5, }); // this.world2=new World({ // point:Point(1,1), // grid:0.5, // }); // this.world.add(this.world2) this.add(this.world) this.runArr=['initAxes','slump']; } init(){ this.progress=new Step(this.runArr,(step,time)=>{ this[step](step,time); cc.ctx.clearRect(0,0,cc.width,cc.height); this.render() }) this.progress.run(); } initAxes(){ // this.show() // this.world.show() this.progress.waitSecondAndGo(); // this.rect={ // top:10,left:-10,bottom:-10,right:10 // } // this.direct='right'; this.slumpObject=[] for(let k=0;k<100;k++){ const circle=new ccCircle(Point(0|Math.random()*75,0|Math.random()*133),2) this.world.add(circle) this.slumpObject.push(circle) } } slump(){ this.slumpObject=this.slumpObject.filter( (obj) =>{ if(obj.point.y>0){ obj.point.y-=0.1; return true }else{ this.world.remove(obj) return false } }) this.progress.waitSecondAndGo(0.1,'slump') } toggle(){ if(this.direct=='right'){ if(this.circle.point.x<this.rect.right){ this.circle.point.x+=1 }else{ this.rect.top-=1; this.direct='bottom' } }else if(this.direct=='bottom'){ if(this.circle.point.y>this.rect.bottom){ this.circle.point.y-=1 }else{ this.rect.right-=1 this.direct='left' } }else if(this.direct=='left'){ if(this.circle.point.x>this.rect.left){ this.circle.point.x-=1 }else{ this.rect.bottom+=1 this.direct='top' } }else if(this.direct=='top'){ if(this.circle.point.y<this.rect.top){ this.circle.point.y+=1 }else{ this.rect.left+=1 this.direct='right' } } if(this.circle.point.x==0&&this.circle.point.y==0){ return; } console.log(this.circle.point) // this.world.grid=this.world.grid+this.addNum; // this.world.angle+=0.3; // this.world2.angle-=0.3; // if(this.world.grid>100){ // this.addNum=-2; // } // if(this.world.grid<20){ // this.addNum=2; // } this.progress.waitSecondAndGo(0.1,'toggle') } touchstart([pos]){ console.log(pos) console.log(this.world.ScreenToWorldPoint(pos)) // console.log(this.world2.ScreenToWorldPoint(pos)) } touchmove([pos]){ } touchend([pos]){ } } new Game().init()
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width,viewport-fit=cover" name="viewport"> <title>坐标系-caoke90</title> </head> <style> html,body { overflow: hidden; height: 100%; } </style> <body> <canvas id="myCanvas"></canvas> <script src="cc.js"></script> <script src="test.js"></script> </body> </html>