I've been having a persistent problem with my image display elements (cubes) not showing the assigned JPG/PNG on their surfaces, usually on the first load of my ThreeJS world from a browser with an empty browser cache. At first, I assumed that this was some kind of a CDN distribution problem with my server, because after you reload the world (host page) once or twice, everything displays fine. This led me to believe, incorrectly, that it was CDN issue.
However, my internet went out for a few days and then I discovered something strange. Please remember that at the time this happened I was working off a local copy of my resources so loading and display of images should have been unaffected by the outage. Instead, it was way worse. Instead of about half or so of the images beeing missing, pretty much all of the JPG/PNG based picture displays (ThreeJS cubes) were blank. (See the screenshot below).
Now my intuition on the problem has completely reversed. It seems that loading the images remotely from my server actually helped reduce the problem. My current theory is that the longer load times on the elements that are loaded before the JPG/PNG surfaces are loaded, a list of videos hosted on my server, changes the timing of that code thus delaying the execution of my animation loop until some of those JPG/PNG surfaces are "ready" (i.e. - the loader callback function has succeeded).
The problem can't be the CDN because there is no CDN involved when running in the local context. When I look in the browser debugger (Chrome DevTools) and I see a bunch of "Texture marked for update but image is undefined" errors coming from ThreeJS during the SetTexutre2D() method call (see screenshot with the call stack)..
I looked at these other posts on the this forum and Stack Overflow:
https://discourse.threejs.org/t/why-texture-marked-for-update-but-image-is-incomplete/353/2
Three.js r72 - Texture marked for update but image is undefined?
But I looked at my code that loads these images, and I am not setting needsUpdate to TRUE anywhere in it:
/**
* Given a URL to an image, build a ThreeJS texture from it.
*
* #param {String} srcUrl - A URL to an image.
* #param {Boolean} bIsRepeated - Whether or not the texture should be repeated.
* #param {Object} theTexture - A ThreeJS texture object.
*
* #return {MeshBasicMaterial} - Returns a ThreeJS MeshBasicMaterial object
* built from the image at the given URL.
*/
function createMaterialFromImage(srcUrl, bIsRepeated=false) {
const errPrefix = `(createMaterialFromImage) `;
if (misc_shared_lib.isEmptySafeString(srcUrl))
throw new Error(errPrefix + `The srcUrl parameter is empty.`);
// Make sure an attempt to load a GIF file is not made with
// this function.
if (srcUrl.toLowerCase().endsWith('.gif'))
throw new Error(errPrefix + `The srcUrl parameter is a GIF file: ${srcUrl}.`);
if (typeof bIsRepeated !== 'boolean')
throw new Error(errPrefix + `The value in the bIsRepeated parameter is not boolean.`);
const threeJsMaterial = new THREE.MeshBasicMaterial();
if (bVerbose) {
console.info(`${errPrefix}Loading image: ${srcUrl}.`);
}
const loader = new THREE.TextureLoader().load(
// resource URL
srcUrl,
// This function fires when the resource is loaded.
function ( theTexture ) {
// If the image is to be repeated, set the wrap
// properties to THREE.RepeatWrapping, otherwise
// use the default wrapping which is THREE.ClampToEdgeWrapping.
theTexture.wrapS = bIsRepeated ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
theTexture.wrapT = bIsRepeated ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
// Assign the texture value to the material map when the texture is loaded.
threeJsMaterial.map = theTexture;
if (bVerbose)
console.info(`${errPrefix}Resource LOADED: ${srcUrl}.`);
},
// This function will be called as the download of an
// image progresses.
function ( xhr ) {
if (bVerbose) {
const pctLoaded = xhr.loaded / xhr.total * 100;
console.info(`${errPrefix}${pctLoaded}}% loaded. Resource: ${srcUrl}.`);
}
},
// This function will be called in the event of an error.
function ( xhr ) {
console.error( `${errPrefix} Download failed for resource: ${srcUrl}.`);
}
);
// Return the threeJsMaterial we created the desired image.
return threeJsMaterial;
}
Why am i getting this error? Should I not add the element to the scene until the async texture loader callback function has already fired?
Also, is this actually what is causing my problem with the blank cube surfaces in my ThreeJS world, or is it something else?
BROWSER DEBUGGER SCREEN SHOT 1:
BROWSER DEBUGGER SCREEN SHOT 2 (Call Stack):
Related
I caught a bug when trying to deploy my site and the three.js canvas would not load (for some reason worked on dev but that's neither here nor there)
The problem is that I am trying to animate text that was loaded using fontLoader.load(). For example, I add text to the scene using the following:
fontLoader.load(
'node_modules/three/examples/fonts/droid/droid_serif_regular.typeface.json',
(droidFont) => {
const textGeometry = new TextGeometry('Scroll to Start', {
size: 5,
height: 1,
font: droidFont,
bevelSize: 5,
bevelThickness: 2,
});
const introTexture = new THREE.TextureLoader().load('suntexture.png');
const textMaterial = new THREE.MeshBasicMaterial({map: introTexture, transparent:true, opacity: .5});
cosnt introText = new THREE.Mesh(textGeometry, textMaterial);
introText.position.set(-5, 37, -140);
scene.add(introText);
}
);
Then, I want it to gently oscillate on the screen so as to not appear static, to do this I would include something like this in my animation function (called at the end of main.js):
function introAnimate() {
introText.position.y += (Math.sin(clock.getElapsedTime())/62.8);
introText.rotation.y += (Math.cos(clock.getElapsedTime())/700);
}
The problem with this is that the console (on dev/preview) says that introText is not defined, I'm assuming because it was declared in a function. I tried to fix this by first declaring them as var or const (didn't work), then adding globalThis. or window. (ie window.introText). But the problem persists.
To be honest, I am surprised the npm run dev version ran correctly in the first place given this reference error.
I have seen some versions of text animation using three.js flow, but I am interested in triggering certain animations on scroll, and rotating/changing other properties that I don't think flow can do. Any suggestions on how to address this would be much appreciated.
I think your problem is that you're running introAnimate() before your font finishes loading.
fontLoader.load() runs asynchronously, meaning it starts loading, and the rest of your code (introAnimate() for instance) keeps running until your font finally loads at which point it calls the callback.
Locally on in your dev environment the font might load instantly, but in production the font takes relatively longer time to download.
And until then, if introAnimate() tries to run, it'll fail because the callback to declare the window.introText was not called yet.
The solution would be to declare window.introText = null before calling fontLoader.load(), overwrite it once it's initialized, and in your introAnimate() check if introText is not null before doing anything:
function introAnimate() {
if (introText === null) return;
introText.position.y += (Math.sin(clock.getElapsedTime())/62.8);
introText.rotation.y += (Math.cos(clock.getElapsedTime())/700);
}
I have a glb model which I am loading into my Vue project via Three js. I have managed to import several other models for practice, but the one I actually want on my page will not load. I have tried playing around with different scaling, positions, background colors (since the object is mostly black), and camera angles but I am not able to get it in frame no matter what I do. I am able to see the model perfectly in any regular gltf view, but I cannot see it in my project, what am I doing wrong here?
Edit: As a side note, I also tried changing the scale in blender, but that did not change the result.
const loader = new GLTFLoader();
let me = this; // must refer to the instance in vue in order to be added to the scene, have tested this with other models
loader.load(
'pantalla_ball_2.glb',
function(gltf) {
gltf.scene.traverse(function( node ) {
if ( node.isMesh ) { node.castShadow = true; }
});
gltf.scene.scale.set(1,1,1) // Have tried several scales from 0.01 to 200
me.scene.add(gltf.scene);
console.log("added") // added is successfully called every time
},
function(xhr) {
console.log(xhr);
},
function(err) {
console.log(err); // no errors appear in console
}
);
The object loading perfectly in 3d viewer below
Thanks to JP4 for his helpful comments, the issue was the ambient lighting was not strong enough to illuminate the object sufficiently enough to be seen. The axis helpers showed me that the object was there thanks to the gaps in the axes.
So far my program is working the way I want it to. This works fine:
// Player object
var player = {
x: 10,
y: 10,
draw: function () {
ctx.drawImage(playerImg, 0, 0);
...
Should I check if playerImg is loaded first, even though it works correctly so far?
Also, what is the best way to check. I was thinking about putting all the images in an array. Then check with the onLoad function. If they are all loaded then I will start the game loop. Is this a good idea?
Thanks
How image loading works
You need to check if the image is loaded as image loading is asynchronous. You may experience that your code works sometimes without. This is mainly because your image exists in the cache and the browser is able to load it fast enough before the drawImage is called, or the image exists on local disk.
However, new users will need to download the data first and you don't want first-time users to experience errors such as images not showing because they are not finished loading.
As it works asynchronous your code will continue to execute while the image loading takes place in the background. This may cause your code to execute before the image has finished loading. So handling image loading is important
Handling multiple images
You can load all your images first (or those you need to start with) and you can define them using array:
var imageURLs = [url1, url2, url3, ...],
images = [],
count = imageURLs.length;
Then iterate and create the image elements:
for(var i = 0; i < count; i++) {
/// create a new image element
var img = new Image();
/// element is valid so we can push that to stack
images.push(img);
/// set handler and url
img.onload = onloadHandler;
img.src = imageURLs[i];
/// if image is cached IE (surprise!) may not trigger onload
if (img.complete) onloadHandler().bind(img);
}
and in the callback function do the inventory count:
function onloadHandler() {
/// optionally: "this" contains current image just loaded
count--;
if (count === 0) callbackDone();
}
Your callback is the code you want to execute next. Your images will be in the array images in the same order as the imageURLs.
For production you should also incorporate an onerror handler in case something goes wrong.
I just started my first JavaScript game project, and I am not sure what is the best way to load assets (images and sounds) in JS. The problem is I am using inheritance, so there is a problem with loading asset when needed ( example: Paddle extends Entity ):
Paddle:
function Paddle( layer )
{
Entity.call(this, layer);
}
Paddle.prototype.SetAnimations = function()
{
this.image.onload = // INITIALIZE this.sprite using this.image
this.image.src = "js/assets/paddle/blue/paddle_blue_idle.png";
};
Entity:
function Entity(layer)
{
this.SetAnimations();
layer.add( this.sprite );
}
So the problem here is, when I am using Paddle constructor, the first thing is to call Entity ( parents ) constructor. Then Entity constructor use Paddle.prototype.SetAnimations to set image source and after load, with KineticJS i am creating Sprite using this loaded Image. But before this happens, Entity tries to add this.sprite to the layer ( and the sprite isnt already initialised ).
My question is, what is the best way in JS to load assets ( before every scene/at whole game startup OR #runtime using maybe event listeners?? ). Any help would be appreciated.
That depends on the size of your game / your assets. I personally load my assets before the game / at a loading screen. I use PxLoader for loading assets. I then store the images / assets in a big array in my game and access them when I need them.
I'm using some javascript to allow users to dynamically load a sketch on click to a canvas element using:
Processing.loadSketchFromSources('canvas_id', ['sketch.pde']);
If I call Processing.loadSketchFromSources(...) a second (or third...) time, it loads a second (or third...) .pde file onto the canvas, which is what I would expect.
I'd like for the user to be able to click another link to load a different sketch, effectively unloading the previous one. Is there a method I can call (or a technique I can use) to check if Processing has another sketch running, and if so, tell it to unload it first?
Is there some sort of Processing.unloadSketch() method I'm overlooking? I could simply drop the canvas DOM object and recreate it, but that (1) seems like using a hammer when I need a needle, and (2) it results in a screen-flicker that I'd like to avoid.
I'm no JS expert, but I've done my best to look through the processing.js source to see what other functions may exist, but I'm hitting a wall. I thought perhaps I could look at Processing.Sketches.length to see if something is loaded already, but simply pop'ing it off the array doesn't seem to work (didn't think it would).
I'm using ProcessingJS 1.3.6.
In case someone else comes looking for the solution, here's what I did that worked. Note that this was placed inside a closure (not included here for brevity) -- hence the this.launch = function(), blah blah blah... YMMV.
/**
* Launches a specific sketch. Assumes files are stored in
* the ./sketches subdirectory, and your canvas is named g_sketch_canvas
* #param {String} item The name of the file (no extension)
* #param {Array} sketchlist Array of sketches to choose from
* #returns true
* #type Boolean
*/
this.launch = function (item, sketchlist) {
var cvs = document.getElementById('g_sketch_canvas'),
ctx = cvs.getContext('2d');
if ($.inArray(item, sketchlist) !== -1) {
// Unload the Processing script
if (Processing.instances.length > 0) {
// There should only be one, so no need to loop
Processing.instances[0].exit();
// If you may have more than one, then use this loop:
for (i=0; i < Processing.instances.length; (i++)) {
// Processing.instances[i].exit();
//}
}
// Clear the context
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, cvs.width, cvs.height);
// Now, load the new Processing script
Processing.loadSketchFromSources(cvs, ['sketches/' + item + '.pde']);
}
return true;
};
I'm not familiar with Processing.js, but the example code from the site has this:
var canvas = document.getElementById("canvas1");
// attaching the sketchProc function to the canvas
var p = new Processing(canvas, sketchProc);
// p.exit(); to detach it
So in your case, you'll want to keep a handle to the first instance when you create it:
var p1 = Processing.loadSketchFromSources('canvas_id', ['sketch.pde']);
When you're ready to "unload" and load a new sketch, I'm guessing (but don't know) that you'll need to clear the canvas yourself:
p1.exit();
var canvas = document.getElementById('canvas_id');
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// Or context.fillRect(...) with white, or whatever clearing it means to you
Then, from the sound of things, you're free to attach another sketch:
var p2 = Processing.loadSketchFromSources('canvas_id', ['sketch2.pde']);
Again, I'm not actually familiar with that library, but this appears straightforward from the documentation.
As of processing.js 1.4.8, Andrew's accepted answer (and the other answers I've found in here) do not seem to work anymore.
This is what worked for me:
var pjs = Processing.getInstanceById('pjs');
if (typeof pjs !== "undefined") {
pjs.exit();
}
var canvas = document.getElementById('pjs')
new Processing(canvas, scriptText);
where pjs is the id of the canvas element where the scrips is being run.