In my world I build walls that are actually cubes but with one side made very small (so it’s no longer a cube but it looks like a wall/panel). One and only one face of the cube gets an image assigned to it, loaded via aTHREE.TextureLoader().load()
call using a URL to a JPG file.
I have added code so that when I press the “C” key the surface with an image texture assigned to it is updated with a new image. However, when I execute the code everything works fine (no errors), but I don’t see the new image in the world. I still see the old one. I tried setting the needsUpdate
property on the new material to true but that doesn’t help.
What is the most likely cause of this problem and how do I fix it?
Here is the code that builds and assigns the texture. I know it works because it is the same call that is used to build the original images and they all display fine. I just can’t seem to update an image (texture).
/**
* 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;
}
/**
* Assign the given color value or image URL to the desired
* surface. Create either a THREE.COLOR() or THREE.MeshBasicMaterial()
* object as needed.
*
* @param {String} surfaceName - The name of the surface to assign the asset to.
* @param {String|Number} numberOrStringVal - Either a color value in numeric
* form, or in string form, or a URL.
*
* @return {string|null} - Returns the ID of the previous materials object
* found in our collection that was disposed of or NULL if there
* was no previous materials object. This can be used to
* seek out other occurrences of the now defunct materials object
* like when updating the aryCubeMaterialsExt property found in
* a PictureDisplay object.
*/
this.assignAsset = function(surfaceName, numberOrStringVal) {
const methodName = self.constructor.name + '::' + `assignAsset`;
let idOfPreviousSurfaceObj = null;
if (!THREEJSSUPPORT.isValidSurfaceName(surfaceName))
throw new Error(errPrefix + `The surfaceName parameter is not a valid surface name.`);
if (misc_shared_lib.isEmptySafeString(numberOrStringVal))
throw new Error(errPrefix + `The newAsset parameter is empty.`);
// If there is an existing asset, then remove it.
if (self[surfaceName] && self[surfaceName].materialContent !== null) {
// Is it a ThreeJS object or any other object that has a dispose method?
if (typeof self[surfaceName].materialContent.dispose !== undefined) {
idOfPreviousSurfaceObj = self[surfaceName].id;
console.info('dispose', `Disposing of current asset for picture(${self.id}) for surface name: ${surfaceName}`);
// Yes. Call its dispose method.
self[surfaceName].materialContent.dispose();
}
}
console.warn('dispose', `Disposing of assets is disabled!`);
// Assign the new asset to the surface.
self[surfaceName] = new ExtendedMaterial();
self[surfaceName].surfaceName = surfaceName;
// Is it a URL?
if (isStringAUrl(numberOrStringVal)) {
try {
// Yes, it is a URL. Create a new material from the URL
// and assign it to the surface.
console.info(`${errPrefix} - Creating a new THREE.MeshBasicMaterial() object from the URL: ${numberOrStringVal}.`);
let theMaterial = null;
// Is it a GIF URL?
console.info(`[Booth: IMAGE LOAD ----->>>>> ${numberOrStringVal}`);
if (numberOrStringVal.toLowerCase().endsWith('.gif'))
// Yes, use the GIF loader.
theMaterial = createMaterialFromGif(numberOrStringVal);
else
// No, use the regular image loader.
theMaterial = createMaterialFromImage(numberOrStringVal);
if (!theMaterial)
throw new Error(errPrefix + `Unable to find a suitable image loader for URL: ${numberOrStringVal}.`);
self[surfaceName].materialContent = theMaterial;
self[surfaceName].isImage = true;
// Set the needsUpdate flag to TRUE.
self[surfaceName].materialContent.needsUpdate = true;
} catch (err) {
// Assume the URL is invalid. Just assign the default
// color to the surface.
self[surfaceName].materialContent = new THREE.MeshBasicMaterial( {color: new THREE.Color(DEFAULT_COLOR_FOR_CUBE_SURFACES) } );
}
}
else {
// No, it is not a URL. Assume it is a color value
// and assign a color to the surface.
// Is it a number or a string?
if (typeof numberOrStringVal === 'number' || typeof numberOrStringVal === 'string') {
// Yes. Assume it is a color value.
self[surfaceName].materialContent = new THREE.MeshBasicMaterial( {color: new THREE.Color(numberOrStringVal) } );
}
else {
// No, it is not a number or a string. For now that
// is an error.
throw new Error(errPrefix + `The new asset value is not a number or a string.`);
}
}
return idOfPreviousSurfaceObj;
};