I use this code to setup a texture atlas animation:
PIXI.loader
.add('out2', 'assets/out2.png')
.load(function (loader, resources){
onRotationsLoaded(loader, resources)
});
function onRotationsLoaded(loader, resources) {
first = new PIXI.extras.AnimatedSprite(setupFrames(resources["out2"].texture.baseTexture));
app.renderer.plugins.prepare.upload(first, function(){
console.log("loaded first");
// ready to go
});
}
function setupFrames(name) {
var frames = [];
array is an array that stores correct position for each frame of animation
for (var i = 0; i < array.length; i++) {
var rect = new PIXI.Rectangle(array[i].frame.x, array[i].frame.y, array[i].frame.w, array[i].frame.h);
frames.push(new PIXI.Texture(name, rect));
}
return frames;
}
I would like to change the texture of the AnimatedSprite first in a click event or something. The new texture needs to be fetched from the server(I do not want to load it at start, because there are too many of them). I could destroy first and create second AnimatedSprite, but is there a way to just change it's texture atlas image?
I'd say simply replacing AnimatedSprite._textures would work.
first.textures = setupFrames('secondOne');
If the new texture has different frame counts from the previous one, you might like to call AnimatedSprite.prototype.gotoAndPlay(frame) right after replacing texture to reset the current frame.
Related
I'm trying to write a program using Javascript and the p5.js library to trigger a random image from an array whenever a peak in an audio file is detected. p5's sound library can detect the audio peak for me and then trigger a function upon that audio peak. However, I don't have much experience in Javascript so I'm not sure where to go from here. I've created an array of images and am planning on creating a function using math.Random to grab one of these images. Can I then call that function within my triggerBeat function?
Also, I've set the image as the background so that it's not within p5's draw function, so I'm trying to change the bg variable. I've preloaded the background image, and I've also got code within the preload function to allow the user to upload an audio file.
Sorry if this doesn't make a ton of sense. I'm pretty new to Javascript and I've spent most of today trying to wrap my head around it.
EDIT: updated code
var cnv, song, fft, peakDetect, img, bg;
var imageset = new Array("1.png","2.png","3.png");
function preload(){
img = loadImage("1.png");
var loader = document.querySelector(".loader");
document.getElementById("audiofile").onchange = function(event) {
if(event.target.files[0]) {
if(typeof song != "undefined") {
song.disconnect();
song.stop();
}
song = loadSound(URL.createObjectURL(event.target.files[0]));
loader.classList.add("loading");
}
}
}
function setup() {
cnv = createCanvas(900,900);
drawImage(imageset[0]);
fft = new p5.FFT();
peakDetect = new p5.PeakDetect();
setupSound();
peakDetect.onPeak(drawImage(imageset));
}
function draw() {
drawImage();
}
function drawImage(arr) {
var bg = loadImage(random(arr));
background(bg);
fill(0);
text('play', width/2, height/2);
fft.analyze();
peakDetect.update(fft);
}
function setupSound() {
cnv.mouseClicked( function() {
if (song.isPlaying() ) {
song.stop();
} else {
song.play();
}
});
}
p5 has math functions, one of which is random.
If one argument is given and it is an array, returns a random element from that array.
EDIT
As the result was more messy after answering the initial question, I updated the whole code.
var cnv, song, fft, peakDetect, img, bg;
var imageset = new Array("pic1.png","pic2.png","pic3.png", "pic4.png");
var imagesArr = [];
//next line will make p5 global. Otherwise would the p5 functions be
//accessable from p5 struct functions only.
new p5();
/*******************************************************************
* PRELOAD
* we are using for loading images/audios only
********************************************************************/
function preload(){
//load all images from 'imageset' into 'imagesArr'
for(var i=0; i<imageset.length; i++){
loadImage('../img/'+imageset[i], function(img) {
imagesArr.push(img);
});
}
// next lets load soundfile(s).
//song = loadSound("../snd/test.mp3");
// I used testfile, didn't touch nor tested your code here,
// BUT, again:
// you should only (pre)load you sounds here, setting event should go
// to the setup()
var loader = document.querySelector(".loader");
document.getElementById("audiofile").onchange = function(event) {
if(event.target.files[0]) {
if(typeof song != "undefined") {
song.disconnect();
song.stop();
}
song = loadSound(URL.createObjectURL(event.target.files[0]));
loader.classList.add("loading");
}
}
}
/*******************************************************************
* SETUP
* run once, use for initialisation.
********************************************************************/
function setup() {
//create canvas, draw initial background and text
cnv = createCanvas(900,900);
drawBackground();
text('play', width/2, height/2);
//initiate fft, peakdetect. Set event (onpeak)
fft = new p5.FFT();
peakDetect = new p5.PeakDetect();
setupSound();
peakDetect.onPeak(drawBackground);
}
/*******************************************************************
* DRAW
* endless loop. Here happens all the action.
* But you cannot draw your background here, as it is done by event.
********************************************************************/
function draw(){
//fft and peakdetecting are in use.
fft.analyze();
peakDetect.update(fft);
}
function drawBackground() {
background(255);
background(random(imagesArr));
}
function setupSound() {
cnv.mouseClicked( function() {
if (song.isPlaying() ) {
song.stop();
} else {
song.play();
}
});
}
Have yourArray[Math.floor(Math.random() * yourArray.length)] to get a random img by calling it in your triggerBeat function
I'm trying to load paths from different SVG files and keep them in an array. I don't really want them in the canvas, since I'm mostly using them to create new paths from them. However, I do want from time to time to actually add them to the canvas to be rendered.
Here's a code snippet that shows my problem:
var canvas = new fabric.Canvas('c', {
backgroundColor: 'skyblue'
});
canvas.renderAll();
var orig_shapes = []; // I want to store the paths here
function loadShape(url){
fabric.loadSVGFromURL(url, function(paths, options){
let shape = paths[0];
orig_shapes.push(shape);
});
}
loadShape('path0.svg');
loadShape('path1.svg');
// Add all the loaded shapes to the canvas
console.log(orig_shapes.length);
orig_shapes.forEach(function(shape, index, arr) {
canvas.add(orig_shapes[index]);
});
canvas.renderAll();
This script is loaded at the end of the <body> element inside the web page.
I expected to see the paths rendered and a 2 in the console. Unfortunately, all I get is a blue background and a 0.
Despite this, if I check orig_shapes.length() inside the console, I do get a 2 back; so apparently the paths are eventually pushed to the array (but not when I need to). I can even add the paths to the canvas writing canvas.add(orig_shapes[i]) in the console. They are rendered with no problems.
So what's the problem? Why isn't this working as expected?
As Durga commented, loadSVGFromURL is an asynchronous function, so I needed to check first that the SVGs had been loaded. Here's a version of the script that uses Promises to make sure of this:
var canvas = new fabric.Canvas('c', {
backgroundColor: 'skyblue'
});
canvas.renderAll();
var orig_shapes = [];
function loadShape(url){
return new Promise(function(resolve, reject){
fabric.loadSVGFromURL(url, function(paths, options){
let shape = paths[0];
orig_shapes.push(shape);
resolve(shape);
});
});
}
function loadShapes(urls, shape_callback) {
let promises = [];
urls.forEach(function(url) {
promises.push(loadShape(url).then(shape_callback));
});
return Promise.all(promises);
}
function addToCanvas(object) {
canvas.add(object);
}
loadShapes(['path0.svg', 'path1.svg'])
.then(function(objs) {
objs.forEach(addToCanvas);
});
loadShape and loadShapes both allow me to set especific actions to execute when a single shape is loaded and when all shapes have been loaded, respectively.
I have a folder with the contents like the following (giving a minimal example)
ex1_1.png
ex1_2.png
ex2_1.png
page.html
and I've written the following JavaScript code. The HTML file has two canvas elements that are designed to draw ex1_1.png and ex2_1.png and each canvas element has an associated "Next" button. If the first one is clicked it erases the first canvas element and draws ex_2.png. What I want is for the Next button to cycle through all my images, going back to the start when the last image is exceeded. The following JavaScript accomplishes this, except for the part where it cycles back. When it reaches the image with source ex1_3.png (which doesn't exist in the folder), I get a crash, but on the draw() command--which tells me that for whatever reason, it's not cycling the source back to ex1_1.png before attempting to draw.
To the best of my ability to debug this, something is going wrong with the img.onerror part, or how its implemented with the global variable window.indicator. When I cycle through using the next button, the indicator shows true then false if I print the value from within the img.onerror function. But if I print from within the next() function, it never shows false. This sounds like some kind of an issue with the window.indicator keeping its value globally.
// Variable to indicate whether the most recently generated image
// was valid.
window.indicator = true;
// Give the file base-name and index as stored in the local address.
// Return the corresponding Image() object.
function initImg(name, ind) {
var img = new Image();
// The file is local, the image is always of the form
// baseName_i.png
window.indicator = true;
img.src = name + "_" + ind + ".png";
img.onerror = function() {
// Find the appropriate canvas state element, and
// update its state back to 1.
for (i = 0; i < canStates.length; i++) {
var n = canStates[i][0].getAttribute("id");
n = n.split("_")[1];
if (name == canStates[i][0].getAttribute("id")) {
canStates[i][1] = 1;
}
window.indicator = false;
}
};
return img;
}
// Give the canvas context and image objects. Draw the image to
// the context, no return value.
function draw(ctx, img) {
// Check that the image is loaded before writing. Keep
// checking every 50 milliseconds.
if (!img.complete) {
setTimeout( function() {
draw(ctx, img);
}, 50);
}
// Clear the current image and draw the new.
ctx.clearRect(0,0, 200,200);
ctx.drawImage(img, 0,0, 200,200);
}
// Give the string canvas id and string base-name, create the
// canvas object and draw the first image to the canvas.
function slideShow(canId, name) {
var can = document.getElementById(canId);
can.width = 300;
can.height = 300;
var ctx = can.getContext('2d');
var img = initImg(name, 1);
draw(ctx, img);
}
// Next button function. Give the name of the canvas, draw the
// next image or cycle to the start.
function next(button) {
var name = button.getAttribute("name");
// Find the appropriate canvas element.
for (i = 0; i < canStates.length; i++) {
var id = canStates[i][0].getAttribute("id");
id = id.split("_")[1];
if (id == name) {
// Use the states to produce an image, and update the
// states.
canStates[i][1] += 1;
var img = initImg(name, canStates[i][1]);
if (!window.indicator) {
img = initImg(name, 1);
}
// Draw to the canvas.
draw(canStates[i][0].getContext('2d'),img);
}
}
}
// Create a global variable tracking all states of "Next" buttons.
// Stored as a list, each element is a list, the left coordinate is
// a canvas and the right coordinate is its state (image index).
// Also initialize all slide shows.
// The variable r stores the canvases and states, initialized
// outside the function in order to pass-by-reference so as to act
// as a global variable.
var canStates = new Array();
window.onload = function() {
var cans = document.getElementsByTagName("canvas");
for (i=0; i < cans.length; i++) {
var c = cans[i];
var n = c.getAttribute("id").split("_")[1];
slideShow("can_"+n, n);
}
for (i = 0; i < cans.length; i++) {
canStates[i] = [cans[i],1];
}
}
I could switch strategies completely here. I've heard that PHP is a decent way to server-side look at the files in a directory, and I could use that, but I don't know how to make a PHP script execute when a browser is loaded, or how to take its results and hand them over to the JavaScript file.
I have close to 1000 (and at some point more) text sprite labels being rendered in a THREEJS scene. Creating the sprite objects is not an issue. When I go to add them to the scene it seems this is blocking and killing any other animations or user interactions that take place.
I have tried splitting the array of sprites into chunks and do a chunk at a time. Recently I have tried using setTimeout but am not having much luck. I am struggling with callbacks and setTimeout.
Here is what I have so far:
function addlabels(data){
label_array = [];
$.each(data.nodes, function (key, value) {
var position = value.pos;
var vector = new THREE.Vector3(position[0], position[1], position[2]);
value.label = makeTextSprite(key, {fontsize:32});
value.label.position.copy(vector);
label_array.push(value.label);
});
function chunk_labels(items, process, context){
var todo = items.concat();
setTimeout(function(){
do {
console.log(context, todo.shift());
scene.add(todo.shift());
} while(todo.length > 0);
if (todo.length > 0){
setTimeout(chunk_labels(todo), 25);
}else{
callback(items);
}
}, 25);
}
chunk_labels(label_array);
}
Im trying to load in jpeg images, frame by frame to create an sequence animation of jpeg images. I'm attempting to load them in a recursive loop using javascript. I need to load images in linearly to achieve progressive playback of the animation. (start playback before all frames are loaded) I get a Stack overflow at line: 0 error from IE due to the natural recursion of the function. (My real code loads in over 60+ frames)
Here is a basic example of how I'm doing this:
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg']; //real code has 60+ frames
var images = [];
var load_index = 0;
var load = function(){
var img = new Image();
img.onload = function(){
if(load_index<=paths.length){
load_index++;
load();
}else{
alert('done loading');
}
}
img.src = paths[load_index];
images.push(img);
}
It seems I can avoid this error by using a setTimeout with an interval of 1 when calling the next step of the load. This seems to let IE "breathe" before loading the next image, but decreases the speed at which the images load dramatically.
Any one know how to avoid this stack overflow error?
http://cappuccino.org/discuss/2010/03/01/internet-explorer-global-variables-and-stack-overflows/
The above link suggests that wrapping the function to remove it from the window object will help avoid stack overflow errors. But I then see strangeness with it only getting about 15 frames through the sequence and just dies.
Put simply, don't use a recursive function for this situation, there isn't any need:
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
var images = [];
var loads = [];
/// all complete function, probably should be renamed to something with a
/// unique namespace unless you are working within your own function scope.
var done = function(){
alert('all loaded');
}
var loaded = function(e,t){
/// fallbacks for old IE
e = e||Event; t = e.target||e.srcElement;
/// keep a list of the loaded images, you can delete this later if wanted
loads.push( t.src );
if ( loads.length >= paths.length ) {
done();
}
}
var load = function(){
var i, l = paths.length, img;
for( i=0; i<l; i++ ){
images.push(img = new Image());
img.onload = loaded;
img.src = paths[i];
}
}
In fact, as you are finding, the method you are using currently is quite intensive. Instead, the above version doesn't create a new function for each onload listener (saves memory) and will trigger off as many concurrent loads as your browser will allow (rather than waiting for each image load).
(the above has been manually typed and not tested, as of yet)
update
Ah, then it makes more sense as to why you are doing things this way :) In that case then your first approach using the setTimeout would probably be the best solution (you should be able to use a timeout of 0). There is still room for rearranging things to see if you can avoid that though. The following may get around the problem...
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
var images = []; /// will contain the image objects
var loads = []; /// will contain loaded paths
var buffer = []; /// temporary buffer
var done = function(){ alert('all loaded'); }
var loaded = function(e,t){
e = e||Event; t = e.target||e.srcElement; loads.push( t.src );
/// you can do your "timing/start animation" calculation here...
/// check to see if we are complete
if ( loads.length >= paths.length ) { done(); }
/// if not fire off the next image load
else { next(); }
}
var next = function(){
/// current will be the next image
var current = buffer.shift();
/// set the load going for the current image
if ( current ) { current.img.src = current.path; }
}
var load = function(){
var i, l = paths.length, img;
for( i=0; i<l; i++ ){
img = new Image();
img.onload = loaded;
/// build up a list of images and paths to load
buffer.push({ img: img, path: paths[i] });
}
/// set everything going
next();
}
If the above doesn't do it, another way of getting around the issue would be to step through your list of paths, one at a time, and append a string of image markup (that would render off-screen) to the DOM with it's own onload="next()" handler... next() would be responsible for inserting the next image. By doing this it would hand off the triggering of the load and the subsequent load event to outside of your code, and should get around stacking calls.