Related
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);
Using certain methods, I drew a 3D cube.
My task is to rotate it around the z axis. I also need to rotate the cube relative to the coordinates (0,0,0) (on my canvas, these will be the coordinates (150,150,0)). Now it rotates correctly, but only changes its angle relative to the 'top left corner of my canvas'.
I think the solution to my problem is as follows:
To rotate the cube relative to the given coordinates, the cube must first be translated to the negative coordinates of this point, and by changing the angle, it must be translated back, but only to the positive coordinates of this point.
In my case the pseudo code would be like this:
// if I want to flip the cube around the coordinates (150,150,0) (this is the center of my canvas), then I need to do the following
translate(-150,-150,0);
rotate();
translate(150,150,0);
In the commented parts of the code, I tried to do this, but when I translate the cube, it gets stuck somewhere in the corner and I don't know how to fix it.
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) ) + min;
}
function randomColor() {
return "rgb(" + randInt(0,255) + "," + randInt(0,255) + "," + randInt(0,255) + ")";
}
function ctg(x) {
return 1 / Math.tan(x);
}
function draw() {
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
let canvasWidth = 300;
canvas.width = canvas.height = canvasWidth;
function drawUW() {
context.lineWidth = 0.1;
context.beginPath();
context.moveTo(0,canvas.height/2);
context.lineTo(canvas.width,canvas.height/2);
context.moveTo(canvas.width/2,0);
context.lineTo(canvas.width/2,canvas.height);
context.stroke();
}
function updateCanvas() {
context.clearRect(0,0,canvas.width,canvas.height);
drawUW();
}
let x = 50, y = 50, z = 50;
let d = 100;
let x0 = canvas.width/2;
let y0 = canvas.height/2;
let x1 = x0+x;
let y1 = y0;
let x2 = x0;
let y2 = y0-y;
let x3 = x0+x;
let y3 = y0-y;
// x' = xd/(z+d)
// y' = yd/(z+d)
let x0p=x0*d/(z+d);
let y0p=y0*d/(z+d);
let x1p=x1*d/(z+d);
let y1p=y1*d/(z+d);
let x2p=x2*d/(z+d);
let y2p=y2*d/(z+d);
let x3p=x3*d/(z+d);
let y3p=y3*d/(z+d);
function getWalls() {
wall01 = [[x0,x2,x3,x1],[y0,y2,y3,y1]];
wall02 = [[x0p,x2p,x2,x0],[y0p,y2p,y2,y0]];
wall03 = [[x0p,x2p,x3p,x1p],[y0p,y2p,y3p,y1p]];
wall04 = [[x1p,x3p,x3,x1],[y1p,y3p,y3,y1]];
wall05 = [[x2,x2p,x3p,x3],[y2,y2p,y3p,y3]];
wall06 = [[x0,x0p,x1p,x1],[y0,y0p,y1p,y1]];
}
function drawWall(wall) {
context.fillStyle = randomColor();
context.strokeStyle = "black";
context.lineWidth = 0.5;
context.beginPath();
context.moveTo(wall[0][0],wall[1][0]);
context.lineTo(wall[0][1],wall[1][1]);
context.lineTo(wall[0][2],wall[1][2]);
context.lineTo(wall[0][3],wall[1][3]);
context.lineTo(wall[0][0],wall[1][0]);
context.fill();
context.stroke();
}
function rotatePoint(x,y,z,fi) {
fi*=Math.PI/180;
arr = [x,y,z,1];
newX = 0;
newY = 0;
newZ = 0;
tx = -150, ty = -150, tz = -150;
arrTranslate = [
[1,0,0,tx],
[0,1,0,ty],
[0,0,1,tz],
[0,0,0,1]
];
// z
arrRotate = [
[Math.cos(fi),-Math.sin(fi),0,0],
[Math.sin(fi),Math.cos(fi),0,0],
[0,0,1,0],
[0,0,0,1]
];
// translate
// for (let i = 0; i < arr.length; i++) {
// newX += arr[i] * arrTranslate[0][i];
// newY += arr[i] * arrTranslate[1][i];
// newZ += arr[i] * arrTranslate[2][i];
// }
//rotate
for (let i = 0; i < arr.length; i++) {
newX += arr[i] * arrRotate[0][i];
newY += arr[i] * arrRotate[1][i];
newZ += arr[i] * arrRotate[2][i];
}
tx = 150, ty = 150, tz = 150;
// translate
// for (let i = 0; i < arr.length; i++) {
// newX += arr[i] * arrTranslate[0][i];
// newY += arr[i] * arrTranslate[1][i];
// newZ += arr[i] * arrTranslate[2][i];
// }
x = newX;
y = newY;
z = newZ;
return [newX,newY,newZ];
}
function rotatePoints(fi) {
x0 = rotatePoint(x0,y0,z,fi)[0];
y0 = rotatePoint(x0,y0,z,fi)[1];
x1 = rotatePoint(x1,y1,z,fi)[0];
y1 = rotatePoint(x1,y1,z,fi)[1];
x2 = rotatePoint(x2,y2,z,fi)[0];
y2 = rotatePoint(x2,y2,z,fi)[1];
x3 = rotatePoint(x3,y3,z,fi)[0];
y3 = rotatePoint(x3,y3,z,fi)[1];
x0p = rotatePoint(x0p,y0p,z,fi)[0];
y0p = rotatePoint(x0p,y0p,z,fi)[1];
x1p = rotatePoint(x1p,y1p,z,fi)[0];
y1p = rotatePoint(x1p,y1p,z,fi)[1];
x2p = rotatePoint(x2p,y2p,z,fi)[0];
y2p = rotatePoint(x2p,y2p,z,fi)[1];
x3p = rotatePoint(x3p,y3p,z,fi)[0];
y3p = rotatePoint(x3p,y3p,z,fi)[1];
}
let inFi = document.getElementById("fi");
inFi.addEventListener("change", updateImage);
function updateImage() {
rotatePoints(inFi.value);
getWalls();
updateCanvas();
drawWall(wall06);
drawWall(wall04);
drawWall(wall03);
drawWall(wall02);
drawWall(wall05);
drawWall(wall01);
}
updateImage();
}
<body onload="draw();">
<canvas id="canvas"></canvas>
<div><input type="number" id="fi" value="0"></div>
</body>
I'm creating a Javascript breakout game but have an issue with detecting the collisions between the ball and both the paddle and bricks.
On running the program, I expect the ball to bounce off the paddle and the bricks if they collide. And if the ball collides with the bricks, the bricks would be removed.
I have tried executing the function checkForCollision(gw, paddle) within the animation code, setting that if the collider is the paddle, the ball will simply bounces off, and if not, it must be a brick. However, unfortunately, the ball simply passed through the colliders (paddle or bricks) and behaved as if the colliders did not exist.
Previously, I've tried several if/else variations to mend it, and have tried another method which specifies the bricks that were collided using its index position (e.g. [Not sure if this is constructed correctly] collidedBrick = bricks[i][j], where i and j is the number of rows and columns of bricks respectively). But the ball in the game still couldn't detect the existence of the colliders.
JavaScript code as follows:
const GWINDOW_WIDTH = 360;
const GWINDOW_HEIGHT = 600;
const N_ROWS = 10;
const N_COLS = 10;
const BRICK_ASPECT_RATIO = 4 / 1;
const BRICK_TO_BALL_RATIO = 3 / 2;
const BRICK_TO_PADDLE_RATIO = 2 / 3;
const BRICK_SEP = 2;
const TOP_FRACTION = 0.1;
const BOTTOM_FRACTION = 0.05;
const N_BALLS = 3;
const TIME_STEP = 10;
const INITIAL_Y_VELOCITY = 3.0;
const MIN_X_VELOCITY = 1.0;
const MAX_X_VELOCITY = 3.0;
const BRICK_WIDTH = (GWINDOW_WIDTH - (N_COLS + 1) * BRICK_SEP) / N_COLS;
const BRICK_HEIGHT = BRICK_WIDTH / BRICK_ASPECT_RATIO;
const PADDLE_WIDTH = BRICK_WIDTH / BRICK_TO_PADDLE_RATIO;
const PADDLE_HEIGHT = BRICK_HEIGHT / BRICK_TO_PADDLE_RATIO;
const PADDLE_Y = (1 - BOTTOM_FRACTION) * GWINDOW_HEIGHT - PADDLE_HEIGHT;
const BALL_SIZE = BRICK_WIDTH / BRICK_TO_BALL_RATIO;
/* Main program */
function Breakout() {
let gw = GWindow(GWINDOW_WIDTH, GWINDOW_HEIGHT);
let bricks = createBricks(gw);
let paddle = createPaddle(gw);
let ball = createBall(gw);
}
function createBricks(gw) {
let x0 = (gw.getWidth() - (N_COLS * BRICK_WIDTH) - (N_COLS - 1) * BRICK_SEP)/2;
let y0 = TOP_FRACTION * gw.getHeight();
let color = ["Red", "Orange", "Green", "Cyan", "Blue"];
for (let i = 0; i < N_ROWS; i++) {
for (let j = 0; j < N_COLS; j++) {
let x = x0 + j * (BRICK_WIDTH + BRICK_SEP);
let y = y0 + i * (BRICK_HEIGHT + BRICK_SEP);
let bricks = GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT);
bricks.setFilled(true);
bricks.setColor("White");
bricks.setFillColor(color[Math.floor(i/2)]);
gw.add(bricks);
}
}
}
function createPaddle(gw) {
let x0 = (gw.getWidth() - PADDLE_WIDTH) / 2;
let y0 = PADDLE_Y;
let paddle = GRect(x0, y0, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle.setFilled(true);
gw.add(paddle);
let mouseMoveAction = function(e) {
let x = e.getX() - (PADDLE_WIDTH / 2);
let y = PADDLE_Y;
if (x > 0 && x <= gw.getWidth() - PADDLE_WIDTH) {
paddle.setLocation(x, y);
}
};
gw.addEventListener("mousemove", mouseMoveAction);
}
function createBall(gw) {
let centerX = gw.getWidth() / 2;
let centerY = gw.getHeight() / 2;
let x0 = centerX - (BALL_SIZE / 2);
let y0 = centerY - (BALL_SIZE / 2);
let ball = GOval(x0, y0, BALL_SIZE, BALL_SIZE);
ball.setFilled(true);
gw.add(ball);
let clickAction = function(e) {
let vy = INITIAL_Y_VELOCITY;
let vx = randomReal(MIN_X_VELOCITY, MAX_X_VELOCITY);
if (randomChance()) vx = -vx;
let step = function() {
if (ball.getX() > 0 && ball.getY() > 0 &&
ball.getX() < gw.getWidth() - BALL_SIZE &&
ball.getY() < gw.getHeight() - BALL_SIZE) {
ball.move(vx, vy);
let collision = checkForCollision(gw, paddle);
}
if (ball.getX() < 0 || ball.getX() > gw.getWidth() - BALL_SIZE) {
vx = -vx;
ball.move(vx, vy);
}
if (ball.getY() < 0) {
vy = -vy;
ball.move(vx, vy);
}
if (ball.getY() > gw.getHeight()) {
clearInterval(timer);
}
};
let timer = setInterval(step, TIME_STEP);
};
gw.addEventListener("click", clickAction);
}
function checkForCollision(gw, paddle) {
let collider = getCollidingObject(gw, ball);
if (collider !== null) {
if (collider !== paddle) {
vy = -vy;
ball.move(vx, vy);
gw.remove(collider);
}
else {
vy = -vy;
ball.move(vx, vy);
}
}
function getCollidingObject(gw, ball) {
let x = ball.getX();
let y = ball.getY();
let object = null;
object = gw.getElementAt(x, y);
if (object !== null) {
return object;
}
object = gw.getElementAt(x, y + BALL_SIZE);
if (object !== null) {
return object;
}
object = gw.getElementAt(x + BALL_SIZE, y);
if (object !== null) {
return object;
}
object = gw.getElementAt(x + BALL_SIZE, y + BALL_SIZE);
if (object !== null) {
return object;
}
}
Below is the html file which directs to JavaScript Graphics library.
<!DOCTYPE html>
<html>
<head>
<title>Breakout</title>
<script type='text/javascript'
src='https://cs106ax.stanford.edu/jslib/JSGraphics.js'></script>
<script type='text/javascript'
src='https://cs106ax.stanford.edu/jslib/RandomLib.js'></script>
<script type='text/javascript'
src='Breakout.js'></script>
</head>
<body onload='Breakout()'></body>
</html>
Your help is much appreciated. Thanks!
On website https://omegalords.ga/world/test.html, I have a code that calculates 1 unit towards a target. In the code the slope is 1. so the x and y of the next spot to jump to should be the same. Why does it at some point change and the 2 coordinates. I have ran the numbers, but it doesn't work properly. Could someone explain hwy?
</canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var findDis = function(x1,y1,x2,y2){
//Finds distance rounded to the nearest 1000th
return Math.trunc(((Math.sqrt((x1-x2) * (x1-x2) + (y1-y2) * (y1-y2)))*1000))/1000
}
var findIntersections = function(b,a,m,r){
var Fraction = algebra.Fraction;
var Expression = algebra.Expression;
var Equation = algebra.Equation;
var coords = {
l:[],
g:[]
}
//b^2 - 2bx + x^2 - 2bm^2x + b^2m^2 - r^2
var sl = algebra.parse(Math.pow(b,2)+" - "+2*b+"x + x^2 + "+Math.pow(m,2)+"x^2 -" + 2 * b * Math.pow(m,2) + "* x + " + Math.pow(b,2) * Math.pow(m,2) + " - " + Math.pow(r,2));
var sr = algebra.parse("0")
var eq = new Equation(sl, sr);
//Solves for x rounded to the nearest 1000
coords.l.push(Math.trunc((eq.solveFor("x")[0]*1000))/1000);
coords.g.push(Math.trunc((eq.solveFor("x")[1]*1000))/1000);
//Solves for y
coords.l.push(Math.trunc((coords.l[0] * m + a - (b * m))*1000)/1000);
coords.g.push(Math.trunc((coords.g[0] * m + a - (b * m))*1000)/1000);
return JSON.stringify(coords);
}
var findSlope = function(x1,y1,x2,y2){
//Finds Slope rounded to the nearest 1000th
return Math.trunc(((y1-y2)/(x1-x2))*1000)/1000;
}
var getRandomInt = function(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
var wolves = [];
var deers = [];
var deer = function(x,y){
this.x = x;
this.y = y;
this.hp = 100;
this.size = 10;
this.hunger = 0*%;
this.preyTypes = [grass];
this.predatorTypes = [wolves];
deers.push(this);
}
var wolf = function(x,y){
this.x = x;
this.y = y;
this.hp = 100;
this.size = 10;
this.hunger = 0 * %;
wolves.push(this);
this.preyTypes = [deers]
}
wolf.prototype.move = function(){
this.nearbyPredators = [];
this.nearbyPrey = [];
//finds all nearby prey
for(var j = 0;j<this.preyTypes.length;j++){
for(var i = 0;i < this.preyTypes[j].length;i++){
if(findDis(this.preyTypes[j][i].x,this.preyTypes[j][i].y,this.x,this.y) < 40){
this.nearbyPrey.push(this.preyTypes[j][i]);
}else{
console.log(findDis(this.preyTypes[j][i].x,this.preyTypes[j][i].y,this.x,this.y))
}
}
}
//If there are no nearby prey, move randomly
if(this.nearbyPrey.length === 0){
wolf.x += getRandomInt(-8,9)
wolf.y -= getRandomInt(-9,8);
//If there is only one nearby prey, move towards it
}else if(this.nearbyPrey.length === 1){
this.nearestPrey = this.nearbyPrey[0]
var slope = findSlope(this.nearestPrey.x,this.nearestPrey.y,this.x,this.y);
console.log(slope);
var coords = JSON.parse(findIntersections(this.x,this.y,slope,1));
console.log(coords);
var ldis = findDis(this.x,this.y,coords.l[0],coords.l[1]);
var gdis = findDis(this.x,this.y,coords.g[0],coords.g[1]);
console.log(ldis,gdis)
if(ldis > gdis){
this.x = coords.l[0]
this.y = coords.l[1]
console.log(this.x,this.y)
}else {
this.x = coords.g[0]
this.y = coords.g[1]
console.log(this.x,this.y)
}
//If there is more than 1, find the nearest prey and move towards it
}else {
for(var i = 0; i < this.nearbyPrey.length; i++){
this.tempDis = getDis(this.x,this.y,this.nearbyPrey[i].x,this.nearbyPrey[i].y)
if(typeof this.nearestPrey == 'undefined'){
this.nearestPrey = {
prey:this.nearbyPrey[i],
distance:this.tempDis
}
this.tempDis = undefined
}else {
if(this.tempDis > this.nearestPrey.distance){
this.nearestPrey = {
prey:this.nearbyPrey[i],
distance:this.tempDis
}
}
}
}
var slope = findSlope(this.nearestPrey.x,this.nearestPrey.y,this.x,this.y)
var coords = JSON.parse(findIntersections(this.nearestPrey.x,this.nearestPrey.y,slope,1));
var ldis = findDis(this.nearestPrey.x,this.nearestPrey.y,coords.l[0],coords.l[1]);
var gdis = findDis(this.nearestPrey.x,this.nearestPrey.y,coords.g[0],coords.g[1]);
if(ldis > gdis){
this.x = coords.l[0]
this.y = coords.l[1]
}else {
this.x = coords.g[0]
this.y = coords.g[1]
}
}
}
wolf.prototype.bite = function(prey){
prey.hp -= this.strength;
this.hunger += 40 * %
console.log(this.hunger);
console.log(this.nearestPrey.hp)
}
new wolf(100,100)
new deer(80,80);
var init = function(){
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5
window.setInterval(draw,1000);
}
var draw = function(){
ctx.clearRect(0,0,canvas.width,canvas.height)
wolves[0].move();
for(var i=0;i<wolves.length;i++){
ctx.beginPath();
ctx.arc(deers[0].x,deers[0].y,10,0,2*Math.PI);
ctx.stroke();
ctx.beginPath()
ctx.arc(wolves[i].x,wolves[i].y,10,0,2*Math.PI);
ctx.stroke();
}
}
init()
</script>
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;
}