I have a webgl being rendered on a canvas element. After it is rendered I want to allow user to draw on it with a mouse (rect for example). Since the getContext does not work for the second time, I added another transparent canvas on top of my webgl canvas and I am want to draw a rect with a mouse on the transparent canvas. The problem is that the coordinates in a mousedown event are very different to the context corrdinates
My canvas are as below
<div id="container">
<canvas id="webglCanvas" tabindex='1'></canvas>
<canvas id="transCanvas" tabindex='1'></canvas>
</div>
to get context
var $canvas1 = document.getElementById('transCanvas');
var ctx = $canvas1.getContext("2d");
mouse down event of transCanvas. Please note that I have hard coded the rect at the moment on mouse down event. Later I will do it on mouse move etc. This works fine on my canvas and I can see the rect on my screen. But the mouse coordinates eg e.clientX and e.clientY are in hundereds and go off the screen?
function handleCanvasMouseMove(e) {
ctx.beginPath();
ctx.fillStyle = '#F30';
ctx.fillRect(75, 75, 75, 75);
}
Remember, you're converting NormalizedDeviceCoords with a range of [-1..1] in each axis to a position on screen. All the transformations you applied takes the model-space vert and essentially put it in a cube of size 2, centred on the origin.
So... I imagine you'd also like to get back mouse-coordinates in this same space. If so, it's just a matter of constructing a matrix and then multiplying the screen-space position by this matrix to get x,y in the range of [-1..1]
When I've done similar things in the past, I've used a series of transformations as follows:
function makeClipToPixMat(width,height)
{
// flip the Y
let s1 = mat4.scaling(1,-1,1);
// translate so space is now [0..2]
let t1 = mat4.translation(1,1,0);
// scale so space is now [0..width], [0..height]
let s2 = mat4.scaling(width/2,height/2,1);
// s1, then t1, then s2 are applied to the input coords
let result = mat4.matrixByMatrix(s1,t1);
result = result.multiply(s2);
return result;
}
But as you'll notice from the name, it's a mapping in the wrong direction. We want to map screen-coords to NDC, but this code does the opposite. What now then? Simple - either invert the matrix or determine the series of transforms needed and construct a matrix that will do them all in one go. It's a simple enough transform, that a matrix-inversion seems like a fantastically expensive way to do something so simple.
In fact, here's the function I use. Inversion works fine too and can decrease the code size at the expensive of run-time.
function pixelsToClipspace(width,height)
{
let scaleMat = mat4.scaling( 1/(width/2), -1/(height/2), 1);
let translateMat = mat4.translation(-width/2, -height/2, 0); //.transpose();
let mat = mat4.matrixByMatrix(translateMat, scaleMat);
return mat;
}
Since I've some time at the moment, I hacked together a quick demo for you. Shame, there's 380 lines of code for the vec4 and the matrix, yet only about 35 for the demo. :laughs: That said, it perfectly illustrates how expensive and complicated the matrix .inverse() function is.
LASTLY: please note, I do not make any claims as to the accuracy of any of the code included yet not utilized. Exercises like this one benefit each of us. You get some understanding, I get some more debug test-cases. :) The matrices are column-major (like all good GL ones should be)
"use strict";
window.addEventListener('load', onLoaded, false);
let s2Clip = null;
function onLoaded(evt)
{
let can = document.querySelector('canvas');
s2Clip = pixelsToClipspace(can.clientWidth, can.clientHeight); // use clienWidth/clientHeight to avoid CSS scaling problems
can.addEventListener('mousemove', onMouse, false);
}
function onMouse(evt)
{
var rawPos = new vec4(evt.offsetX, evt.offsetY, 0, 1);
var trPos = s2Clip.timesVector(rawPos);
document.getElementById('rawMouse').innerText = `${rawPos.x}, ${rawPos.y}`
document.getElementById('transMouse').innerText = `${trPos.x.toFixed(2)}, ${trPos.y.toFixed(2)}`
}
function pixelsToClipspace(width,height)
{
let scaleMat = mat4.scaling( 1/(width/2), -1/(height/2), 1);
let translateMat = mat4.translation(-width/2, -height/2, 0); //.transpose();
let mat = mat4.matrixByMatrix(translateMat, scaleMat);
return mat;
}
// </script>
// <script origSrc='vector.js'>
class vec4
{
// w=0 for dir (cant translate), w=1 for pos (can)
constructor(x=0,y=0,z=0,w=0){this.values = [x,y,z,w];}
clone(){ return new vec4(this.x,this.y,this.z,this.w); }
get x(){return this.values[0];}
get y(){return this.values[1];}
get z(){return this.values[2];}
get w(){return this.values[3];}
set x(x){this.values[0]=x;}
set y(y){this.values[1]=y;}
set z(z){this.values[2]=z;}
set w(w){this.values[3]=w;}
get length(){return Math.hypot( ...this.values ); }
normalize(){ var l = this.length; if (l>1e-6) {this.x/=l;this.y/=l;this.z/=l;this.w/=l;} return this;}
scaleBy(scalar){this.x*=scalar;this.y*=scalar;this.z*=scalar;this.w*=scalar;return this;}
divBy(scalar){this.x/=scalar;this.y/=scalar;this.z/=scalar;this.w/=scalar;return this;}
add(other){return new vec4(this.x+other.x, this.y+other.y, this.z+other.z, this.w+other.w);}
sub(other){return new vec4(this.x-other.x, this.y-other.y, this.z-other.z, this.w-other.w);}
get xyz(){return new vec3(this.x,this.y,this.z);}
toStringN(n){return `[${pad(this.x,n)}, ${pad(this.y,n)}, ${pad(this.z,n)}, ${pad(this.w,n)}]`;}
timesMatrix(matrix)
{
let m0 = matrix.getCol(0), m1 = matrix.getCol(1), m2 = matrix.getCol(2), m3 = matrix.getCol(3);
return new vec4(
(m0.x*this.x) + (m1.x*this.y) + m2.x*this.z + m3.x*this.w,
(m0.y*this.x) + (m1.y*this.y) + m2.y*this.z + m3.y*this.w,
(m0.z*this.x) + (m1.z*this.y) + m2.z*this.z + m3.z*this.w,
(m0.w*this.x) + (m1.w*this.y) + m2.w*this.z + m3.w*this.w
);
}
vecByMatrix(m) /// operator * (matrix, vector)
{
let mc0 = m.getCol(0), mc1=m.getCol(1), mc2=m.getCol(2), mc3=m.getCol(3);
return new vec4(
(mc0.x * this.x) + (mc1.x * this.y) + (mc2.x * this.z) + (mc3.x * this.w),
(mc0.y * this.x) + (mc1.y * this.y) + (mc2.y * this.z) + (mc3.y * this.w),
(mc0.z * this.x) + (mc1.z * this.y) + (mc2.z * this.z) + (mc3.z * this.w),
(mc0.w * this.x) + (mc1.w * this.y) + (mc2.w * this.z) + (mc3.w * this.w),
);
}
matrixByVec(m) /// operator * (vector, matrix)
{
let mCol0 = m.getCol(0), mCol1=m.getCol(1), mCol2=m.getCol(2), mCol3=m.getCol(3);
return new vec4(
this.x*mCol0.x + this.y*mCol0.y + this.z*mCol0.z + this.w*mCol0.w,
this.x*mCol1.x + this.y*mCol1.y + this.z*mCol1.z + this.w*mCol1.w,
this.x*mCol2.x + this.y*mCol2.y + this.z*mCol2.z + this.w*mCol2.w,
this.x*mCol3.x + this.y*mCol3.y + this.z*mCol3.z + this.w*mCol3.w
);
}
}
class mat4
{
constructor(xVec4=new vec4(1,0,0,0), yVec4=new vec4(0,1,0,0), zVec4=new vec4(0,0,1,0), wVec4=new vec4(0,0,0,1) )
{
this.columns = [
xVec4.clone(),
yVec4.clone(),
zVec4.clone(),
wVec4.clone()
];
}
getCol(colIndex) {return this.columns[colIndex];}
setCol(colIndex, newVec) {this.columns[colIndex] = newVec.clone();}
setIdentity()
{
let x=new vec4(1,0,0,0);
let y=new vec4(0,1,0,0);
let z=new vec4(0,0,1,0);
let w=new vec4(0,0,0,1);
this.setCol(0,x);
this.setCol(0,y);
this.setCol(0,z);
this.setCol(0,w);
return this;
}
static clone(other)
{
var result = new mat4( other.columns[0], other.columns[1], other.columns[2], other.columns[3] );
return result;
}
clone()
{
return mat4.clone(this);
}
static scaling(sx=1,sy=1,sz=1)
{
let x = new vec4(sx,0,0,);
let y = new vec4(0,sy,0,);
let z = new vec4(0,0,sz,);
let w = new vec4(0,0,0,1);
return new mat4(x,y,z,w);
}
static translation(tx=0,ty=0,tz=0)
{
let X = new vec4(1,0,0,tx);
let Y = new vec4(0,1,0,ty);
let Z = new vec4(0,0,1,tz);
let W = new vec4(0,0,0,1);
return new mat4(X,Y,Z,W);
}
static matrixByMatrix(m1, m2)
{
let mCol0 = m2.getCol(0), mCol1=m2.getCol(1), mCol2=m2.getCol(2), mCol3=m2.getCol(3);
let X = mCol0.vecByMatrix(m1);
let Y = mCol1.vecByMatrix(m1);
let Z = mCol2.vecByMatrix(m1);
let W = mCol3.vecByMatrix(m1);
return new mat4(X,Y,Z,W);
}
static matTimeMat(m1,m2)
{
let mc0=m2.getCol(0),mc1=m2.getCol(1),mc2=m2.getCol(2),mc3=m2.getCol(3);
let x = m1.timesVector(mc0);
let y = m1.timesVector(mc1);
let z = m1.timesVector(mc2);
let w = m1.timesVector(mc3);
return new mat4(x,y,z,w);
}
multiply(other,shouldPrepend=false)
{
var a=this,b=other,c;
if (shouldPrepend===true){a=other;b=this;}
c = mat4.matrixByMatrix(a,b);
this.columns = c.columns.slice();
return this;
}
translate(tx=0,ty=0,tz=0)
{
return this.multiply( mat4.translation(tx,ty,tz) );
}
setScale(sx=1,sy=1,sz=1)
{
let x = new vec4(sx,0,0,0);
let y = new vec4(0,sy,0,0);
let z = new vec4(0,0,sz,0);
let w = new vec4(0,0,0,1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setTrans(tx=0,ty=0,tz=0)
{
let x = new vec4( 1, 0, 0, 0);
let y = new vec4( 0, 1, 0, 0);
let z = new vec4( 0, 0, 1, 0);
let w = new vec4( tx, ty, tz, 1);
var tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setRotX(degrees)
{
let cosa = Math.cos(degrees * 3.141/180);
let sina = Math.sin(degrees * 3.141/180);
let x = new vec4(1,0,0,0);
let y = new vec4(0,cosa,sina,0)
let z = new vec4(0,-sina,cosa,0);
let w = new vec4(0,0,0,1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setRotY(degrees)
{
let cosa = Math.cos(degrees * 3.141/180);
let sina = Math.sin(degrees * 3.141/180);
let x = new vec4( cosa, 0,-sina,0);
let y = new vec4( 0, 1, 0, 0)
let z = new vec4( sina, 0,cosa, 0);
let w = new vec4( 0, 0, 0, 1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
setRotZ(degrees)
{
let cosa = Math.cos(degrees * 3.141/180);
let sina = Math.sin(degrees * 3.141/180);
let x = new vec4(cosa,sina,0,0);
let y = new vec4(-sina,cosa,0,0)
let z = new vec4(0,0,1,0);
let w = new vec4(0,0,0,1);
let tmp = new mat4(x,y,z,w);
this.columns = tmp.columns.slice();
return this;
}
scaleEach(sX=1,sY=1,sZ=1,shouldPrepend=false)
{
let tmp = new mat4();
let X = tmp.getCol(0);
X.x = sX;
tmp.setCol(0,X);
let Y = tmp.getCol(1);
Y.y = sY;
tmp.setCol(1,Y);
let Z = tmp.getCol(2);
Z.z = sZ;
tmp.setCol(2,Z);
return this.multiply(tmp, shouldPrepend);
//return this;
}
scaleAll(sXYZ, shouldPrepend=false)
{
return this.scaleEach(sXYZ,sXYZ,sXYZ,shouldPrepend);
//return this;
}
/*
translate(tX=0, tY=0, tZ=0, shouldPrepend=false)
{
let tmp = new mat4();
let W = tmp.getCol(3);
W.x = tX;
W.y = tY;
W.z = tZ;
tmp.setCol(3,W);
return this.multiply(tmp, shouldPrepend);
}
*/
timesVector(vector)
{
let m0=this.getCol(0), m1=this.getCol(1), m2=this.getCol(2), m3=this.getCol(3);
return new vec4(
(vector.x*m0.x) + (vector.y*m0.y) + (vector.z*m0.z) + (vector.w*m0.w),
(vector.x*m1.x) + (vector.y*m1.y) + (vector.z*m1.z) + (vector.w*m1.w),
(vector.x*m2.x) + (vector.y*m2.y) + (vector.z*m2.z) + (vector.w*m2.w),
(vector.x*m3.x) + (vector.y*m3.y) + (vector.z*m3.z) + (vector.w*m3.w)
);
}
toString()
{
let result = '', row=0,col=0;
result = `[ ${this.getCol(0).x}, ${this.getCol(1).x}, ${this.getCol(2).x}, ${this.getCol(3).x} ]\n`;
result += `[ ${this.getCol(0).y}, ${this.getCol(1).y}, ${this.getCol(2).y}, ${this.getCol(3).y} ]\n`;
result += `[ ${this.getCol(0).z}, ${this.getCol(1).z}, ${this.getCol(2).z}, ${this.getCol(3).z} ]\n`;
result += `[ ${this.getCol(0).w}, ${this.getCol(1).w}, ${this.getCol(2).w}, ${this.getCol(3).w} ]\n`;
return result;
}
toStrN(n)
{
return this.toStringN(n);
}
toStringN(nDigs)
{
let result = '';
let xVec=this.getCol(0).clone(),
yVec=this.getCol(1).clone(),
zVec=this.getCol(2).clone(),
wVec=this.getCol(3).clone();
let vs=[xVec,yVec,zVec,wVec];
for (var i=0,n=vs.length; i<n; i++)
{
vs[i].x = pad(vs[i].x, nDigs);
vs[i].y = pad(vs[i].y, nDigs);
vs[i].z = pad(vs[i].z, nDigs);
vs[i].w = pad(vs[i].w, nDigs);
}
result = `[ ${xVec.x}, ${yVec.x}, ${zVec.x}, ${wVec.x} ]\n`;
result += `[ ${xVec.y}, ${yVec.y}, ${zVec.y}, ${wVec.y} ]\n`;
result += `[ ${xVec.z}, ${yVec.z}, ${zVec.z}, ${wVec.z} ]\n`;
result += `[ ${xVec.w}, ${yVec.w}, ${zVec.w}, ${wVec.w} ]\n`;
return result;
}
asRows(nDigs=2)
{
let result = '',xVec=this.getCol(0),yVec=this.getCol(1),zVec=this.getCol(2),wVec=this.getCol(3);
result = `[${xVec.x.toFixed(nDigs)}, ${xVec.y.toFixed(nDigs)}, ${xVec.z.toFixed(nDigs)}, ${xVec.w.toFixed(nDigs)}]\n`;
result += `[${yVec.x.toFixed(nDigs)}, ${yVec.y.toFixed(nDigs)}, ${yVec.z.toFixed(nDigs)}, ${yVec.w.toFixed(nDigs)}]\n`;
result += `[${zVec.x.toFixed(nDigs)}, ${zVec.y.toFixed(nDigs)}, ${zVec.z.toFixed(nDigs)}, ${zVec.w.toFixed(nDigs)}]\n`;
result += `[${wVec.x.toFixed(nDigs)}, ${wVec.y.toFixed(nDigs)}, ${wVec.z.toFixed(nDigs)}, ${wVec.w.toFixed(nDigs)}]\n`;
return result;
}
transpose()
{
let X=this.getCol(0), Y=this.getCol(1), Z=this.getCol(2), W=this.getCol(3);
let tmp = new mat4(
new vec4(X.x,Y.x,Z.x,W.x),
new vec4(X.y,Y.y,Z.y,W.y),
new vec4(X.z,Y.z,Z.z,W.z),
new vec4(X.w,Y.w,Z.w,W.w),
);
this.setCol(0,X);
this.setCol(1,Y);
this.setCol(2,Z);
this.setCol(3,W);
return tmp; //this.copy(tmp);
}
inverse()
{
let X = this.getCol(0), Y = this.getCol(1), Z = this.getCol(2), W = this.getCol(3);
let m00=X.x, m01=X.y, m02=X.z, m03=X.w,
m10=Y.x, m11=Y.y, m12=Y.z, m13=Y.w,
m20=Z.x, m21=Z.y, m22=Z.z, m23=Z.w,
m30=W.x, m31=W.y, m32=W.z, m33=W.w;
let tmp_0=m22*m33, tmp_1=m32*m23, tmp_2=m12*m33,
tmp_3=m32*m13, tmp_4=m12*m23, tmp_5=m22*m13,
tmp_6=m02*m33, tmp_7=m32*m03, tmp_8=m02*m23,
tmp_9=m22*m03, tmp_10=m02*m13,tmp_11=m12*m03,
tmp_12=m20*m31,tmp_13=m30*m21,tmp_14=m10*m31,
tmp_15=m30*m11,tmp_16=m10*m21,tmp_17=m20*m11,
tmp_18=m00*m31,tmp_19=m30*m01,tmp_20=m00*m21,
tmp_21=m20*m01,tmp_22=m00*m11,tmp_23=m10*m01;
var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
let Xo = new vec4(d*t0, d*t1, d*t2, d*t3);
// d * t0,
// d * t1,
// d * t2,
// d * t3,
let Yo = new vec4(
d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20))
);
let Zo = new vec4(
d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23))
);
let Wo = new vec4(
d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
);
this.columns = [Xo,Yo,Zo,Wo];
return this;
}
}
function pad(num, n)
{
let str = num.toFixed(n);
if (num >= 0)
str = " " + str;
return str;
}
canvas
{
background-color: #333;
cursor: crosshair;
}
<body>
<canvas width='300' height='300'></canvas><br>
<div>Screen Coords of mouse: <span id='rawMouse'></span></div>
<div>(2d) NDC of mouse: <span id='transMouse'></span></div>
</body>
I've put explanations as comments in the code
// Canvas viewport (4:3)
const DRAW_WIDTH = 800;
const DRAW_HEIGHT = 600;
const RECT_SIZE = 10;
const RECT_FILL = 'black';
let canvas, ctx;
function init() {
canvas = document.querySelector("canvas");
// setup canvas drawing space
// this will give an aspect-ratio to the canvas
canvas.setAttribute('width', DRAW_WIDTH);
canvas.setAttribute('height', DRAW_HEIGHT);
ctx = canvas.getContext('2d');
// attach listener
canvas.addEventListener("click", onMouseDown);
}
function onMouseDown(e) {
// get canvas position and size infos:
const bbox = canvas.getBoundingClientRect();
const {
x: canvasX,
y: canvasY,
width: canvasW,
height: canvasH
} = bbox;
// mouse click position
const {
clientX: mouseX,
clientY: mouseY
} = e;
// compute ratio between drawing size (viewport) and actual size
const widthRatio = DRAW_WIDTH / canvasW;
// compute x relative to your canvas
const relativeX = (mouseX - canvasX);
// I advise you to use int values when drawing on canvas
// thus Math.round
const finalX = Math.round(widthRatio * relativeX);
// same for Y-axis
const heightRatio = DRAW_HEIGHT / canvasH;
const relativeY = (mouseY - canvasY);
const finalY = Math.round(heightRatio * relativeY);
// draw something with that:
ctx.fillStyle = RECT_FILL;
ctx.rect(finalX - RECT_SIZE / 2, finalY - RECT_SIZE / 2, RECT_SIZE, RECT_SIZE);
ctx.fill();
ctx.closePath();
}
init();
/* set canvas width in the document */
canvas {
width: 80vw;
margin-left: 5vw;
background: coral;
display: block;
}
<canvas></canvas>
I'm new to THREE.js and with a very poor knowledge in physics but still I want to make a football manager game (played from top view) and I need to know that the kick of the ball is realistic as possible.
I was able to make the ball move and rotate in the correct direction while changing the position of the movement when the ball hits its boundaries.
now I need to deal with a issue of the curve of the ball and how do I make it so the ball with move in an arc to the top and to the sides (X / Y) depending of the angle of the foot hitting the ball
lets just say, I need to know how to handle two scenarios:
1) when kick start from the near bottom axis of the ball
2) when kick start from the near right axis of the ball
your help is highly appropriated. Thank you!
**
- I've added a code showing what i have so far
- I've added an image illustrating my goal (or this person scoring a goal)
/*
*
* SET UP MOTION PARAMS
*
*/
var boundries = [40, 24] //indicate where the ball needs to move in mirror position
var completeFieldDistance = boundries[0] * 2;
var fullPower = 1.8; //the power needed to move the ball the enitre field in one kick
var power = null; //will be set when the kick set in depending on the distance
var isKickStop = false; //indicate the renderer weather to stop the kick
var velocityX = null;
var velocityY = null;
//*** this is where i need help! ***
//how can I make the ball move in the Z axis with a nice curv up depending on a given angle
var curv = 15;
var peak = curv;
var velocityZ = 0;
var friction = 0.98;
var gravity = 0.5;
var bounciness = 0.8;
var minVelocity = 0.035; //for when it need to stop the kick rendering
var ballRadius = 3;
var ballCircumference = Math.PI * ballRadius * 2;
var ballVelocity = new THREE.Vector3();
var ballRotationAxis = new THREE.Vector3(0, 1, 0);
//world meshes
var ball = {};
var field = {};
/*
*
* THE KICK HANDLERS
*
*/
function onKick(angleDeg, distance) {
isKickStop = true;
peak = curv;
power = (distance / completeFieldDistance) * fullPower;
velocityX = Math.cos(angleDeg) * power;
velocityY = Math.sin(angleDeg) * power;
velocityZ = peak / (distance / 2);
requestAnimationFrame(function (params) {
isKickStop = false;
animateKick();
})
}
//** THIS IS WHERE I NEED HELP - how do I make the ball move
// render the movements of the ball
var animateKick = function (params) {
if (isKickStop) { return; }
ball.position.x += velocityX;
ball.position.z += velocityZ;
ball.position.y += velocityY;
if (Math.abs(velocityX) < minVelocity && Math.abs(velocityY) < minVelocity) {
ball.position.z = ball.bottom;
isKickStop = true;
console.log("DONE!");
return;
}
if (ball.position.z >= peak) {
ball.position.z = peak;
velocityZ *= -1;
}
if (ball.position.z < ball.bottom) {
peak *= gravity;
velocityZ *= -1;
ball.position.z = ball.bottom;
}
// Figure out the rotation based on the velocity and radius of the ball...
ballVelocity.set(velocityX, velocityY, 0);
ballRotationAxis.set(0, 0, 1).cross(ballVelocity).normalize();
var velocityMag = ballVelocity.length();
var rotationAmount = velocityMag * (Math.PI * 2) / ballCircumference;
ball.rotateOnWorldAxis(ballRotationAxis, rotationAmount);
//reduce velocity due to friction
velocityX *= friction;
velocityY *= friction;
//making sure ball is not outside of its boundries
if (Math.abs(ball.position.x) > boundries[0]) {
velocityX *= -1;
ball.position.x = (ball.position.x < 0) ? boundries[0] * -1 : boundries[0];
}
if (Math.abs(ball.position.y) > boundries[1]) {
velocityY *= -1;
ball.position.y = (ball.position.y < 0) ? boundries[1] * -1 : boundries[1];
}
}
window.onload = (function (params) {
/*
*
* SET UP THE WORLD
*
*/
//set up the ratio
var gWidth = window.innerWidth;
var gHeight = window.innerHeight;
var ratio = gWidth / gHeight;
//set the scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xeaeaea);
//set the camera
var camera = new THREE.PerspectiveCamera(35, ratio, 0.1, 1000);
camera.position.z = 120;
//set the light
var light = new THREE.SpotLight(0xffffff, 1);
light.castShadow = true;
light.position.set(0, 0, 35);
scene.add(light);
// set the renderer
var renderer = new THREE.WebGLRenderer();
//properties for casting shadow
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(gWidth, gHeight);
document.body.appendChild(renderer.domElement);
/*
*
* ADD MESH TO SCENE
*
*/
// create and add the ball
var geometry = new THREE.SphereGeometry(ballRadius, 8, 8);
//make a checkerboard texture for the ball...
var canv = document.createElement('canvas')
canv.width = canv.height = 256;
var ctx = canv.getContext('2d')
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = 'black';
for (var y = 0; y < 16; y++)
for (var x = 0; x < 16; x++)
if ((x & 1) != (y & 1)) ctx.fillRect(x * 16, y * 16, 16, 16);
var ballTex = new THREE.Texture(canv);
ballTex.needsUpdate = true;
var material = new THREE.MeshLambertMaterial({
map: ballTex
});
ball = new THREE.Mesh(geometry, material);
ball.castShadow = true;
ball.receiveShadow = false;
ball.bottom = ballRadius / 2;
scene.add(ball);
// create and add the field
var margin = 20;
var fieldRatio = 105 / 68;
var width = 90;
var height = width / fieldRatio;
var material = new THREE.MeshLambertMaterial({ color: 'green' });
var geometry = new THREE.BoxGeometry(width, height, 1);
field = new THREE.Mesh(geometry, material);
field.receiveShadow = true;
field.position.z = -1;
scene.add(field);
/*
*
* HANDLING EVENTS
*
*/
var domEvents = new THREEx.DomEvents(camera, renderer.domElement);
domEvents.addEventListener(field, 'click', function (e) {
//set points 1 and 2
var p1 = { x: e.intersect.point.x, y: e.intersect.point.y };
var p2 = { x: ball.position.x, y: ball.position.y };
var angleDeg = Math.atan2(p1.y - p2.y, p1.x - p2.x);
var a = p1.x - p2.x;
var b = p1.y - p2.y;
var distance = Math.sqrt(a * a + b * b);
window.onKick(angleDeg, distance);
}, false);
/*
*
* ANIMATION STEP
*
*/
var render = function (params) {
//render kick if it is on the go
if(!isKickStop){
animateKick();
}
//render the page
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
})()
body {
padding: 0;
margin: 0;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<script src="https://www.klika.co.il/scripts/three.events.js"></script>
</head>
<body>
</body>
</html>
I build a model to mock this, the model accept several parameters, initial velocity and angular velocity, there are three force on the ball, gravity, air resistance force and Magnus force.
v0_x = 0; //initial velocity
v0_y = 4;
v0_z = 1;
w_x = 0 * Math.PI; // initial angular velocity
w_y = 2 * Math.PI;
w_z = 0 * Math.PI;
m = 2; //weight
rho = 1.2; // air density
g = 9.8; // gravity
f = 10; //frequency of the rotation of the ball
cl = 1.23; //horizontal tension coefficient
cd = 0.5; //air resistance coefficient
D = 0.22; // diameter of the ball
A = Math.PI * Math.pow((0.5 * D), 2); //cross-sectional area of the ball
t_step = 1 / 60;
b = (1 / 2) * cd * rho * A; //for convenience
c = cl * rho * Math.pow(D, 3) * f; // for convenience
vt_x = v0_x
vt_y = v0_y
vt_z = v0_z
animateKick = function() {
if (ball.position.y < 0) {
return;
}
tmp_1 = c * Math.pow(Math.pow(vt_x, 2) + Math.pow(vt_z, 2) + Math.pow(vt_y, 2), 2)
tmp_2 = (Math.sqrt(Math.pow(w_z * vt_y - w_y * vt_z, 2) + Math.pow(w_y * vt_x - w_x * vt_y, 2) + Math.pow(w_x * vt_z - w_z * vt_x, 2)))
tmp = tmp_1 / tmp_2
Fl_x = tmp * (w_z * vt_y - w_y * vt_z)
Fl_z = tmp * (w_y * vt_x - w_x * vt_y)
Fl_y = tmp * (w_x * vt_z - w_z * vt_y)
//Motion differential equation
a_x = -(b / m) * Math.sqrt((Math.pow(vt_z, 2) + Math.pow(vt_y, 2) + Math.pow(vt_x, 2))) * vt_x + (Fl_x / m)
a_z = -(b / m) * Math.sqrt((Math.pow(vt_z, 2) + Math.pow(vt_y, 2) + Math.pow(vt_x, 2))) * vt_z + (Fl_z / m)
a_y = -g - (b / m) * Math.sqrt((Math.pow(vt_z, 2) + Math.pow(vt_y, 2) + Math.pow(vt_x, 2))) * vt_y + (Fl_y / m)
//use formula : s_t = s_0 + v_0 * t to update the position
ball.position.x = ball.position.x + vt_x * t_step
ball.position.z = ball.position.z + vt_z * t_step
ball.position.y = ball.position.y + vt_y * t_step
//use formula : v_t = a * t to update the velocity
vt_x = vt_x + a_x * t_step
vt_z = vt_z + a_z * t_step
vt_y = vt_y + a_y * t_step
}
window.onload = (function() {
gWidth = window.innerWidth;
gHeight = window.innerHeight;
ratio = gWidth / gHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xeaeaea);
camera = new THREE.PerspectiveCamera(35, ratio, 0.1, 1000);
camera.position.z = -15;
light = new THREE.SpotLight(0xffffff, 1);
light.castShadow = true;
light.position.set(0, 5, -10);
scene.add(light);
renderer = new THREE.WebGLRenderer();
//properties for casting shadow
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(gWidth, gHeight);
document.body.appendChild(renderer.domElement);
geometry = new THREE.SphereGeometry(D, 8, 8);
//make a checkerboard texture for the ball...
canv = document.createElement('canvas')
canv.width = canv.height = 256;
ctx = canv.getContext('2d')
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = 'black';
for (y = 0; y < 16; y++)
for (x = 0; x < 16; x++)
if ((x & 1) != (y & 1)) ctx.fillRect(x * 16, y * 16, 16, 16);
ballTex = new THREE.Texture(canv);
ballTex.needsUpdate = true;
material = new THREE.MeshLambertMaterial({
map: ballTex
});
ball = new THREE.Mesh(geometry, material);
ball.castShadow = true;
ball.receiveShadow = false;
ball.bottom = D / 2;
scene.add(ball);
camera.lookAt(ball.position);
plane_geometry = new THREE.PlaneGeometry(20, 100, 32);
plane_material = new THREE.MeshBasicMaterial({
color: 'green',
side: THREE.DoubleSide
});
ground_plane = new THREE.Mesh(plane_geometry, plane_material);
ground_plane.rotation.x = 0.5 * Math.PI
ground_plane.position.y = -1
ground_plane.position.z = 20
scene.add(ground_plane);
render = function(params) {
animateKick();
renderer.render(scene, camera);
requestAnimationFrame(render);
};
render();
})
body {
padding: 0;
margin: 0;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<script src="https://www.klika.co.il/scripts/three.events.js"></script>
</head>
<body>
</body>
</html>