As a beginner in THREE.js and lacking knowledge in physics, my goal is to create a top-view football manager game with realistic ball movement. I have successfully made the ball move and rotate in the right direction, adjusting its path when it reaches boundaries.
Now, I am facing an issue with giving the ball a curved trajectory, making it move upwards and sideways (X / Y) based on the angle of impact from the foot.
I need assistance with handling two specific scenarios:
- 1) Starting the kick from the bottom axis of the ball
- 2) Starting the kick from the right axis of the ball
Your input would be greatly appreciated. Thank you!
** - Included below is the code showing my progress so far - Also attached is an image illustrating my goal (or someone scoring a goal)
https://i.sstatic.net/bbEEG.jpg
/*
*
* SETTING UP MOTION PARAMETERS
*
*/
var boundaries = [40, 24] //indicating where the ball should mirror its movements
var completeFieldDistance = boundaries[0] * 2;
var fullPower = 1.8; //power required to move the ball across the field in one kick
var power = null; //to be determined based on kick distance
var isKickStopped = false; //flag to stop the kick
var velocityX = null;
var velocityY = null;
//** seeking help with achieving a nice curve upward on the Z-axis based 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; //threshold for stopping 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 = {};
/*
*
* KICK HANDLING FUNCTIONS
*
*/
function onKick(angleDeg, distance) {
isKickStopped = true;
peak = curv;
power = (distance / completeFieldDistance) * fullPower;
velocityX = Math.cos(angleDeg) * power;
velocityY = Math.sin(angleDeg) * power;
velocityZ = peak / (distance / 2);
requestAnimationFrame(function (params) {
isKickStopped = false;
animateKick();
})
}
//** HELP NEEDED HERE - How to make the ball move in a curved manner
// render the ball movements
var animateKick = function (params) {
if (isKickStopped) { 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;
isKickStopped = 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;
}
// Determine rotation based on ball's velocity and radius...
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;
//keep ball within boundaries
if (Math.abs(ball.position.x) > boundaries[0]) {
velocityX *= -1;
ball.position.x = (ball.position.x < 0) ? boundaries[0] * -1 : boundaries[0];
}
if (Math.abs(ball.position.y) > boundaries[1]) {
velocityY *= -1;
ball.position.y = (ball.position.y < 0) ? boundaries[1] * -1 : boundaries[1];
}
}
window.onload = (function (params) {
/*
*
* INITIALIZE THE WORLD
*
*/
//set up dimensions
var gWidth = window.innerWidth;
var gHeight = window.innerHeight;
var ratio = gWidth / gHeight;
//create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xeaeaea);
//camera setup
var camera = new THREE.PerspectiveCamera(35, ratio, 0.1, 1000);
camera.position.z = 120;
//lighting
var light = new THREE.SpotLight(0xffffff, 1);
light.castShadow = true;
light.position.set(0, 0, 35);
scene.add(light);
//renderer configuration
var renderer = new THREE.WebGLRenderer();
//enable shadow casting
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(gWidth, gHeight);
document.body.appendChild(renderer.domElement);
//add mesh elements to the scene
// add the ball
var geometry = new THREE.SphereGeometry(ballRadius, 8, 8);
//apply checkerboard texture to 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);
// 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);
//handle user events
var domEvents = new THREEx.DomEvents(camera, renderer.domElement);
domEvents.addEventListener(field, 'click', function (e) {
//define 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 loop
var render = function (params) {
//render kick animation
if(!isKickStopped){
animateKick();
}
//render the scene
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"></s cript>
</head>
<body>
</body>
</html>