Related
I am trying to make my own 3D renderer in JavaScript using raycasting, but despite checking over the math and the code countless times, it still does not seem to be working. I've tried everything I possibly could to get this thing to work and it won't, so I'm hoping someone else can figure it out.
My code runs an Update method every frame, increasing the yaw (Camera.Rot.Yaw) by 0.1 radians every iteration, but it ends up looking weird and unrealistic, and I can't figure out why. Sorry if it's confusing and long, I can't really think of a way to make a minimal reproducible example of this.
This is the Update method:
Update(Canvas, Ctx, Map, Camera) {
var id = Ctx.getImageData(0, 0, Canvas.width, Canvas.height);
var Pixels = id.data;
//Distance of projection plane from camera
//It should be behind I think
var PlaneDist = 64;
//Divides the second slopes by this so each ray goes a shorter
//distance each iteration, effectively increasing quality
var Quality = 160;
//The midpoint of the projection plane for each coordinate
var MidX =
Camera.Pos.X +
PlaneDist * Math.cos(Camera.Rot.Pitch) * Math.cos(Camera.Rot.Yaw);
var MidY = Camera.Pos.Y + PlaneDist * Math.sin(Camera.Rot.Pitch);
var MidZ =
Camera.Pos.Z +
PlaneDist * Math.cos(Camera.Rot.Pitch) * Math.sin(Camera.Rot.Yaw);
//Slopes to get to other points on the projection plane
var SlopeX =
Math.sin(Camera.Rot.Yaw) +
(Canvas.height / Canvas.width) *
Math.cos(Camera.Rot.Yaw) *
Math.sin(Camera.Rot.Pitch);
var SlopeY = -Math.cos(Camera.Rot.Pitch);
var SlopeZ =
Math.cos(Camera.Rot.Yaw) +
(Canvas.height / Canvas.width) *
Math.sin(Camera.Rot.Yaw) *
Math.sin(Camera.Rot.Pitch);
//Loops for every point on the projection plane
for (let i = 0; i < Canvas.height; i++) {
for (let j = 0; j < Canvas.width; j++) {
let NewX = Camera.Pos.X;
let NewY = Camera.Pos.Y;
let NewZ = Camera.Pos.Z;
//Slopes for the actual ray to follow, just the distance between
//the plane point and the camera divided by quality
let SlopeX2 = (Camera.Pos.X-(MidX - SlopeX * (j - Canvas.width / 2)))/ Quality;
let SlopeY2 = (Camera.Pos.Y-(MidY - SlopeY * (i - Canvas.height / 2))) / Quality;
let SlopeZ2 = (Camera.Pos.Z-(MidZ - SlopeZ * (j - Canvas.width / 2)))/ Quality;
//Ray's current map position, divides the map into a 16x32x16
//list of blocks (map initialization shown elsewhere)
let MapPos =
Map.MData[0][Math.floor(NewX / 16) + 2][Math.floor(NewY / 16)][
Math.floor(NewZ / 16)
];
//Iterates until ray either hits a block with max opacity, or
//hits the boundary of the map
while (
MapPos[3] !== 255 &&
NewX + SlopeX2 < 256 &&
NewY + SlopeY2 < 512 &&
NewZ + SlopeZ2 < 256 &&
NewX + SlopeX2 >= 0 &&
NewY + SlopeY2 >= 0 &&
NewZ + SlopeZ2 >= 0
) {
//Advances ray's current position according to slopes
NewX += SlopeX2;
NewY += SlopeY2;
NewZ += SlopeZ2;
MapPos =
Map.MData[0][Math.floor(NewX / 16) + 2][Math.floor(NewY / 16)][
Math.floor(NewZ / 16)
];
}
//Sets pixel on screen to the color of the block the ray hit
//or just white (opacity 0) if it hit the boundary
Pixels[(i * id.width + j) * 4] = MapPos[0];
Pixels[(i * id.width + j) * 4 + 1] = MapPos[1];
Pixels[(i * id.width + j) * 4 + 2] = MapPos[2];
Pixels[(i * id.width + j) * 4 + 3] = MapPos[3];
}
}
//Displays the final image
Ctx.putImageData(id, 0, 0);
}
The map initialization (CreateChunk) looks like this:
constructor() {
this.MData = [];
}
CreateChunk(X, Y) {
let Chunk = [X, Y];
for (let x = 0; x < 16; x++) {
let Plane = [];
for (let y = 0; y < 32; y++) {
let Row = [];
for (let z = 0; z < 16; z++) {
//Colors are just to help tell which pixels are at what coordinates
if (y < 8) Row.push([x * 15, y * 7, z * 15, 255]);
else Row.push([0, 0, 0, 0]);
}
Plane.push(Row);
}
Chunk.push(Plane);
}
this.MData.push(Chunk);
}
I'm hoping it's just some coding mistake I've made, but despite my countless checks it may be the trigonometry that's wrong.
I'm new to THREE.js and with a very poor knowledge in physics - but I am trying to build a football game engine (viewed from top) and right now I'm struggling with the movement of the ball.
when trying to move the ball from side to side, the rotation is always facing one direction and I dont understand how to make this rotate in the direction its moving at.
Ive added a simple code showing this issue. your help is much appreciated.
/*
*
* SET UP MOTION PARAMS
*
*/
var degrees = 10;
var power = 1;
var angleRad = degrees * Math.PI / 120;
var velocityX = Math.cos(angleRad) * power;
var velocityY = Math.sin(angleRad) * power;
var velocityZ = 1;
var friction = 1;
var gravity = 0.2;
var bounciness = 0.9;
window.onload = function (params) {
/*
*
* SET UP THE WORLD
*
*/
//set up the ratio
var gWidth = window.innerWidth;
var gHeight = window.innerHeight;
var ratio = gWidth / gHeight;
var borders = [40, 24] //indicate where the ball needs to move in mirror position
//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.position.set(100, 1, 0);
light.castShadow = true;
light.position.set(0, 0, 100);
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(5, 5, 5);
var material = new THREE.MeshLambertMaterial({ color: 'gray' });
var ball = new THREE.Mesh(geometry, material);
ball.castShadow = true;
ball.receiveShadow = false;
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);
var field = new THREE.Mesh(geometry, material);
field.receiveShadow = true;
field.position.z = -1;
scene.add(field);
/*
* setting up rotation axis
*/
var rotation_matrix = null;
var setQuaternions = function () {
setMatrix();
ball.rotation.set(Math.PI / 2, Math.PI / 4, Math.PI / 4); // Set initial rotation
ball.matrix.makeRotationFromEuler(ball.rotation); // Apply rotation to the object's matrix
}
var setMatrix = function () {
rotation_matrix = new THREE.Matrix4().makeRotationZ(angleRad); // Animated rotation will be in .01 radians along object's X axis
}
setQuaternions();
/*
*
* ANIMATION STEP
*
*/
var render = function (params) {
// add velocity to ball
ball.position.x += velocityX;
ball.position.z += velocityZ;
ball.position.y += velocityY;
//validate if ball is stop moving
if (Math.abs(velocityX) < 0.02 && Math.abs(velocityY) < 0.02) {
console.log("DONE!");
return;
}
// handle boucing effect
if (ball.position.z < 1) {
velocityZ *= -bounciness;
ball.position.z = 1
}
// Update the object's rotation & apply it
ball.matrix.multiply(rotation_matrix);
ball.rotation.setFromRotationMatrix(ball.matrix);
//reducing speed by friction
angleRad *= friction;
velocityX *= friction;
velocityY *= friction;
velocityZ *= friction;
//set up the matrix
setMatrix();
//validate ball is withing its borders otherwise go in the mirror direction
if (Math.abs(ball.position.x) > borders[0]) {
velocityX *= -1;
ball.position.x = (ball.position.x < 0) ? borders[0] * -1 : borders[0];
}
if (Math.abs(ball.position.y) > borders[1]) {
velocityY *= -1;
ball.position.y = (ball.position.y < 0) ? borders[1] * -1 : borders[1];
}
// reduce ball height with gravity
velocityZ -= gravity;
//render the page
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
}
body {
padding: 0;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<html>
<head>
</head>
<body>
</body>
</html>
This is actually a pretty advanced bit of physics to do in a super realistic way if you want to include friction and inertia, etc. But you can take some shortcuts to get a decent visual rolling effect...
If you take the vector in the movement direction of the ball, you can get a perpendicular vector.. by taking the .cross product of the movement vector, with the world up vector.
That vector is the axis that a ball would rotate around if it had complete friction with the ground. Once you have that axis, you can use .rotateOnWorldAxis ( axis : Vector3, angle : Float ) with the object..
then you have to figure out how much to rotate, based on the radius of the ball, and the distance travelled.. so it's the length (called magnitude in my code below) of the movement vector * (PI*2) / the circumference of the ball.
Let me know if this helps...
p.s - Your "angleRad" computation was dividing by 120 instead of 180.. i fixed that.
/*
*
* SET UP MOTION PARAMS
*
*/
var degrees = 35;
var power = 0.45;
var angleRad = degrees * Math.PI / 180;
var velocityX = Math.cos(angleRad) * power;
var velocityY = Math.sin(angleRad) * power;
var velocityZ = 1;
var friction = 1;
var gravity = 0.2;
var bounciness = 0.9;
var ballRadius = 5;
var ballCircumference = Math.PI * ballRadius * 2;
var ballVelocity = new THREE.Vector3();
var ballRotationAxis = new THREE.Vector3(0, 1, 0);
window.onload = function(params) {
/*
*
* SET UP THE WORLD
*
*/
//set up the ratio
var gWidth = window.innerWidth;
var gHeight = window.innerHeight;
var ratio = gWidth / gHeight;
var borders = [40, 24] //indicate where the ball needs to move in mirror position
//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.position.set(100, 1, 0);
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
});
var ball = new THREE.Mesh(geometry, material);
ball.castShadow = true;
ball.receiveShadow = false;
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);
var field = new THREE.Mesh(geometry, material);
field.receiveShadow = true;
field.position.z = -1;
scene.add(field);
/*
* setting up rotation axis
*/
var rotation_matrix = null;
var setQuaternions = function() {
setMatrix();
ball.rotation.set(Math.PI / 2, Math.PI / 4, Math.PI / 4); // Set initial rotation
ball.matrix.makeRotationFromEuler(ball.rotation); // Apply rotation to the object's matrix
}
var setMatrix = function() {
rotation_matrix = new THREE.Matrix4().makeRotationZ(angleRad); // Animated rotation will be in .01 radians along object's X axis
}
setQuaternions();
/*
*
* ANIMATION STEP
*
*/
var render = function(params) {
// add velocity to ball
ball.position.x += velocityX;
ball.position.z += velocityZ;
ball.position.y += velocityY;
//validate if ball is stop moving
if (Math.abs(velocityX) < 0.02 && Math.abs(velocityY) < 0.02) {
console.log("DONE!");
return;
}
// handle boucing effect
if (ball.position.z < 1) {
velocityZ *= -bounciness;
ball.position.z = 1
}
// Update the object's rotation & apply it
/*
ball.matrix.multiply(rotation_matrix); ball.rotation.setFromRotationMatrix(ball.matrix);
//set up the matrix
setMatrix();
*/
// Figure out the rotation based on the velocity and radius of the ball...
ballVelocity.set(velocityX, velocityY, velocityZ);
ballRotationAxis.set(0, 0, 1).cross(ballVelocity).normalize();
var velocityMag = ballVelocity.length();
var rotationAmount = velocityMag * (Math.PI * 2) / ballCircumference;
ball.rotateOnWorldAxis(ballRotationAxis, rotationAmount)
//reducing speed by friction
angleRad *= friction;
velocityX *= friction;
velocityY *= friction;
velocityZ *= friction;
//validate ball is withing its borders otherwise go in the mirror direction
if (Math.abs(ball.position.x) > borders[0]) {
velocityX *= -1;
ball.position.x = (ball.position.x < 0) ? borders[0] * -1 : borders[0];
}
if (Math.abs(ball.position.y) > borders[1]) {
velocityY *= -1;
ball.position.y = (ball.position.y < 0) ? borders[1] * -1 : borders[1];
}
// reduce ball height with gravity
velocityZ -= gravity;
//render the page
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
}
body {
padding: 0;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<html>
<head>
</head>
<body>
</body>
</html>
If I understand your situation correctly, then you'll want to apply a rotation to the ball, that is based around the "right axis" of the ball's local space.
THREE.js provides a number of helper methods to simplify this math, namely the makeRotationAxis() method on the THREE.Matrix4 class.
Conceptually and practically, a minor set of adjustments to your ball.rotation math should achieve what you are wanting. Please see the following code snippet to see how this can be done (or see this working jsFiddle):
/*
*
* SET UP MOTION PARAMS
*
*/
var rotationAngle = 0;
var degrees = 10;
var power = 1;
var angleRad = degrees * Math.PI / 120;
var velocityX = Math.cos(angleRad) * power;
var velocityY = Math.sin(angleRad) * power;
var velocityZ = 1;
var friction = 1;
var gravity = 0.2;
var bounciness = 0.9;
window.onload = function (params) {
/*
*
* SET UP THE WORLD
*
*/
//set up the ratio
var gWidth = window.innerWidth;
var gHeight = window.innerHeight;
var ratio = gWidth / gHeight;
var borders = [40, 24] //indicate where the ball needs to move in mirror position
//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.position.set(100, 1, 0);
light.castShadow = true;
light.position.set(0, 0, 100);
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(5, 5, 5);
var material = new THREE.MeshLambertMaterial({ color: 'gray' });
var ball = new THREE.Mesh(geometry, material);
ball.castShadow = true;
ball.receiveShadow = false;
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);
var field = new THREE.Mesh(geometry, material);
field.receiveShadow = true;
field.position.z = -1;
scene.add(field);
/*
* setting up rotation axis
*/
var rotation_matrix = null;
var setQuaternions = function () {
setMatrix();
ball.rotation.set(Math.PI / 2, Math.PI / 4, Math.PI / 4); // Set initial rotation
ball.matrix.makeRotationFromEuler(ball.rotation); // Apply rotation to the object's matrix
}
var setMatrix = function () {
rotation_matrix = new THREE.Matrix4().makeRotationZ(angleRad); // Animated rotation will be in .01 radians along object's X axis
}
setQuaternions();
/*
*
* ANIMATION STEP
*
*/
var render = function (params) {
// add velocity to ball
ball.position.x += velocityX;
ball.position.z += velocityZ;
ball.position.y += velocityY;
//validate if ball is stop moving
if (Math.abs(velocityX) < 0.02 && Math.abs(velocityY) < 0.02) {
console.log("DONE!");
return;
}
// handle boucing effect
if (ball.position.z < 1) {
velocityZ *= -bounciness;
ball.position.z = 1
}
// Update the object's rotation & apply it
// ball.matrix.multiply(rotation_matrix);
// Compute the direction vector of the balls current forward direction of motion
var vectorDirection = new THREE.Vector3(velocityX, velocityY, velocityZ);
// Compute the vector about which the balls rotation is calculated. This is at a
// right angle to the vectorDirection, and so we use the cross product to
// calculate this
var axisOfRotation = new THREE.Vector3().crossVectors(vectorDirection, new THREE.Vector3(0,0,1) );
// Normalise the rotation axis to unit length
axisOfRotation.normalize();
// Build a rotation matrix around the rotation axis.
var rotation = new THREE.Matrix4();
rotation .makeRotationAxis(axisOfRotation, rotationAngle)
ball.rotation.setFromRotationMatrix(rotation );
// Decrement the rotation angle to achieve the rolling effect
rotationAngle -= 0.1;
// ball.rotation.setFromRotationMatrix(ball.matrix);
//reducing speed by friction
angleRad *= friction;
velocityX *= friction;
velocityY *= friction;
velocityZ *= friction;
//set up the matrix
setMatrix();
//validate ball is withing its borders otherwise go in the mirror direction
if (Math.abs(ball.position.x) > borders[0]) {
velocityX *= -1;
ball.position.x = (ball.position.x < 0) ? borders[0] * -1 : borders[0];
}
if (Math.abs(ball.position.y) > borders[1]) {
velocityY *= -1;
ball.position.y = (ball.position.y < 0) ? borders[1] * -1 : borders[1];
}
// reduce ball height with gravity
velocityZ -= gravity;
//render the page
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
}
body {
padding: 0;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<html>
<head>
</head>
<body>
</body>
</html>
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>
How to change the cycle for drawing points on a sphere THREEjs? Its very slow and i cant see my meshes.
my code below:
let R = 600;
let phi_rad = phi * Math.PI / 180;
let theta_rad = theta * Math.PI / 180;
for (var theta = 0; theta < 180; theta++) {
for (var phi = 0; phi < 360; phi++) {
let random_bm_geometry = new THREE.PlaneGeometry(100,100);
let random_bm_material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
side: THREE.DoubleSide
});
let random_bm_mesh = new THREE.Mesh(random_bm_geometry, random_bm_material);
let x = R * Math.cos(theta_rad) * Math.cos(phi_rad);
let y = R * Math.cos(theta_rad) * Math.sin(phi_rad);
let z = R * Math.sin(theta_rad) * Math.sin(phi_rad);
let sq = 1/Math.sqrt(x*x + y*y + z*z);
random_bm_mesh.position.x = R*x*sq;
random_bm_mesh.position.y = R*y*sq;
random_bm_mesh.position.z = R*z*sq;
scene.add(random_bm_mesh);
random_bm_array.push(random_bm_mesh);
};
};
What shoud i change, because 2 for cycles are weird.
I'm trying to create a hyperdrive effect, like from Star Wars, where the stars have a motion trail. I've gotten as far as creating the motion trail on a single circle, it still looks like the trail is going down in the y direction and not forwards or positive in the z direction.
Also, how could I do this with (many) randomly placed circles as if they were stars?
My code is on jsfiddle (https://jsfiddle.net/5m7x5zxu/) and below:
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");
var xPos = 180;
var yPos = 100;
var motionTrailLength = 16;
var positions = [];
function storeLastPosition(xPos, yPos) {
// push an item
positions.push({
x: xPos,
y: yPos
});
//get rid of first item
if (positions.length > motionTrailLength) {
positions.pop();
}
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i = positions.length-1; i > 0; i--) {
var ratio = (i - 1) / positions.length;
drawCircle(positions[i].x, positions[i].y, ratio);
}
drawCircle(xPos, yPos, "source");
var k=2;
storeLastPosition(xPos, yPos);
// update position
if (yPos > 125) {
positions.pop();
}
else{
yPos += k*1.1;
}
requestAnimationFrame(update);
}
update();
function drawCircle(x, y, r) {
if (r == "source") {
r = 1;
} else {
r*=1.1;
}
context.beginPath();
context.arc(x, y, 3, 0, 2 * Math.PI, true);
context.fillStyle = "rgba(255, 255, 255, " + parseFloat(1-r) + ")";
context.fill();
}
Canvas feedback and particles.
This type of FX can be done many ways.
You could just use a particle systems and draw stars (as lines) moving away from a central point, as the speed increase you increase the line length. When at low speed the line becomes a circle if you set ctx.lineWidth > 1 and ctx.lineCap = "round"
To add to the FX you can use render feedback as I think you have done by rendering the canvas over its self. If you render it slightly larger you get a zoom FX. If you use ctx.globalCompositeOperation = "lighter" you can increase the stars intensity as you speed up to make up for the overall loss of brightness as stars move faster.
Example
I got carried away so you will have to sift through the code to find what you need.
The particle system uses the Point object and a special array called bubbleArray to stop GC hits from janking the animation.
You can use just an ordinary array if you want. The particles are independent of the bubble array. When they have moved outside the screen they are move to a pool and used again when a new particle is needed. The update function moves them and the draw Function draws them I guess LOL
The function loop is the main loop and adds and draws particles (I have set the particle count to 400 but should handle many more)
The hyper drive is operated via the mouse button. Press for on, let go for off. (It will distort the text if it's being displayed)
The canvas feedback is set via that hyperSpeed variable, the math is a little complex. The sCurce function just limits the value to 0,1 in this case to stop alpha from going over or under 1,0. The hyperZero is just the sCurve return for 1 which is the hyper drives slowest speed.
I have pushed the feedback very close to the limit. In the first few lines of the loop function you can set the top speed if(mouse.button){ if(hyperSpeed < 1.75){ Over this value 1.75 and you will start to get bad FX, at about 2 the whole screen will just go white (I think that was where)
Just play with it and if you have questions ask in the comments.
const ctx = canvas.getContext("2d");
// very simple mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// High performance array pool using buubleArray to separate pool objects and active object.
// This is designed to eliminate GC hits involved with particle systems and
// objects that have short lifetimes but used often.
// Warning this code is not well tested.
const bubbleArray = () => {
const items = [];
var count = 0;
return {
clear(){ // warning this dereferences all locally held references and can incur Big GC hit. Use it wisely.
this.items.length = 0;
count = 0;
},
update() {
var head, tail;
head = tail = 0;
while(head < count){
if(items[head].update() === false) {head += 1 }
else{
if(tail < head){
const temp = items[head];
items[head] = items[tail];
items[tail] = temp;
}
head += 1;
tail += 1;
}
}
return count = tail;
},
createCallFunction(name, earlyExit = false){
name = name.split(" ")[0];
const keys = Object.keys(this);
if(Object.keys(this).indexOf(name) > -1){ throw new Error(`Can not create function name '${name}' as it already exists.`) }
if(!/\W/g.test(name)){
let func;
if(earlyExit){
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ if (items[i++].${name}() === true) { break } }`;
}else{
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ items[i++].${name}() }`;
}
!this.items && (this.items = items);
this[name] = new Function(func);
}else{ throw new Error(`Function name '${name}' contains illegal characters. Use alpha numeric characters.`) }
},
callEach(name){var i = 0; while(i < count){ if (items[i++][name]() === true) { break } } },
each(cb) { var i = 0; while(i < count){ if (cb(items[i], i++) === true) { break } } },
next() { if (count < items.length) { return items[count ++] } },
add(item) {
if(count === items.length){
items.push(item);
count ++;
}else{
items.push(items[count]);
items[count++] = item;
}
return item;
},
getCount() { return count },
}
}
// Helpers rand float, randI random Int
// doFor iterator
// sCurve curve input -Infinity to Infinity out -1 to 1
// randHSLA creates random colour
// CImage, CImageCtx create image and image with context attached
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const sCurve = (v,p) => (2 / (1 + Math.pow(p,-v))) -1;
const randHSLA = (h, h1, s = 100, s1 = 100, l = 50, l1 = 50, a = 1, a1 = 1) => { return `hsla(${randI(h,h1) % 360},${randI(s,s1)}%,${randI(l,l1)}%,${rand(a,a1)})` }
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"),c.width = w,c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w,h), c.ctx = c.getContext("2d"), c);
// create image to hold text
var textImage = CImageCtx(1024, 1024);
var c = textImage.ctx;
c.fillStyle = "#FF0";
c.font = "64px arial black";
c.textAlign = "center";
c.textBaseline = "middle";
const text = "HYPER,SPEED FX,VII,,Battle of Jank,,Hold the mouse,button to increase,speed.".split(",");
text.forEach((line,i) => { c.fillText(line,512,i * 68 + 68) });
const maxLines = text.length * 68 + 68;
function starWarIntro(image,x1,y1,x2,y2,pos){
var iw = image.width;
var ih = image.height;
var hh = (x2 - x1) / (y2 - y1); // Slope of left edge
var w2 = iw / 2; // half width
var z1 = w2 - x1; // Distance (z) to first line
var z2 = (z1 / (w2 - x2)) * z1 - z1; // distance (z) between first and last line
var sk,t3,t3a,z3a,lines, z3, dd = 0, a = 0, as = 2 / (y2 - y1);
for (var y = y1; y < y2 && dd < maxLines; y++) { // for each line
t3 = ((y - y1) * hh) + x1; // get scan line top left edge
t3a = (((y+1) - y1) * hh) + x1; // get scan line bottom left edge
z3 = (z1 / (w2 - t3)) * z1; // get Z distance to top of this line
z3a = (z1 / (w2 - t3a)) * z1; // get Z distance to bottom of this line
dd = ((z3 - z1) / z2) * ih; // get y bitmap coord
a += as;
ctx.globalAlpha = a < 1 ? a : 1;
dd += pos; // kludge for this answer to make text move
// does not move text correctly
lines = ((z3a - z1) / z2) * ih-dd; // get number of lines to copy
ctx.drawImage(image, 0, dd , iw, lines, t3, y, w - t3 * 2, 1.5);
}
}
// canvas settings
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// diagonal distance used to set point alpha (see point update)
var diag = Math.sqrt(w * w + h * h);
// If window size is changed this is called to resize the canvas
// It is not called via the resize event as that can fire to often and
// debounce makes it feel sluggish so is called from main loop.
function resizeCanvas(){
points.clear();
canvas.width = innerWidth;
canvas.height = innerHeight;
w = canvas.width;
h = canvas.height;
cw = w / 2; // center
ch = h / 2;
diag = Math.sqrt(w * w + h * h);
}
// create array of points
const points = bubbleArray();
// create optimised draw function itterator
points.createCallFunction("draw",false);
// spawns a new star
function spawnPoint(pos){
var p = points.next();
p = points.add(new Point())
if (p === undefined) { p = points.add(new Point()) }
p.reset(pos);
}
// point object represents a single star
function Point(pos){ // this function is duplicated as reset
if(pos){
this.x = pos.x;
this.y = pos.y;
this.dead = false;
}else{
this.x = 0;
this.y = 0;
this.dead = true;
}
this.alpha = 0;
var x = this.x - cw;
var y = this.y - ch;
this.dir = Math.atan2(y,x);
this.distStart = Math.sqrt(x * x + y * y);
this.speed = rand(0.01,1);
this.col = randHSLA(220,280,100,100,50,100);
this.dx = Math.cos(this.dir) * this.speed;
this.dy = Math.sin(this.dir) * this.speed;
}
Point.prototype = {
reset : Point, // resets the point
update(){ // moves point and returns false when outside
this.speed *= hyperSpeed; // increase speed the more it has moved
this.x += Math.cos(this.dir) * this.speed;
this.y += Math.sin(this.dir) * this.speed;
var x = this.x - cw;
var y = this.y - ch;
this.alpha = (Math.sqrt(x * x + y * y) - this.distStart) / (diag * 0.5 - this.distStart);
if(this.alpha > 1 || this.x < 0 || this.y < 0 || this.x > w || this.h > h){
this.dead = true;
}
return !this.dead;
},
draw(){ // draws the point
ctx.strokeStyle = this.col;
ctx.globalAlpha = 0.25 + this.alpha *0.75;
ctx.beginPath();
ctx.lineTo(this.x - this.dx * this.speed, this.y - this.dy * this.speed);
ctx.lineTo(this.x, this.y);
ctx.stroke();
}
}
const maxStarCount = 400;
const p = {x : 0, y : 0};
var hyperSpeed = 1.001;
const alphaZero = sCurve(1,2);
var startTime;
function loop(time){
if(startTime === undefined){
startTime = time;
}
if(w !== innerWidth || h !== innerHeight){
resizeCanvas();
}
// if mouse down then go to hyper speed
if(mouse.button){
if(hyperSpeed < 1.75){
hyperSpeed += 0.01;
}
}else{
if(hyperSpeed > 1.01){
hyperSpeed -= 0.01;
}else if(hyperSpeed > 1.001){
hyperSpeed -= 0.001;
}
}
var hs = sCurve(hyperSpeed,2);
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,0,0); // reset transform
//==============================================================
// UPDATE the line below could be the problem. Remove it and try
// what is under that
//==============================================================
//ctx.fillStyle = `rgba(0,0,0,${1-(hs-alphaZero)*2})`;
// next two lines are the replacement
ctx.fillStyle = "Black";
ctx.globalAlpha = 1-(hs-alphaZero) * 2;
//==============================================================
ctx.fillRect(0,0,w,h);
// the amount to expand canvas feedback
var sx = (hyperSpeed-1) * cw * 0.1;
var sy = (hyperSpeed-1) * ch * 0.1;
// increase alpha as speed increases
ctx.globalAlpha = (hs-alphaZero)*2;
ctx.globalCompositeOperation = "lighter";
// draws feedback twice
ctx.drawImage(canvas,-sx, -sy, w + sx*2 , h + sy*2)
ctx.drawImage(canvas,-sx/2, -sy/2, w + sx , h + sy)
ctx.globalCompositeOperation = "source-over";
// add stars if count < maxStarCount
if(points.getCount() < maxStarCount){
var cent = (hyperSpeed - 1) *0.5; // pulls stars to center as speed increases
doFor(10,()=>{
p.x = rand(cw * cent ,w - cw * cent); // random screen position
p.y = rand(ch * cent,h - ch * cent);
spawnPoint(p)
})
}
// as speed increases make lines thicker
ctx.lineWidth = 2 + hs*2;
ctx.lineCap = "round";
points.update(); // update points
points.draw(); // draw points
ctx.globalAlpha = 1;
// scroll the perspective star wars text FX
var scrollTime = (time - startTime) / 5 - 2312;
if(scrollTime < 1024){
starWarIntro(textImage,cw - h * 0.5, h * 0.2, cw - h * 3, h , scrollTime );
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Here's another simple example, based mainly on the same idea as Blindman67, concetric lines moving away from center at different velocities (the farther from center, the faster it moves..) also no recycling pool here.
"use strict"
var c = document.createElement("canvas");
document.body.append(c);
var ctx = c.getContext("2d");
var w = window.innerWidth;
var h = window.innerHeight;
var ox = w / 2;
var oy = h / 2;
c.width = w; c.height = h;
const stars = 120;
const speed = 0.5;
const trailLength = 90;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "#fff"
ctx.fillRect(ox, oy, 1, 1);
init();
function init() {
var X = [];
var Y = [];
for(var i = 0; i < stars; i++) {
var x = Math.random() * w;
var y = Math.random() * h;
X.push( translateX(x) );
Y.push( translateY(y) );
}
drawTrails(X, Y)
}
function translateX(x) {
return x - ox;
}
function translateY(y) {
return oy - y;
}
function getDistance(x, y) {
return Math.sqrt(x * x + y * y);
}
function getLineEquation(x, y) {
return function(n) {
return y / x * n;
}
}
function drawTrails(X, Y) {
var count = 1;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
function anim() {
for(var i = 0; i < X.length; i++) {
var x = X[i];
var y = Y[i];
drawNextPoint(x, y, count);
}
count+= speed;
if(count < trailLength) {
window.requestAnimationFrame(anim);
}
else {
init();
}
}
anim();
}
function drawNextPoint(x, y, step) {
ctx.fillStyle = "#fff";
var f = getLineEquation(x, y);
var coef = Math.abs(x) / 100;
var dist = getDistance( x, y);
var sp = speed * dist / 100;
for(var i = 0; i < sp; i++) {
var newX = x + Math.sign(x) * (step + i) * coef;
var newY = translateY( f(newX) );
ctx.fillRect(newX + ox, newY, 1, 1);
}
}
body {
overflow: hidden;
}
canvas {
position: absolute;
left: 0;
top: 0;
}