Converting Three.js from vanilla JavaScript to React Three Fiber - javascript

I've been trying to convert three.js written in vanilla JS to React Three fiber.
`import * as THREE from 'three';
let scene, camera, renderer;
//Canvas
const canvas = document.querySelector('canvas')
//Number of line particles
let lineCount = 6000;
//adding buffer Geometry
let geom = new THREE.BufferGeometry();
//Giving the Buffer Geometry attributes
geom.setAttribute('position', new THREE.BufferAttribute(new Float32Array(6 * lineCount), 3));
geom.setAttribute('velocity', new THREE.BufferAttribute(new Float32Array(2 * lineCount), 1));
//creating array variable for the position
let pos = geom.getAttribute('position');
let posArray = pos.array;
//creating array variable for the velocity
let vel = geom.getAttribute('velocity');
let velArray = vel.array;
//function to initiate
const init = () => {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 1, 500);
camera.position.z = 200;
renderer = new THREE.WebGLRenderer({antialias: true, canvas: canvas});
renderer.setSize(window.innerWidth, window.innerHeight);
for (let lineIndex = 0; lineIndex < lineCount; lineIndex++){
let x = Math.random() * 400 - 200;
let y = Math.random() * 200 - 100;
let z = Math.random() * 500 - 100;
let xx = x;
let yy = y;
let zz = z;
//line starting position
posArray[6 * lineIndex] = x;
posArray[6 * lineIndex + 1] = y;
posArray[6 * lineIndex + 2] = z;
//line ending position
posArray[6 * lineIndex + 3] = xx;
posArray[6 * lineIndex + 4] = yy;
posArray[6 * lineIndex + 5] = zz;
velArray[2 * lineIndex] = velArray[2 * lineIndex + 1] = 0;
}
let lineMat = new THREE.LineBasicMaterial({color: '#ffffff'});
let lines = new THREE.LineSegments(geom, lineMat);
scene.add(lines);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth/ window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, false);
animate();
}
const animate = () => {
for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
velArray[2 * lineIndex] += 0.03;
velArray[2 * lineIndex + 1] += 0.025;
posArray[6 * lineIndex + 2] += velArray[2 * lineIndex];
posArray[6 * lineIndex + 5] += velArray[2 * lineIndex + 1];
if (posArray[6 * lineIndex + 5] > 200) {
let z = Math.random() * 200 - 100;
posArray[6 * lineIndex + 2] = z;
posArray[6 * lineIndex + 5] = z;
velArray[2 * lineIndex] = 0;
velArray[2 * lineIndex + 1] = 0;
}
}
pos.needsUpdate = true;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
init();
`
I'm having difficulty in converting the init and animate function.
This is what I have so far, I've added the positions and velocity as bufferAttributes and specified the starting and ending coordinates in the for loop
const StarLine = () => {
const warpFieldMesh = useRef();
const bufAtPos = useRef();
const bufAtVel = useRef();
const count = 100;
const [positions, velocity] = useMemo(() => {
let positions = []
let velocity = []
for (let lineIndex = 0; lineIndex < count; lineIndex++){
let x = Math.random() * 400 - 200;
let y = Math.random() * 200 - 100;
let z = Math.random() * 500 - 100;
let xx = x;
let yy = y;
let zz = z;
//line starting position
positions[6 * lineIndex] = x;
positions[6 * lineIndex + 1] = y;
positions[6 * lineIndex + 2] = z;
//line ending position
positions[6 * lineIndex + 3] = xx;
positions[6 * lineIndex + 4] = yy;
positions[6 * lineIndex + 5] = zz;
velocity[2 * lineIndex] = velocity[2 * lineIndex + 1] = 0;
}
return [new Float32Array(positions), new Float32Array(velocity)]
}, [])
useFrame(() => {
})
return (
<line ref={warpFieldMesh}>
<bufferGeometry attach="geometry">
<bufferAttribute
ref={bufAtPos}
attachObject={["attributes", "position"]}
count={positions.length / 3}
array={positions}
itemSize={3}
/>
<bufferAttribute
ref={bufAtVel}
attachObject={["attributes", "velocity"]}
count={velocity.length / 2}
array={velocity}
itemSize={1}
/>
</bufferGeometry>
<lineBasicMaterial
attach="material"
color={'#ffffff'}
/>
</line>
)
}
The vanilla JavaScript produces
If anyone could help, that would be great!

The stuff in your animate function can go in the useFrame hook, its a r3f hook that is called every frame

Related

"Uncaught TypeError: Cannot read properties of undefined (reading '310')" when running Canvas animation

I'm working on another tunnel effect demo. This time I'm trying to make the tunnel move within the image.
However, the function that handles rendering the tunnel always throws an error, and I'm not entirely sure why:
function draw(time) {
let animation = time / 1000.0;
let shiftX = ~~(texWidth * animation);
let shiftY = ~~(texHeight * 0.25 * animation);
let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation))
let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation))
for (y = 0; y < buffer.height; y++) {
for (x = 0; x < buffer.width; x++) {
let id = (y * buffer.width + x) * 4;
let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth;
let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight;
let tex = (a * texture.width + d) * 4;
buffer.data[id] = texture.data[tex];
buffer.data[id+1] = texture.data[tex+1];
buffer.data[id+2] = texture.data[tex+2];
buffer.data[id+3] = texture.data[tex+3];
}
}
ctx.putImageData(buffer, 0, 0);
window.requestAnimationFrame(draw);
}
The rest of the code is viewable here, just in case the problem happens to be somewhere else.
I have identified a possible cause -- if the first index used to read from distanceTable or
angleTable is anything other than y, the error appears, even if it's simply a value being added to y. Unfortunately, I haven't figured out what causes it, or why the second index isn't affected by this.
I've also searched for similar questions, but it seems like the people asking them all got this error for different reasons, so I'm kind of stuck.
It appears that setting the for loops to use the canvas' height and width as the upper limit instead of the pixel buffer's width and height was enough to fix it.
I have absolutely no idea why, though. Was it because the buffer was twice the size of the canvas?
var texWidth = 256;
var texHeight = 256;
var screenWidth = 640;
var screenHeight = 480;
var canvas = document.createElement('canvas');
canvas.width = screenWidth;
canvas.height = screenHeight;
var ctx = canvas.getContext("2d");
var texture = new ImageData(texWidth, texHeight);
var distanceTable = [];
var angleTable = [];
var buffer = new ImageData(canvas.width * 2, canvas.height * 2);
for (let y = 0; y < texture.height; y++) {
for (let x = 0; x < texture.width; x++) {
let id = (y * texture.width + x) * 4;
let c = x ^ y;
texture.data[id] = c;
texture.data[id+1] = c;
texture.data[id+2] = c;
texture.data[id+3] = 255;
}
}
for (let y = 0; y < buffer.height; y++) {
distanceTable[y] = [];
angleTable[y] = [];
let sqy = Math.pow(y - canvas.height, 2);
for (let x = 0; x < buffer.width; x++) {
let sqx = Math.pow(x - canvas.width, 2);
let ratio = 32.0;
let distance = ~~(ratio * texHeight / Math.sqrt(sqx + sqy)) % texHeight;
let angle = Math.abs(~~(0.5 * texWidth * Math.atan2(y - canvas.height, x - canvas.width)) / Math.PI);
distanceTable[y][x] = distance;
angleTable[y][x] = angle;
}
}
function draw(time) {
let animation = time / 1000.0;
let shiftX = ~~(texWidth * animation);
let shiftY = ~~(texHeight * 0.25 * animation);
let shiftLookX = (screenWidth / 2) + ~~(screenWidth / 2 * Math.sin(animation))
let shiftLookY = (screenHeight / 2) + ~~(screenHeight / 2 * Math.sin(animation * 2.0))
for (y = 0; y < canvas.height; y++) {
for (x = 0; x < canvas.width; x++) {
let id = (y * buffer.width + x) * 4;
let d = ~~(distanceTable[y + shiftLookY][x + shiftLookX] + shiftX) % texWidth;
let a = ~~(angleTable[y + shiftLookY][x + shiftLookX] + shiftY) % texHeight;
let tex = (a * texture.width + d) * 4;
buffer.data[id] = texture.data[tex];
buffer.data[id+1] = texture.data[tex+1];
buffer.data[id+2] = texture.data[tex+2];
buffer.data[id+3] = texture.data[tex+3];
}
}
ctx.putImageData(buffer, 0, 0);
window.requestAnimationFrame(draw);
}
document.body.appendChild(canvas);
window.requestAnimationFrame(draw);

What is the best approach to make procedural world generation in three.js?

I have the function CreateChunk(x,z) that creates a "chunk" of terrain in the specified coordinates x and z that is a plane, whose vertex heights are modified with Perlin noise and then painted based on their height (a layer of water is added too) as you see below :
A single chunk
Everything works fine until I try to make more chunks:
Many chunks
I know this is how it should work and there is nothing wrong, but, what can I do to "synchronize" them so where one ends, the other starts? While keeping a procedural generation.
If you need the code tell me, but I was just asking for an idea to follow.
You need to know what tile you want to build and what density of noise you want to have on the tiles.
For some ideas, have a look at this forum post: https://discourse.threejs.org/t/help-getting-the-actual-position-of-a-vertices-in-a-buffer-geometry/29649/4
And I'll leave the snippet here. Maybe it will be helpful for other seekers :)
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.135.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.135.0/examples/jsm/controls/OrbitControls";
import {ImprovedNoise} from "https://cdn.skypack.dev/three#0.135.0/examples/jsm/math/ImprovedNoise";
THREE.BufferGeometry.prototype.toQuads = ToQuads;
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 8, 13);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
})
const perlin = new ImprovedNoise();
let controls = new OrbitControls(camera, renderer.domElement);
let step = 20;
for(let z = -4; z <= 4; z ++){
for(let x = -4; x <= 4; x++){
let p = createPlane(step, Math.random() * 0x7f7f7f + 0x7f7f7f);
setNoise(p.geometry, new THREE.Vector2(x, z), 2, 5);
p.geometry.rotateX(Math.PI * 0.5);
p.position.set(x, 0, z).multiplyScalar(step);
scene.add(p);
}
}
renderer.setAnimationLoop( _ => {
renderer.render(scene, camera);
})
function createPlane( step, color){
let g = new THREE.PlaneGeometry(step, step, 25, 25).toQuads();
let m = new THREE.LineBasicMaterial({color: color});
let l = new THREE.LineSegments(g, m);
return l;
}
function setNoise(g, uvShift, multiplier, amplitude){
let pos = g.attributes.position;
let uv = g.attributes.uv;
let vec2 = new THREE.Vector2();
for(let i = 0; i < pos.count; i++){
vec2.fromBufferAttribute(uv, i).add(uvShift).multiplyScalar(multiplier);
pos.setZ(i, perlin.noise(vec2.x, vec2.y, 0) * amplitude );
}
}
function ToQuads() {
let g = this;
let p = g.parameters;
let segmentsX = (g.type == "TorusBufferGeometry" ? p.tubularSegments : p.radialSegments) || p.widthSegments || p.thetaSegments || (p.points.length - 1) || 1;
let segmentsY = (g.type == "TorusBufferGeometry" ? p.radialSegments : p.tubularSegments) || p.heightSegments || p.phiSegments || p.segments || 1;
let indices = [];
for (let i = 0; i < segmentsY + 1; i++) {
let index11 = 0;
let index12 = 0;
for (let j = 0; j < segmentsX; j++) {
index11 = (segmentsX + 1) * i + j;
index12 = index11 + 1;
let index21 = index11;
let index22 = index11 + (segmentsX + 1);
indices.push(index11, index12);
if (index22 < ((segmentsX + 1) * (segmentsY + 1) - 1)) {
indices.push(index21, index22);
}
}
if ((index12 + segmentsX + 1) <= ((segmentsX + 1) * (segmentsY + 1) - 1)) {
indices.push(index12, index12 + segmentsX + 1);
}
}
g.setIndex(indices);
return g;
}
</script>

How can I achieve an even distribution of sprites across the surface of a sphere in THREE.js?

I'm trying to make a database of words where the most important words are closer to the top of the sphere and the less important are further away. So I created a sphere with enough vertices for each word, created a list of those vertices in order of distance from the top of the sphere, and placed the text sprites at the positions of the vertices in order of that sorted list.
Video version: https://i.gyazo.com/aabaf0b4a26f4413dc6a0ebafab2b4bd.mp4
Sounded like a good plan in my head, but clearly the geometry of a sphere causes the words to be further spread out the further away from the top they are. I need a result that looks like a somewhat even distribution across the surface. It doesn't have to be perfect, just visually closer than this.
How can I achieve the desired effect?
Here are the relevant methods:
positionDb(db) {
console.log("mostRelated", db.mostRelated);
console.log("depthList", this.depthList);
let mostRelated = db.mostRelated;
let depthList = this.depthList;
for (let i = 0; i < mostRelated.length; i++) {
this.addTextNode(mostRelated[i].data, this.depthList[i].vertice, this.depthList[i].depth);
}
}
addTextNode(text, vert, distance) {
let fontSize = 0.5 * (600 / distance);
let sprite = new THREE.TextSprite({
fillStyle: '#000000',
fontFamily: '"Arial", san-serif',
fontSize: fontSize,
fontWeight: 'bold',
text: text
});
this.scene.add(sprite);
sprite.position.set(vert.x, vert.y, vert.z);
setTimeout(() => {
sprite.fontFamily = '"Roboto", san-serif';
}, 1000)
}
this.scene = scene;
this.geometry = new THREE.SphereGeometry(420, 50, 550);
var material = new THREE.MeshBasicMaterial({
color: 0x0011ff
});
var sphere = new THREE.Mesh(this.geometry, wireframe);
var wireframe = new THREE.WireframeGeometry(this.geometry);
let frontVert = {
x: 0,
y: 100,
z: 0
}
let depthList = [];
this.geometry.vertices.forEach(vertice => {
let depth = getDistance(frontVert, vertice);
if (depthList.length === 0) {
depthList.push({
depth,
vertice
});
} else {
let flag = false;
for (let i = 0; i < depthList.length; i++) {
let item = depthList[i];
if (depth < item.depth) {
flag = true;
depthList.splice(i, 0, {
depth,
vertice
});
break;
}
}
if (!flag) depthList.push({
depth,
vertice
});
}
});
Maybe a fibonacci sphere
function fibonacciSphere(numPoints, point) {
const rnd = 1;
const offset = 2 / numPoints;
const increment = Math.PI * (3 - Math.sqrt(5));
const y = ((point * offset) - 1) + (offset / 2);
const r = Math.sqrt(1 - Math.pow(y, 2));
const phi = (point + rnd) % numPoints * increment;
const x = Math.cos(phi) * r;
const z = Math.sin(phi) * r;
return new THREE.Vector3(x, y, z);
}
Example:
function fibonacciSphere(numPoints, point) {
const rnd = 1;
const offset = 2 / numPoints;
const increment = Math.PI * (3 - Math.sqrt(5));
const y = ((point * offset) - 1) + (offset / 2);
const r = Math.sqrt(1 - Math.pow(y, 2));
const phi = (point + rnd) % numPoints * increment;
const x = Math.cos(phi) * r;
const z = Math.sin(phi) * r;
return new THREE.Vector3(x, y, z);
}
function main() {
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
function addTextNode(text, vert) {
const div = document.createElement('div');
div.className = 'label';
div.textContent = text;
div.style.marginTop = '-1em';
const label = new THREE.CSS2DObject(div);
label.position.copy(vert);
scene.add(label);
}
const renderer = new THREE.CSS2DRenderer();
const container = document.querySelector('#c');
container.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
const numPoints = 50;
for (let i = 0; i < numPoints; ++i) {
addTextNode(`p${i}`, fibonacciSphere(numPoints, i));
}
function render(time) {
time *= 0.001;
// three's poor choice of how to hanlde size strikes again :(
renderer.setSize(container.clientWidth, container.clientHeight);
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
overflow: hidden;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
.label {
color: red;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/examples/js/renderers/CSS2DRenderer.js"></script>
<div id="c"></div>

Mapping mouse coordinates with context

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>

THREE.js - shooting a ball with a curve ( X / Z axis)

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>

Categories