Using THREE.js to shoot a ball with a curve on the X or Z axis

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>

Answer №1

A model was developed to simulate the scenario, incorporating parameters such as initial velocity and angular velocity. The ball experiences three main forces: 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);
  
  // Additional code for texture generation omitted for brevity
  
  material = new THREE.MeshLambertMaterial({
    map: ballTex
  });
  ball = new THREE.Mesh(geometry, material);

  // Code for setting up shadows on the ball omitted 

  scene.add(ball);
  camera.lookAt(ball.position);

  // Other initialization code related to scene setup is not shown here

  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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

utilizing jQuery to deliver a $.post request to a .aspx file

Recently, I started following a jQuery tutorial on the phpAcademy channel hosted on thenewboston. In this tutorial, they demonstrate how to create an email validation form using ASP.net instead of PHP. However, despite following all the steps in the tutor ...

Turn off auto-suggestion feature on the iPad and iPhone keyboard using JavaScript

I'm facing a challenge where I want to turn off the predictive suggestions and autocorrect feature on the iPad keyboard. This image is just for display purposes and not from my actual project. I need to hide the highlighted suggestion bar, and I am u ...

Is it possible to retrieve text from various iframes using the rangy library?

Here's a question that follows up on a problem I've been having with grabbing selected text from iframes using rangy. The code works well for the first iframe, but when I try to grab elements from multiple iframes using the same button, it doesn& ...

Is it possible to implement a single OrbitControls with two cameras in ThreeJS?

Is there a way to link the OrbitControls of two canvases on the same page? For example, if the user zooms in on one canvas, I want the other canvas to also zoom in simultaneously. How could I achieve this synchronization between multiple canvases? ...

Text input fields within a grid do not adjust to different screen sizes when placed within a tab

I noticed that my component under a tab is causing the Textfield to become unresponsive on small screens. To demonstrate this, I checked how the Textfield appears on an iPhone 5/SE screen size. https://i.stack.imgur.com/d8Bql.png Is there a way to make t ...

What is the best method to trigger a bootstrap modal window from a separate component in Angular 8?

I have successfully implemented a bootstrap modal window that opens on a button click. However, I am now facing difficulty in opening the same modal window from a different component. Below is the code I have tried: <section> <button type=&quo ...

How can you determine the status of an individual checkbox within a reactjs (material-table) component?

In my project, I have implemented a table that displays a list of students retrieved from a database. Upon clicking on each student row, a modal popup appears showing another table with 4 rows and 3 columns. Each column in the modal contains 4 checkboxes. ...

Maintain Vue Router Query Parameters Across Parent Components

In my application, I have a component named Foo, which serves as the template for a route called app/foo. Within this component, there are child components that also act as templates for routes such as app/foo/bar and app/foo/baz. I've implemented a ...

Changing the visual appearance of an alert in JavaScript and HTML

My knowledge in JavaScript is limited, but I have a script that retrieves a query from a Python service to a Mongodb database. The query is returned in the following format: [{CHAIN: "STREET ELM, ELMER", CODE: "1234"}, {CHAIN: "STREET LM, LMAO", CODE: ...

Acquiring row data upon checkbox selection: A step-by-step guide

I'm struggling to separate and assign the values returned by a function to different parameters. function saveTaxes() { $('input[type=checkbox]').each(function() { if ($(this).is(':checked')) { //test console.log ...

Using Vue to change select box data separately

I have a functional select box that is currently sending the selected value to a method when the change event occurs. However, I am wondering about something: Let's say I also want to send the cat_id value at the time of selection (to create an objec ...

Skrollr's transformation effect makes the text tremble

Using skrollr.js, I am implementing transition effects on an inner div nested inside another div. The inner div is positioned relatively. Here are my codes: HTML Structure <div class="outer"> <div class="inner-div" style="transition:transform ...

Saving the accurate x and y coordinates into the database

My website features draggable images that each have their own x and y positions. I am attempting to pass these values into a MySQL database, but I'm facing an issue where all the images are getting assigned the same x and y values in the database. In ...

The custom validation in Node.js using Express-Validator is ineffective

I have implemented custom validators using express-validator to include specific validations: middlewares.js module.exports = function (app) { console.log('making sure I am being called'); return function (request, response, next) { ...

Instructions on uploading a PDF file from a Wordpress page and ensuring the file is stored in the wp-content upload directory folder

What is the process for uploading a PDF file on a WordPress page? <form action="" method="POST"> <input type="file" name="file-upload" id="file-upload" /> <?php $attachment_id = media_handle_upload('file-upload', $post->I ...

When trying to import axios from the 'axios.js' file in the 'lib' directory, a SyntaxError was encountered with the message: Unexpected identifier

My server.ts is causing issues. What am I doing wrong? const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const morgan = require('morgan'); const axios = requ ...

Automatically refreshing controller functionality in CodeIgniter

Greetings everyone, I'm looking for a way to automatically refresh a controller function every 5 seconds. Currently, I am using header('Refresh: 10.2'); within the controller function like this: public function delete() { heade ...

What are the steps to creating a screen that mimics a mirror in the physical world?

Is there a way for the user to see themselves when they open a URL? I'm not looking for a mirror effect like the one produced by jQuery reflection js. I don't want to rely on using the front camera or any other camera to achieve this. Any sugges ...

A guide on integrating a submission button that combines values from multiple dropdown forms and redirects to a specific URL

Is there a way to make the submit button on this form act based on the combined values of two different dropdown menus? For instance, if "west" is selected from the first dropdown and "winter" is selected from the second dropdown, I want it to navigate to ...

Why isn't the nested intricate directive being executed?

After watching a tutorial on YouTube by John Lindquist from egghead.io, where he discussed directives as components and containers, I decided to implement a similar structure but with a more dynamic approach. In his example, it looked something like this ...