Implement 360 degree rotation audio using Web Audio API - javascript

What I'm currently trying to implement is 360 degree rotation audio (I don't know the exact term for it--maybe 8d audio?) using PannerNode.
As far as I think, what I need to do is just rotate the position of PannerNode around y axis, with AudioListener being at (0, 0, 0).
But the result sounds like the audio is not changed at all. The below is my code.
const $fileInput = document.createElement('input');
$fileInput.setAttribute('type', 'file');
document.body.appendChild($fileInput);
const $audio = document.createElement('audio');
$audio.setAttribute('controls', true);
document.body.appendChild($audio);
$fileInput.addEventListener('change', async (e) => {
const file = $fileInput.files[0];
const arrayBuffered = await file.arrayBuffer();
const actx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: 'interactive', sampleRate: 44100 });
const decoded = await actx.decodeAudioData(arrayBuffered);
const oactx = new OfflineAudioContext({ numberOfChannels: 2, length: decoded.length, sampleRate: actx.sampleRate });
const absn = new AudioBufferSourceNode(oactx, { buffer: decoded });
const pn = new PannerNode(oactx, {
panningModel: 'equalpower',
distanceModel: 'inverse',
positionX: 0,
positionY: 0,
positionZ: 0,
orientationX: 1,
orientationY: 0,
orientationZ: 0,
refDistance: 1,
maxDistance: 10000,
rolloffFactor: 1,
coneInnerAngle: 360,
coneOuterAngle: 360,
coneOuterGain: 0
});
oactx.listener.positionX.value = 0;
oactx.listener.positionY.value = 0;
oactx.listener.positionZ.value = 0;
oactx.listener.forwardX.value = 0;
oactx.listener.forwardY.value = 0;
oactx.listener.forwardZ.value = -1;
oactx.listener.upX.value = 0;
oactx.listener.upY.value = 1;
oactx.listener.upZ.value = 0;
// rotation
for (let t = 0; t < decoded.duration; t++) {
const rad = t * Math.PI / 180;
const x = pn.positionX.value * Math.cos(rad) - pn.positionZ.value * Math.sin(rad);
const z = pn.positionX.value * Math.sin(rad) + pn.positionZ.value * Math.cos(rad);
pn.positionX.setValueAtTime(x, t);
pn.positionZ.setValueAtTime(z, t);
}
absn.connect(pn);
pn.connect(oactx.destination);
absn.start();
const resultBuffer = await oactx.startRendering();
const test = new AudioBufferSourceNode(actx, { buffer: resultBuffer });
test.connect(actx.destination);
test.start();
});

// from
PannerNode.positionX: 0
// to
PannerNode.positionX: 1
// from
for (let t = 0; t < decoded.duration; t++) {
const rad = t * Math.PI / 180;
}
// to
for (let t = 0; t < decoded.duration; t += 0.01) {
const rad = 100 * t * Math.PI / 180;
}
I set PannerNode.positionX to 1 because in order for PannerNode to rotate around AudioListener, PannerNode needs to be have some distance from AudioListener.
I change for statement because I want a fast, smoothly changing effect.

Related

How to make background animation wave with JavaScript?

I want to make a background animation wave like this website hero section. https://raze.network/
Could anyone help me to make this animation background wave?
Thank you :)
There is a great example on codepen that you can use to get a general idea on how to animate this wave using JavaScript. The author allows commenting on CodePen so I would get in touch with him.
let noise = new SimplexNoise();
function draw(e) {
let xCount = 35;
let yCount = 80;
let iXCount = 1 / (xCount - 1);
let iYCount = 1 / (yCount - 1);
let time = e * 0.001;
let timeStep = 0.01;
let grad = ctx.createLinearGradient(-width, 0, width, height);
let t = time % 1;
let tSide = floor(time % 2) === 0;
let hueA = tSide ? 340 : 210;
let hueB = !tSide ? 340 : 210;
let colorA = hsl(hueA, 100, 50);
let colorB = hsl(hueB, 100, 50);
grad.addColorStop(map(t, 0, 1, THIRD, ZERO), colorA);
grad.addColorStop(map(t, 0, 1, TWO_THIRDS, THIRD), colorB);
grad.addColorStop(map(t, 0, 1, ONE, TWO_THIRDS), colorA);
ctx.globalAlpha = map(cos(time), -1, 1, 0.15, 0.3);
background(grad);
ctx.globalAlpha = 1;
beginPath();
for(let j = 0; j < yCount; j++) {
let tj = j * iYCount;
let c = cos(tj * TAU + time) * 0.1;
for(let i = 0; i < xCount; i++) {
let t = i * iXCount;
let n = noise.noise3D(t, time, c);
let y = n * height_half;
let x = t * (width + 20) - width_half - 10;
(i ? lineTo : moveTo)(x, y);
}
time += timeStep;
}
compositeOperation(compOper.lighter);
ctx.filter = 'blur(10px)';
stroke(grad, 5);
ctx.filter = 'blur(5px)';
stroke(hsl(0, 0, 100, 0.8), 2);
}
https://codepen.io/Alca/pen/gzxXLq

Problems integrating P5 blob maker with React-P5 wrapper and as a graphics layer

I am trying to integrate this P5 Blob Maker into one of my React components. It's rendered as a graphics layer on top of an image loaded from a file reader. The P5 canvas size is set to the same size as any image loads. I had to substitute TWO_PI with Math.PI*2, HALF_PI with Math.PI/2, and all of the other math operators from P5 like random(), cos() and sin() with a Math. in front. I think the things listed above should not be causing problems, however, my blob shape is not very blobby and remains stuck at the corner of the canvas. I also added a transparency to the shape. I see it kind of working as the graphics layer is cleared when the blob is being re-built, but then it becomes opaque again.
Please help me review my code:
import React from 'react';
import Sketch from 'react-p5';
import ColorSelector from './ColorSelector';
import FileInput from '../FileInput';
var b;
var img;
var pg;
var imgWidth;
var imgHeight;
var angleSlider;
let distanceSlider;
var blobPoints = [];
let numPoints = 6; // try different values for different shaped blobs
let baseRadius = 100;
let radiusRandomness = 0.2; // amount of random variation in the blob radius
let cpOffsetAngle;
let cpdist;
var transparency;
export default function P5Mold() {
const [color, setColor] = React.useState(['#ff0000']);
const [image, setImage] = React.useState(null);
const setup = (p5, canvasParentRef) => {
p5.createCanvas(400, 400).parent(canvasParentRef);
pg = p5.createGraphics(600, 600);
img = p5.loadImage(image, img => {
p5.image(img, 0, 0);
});
angleSlider = p5.createSlider(0, 2.4, 2, 0.05);
angleSlider.position(200, 250);
angleSlider.changed(buildBlob);
distanceSlider = p5.createSlider(10, 150, 50, 5);
distanceSlider.position(200, 300);
distanceSlider.changed(buildBlob);
buildBlob();
}
const draw = p5 => {
if (image) {
imgWidth = img.width;
imgHeight = img.height;
if (imgWidth > 0 && imgHeight > 0) {
p5.resizeCanvas(imgWidth, imgHeight);
}
p5.image(img, 0, 0);
transparency = p5.color(color);
transparency.setAlpha(10);
pg.fill(transparency);
pg.stroke(transparency);
pg.beginShape();
pg.vertex();
for (b = 1; b < blobPoints.length; b++) {
let bp = blobPoints[b];
let pp = blobPoints[b - 1];
pg.bezierVertex(pp.cp[1].x, pp.cp[1].y, bp.cp[0].x, bp.cp[0].y, bp.x, bp.y);
let lastp = blobPoints[blobPoints.length - 1];
let firstp = blobPoints[0]
pg.bezierVertex(lastp.cp[1].x, lastp.cp[1].y, firstp.cp[0].x, firstp.cp[0].y, firstp.x, firstp.y);
pg.endShape();
}
p5.image(pg, 0, 0, imgWidth, imgHeight);
} else {
return null
}
}
function buildBlob(p5) {
pg.clear();
blobPoints = [];
cpOffsetAngle = angleSlider.value();
cpdist = distanceSlider.value();
for (let p = 0; p < numPoints; p++) {
let a = p * (Math.PI * 2) / numPoints;
let r = baseRadius + Math.random(-radiusRandomness * baseRadius, radiusRandomness * baseRadius);
let bp = {
x: Math.cos(a) * r,
y: Math.sin(a) * r,
angle: a,
cp: []
};
blobPoints.push(bp);
}
for (let b = 0; b < blobPoints.length; b++) {
let thisp = blobPoints[b];
let randomangle = Math.random(-cpOffsetAngle, cpOffsetAngle);
let cp1angle = thisp.angle - ((Math.PI / 2) + randomangle);
let cp2angle = thisp.angle + ((Math.PI / 2) - randomangle);
let cp1 = {
x: thisp.x + (Math.cos(cp1angle) * cpdist),
y: thisp.y + (Math.sin(cp1angle) * cpdist)
};
let cp2 = {
x: thisp.x + (Math.cos(cp2angle) * cpdist),
y: thisp.y + (Math.sin(cp2angle) * cpdist)
};
thisp.cp = [cp1, cp2];
}
}
if (image) {
return (
<div>
{image && (
<div>
<Sketch setup={setup} draw={draw} />
</div>
)}
<ColorSelector selectColor={color => setColor(color)} />
</div>
)
} else {
return (
<FileInput selectImage={setImage} />
);
}
}
Math.random() works differently than the p5.js random(min, max) function. The built-in JavaScript Math.random() function does not take any parameters and always returns a number between 0 and 1. In order to use Math.random() in place of the p5.js random(min, max) function you would want to use an expression like this:
min + Math.random() * (max - min)
However, in every case where you've switched from using p5.js functions and variables to using the built in JavaScript alternatives you could have just used the p5 object (i.e. p5.sin(), p5.PI, p5.random() etc).

Runtime differences between two machines in TF.js

I was working on a small scale project for Fashion MNIST. I have used the below code. I first tried executing the code on my primary machine and received an unchanging loss of ~2, after which I tried running the same code on my secondary machine and I could see that my loss and accuracy metrics were performing just the way they should have.
Here is my index.html
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#latest"></script>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs-vis"></script>
</head>
<body>
<h1>Fashion Classifier!</h1>
<canvas id="canvas" width="280" height="280" style="position:absolute;top:100;left:100;border:8px solid;"></canvas>
<img id="canvasimg" style="position:absolute;top:10%;left:52%;width=280;height=280;display:none;">
<input type="button" value="classify" id="sb" size="48" style="position:absolute;top:400;left:100;">
<input type="button" value="clear" id="cb" size="23" style="position:absolute;top:400;left:180;">
<script src="fashion-data.js" type="module"></script>
<script src="fashion-script_exercise.js" type="module"></script>
</body>
</html>
A JS code to get the data
const IMAGE_SIZE = 784;
const NUM_CLASSES = 10;
const NUM_DATASET_ELEMENTS = 70000;
const TRAIN_TEST_RATIO = 1 / 7;
const NUM_TRAIN_ELEMENTS = Math.floor(TRAIN_TEST_RATIO * NUM_DATASET_ELEMENTS);
const NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS;
const MNIST_IMAGES_SPRITE_PATH =
'https://storage.googleapis.com/learnjs-data/model-builder/fashion_mnist_images.png';
const MNIST_LABELS_PATH =
'https://storage.googleapis.com/learnjs-data/model-builder/fashion_mnist_labels_uint8';
export class FMnistData {
constructor() {
this.shuffledTrainIndex = 0;
this.shuffledTestIndex = 0;
}
async load() {
// Make a request for the MNIST sprited image.
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgRequest = new Promise((resolve, reject) => {
img.crossOrigin = '';
img.onload = () => {
img.width = img.naturalWidth;
img.height = img.naturalHeight;
const datasetBytesBuffer =
new ArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
const chunkSize = 5000;
canvas.width = img.width;
canvas.height = chunkSize;
for (let i = 0; i < NUM_DATASET_ELEMENTS / chunkSize; i++) {
const datasetBytesView = new Float32Array(
datasetBytesBuffer, i * IMAGE_SIZE * chunkSize * 4,
IMAGE_SIZE * chunkSize);
ctx.drawImage(
img, 0, i * chunkSize, img.width, chunkSize, 0, 0, img.width,
chunkSize);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let j = 0; j < imageData.data.length / 4; j++) {
// All channels hold an equal value since the image is grayscale, so
// just read the red channel.
datasetBytesView[j] = imageData.data[j * 4] / 255;
}
}
this.datasetImages = new Float32Array(datasetBytesBuffer);
resolve();
};
img.src = MNIST_IMAGES_SPRITE_PATH;
});
const labelsRequest = fetch(MNIST_LABELS_PATH);
const [imgResponse, labelsResponse] =
await Promise.all([imgRequest, labelsRequest]);
this.datasetLabels = new Uint8Array(await labelsResponse.arrayBuffer());
this.trainIndices = tf.util.createShuffledIndices(NUM_TRAIN_ELEMENTS);
this.testIndices = tf.util.createShuffledIndices(NUM_TEST_ELEMENTS);
this.trainImages =
this.datasetImages.slice(0, IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
this.testImages = this.datasetImages.slice(IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
this.trainLabels =
this.datasetLabels.slice(0, NUM_CLASSES * NUM_TRAIN_ELEMENTS);
this.testLabels =
this.datasetLabels.slice(NUM_CLASSES * NUM_TRAIN_ELEMENTS);
}
nextTrainBatch(batchSize) {
return this.nextBatch(
batchSize, [this.trainImages, this.trainLabels], () => {
this.shuffledTrainIndex =
(this.shuffledTrainIndex + 1) % this.trainIndices.length;
return this.trainIndices[this.shuffledTrainIndex];
});
}
nextTestBatch(batchSize) {
return this.nextBatch(batchSize, [this.testImages, this.testLabels], () => {
this.shuffledTestIndex =
(this.shuffledTestIndex + 1) % this.testIndices.length;
return this.testIndices[this.shuffledTestIndex];
});
}
nextBatch(batchSize, data, index) {
const batchImagesArray = new Float32Array(batchSize * IMAGE_SIZE);
const batchLabelsArray = new Uint8Array(batchSize * NUM_CLASSES);
for (let i = 0; i < batchSize; i++) {
const idx = index();
const image =
data[0].slice(idx * IMAGE_SIZE, idx * IMAGE_SIZE + IMAGE_SIZE);
batchImagesArray.set(image, i * IMAGE_SIZE);
const label =
data[1].slice(idx * NUM_CLASSES, idx * NUM_CLASSES + NUM_CLASSES);
batchLabelsArray.set(label, i * NUM_CLASSES);
}
const xs = tf.tensor2d(batchImagesArray, [batchSize, IMAGE_SIZE]);
const labels = tf.tensor2d(batchLabelsArray, [batchSize, NUM_CLASSES]);
return {xs, labels};
}
}
The implementation JS file
import {FMnistData} from './fashion-data.js';
var canvas, ctx, saveButton, clearButton;
var pos = {x:0, y:0};
var rawImage;
var model;
function getModel() {
model = tf.sequential();
model.add(tf.layers.conv2d({inputShape: [28, 28, 1], kernelSize: 3, filters: 8, activation: 'relu'}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
model.add(tf.layers.conv2d({filters: 16, kernelSize: 3, activation: 'relu'}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
model.add(tf.layers.flatten());
model.add(tf.layers.dense({units: 128, activation: 'relu'}));
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
model.compile({optimizer: tf.train.adam(), loss: 'categoricalCrossentropy', metrics: ['accuracy']});
return model;
}
async function train(model, data) {
const metrics = ['loss', 'val_loss', 'acc', 'val_acc'];
const container = { name: 'Model Training', styles: { height: '1000px' } };
const fitCallbacks = tfvis.show.fitCallbacks(container, metrics);
const BATCH_SIZE = 512;
const TRAIN_DATA_SIZE = 6000;
const TEST_DATA_SIZE = 1000;
const [trainXs, trainYs] = tf.tidy(() => {
const d = data.nextTrainBatch(TRAIN_DATA_SIZE);
return [
d.xs.reshape([TRAIN_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
const [testXs, testYs] = tf.tidy(() => {
const d = data.nextTestBatch(TEST_DATA_SIZE);
return [
d.xs.reshape([TEST_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
return model.fit(trainXs, trainYs, {
batchSize: BATCH_SIZE,
validationData: [testXs, testYs],
epochs: 10,
shuffle: true,
callbacks: fitCallbacks
});
}
function setPosition(e){
pos.x = e.clientX-100;
pos.y = e.clientY-100;
}
function draw(e) {
if(e.buttons!=1) return;
ctx.beginPath();
ctx.lineWidth = 24;
ctx.lineCap = 'round';
ctx.strokeStyle = 'white';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
rawImage.src = canvas.toDataURL('image/png');
}
function erase() {
ctx.fillStyle = "black";
ctx.fillRect(0,0,280,280);
}
function save() {
var raw = tf.browser.fromPixels(rawImage,1);
var resized = tf.image.resizeBilinear(raw, [28,28]);
var tensor = resized.expandDims(0);
var prediction = model.predict(tensor);
var pIndex = tf.argMax(prediction, 1).dataSync();
var classNames = ["T-shirt/top", "Trouser", "Pullover",
"Dress", "Coat", "Sandal", "Shirt",
"Sneaker", "Bag", "Ankle boot"];
alert(classNames[pIndex]);
}
function init() {
canvas = document.getElementById('canvas');
rawImage = document.getElementById('canvasimg');
ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0,0,280,280);
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mousedown", setPosition);
canvas.addEventListener("mouseenter", setPosition);
saveButton = document.getElementById('sb');
saveButton.addEventListener("click", save);
clearButton = document.getElementById('cb');
clearButton.addEventListener("click", erase);
}
async function run() {
const data = new FMnistData();
await data.load();
const model = getModel();
tfvis.show.modelSummary({name: 'Model Architecture'}, model);
await train(model, data);
await model.save('downloads://my_model');
init();
alert("Training is done, try classifying your drawings!");
}
document.addEventListener('DOMContentLoaded', run);
I used the same version of Chrome and the Chrome server extension to run the code. What could possibly be the problem? Note: I have also checked the console logs and receive no errors there too.
I can see in your code no initialization of the kernel weights, so depending on the default implementation on different machines, you might have the weight initialized to 0, which makes it very difficult for the optimizer to initiate its convergence.
Try in the implementation JS file section, in the get_model function, in the layers definition, to add an option kernelInitializer: 'glorotUniform' to see if any improvement.

Use same animation for multiple elements

I have this working canvas javascript animation but i would like to use it multiple times, currently it's only possible to have one canvas element with the id "stars" and use that one. Could i perhaps add a class for them instead and get the elements class and loop or what would be my best solution for achieving this? I would like to make this work without repeating to much since i could end up using the animation on different pages.
// Settings
var particleCount = 40,
flareCount = 0,
motion = 0.05,
tilt = 0.05,
color = '#00FF7B',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 0, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.4,
renderParticles = true,
renderParticleGlare = true,
renderFlares = false,
renderLinks = false,
renderMesh = false,
flicker = false,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 3;
var canvas = document.getElementById('stars'),
context = canvas.getContext('2d'),
mouse = {
x: 0,
y: 0
},
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {
x: 0,
y: 0
},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x * c, p.y * c]);
}
vertices = Delaunay.triangulate(points);
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
var fps = 15;
var now;
var then = Date.now();
var interval = 1000 / fps;
var delta;
// Animation loop
(function animloop() {
requestAnimFrame(animloop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
resize();
render();
}
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0, 4);
this.color = color;
this.opacity = random(0.1, 1, true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background: #000;
background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0.00) 0%, #000000 100%);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<script src="http://requirejs.org/docs/release/2.1.15/minified/require.js"></script>
<canvas id="stars" width="300" height="300"></canvas>
id="identifier" is unique
class="identifier" could be shared by a list of items
As you mention using class could be an option but you'll need to change your code to select all elements by that class before:
$(".identifier").each(function(a,b)
{
// Actions for each element
}
With javascript:
var elementList = document.getElementsByClassName("identifier");
var elementListSize=elementList.length;
for(var i=0;i<elementListSize;i++) {
// Actions for each element (elementList[i])
}

Canvas problems. Not able to reproduce design

I need to build canvas animation like design requires. I spend almost 3 days but I'm not able to do anything like in design. Here a REQUESTED design!. And here - what I've got for now: current implementation which definitely not what requested from design .I need only animation of planet from particles at background (also whole process of animation changes in time, it starts from few particles but then amount growing and movings directions of particles changes)
here my current code:
export class CanvasComponent implements OnInit {
sphereRad = 280;
radius_sp = 1;
distance = 600;
particle_size = 0.7;
constructor() { }
ngOnInit() {
this.canvasApp();
}
canvasApp () {
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let displayWidth;
let displayHeight;
let wait;
let count;
let numToAddEachFrame;
let particleList;
let recycleBin;
let particleAlpha;
let r, g, b;
let fLen;
let m;
let projCenterX;
let projCenterY;
let zMax;
let turnAngle;
let turnSpeed;
let sphereCenterX, sphereCenterY, sphereCenterZ;
let particleRad;
let zeroAlphaDepth;
let randAccelX, randAccelY, randAccelZ;
let gravity;
let rgbString;
// we are defining a lot of letiables used in the screen update functions globally so that they don't have to be redefined every frame.
let p;
let outsideTest;
let nextParticle;
let sinAngle;
let cosAngle;
let rotX, rotZ;
let depthAlphaFactor;
let i;
let theta, phi;
let x0, y0, z0;
// INITIALLI
const init = () => {
wait = 1;
count = wait - 1;
numToAddEachFrame = 30;
// particle color
r = 255;
g = 255;
b = 255;
rgbString = 'rgba(' + r + ',' + g + ',' + b + ','; // partial string for color which will be completed by appending alpha value.
particleAlpha = 1; // maximum alpha
displayWidth = canvas.width;
displayHeight = canvas.height;
fLen = this.distance; // represents the distance from the viewer to z=0 depth.
// projection center coordinates sets location of origin
projCenterX = displayWidth / 2;
projCenterY = displayHeight / 2;
// we will not draw coordinates if they have too large of a z-coordinate (which means they are very close to the observer).
zMax = fLen - 2;
particleList = {};
recycleBin = {};
// random acceleration factors - causes some random motion
randAccelX = 0.1;
randAccelY = 0.1;
randAccelZ = 0.1;
gravity = -0; // try changing to a positive number (not too large, for example 0.3), or negative for floating upwards.
particleRad = this.particle_size;
sphereCenterX = 0;
sphereCenterY = 0;
sphereCenterZ = -3 - this.sphereRad;
// alpha values will lessen as particles move further back, causing depth-based darkening:
zeroAlphaDepth = 0;
turnSpeed = 2 * Math.PI / 1200; // the sphere will rotate at this speed (one complete rotation every 1600 frames).
turnAngle = 0; // initial angle
// timer = setInterval(onTimer, 10 / 24);
onTimer();
}
const onTimer = () => {
// if enough time has elapsed, we will add new particles.
count++;
if (count >= wait) {
count = 0;
for (i = 0; i < numToAddEachFrame; i++) {
theta = Math.random() * 2 * Math.PI;
phi = Math.acos(Math.random() * 2 - 1);
x0 = this.sphereRad * Math.sin(phi) * Math.cos(theta);
y0 = this.sphereRad * Math.sin(phi) * Math.sin(theta);
z0 = this.sphereRad * Math.cos(phi);
// We use the addParticle function to add a new particle. The parameters set the position and velocity components.
// Note that the velocity parameters will cause the particle to initially fly outwards away from the sphere center (after
// it becomes unstuck).
const p = addParticle(x0, sphereCenterY + y0, sphereCenterZ + z0, 0.002 * x0, 0.002 * y0, 0.002 * z0);
// we set some 'envelope' parameters which will control the evolving alpha of the particles.
p.attack = 50;
p.hold = 50;
p.decay = 100;
p.initValue = 0;
p.holdValue = particleAlpha;
p.lastValue = 0;
// the particle will be stuck in one place until this time has elapsed:
p.stuckTime = 90 + Math.random() * 20;
p.accelX = 0;
p.accelY = gravity;
p.accelZ = 0;
}
}
// update viewing angle
turnAngle = (turnAngle + turnSpeed) % (2 * Math.PI);
sinAngle = Math.sin(turnAngle);
cosAngle = Math.cos(turnAngle);
// background fill
context.fillStyle = '#000000';
context.fillRect(0, 0, displayWidth, displayHeight);
// update and draw particles
p = particleList.first;
while (p != null) {
// before list is altered record next particle
nextParticle = p.next;
// update age
p.age++;
// if the particle is past its 'stuck' time, it will begin to move.
if (p.age > p.stuckTime) {
p.velX += p.accelX + randAccelX * (Math.random() * 2 - 1);
p.velY += p.accelY + randAccelY * (Math.random() * 2 - 1);
p.velZ += p.accelZ + randAccelZ * (Math.random() * 2 - 1);
p.x += p.velX;
p.y += p.velY;
p.z += p.velZ;
}
/*
We are doing two things here to calculate display coordinates.
The whole display is being rotated around a vertical axis, so we first calculate rotated coordinates for
x and z (but the y coordinate will not change).
Then, we take the new coordinates (rotX, y, rotZ), and project these onto the 2D view plane.
*/
rotX = cosAngle * p.x + sinAngle * (p.z - sphereCenterZ);
rotZ = -sinAngle * p.x + cosAngle * (p.z - sphereCenterZ) + sphereCenterZ;
// m = this.radius_sp * fLen / (fLen - rotZ);
m = this.radius_sp;
p.projX = rotX * m + projCenterX;
p.projY = p.y * m + projCenterY;
p.projZ = rotZ * m + projCenterX;
// update alpha according to envelope parameters.
if (p.age < p.attack + p.hold + p.decay) {
if (p.age < p.attack) {
p.alpha = (p.holdValue - p.initValue) / p.attack * p.age + p.initValue;
} else if (p.age < p.attack + p.hold) {
p.alpha = p.holdValue;
} else if (p.age < p.attack + p.hold + p.decay) {
p.alpha = (p.lastValue - p.holdValue) / p.decay * (p.age - p.attack - p.hold) + p.holdValue;
}
} else {
p.dead = true;
}
// see if the particle is still within the viewable range.
if ((p.projX > displayWidth) || (p.projX < 0) || (p.projY < 0) || (p.projY > displayHeight) || (rotZ > zMax)) {
outsideTest = true;
} else {
outsideTest = false;
}
if (outsideTest || p.dead ||
(p.projX > displayWidth / (2 + (1 - Math.random())) && p.projZ + displayWidth * 0.1 > displayWidth / 2) ||
(p.projX < displayWidth / (2 - (1 - Math.random())) && p.projZ + displayWidth * 0.25 < displayWidth / 2)
) {
recycle(p);
} else {
// depth-dependent darkening
// console.log(turnAngle, rotZ)
depthAlphaFactor = 1;
// depthAlphaFactor = (1 - (1.5 + rotZ / 100));
depthAlphaFactor = (depthAlphaFactor > 1) ? 1 : ((depthAlphaFactor < 0) ? 0 : depthAlphaFactor);
context.fillStyle = rgbString + depthAlphaFactor * p.alpha + ')';
// draw
context.beginPath();
context.arc(p.projX, p.projY, m * particleRad, 0, 2 * Math.PI, false);
context.closePath();
context.fill();
}
p = nextParticle;
}
window.requestAnimationFrame(onTimer);
}
const addParticle = (x0, y0, z0, vx0, vy0, vz0) => {
let newParticle;
// const color;
// check recycle bin for available drop:
if (recycleBin.first != null) {
newParticle = recycleBin.first;
// remove from bin
if (newParticle.next != null) {
recycleBin.first = newParticle.next;
newParticle.next.prev = null;
} else {
recycleBin.first = null;
}
} else {
newParticle = {};
}
// if the recycle bin is empty, create a new particle (a new empty object):
// add to beginning of particle list
if (particleList.first == null) {
particleList.first = newParticle;
newParticle.prev = null;
newParticle.next = null;
} else {
newParticle.next = particleList.first;
particleList.first.prev = newParticle;
particleList.first = newParticle;
newParticle.prev = null;
}
// initialize
newParticle.x = x0;
newParticle.y = y0;
newParticle.z = z0;
newParticle.velX = vx0;
newParticle.velY = vy0;
newParticle.velZ = vz0;
newParticle.age = 0;
newParticle.dead = false;
if (Math.random() < 0.5) {
newParticle.right = true;
} else {
newParticle.right = false;
}
return newParticle;
}
const recycle = (p) => {
// remove from particleList
if (particleList.first === p) {
if (p.next != null) {
p.next.prev = null;
particleList.first = p.next;
} else {
particleList.first = null;
}
} else {
if (p.next == null) {
p.prev.next = null;
} else {
p.prev.next = p.next;
p.next.prev = p.prev;
}
}
// add to recycle bin
if (recycleBin.first == null) {
recycleBin.first = p;
p.prev = null;
p.next = null;
} else {
p.next = recycleBin.first;
recycleBin.first.prev = p;
recycleBin.first = p;
p.prev = null;
}
};
init();
}
}
So I will be happy with any help also REWARD(for full implementation) is possible (ETH, BTC any currency you wish).

Categories

Resources