Kode
Kode

Reputation: 3225

Angular or Three.js not pulling image from server, but rather browser cache

Please see Update 3 at the end of this post for the most recent activity/plunker

I am using AngularJS on top of a Node/Express app. In this app, I am using three.js to display a 3D object. The texture that wraps this object changes, but the file name does not (ex. color of the texture image changes but the filename remains Texture_0.png).

The texture file is stored in Azure Blob Storage and I have CORS enabled so it is displaying properly. If I update an image and upload it to Azure Blob Storage in a fresh session, the first time I render my 3D object it shows the texture with its changes. However, anytime I upload new changes and try to re-render it is showing the browser cached image instead of the new image that is stored and referenced in the server. It takes a page refresh to see the changes, but I need this to be displayed once I click a button that pops a modal, and not a page refresh.

How do I get AngularJS to display the server version of the texture image instead of the browser cache without refreshing my page?

I have tried adding some random math to force the browser to download from the server, but it's not working. Relevant code is as follows:

Three.JS 3D Object Code:

$scope.generate3D = function () {

// 3D OBJECT - Variables
var texture0 = baseBlobURL + 'Texture_0.png?' + Math.random();
var boxDAE = baseBlobURL + 'Box.dae?' + Math.random();
var scene;
var camera;
var renderer;
var box;
var controls;
var newtexture;


// 3D OBJECT - Generate  

newtexture = THREE.ImageUtils.loadTexture(texture0);

//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();

loader.options.convertUpAxis = true;
loader.load(boxDAE, function (collada) {

box = collada.scene;

box.traverse(function (child) {

if (child instanceof THREE.SkinnedMesh) {

var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();

}
});

box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();

init();
animate();
});

function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();

renderer.setClearColor(0xdddddd);

renderer.setSize(500, 500);

// Load the box file
scene.add(box);

// Lighting
var light = new THREE.AmbientLight();
scene.add(light);

// Camera
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;

camera.lookAt(scene.position);

// Rotation Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);

controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;

controls.noZoom = false;
controls.noPan = false;

var myEl = angular.element(document.querySelector('#webGL-container'));
myEl.append(renderer.domElement);

}

function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);               
}
}

Angular Function to call 3D Object and Pop in Modal:

$scope.boxPreview = function () {
$scope.generate3D();
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: 'lg'
}

Modal embedded as a script tag (I need this in the view since the image URLs are unique and the code to run my view needs these variables as well as the 3D object code):

<script type="text/ng-template" id="myModalContent.html">
    <div class="modal-header">
        <h3 class="modal-title">Box Preview</h3>
    </div>
    <div class="modal-body">

        <div id="webGL-container" width="500px">
        </div>

    </div>
    <div class="modal-footer">
        <button class="btn btn-primary" data-ng-click="ok()">Close</button>
    </div>
</script>

Update 1:

When viewing the network tab in Chrome, I can see that it is pulling down the correct image, but just not showing it in the Modal. I tried disabling the cache, but this had no effect. It is like three.js is holding the image somewhere... but I just don't know how to clear it.

Update 2:

Using advice from Mr.doob listed in this post, I am able to get it to reload without a page refresh once before I get the following error:

RangeError: Maximum call stack size exceeded

When I add the following code to where I instantiate my loader:

THREE.ImageLoader.prototype._load = THREE.ImageLoader.prototype.load;
THREE.ImageLoader.prototype.load = function (url, onLoad, onProgress, onError) {
this._load(url + '?' + Math.random(), onLoad, onProgress, onError);
};

//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();

Here is the sequence of events:

1) On first click to show the 3D box in a new session, the change displays

2) Change the texture and upload it, then click to the show the 3D box in the same session, it shows the changes. Previously, it would not show until the page was refreshed.

3) Change the texture and upload it, then click to the show the 3D box in the same session. RangeError: Maximum call stack size exceeded

To be clear, I am opening this in a Modal. and have seen issues with Modals generating these errors searching around, except I keep only one controller call at the routing level.

UPDATE 3:

I have put together the following plunk:

http://plnkr.co/edit/DGZA9LUBZWsLWrmXaaKD

It is using my dropbox public folder for getting the images. I get the same issue, where if I overwrite the Texture file, it is not updated when I select box preview unless I refresh the page. I need this to refresh the box texture on click. A few critical notes:

1) I console logged the term "loaded" to show that this continues to render even after the Modal is closed. I am not sure if I need to continuously render/animate since I just need to spin the box around using the OrbitControls. Please let me know if there is a better way or if this is causing the caching issue/we need to destroy the scene/recreate it for the box to update

2) Using the code recommended by Mr.Doob, I can update the image on the server and have it re-render properly once before it gives me an error of:

Uncaught RangeError: Maximum call stack size exceeded

Here is the code:

THREE.ImageLoader.prototype._load = THREE.ImageLoader.prototype.load;
            THREE.ImageLoader.prototype.load = function (url, onLoad, onProgress, onError) {
               this._load(url + '?' + Math.random(), onLoad, onProgress, onError);
           };

I have left this code in place, since it achieved what I desired at least once before crashing.

3) Since we are using a public dropbox I am not sure if those assisting me will be able to overwrite the Texture_0.png file with changes (I opened it in paint and just dabbled some stuff on it and re-uploaded to dropbox), but you could change the URLs to your own dropbox for testing since you would have the ability to upload and see that that box texture does not refresh unless you reload the Web page (plunker).

Upvotes: 0

Views: 1404

Answers (2)

Kode
Kode

Reputation: 3225

@weslangley answer is technically correct for most of the users of three.js since they use materials. In my very unique case, I have a distinct Collada file where I had to edit the ImageLoader.js file in order to get it to return an image file with the same filename (but different image content) on the fly from the server. I don't recommend touching the source code, but in my case I added the following line under the load function withing the ImageLoader.js:

url = url + '?' + Math.random();

For me this worked, but again, for the 99.9% other users of three.js this would likely not apply.

Upvotes: 0

WestLangley
WestLangley

Reputation: 104833

If you are editing the image file behind-the-back of three.js and not changing the filename, and you want to refresh the material texture, you can use a pattern like this one:

mesh.material.map.dispose(); // unrelated, but important!
mesh.material.map = THREE.ImageUtils.loadTexture( "filename.jpg" + "?" + Math.random() );

If there is no animation loop, you need to force a re-render, like so:

mesh.material.map.dispose(); // unrelated, but important!
mesh.material.map = THREE.ImageUtils.loadTexture( "filename.jpg" + "?" + Math.random(), undefined, render );

three.js r.72

Upvotes: 2

Related Questions