Skip to content

Instantly share code, notes, and snippets.

@ECHibiki
Created December 23, 2021 02:23
Show Gist options
  • Save ECHibiki/5841bc95b7cc37400d514b7b9b3f92d5 to your computer and use it in GitHub Desktop.
Save ECHibiki/5841bc95b7cc37400d514b7b9b3f92d5 to your computer and use it in GitHub Desktop.
Particle Emitter
<strong>Click to trigger particles</strong><br />
<p>Stars Created : <span id="stars">0</span></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix.js"></script>
<audio crossOrigin="anonymous" id="audio" src="https://kissu.moe/static/sounds/60t3mf.mp3" controls></audio><br />
<canvas oncontextmenu="return false;" id="canvas" width="1159" height="777"></canvas><br />
<p>Audio Source: Some Tekken 7 Lucky Chloe thing<br />
Image Source: https://twitter.com/asa_410st/status/1472023559933415426</p>
<img id="test" />
const IMAGE_SOURCE =
"https://kissu.moe/static/themes/694211f92274f299466b558ab027046b.jpg";
const PARTICLE_SOURCE = "https://kissu.moe/static/themes/star.png";
const WIDTH = 1159;
const HEIGHT = 777;
const GRAVITY = -0.001;
var bloom_active = 2;
// boilerplate from https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/
// Vertex shader program
const generic_vshader = `#version 300 es
in vec4 aVertexPosition;
in vec2 aTextureCoord;
out mediump vec2 vTextureCoord;
void main() {
gl_Position = aVertexPosition;
vTextureCoord = aTextureCoord;
}
`;
const instance_vshader = `#version 300 es
in vec4 aVertexPosition;
in vec2 aTextureCoord;
uniform mediump float aVertexScaling;
uniform vec4 aVertexTranslation;
uniform mat4 aVertexRotation;
out highp vec2 vTextureCoord;
void main() {
gl_Position = aVertexRotation * aVertexPosition * vec4(aVertexScaling , aVertexScaling , 1.0 , 1.0) + aVertexTranslation;
vTextureCoord = aTextureCoord;
}
`;
const texture_shader = `#version 300 es
in mediump vec2 vTextureCoord;
uniform sampler2D uSampler;
out mediump vec4 fragColor;
void main(){
fragColor = texture(uSampler , vTextureCoord);
}
`;
function initAudio() {
var audio = document.getElementById("audio");
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source = audioCtx.createMediaElementSource(audio);
var analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
// Create a gain node
var gainNode = audioCtx.createGain();
gainNode.gain.value = 1.0;
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
source.connect(analyser);
return { audioCtx: audioCtx, analyser: analyser };
}
//
// Initialize a texture and load an image.
// When the image finished loading copy it into the texture.
//
function loadTexture(gl, url) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Because images have to be downloaded over the internet
// they might take a moment until they are ready.
// Until then put a single pixel in the texture so we can
// use it immediately. When the image has finished downloading
// we'll update the texture with the contents of the image.
const level = 0;
const internalFormat = gl.RGBA;
const width = 1;
const height = 1;
const border = 0;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
const pixel = new Uint8Array([200, 200, 255, 255]); // opaque blue
gl.texImage2D(
gl.TEXTURE_2D,
level,
internalFormat,
width,
height,
border,
srcFormat,
srcType,
pixel
);
const image = new Image();
image.crossOrigin = "Anonymous";
image.onload = function () {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(
gl.TEXTURE_2D,
level,
internalFormat,
srcFormat,
srcType,
image
);
// WebGL1 has different requirements for power of 2 images
// vs non power of 2 images so check if the image is a
// power of 2 in both dimensions.
if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
// Yes, it's a power of 2. Generate mips.
gl.generateMipmap(gl.TEXTURE_2D);
} else {
// No, it's not a power of 2. Turn off mips and set
// wrapping to clamp to edge
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
}
};
image.src = url;
return texture;
}
function isPowerOf2(value) {
return (value & (value - 1)) == 0;
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
// Send the source to the shader object
gl.shaderSource(shader, source);
// Compile the shader program
gl.compileShader(shader);
// See if it compiled successfully
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(
"An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader)
);
gl.deleteShader(shader);
return null;
}
return shader;
}
function initBuffers(gl) {
// Create a buffer for the square's positions.
const positionBuffer = gl.createBuffer();
// Select the positionBuffer as the one to apply buffer
// operations to from here out.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Now create an array of positions for the square.
const positions = [-1.0, 1.0,
1.0, 1.0,
-1.0, -1.0,
1.0, -1.0];
// Now pass the list of positions into WebGL to build the
// shape. We do this by creating a Float32Array from the
// JavaScript array, then use it to fill the current buffer.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// This array defines each face as two triangles, using the
// indices into the vertex array to specify each triangle's
// position.
const indices = [
0, 1, 2, 1, 2, 3 // front
];
// Now send the element array to GL
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(indices),
gl.STATIC_DRAW
);
const textureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
const textureCoordinates = [
// Front
0.0,
0.0,
1.0,
0.0,
0.0,
1.0,
1.0,
1.0
];
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(textureCoordinates),
gl.STATIC_DRAW
);
return {
position: positionBuffer,
textureCoord: textureCoordBuffer,
indices: indexBuffer
};
}
// init() is a reusable and minimable webgl initialization
// initBuffers and loadShader should be implemented(or copied from here)
function init() {
const canvas = document.getElementById("canvas");
// Initialize the GL context
const gl = canvas.getContext("webgl2");
// Only continue if WebGL is available and working
if (!gl) {
alert(
"Unable to initialize WebGL. Your browser or machine may not support it."
);
return;
}
console.log(gl.getParameter(gl.SHADING_LANGUAGE_VERSION));
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, generic_vshader);
const instanceVertexShader = loadShader(gl, gl.VERTEX_SHADER, instance_vshader);
const textureShader = loadShader(gl, gl.FRAGMENT_SHADER, texture_shader);
// Create the shader program
const textureProgram = gl.createProgram();
gl.attachShader(textureProgram, vertexShader);
gl.attachShader(textureProgram, textureShader);
gl.linkProgram(textureProgram);
if (!gl.getProgramParameter(textureProgram, gl.LINK_STATUS)) {
alert(
"Unable to initialize the shader textureProgram: " +
gl.getProgramInfoLog(textureProgram)
);
return null;
}
// Create the shader program
const instanceProgram = gl.createProgram();
gl.attachShader(instanceProgram, instanceVertexShader);
gl.attachShader(instanceProgram, textureShader);
gl.linkProgram(instanceProgram);
if (!gl.getProgramParameter(instanceProgram, gl.LINK_STATUS)) {
alert(
"Unable to initialize the shader instanceProgram: " +
gl.getProgramInfoLog(instanceProgram)
);
return null;
}
var background_buffers = initBuffers(gl);
var particle_buffers = initBuffers(gl);
const buffers = {
background: background_buffers,
particle: particle_buffers
};
var background_texture = loadTexture(gl, IMAGE_SOURCE);
var particle_texture = loadTexture(gl, PARTICLE_SOURCE);
const textures = {
background: background_texture,
particle: particle_texture
};
const programInfo = {
program: {
textureProgram: textureProgram,
instanceProgram: instanceProgram,
},
shaders: {
vertexShader: vertexShader,
textureShader: textureShader
},
attribLocations: {
vertexPosition: gl.getAttribLocation(textureProgram, "aVertexPosition"),
instancePosition: gl.getAttribLocation(instanceProgram, "aVertexPosition"),
textureCoord: gl.getAttribLocation(textureProgram, "aTextureCoord"),
instanceCoord: gl.getAttribLocation(instanceProgram, "aTextureCoord"),
},
uniformLocations: {
// instanceScaling: gl.getUniformLocation(instanceProgram, "aVertexScaling"),
// instanceRotation: gl.getUniformLocation(instanceProgram, "aVertexRotation"),
// instanceTranslation: gl.getUniformLocation(instanceProgram, "aVertexTranslation"),
instanceScaling: gl.getUniformLocation(instanceProgram, "aVertexScaling"),
instanceRotation: gl.getUniformLocation(instanceProgram, "aVertexRotation"),
instanceTranslation: gl.getUniformLocation(instanceProgram, "aVertexTranslation"),
sampler: gl.getUniformLocation(textureProgram, "uSampler") ,
instanceSampler: gl.getUniformLocation(instanceProgram, "uSampler") ,
}
};
// particles should be created in a +z
var flat_emitter = createParticleEmitter( { angle: 0.0 , rotation:0.0, position: vec4.fromValues( 0.0 , 1.05, 0.0 , 0.0 ) , length: 2.0 , inverse_normal:true} );
var circle_emitter = createParticleEmitter({ angle: 360.0 , rotation:0.0, position: vec4.fromValues( 0.0 , 0.0, 0.0 , 0.0 ) , length: 0.5 , inverse_normal:true} );
const emmitters = {
flat: flat_emitter,
circle: circle_emitter
}
var particle_properties = [
{
location: vec4.fromValues(0.0,0.0,0.0,0.0),// where it is now
velocity: vec4.fromValues(0.0,0.0,0.0,0.0), // how fast it is moving
expires: 100000000000000, // a value showing how many miliseconds it has left to live
rotational_velocity: 1.0, // how it's rotating
rotation: 1.0, //rotational position
scaling: 0.5, // how big it is
inverse_normal: true // reverse normals
}
];
document.getElementById("canvas").onmousedown = function (e) {
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
particle_properties = particle_properties.concat( circle_emitter.createParticles(50 ,
{
additional_position: vec4.fromValues( x / (WIDTH / 2) - 1 , 1 - y / (HEIGHT / 2), 0.0 , 0.0 ) ,
}
)
);
document.getElementById("stars").textContent = parseInt(document.getElementById("stars").textContent) + 50;
};
var audio_obj = initAudio();
//audio_obj.audioCtx
//audio_obj.analyser
setInterval(function () {
var audio_bands = audiovisualFX(audio_obj);
// from audio bands create particles
{
if(audio_bands[0] + audio_bands[1] + audio_bands[2] + audio_bands[3] + audio_bands[4] + audio_bands[5] > 2.3){
console.log(audio_bands[0] + audio_bands[1] + audio_bands[2] + audio_bands[3] + audio_bands[4] + audio_bands[5])
document.getElementById("stars").textContent = parseInt(document.getElementById("stars").textContent) + 1;
particle_properties = particle_properties.concat(flat_emitter.createParticles( 2 , {
expiration : 10000,
velocity_mod: 0,
} ) );
}
}
// limit active particles to infinity , trim oldest
moveParticles(particle_properties);
draw(gl, programInfo, buffers, textures, emmitters, audio_bands , particle_properties);
}, 16);
}
function createParticleEmitter(emitter_settings){
// must be radians
const ARC_ANGLE = emitter_settings.angle;
const ROTATION = emitter_settings.rotation;
const EMITTER_LEN = emitter_settings.length;
// half way point of segment laid flat(0 angle). not a focal point
const EMITTER_POSITION = emitter_settings.position;
// allows for emitters to fire internally into arcs or easier reversing in general
const INVERSE_NORMALS = emitter_settings.inverse_normal;
var rotationMatrix = mat4.create()
mat4.rotate(rotationMatrix, // destination matrix
rotationMatrix, // matrix to rotate
ROTATION, // amount to rotate in radians
[0, 0, 1]);
// division by 0 impossible so use a different system
var normal_vector_fn = function(){ return vec4.fromValues(0.0 , 0.0 , 0.0, 0.0);}
var emission_fn = function(){}
if (ARC_ANGLE == 0.0) {
// normal vector of line is easy, and rotate it by rotation value
normal_vector_fn = function(){
var norm = vec4.create();
mat4.multiply( norm , rotationMatrix , vec4.fromValues( 0.0 , 1.0 , 0.0 , 0.0));
vec4.scale( norm , norm , (INVERSE_NORMALS ? -1 : 1))
return norm;
}
// set the position of a particle
emission_fn = function(position_segment){
// pick a position on a centered line, then rotate it
var distance = position_segment * EMITTER_LEN - EMITTER_LEN / 2.0;
var position = vec4.create();
mat4.multiply( position , rotationMatrix , vec4.fromValues( distance , 0.0 , 0.0 , 0.0 ));
vec4.add(position , position , EMITTER_POSITION)
return position;
}
} else{
// grab the center
var radius_len = EMITTER_LEN / ARC_ANGLE;
// polar coordinates can determine cartesian coordinates easily.
// Normal vectors will be the circle radius vector. Distance from center determined by radius vector.
normal_vector_fn = function(arc_segment){
// arc segment is a 0.0 to 1.0 value representing the distace down the arc it is
// theta is bound to be a ratio of the max angle permitted. Create the arc then rotate it to be centered
var theta = arc_segment * ARC_ANGLE + Math.PI / 2.0 - ARC_ANGLE / 2.0;
var norm = vec4.create();
mat4.multiply( norm , rotationMatrix , vec4.fromValues( Math.cos(theta) , Math.sin(theta) , 0.0 , 0.0));
vec4.scale( norm , norm , (INVERSE_NORMALS ? -1 : 1))
return norm ;
}
emission_fn = function(arc_segment){
// pick a position on a centered line, then rotate it
var theta = arc_segment * ARC_ANGLE + Math.PI / 2.0 - ARC_ANGLE / 2.0;
var position = vec4.create();
mat4.multiply( position , rotationMatrix , vec4.fromValues( radius_len * Math.cos(theta) , radius_len * Math.sin(theta) , 0.0 , 0.0));
vec4.add(position , position , EMITTER_POSITION)
return position;
}
}
return {
createParticles: function(quanitity , additional_obj){
if(!additional_obj){
additional_obj = new Object();
}
var new_particles = [
// {
// location: , // where it is now
// velocity:, // how fast it is moving
// expires: , // a value showing how many miliseconds it has left to live
// rotational_velocity: , // how it's rotating
// rotation: , //rotational position
// scaling: // how big it is
// }
];
for (var p = 0 ; p < quanitity ; p++){
var arc_rand = Math.random();
var velocity_rand = Math.random() * 0.125 * (additional_obj.velocity_mod != undefined ? additional_obj.velocity_mod : 1.0);
var particle_location = vec4.create();
new_particles.push(
{
location: (additional_obj.additional_position ? vec4.add(particle_location , emission_fn(arc_rand) , additional_obj.additional_position ) : emission_fn(arc_rand)), // where it is now
velocity: vec4.multiply(normal_vector_fn(arc_rand) , normal_vector_fn(arc_rand) , vec4.fromValues(velocity_rand , velocity_rand , 0.0 , 0.0) ) , // how fast it is moving
expires: Date.now() + (additional_obj.expiration ? additional_obj.expiration : 2000), // a value showing how many miliseconds it has left to live
rotational_velocity: Math.random() * Math.PI / 20, // how it's rotating
rotation: Math.random() * 2 * Math.PI, //rotational position
scaling: Math.random() * (0.1 - 0.01) + 0.01
});
}
return new_particles;
}
}
}
function moveParticles(particle_properties){
var removal_indices = [];
var skip_count = particle_properties.length - 500000;
particle_properties.forEach(function(particle, index) {
if(particle.expires < Date.now() || index < skip_count) {
removal_indices.push(index);
return;
}
particle_properties[index].rotation += particle_properties[index].rotational_velocity;
vec4.add(particle_properties[index].location , particle_properties[index].location, particle_properties[index].velocity);
vec4.add(particle_properties[index].velocity , particle_properties[index].velocity, vec4.fromValues(0.0, GRAVITY, 0.0, 0.0));
});
removal_indices.reverse().forEach(function(indice){
particle_properties.splice(indice , 1);
});
return particle_properties;
}
function draw(gl, programInfo, buffers, textures, emmitters, audio_bands , particle_properties) {
gl.clearColor(0.75, 0.75, 0.75, 1.0); // Clear to black, fully opaque
gl.clearDepth(1.0); // Clear everything
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
// Clear the canvas before we start drawing on it.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
drawBackground(gl, programInfo, buffers.background, textures.background);
drawParticles(gl, programInfo, buffers.particle, textures.particle , particle_properties);
}
function drawBackground(gl, programInfo, buffers, texture){
// Tell WebGL to use our program when drawing
gl.useProgram(programInfo.program.textureProgram);
{
const numComponents = 2; // pull out 2 values per iteration
const type = gl.FLOAT; // the data in the buffer is 32bit floats
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set of values to the next
// 0 = use type and numComponents above
const offset = 0; // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(
programInfo.attribLocations.vertexPosition);
}
// tell webgl how to pull out the texture coordinates from buffer
{
const num = 2; // every coordinate composed of 2 values
const type = gl.FLOAT; // the data in the buffer is 32 bit float
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set to the next
const offset = 0; // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, num, type, normalize, stride, offset);
gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
}
// Tell WebGL we want to affect texture unit 0
gl.activeTexture(gl.TEXTURE0);
// Bind the texture to texture unit 0
gl.bindTexture(gl.TEXTURE_2D, texture);
// Tell the shader we bound the texture to texture unit 0
gl.uniform1i(programInfo.uniformLocations.sampler, 0);
// Tell WebGL which indices to use to index the vertices
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
{
const vertexCount = 6;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
}
function drawParticles(gl, programInfo, buffers, texture, object_physics){
// Tell WebGL to use our program when drawing
gl.useProgram(programInfo.program.instanceProgram);
// typical instantiation
{
const numComponents = 2; // pull out 2 values per iteration
const type = gl.FLOAT; // the data in the buffer is 32bit floats
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set of values to the next
// 0 = use type and numComponents above
const offset = 0; // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(
programInfo.attribLocations.instancePosition,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(
programInfo.attribLocations.instancePosition);
}
// tell webgl how to pull out the texture coordinates from buffer
{
const num = 2; // every coordinate composed of 2 values
const type = gl.FLOAT; // the data in the buffer is 32 bit float
const normalize = false; // don't normalize
const stride = 0; // how many bytes to get from one set to the next
const offset = 0; // how many bytes inside the buffer to start from
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
gl.vertexAttribPointer(programInfo.attribLocations.instanceCoord, num, type, normalize, stride, offset);
gl.enableVertexAttribArray(programInfo.attribLocations.instanceCoord);
}
// Tell WebGL we want to affect texture unit 0
gl.activeTexture(gl.TEXTURE0);
// Bind the texture to texture unit 0
gl.bindTexture(gl.TEXTURE_2D, texture);
// Tell the shader we bound the texture to texture unit 0
gl.uniform1i(programInfo.uniformLocations.instanceSampler, 0);
// Tell WebGL which indices to use to index the vertices
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
// position the instances
object_physics.forEach(function(particle_object , index) {
// scale it to a size
gl.uniform1f(
programInfo.uniformLocations.instanceScaling,
particle_object.scaling
);
// translate from the top left corner
gl.uniform4fv(
programInfo.uniformLocations.instanceTranslation,
particle_object.location
);
// rotation
const rotationMatrix = mat4.create();
mat4.rotate(rotationMatrix, // destination matrix
rotationMatrix, // matrix to rotate
particle_object.rotation, // amount to rotate in radians
[0, 0, 1]); // axis to rotate around
gl.uniformMatrix4fv(
programInfo.uniformLocations.instanceRotation,
false,
rotationMatrix
);
{
const vertexCount = 6;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset , );
}
});
// console.log(programInfo.uniformLocations.instanceScaling , programInfo.uniformLocations.instanceTranslation , programInfo.uniformLocations.instanceRotation , programInfo.attribLocations.instancePosition, programInfo.attribLocations.instanceCoord , programInfo.uniformLocations.instanceSampler);
// gl.uniform1f(
// programInfo.uniformLocations.instanceScaling,
// scales
// );
// gl.uniformMatrix4fv(
// programInfo.uniformLocations.instanceTranslation,
// false,
// translations
// );
// gl.uniformMatrix4fv(
// programInfo.uniformLocations.instanceRotation,
// false,
// rotations
// );
}
function audiovisualFX(audio_obj) {
var frequency_profile = new Uint8Array(audio_obj.analyser.frequencyBinCount);
audio_obj.analyser.getByteFrequencyData(frequency_profile);
var bands = assessBandVolumes(frequency_profile);
return bands;
}
function assessBandVolumes(frequency_profile) {
var bands = [
0, // "low low ":
0, // "low "
0, // "mid low ":
0, // "mid ":
0, // "mid high ":
0 // "high ":
];
var len = frequency_profile.length; // add in a manual frequency cuttoff since certain ranges are empty
const DIVISIONS = 170;
var empty_point = 0;
for (var fq = 0; fq < len; fq++) {
bands[fq / DIVISIONS > 5 ? 5 : Math.floor(fq / DIVISIONS)] +=
frequency_profile[fq];
}
for (var band_no = 0; band_no < 6; band_no++) {
bands[band_no] = bands[band_no] / DIVISIONS / 256;
}
return bands;
}
init();
/*
1) basic image init
2) circlar partical effect that can be unraveled into a line
3) sync to audio
*/
/*
1 ) render all vissibles to screen (background and 2 stars in different locations)
2) Design emitter for circle and band
3) On location click, emit with from random position with acceleration normal to surface and random rotation
*/
/*
Concepts:
1) scaling and translation of items
2) glDrawElementsInstanced
3) Creating an emission surface of length and curvature angles (0 is flat, 360 is circle, 180 is half)
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment