Image rendering not working with TextureLoader in three.js - javascript

Following is my method for adding an image using three.js -
rendererModule.addImage = function (primitive){
var self = this;
var textureLoader = new THREE.TextureLoader();
console.log("HERE 1");
textureLoader.load("image/myimage.jpg", function(map){
console.log("HERE 2");
map.minFilter = THREE.LinearFilter;
var mat = new THREE.SpriteMaterial({map:map, color:0xFFFFFF, fog:true});
var sprite = new THREE.Sprite(mat);
sprite.name = primitive.id;
self.setProperties(sprite,primitive);
sprite.position.z = -15;
var distance = camera.position.distanceTo(sprite.position);
var halfHeight = distance * Math.atan( camera.fov/2 * Math.PI / 180 );
sprite.scale.x = halfHeight * 2;
sprite.scale.y = halfHeight * 2;
scene.add(sprite);
self.renderView();
});
console.log("HERE 3");
}
and I am using the method like this -
workitems.forEach(function(item, index, array){
if(item['type'] === "planar-item" ) {
vedit.renderer.addPlane(item);
} else if(item['type']==="image-item"){
vedit.renderer.addImage(item);
}
});
Though my plane method is working fine but not the image one.....another surprise is that "HERE 1" & "HERE 3" is printing in the console, but not 2. Let me know what I am doing wrong here.

The second question has the easier answer: function(map) {...} is a callback for an asynchronous operation, so inline code before and after it is guaranteed to run first (the Javascript engine won't pick up async operations until after the current context finishes) and it's not necessarily the case that it will run at all. Since it's an "onload" in this case, it won't run if the resource is never loaded. You probably want to put an onError callback in there to see what's going on.
As for the first, I can't reliably answer that without an error message, but the most likely cause of course is just that the file doesn't exist (at that path). An easy way to test is to just create an <img src="image/myimage.jpg"/>, programatically or hard-coded, and see if it appears.

In according to other answer the problem is most likely the opening failure of the image; you can easly check in your browser developer tool in the network section.
The main causes of this issue can be:
Wrong path, if your image path is http://yoursite/imahe/myimage.jpg try to add slash in your load parameter
textureLoader.load("/image/myimage.jpg", ...
Privilege error, you have to check the privilege, the owner of the file and permission (644 can be fine)
Other problems can be caused bywrong virtualhost, .htaccess or other webserver configuration, but is hard to say what...
Notice that if you try lo load image from other domain (like http://lorempixel.com/image_output/nature-q-c-150-150-8.jpg) you have a cross-domain error and the load fail!

Related

Can't center an element generated from XMLHttpRequest

I am building a fairly complex web app. The main page loads and then all menu items (300+), form submissions, ect. are loaded via XMLHttpRequest. I have a basic "panel" template that allows the the panel to look and act (drag, resize, ect.) like a child window of the app. I load all XMLHttpRequest requested pages into the the content section of the "panel" template.
The problem I am running into is that if I try to center the new "panel" it does not seem to find the new "panels" size. My code is setup so that when a menu item is clicked it runs a function that calls the XMLHttpRequest function, the originating function passes to the XMLHttpRequest a callback function. The callback function then clones the panel template, so I can change several element attributes, I then append the response to the cloned "panel" template, in a document fragment. And then all that is appended to the displayed HTML, after which I find the new "panels" size and try to center it but it always fails.
As each function has a lot more going on than just what I spelled out above what follows is hopefully an accurate striped down version of the relevant parts of the code.
The XMLHttpRequest function in nothing unusual, and once it has a successful response the callback will run the "OpenPanel" function (see below).
Callback function:
function OpenPanel(e, response)
{
var rescontent = response.querySelector('.content');
var newid = rescontent.getAttribute('data-id');
var titlebar = rescontent.getAttribute('data-title');
var frag = document.createDocumentFragment();
var clonepanel = doc.getElementById('paneltemplate').cloneNode(true);
clonepanel.id = newid;
frag.appendChild(clonepanel);
frag.querySelector('.titlebar').innerHTML = titlebar;
var replacelem = frag.querySelector('.content');
replacelem.parentNode.replaceChild(rescontent, replacelem);
doc.getElementById('mainbody').appendChild(frag);
var newpanel = document.getElementById(newid);
newpanel.addEventListener('mousedown', PanelSelect, true);
newpanel.style.cssText = PanelPosition(newpanel);
}
PanelPosition function:
function PanelPosition(panel)
{
var lh = panel.clientHeight;
var lw = panel.clientWidth;
var wh = panel.parentNode.clientHeight;
var ww = panel.parentNode.clientWidth;
var paneltoppos = (wh - lh) / 2;
var panelleftpos = (ww - lw) / 2;
return 'top: ' + paneltoppos + 'px; left: ' + panelleftpos + 'px;';
}
I tried using setTimeout with a delay of 1ms, but that causes the panel to flash on the screen, in the wrong position, before its moved. Which from my perspective makes the app feel cheap or like only a second best effort was given. And even if it didn't flash setTimeout seems like a hack more than a solution.
I have tried this code with a few different "pages" (xhr requests) and I almost get the sense that the XMLHttpRequest hasn't finished loading when the callback function is ran (which I doubt is possible). For example, I put
console.log('top: '+wh+' - '+lh+'(wh - lh) left: '+ww+' - '+lw+'(ww - lw)');
in the "PanelPosition" function, and without the setTimeout the panel height (lh) and width (lw) are between 100 and 200 pixels. But with setTimeout the panels usually are over 500 pixels in height and width. And of course that severely effects where centered is.
I have tried several searches over the last few days but nothing has turned up. So if there is a good post or article describing the problem and the solution, feel free point me to it.
Should note that as I am running the web app exclusively in node-webkit/nw.js (chromium/webkit browser) there is no need for a cross-browser solution.
Well I guess I am going to answer my own question.
While looking for something completely unrelated I found this this SO post. The accepted answer gives a clue that explains the issue.
Unfortunately, it seems that you have to hand the controls back to the browser (using setTimeout() as you did) before the final dimensions can be observed; luckily, the timeout can be very short.
Basically javascript does not draw the appended element till the end of the function call. So setTimeout is one solution to my problem. But, there is a second solution. If I just have to wait till the end of the function then lets make that one heck of a small (focused) function. So I just moved all the code need to create the appended "panel" to a totally separate function. And in so doing I solved another pending issue.
Edit:
Or not. Apparently a separate function doesn't work now, but it did before I posted. Who knows maybe I didn't save the change before reloading the page.

Shared Chrome AudioContext in Persistent Background Script

var audioContext = new window.AudioContext
chrome.runtime.onMessage.addListener(
function(imageUrl, sender, sendResponse) {
if (imageUrl != "") sound(523.251, 587.330) else sound(523.251, 493.883)
})
function sound(frequency1, frequency2) {
soundDuration = 0.1
var audioGain1 = audioContext.createGain()
audioGain1.gain.value = 0.1
audioGain1.connect(audioContext.destination)
var audioGain2 = audioContext.createGain()
audioGain2.gain.value = 0.1
audioGain2.connect(audioContext.destination)
var audioOscillator1 = audioContext.createOscillator()
audioOscillator1.type = "sine"
audioOscillator1.frequency.value = frequency1
audioOscillator1.connect(audioGain1)
var audioOscillator2 = audioContext.createOscillator()
audioOscillator2.type = "sine"
audioOscillator2.frequency.value = frequency2
audioOscillator2.connect(audioGain2)
audioOscillator1.start(0); audioOscillator1.stop(soundDuration)
audioOscillator2.start(soundDuration); audioOscillator2.stop(soundDuration*2)
}
I am developing a Google Chrome extension (Version 47.0.2526.111 m). I ran into the problem were I exceed the AudioContext (AC) limit of six (6) with code running in the Web Page Content Script (CS). I rewrote the code to have the CS send a message to a persistent Background Script (BS). I defined the AudioContext in the body of the BS hoping that would only create one copy. Each time the CS sends a message to the BS, I want to play two (2) tones. I found I needed to create the GainNodes and OscillatorNodes in the BS .onMessage.addListener function in order to avoid the “one time use” behavior of these nodes.
When tested, no tones are generated. If I breakpoint the code and step through the .start() and .stop() statements, the tones are generated. If I let the code run free across the .start() and .stop() and breakpoint just after the .stop(), no tones. I suspected scope issues and tried the .createGain() and .createOscillator() creating local (var) and global (no var) variables, but that does not change the behavior.
If I put all the AC object creation in the listener function, it works fine, but I am back to running out of ACs.
The BS script code is above
I found the answer after reading a lot of web research. The issue seems to be the .start()/.stop() values being passed. I changed:
audioOscillator1.start(0); audioOscillator1.stop(soundDuration)
to
audioOscillator1.start(audioContext.currentTime + 0)
audioOscillator1.stop(audioContext.currentTime + soundDuration)
The code now works with the audiocontext in the script body (global) and does not hit the audioconext limit. The gain/oscillator nodes are still local to the onMessage function.

Audio Buffering Produces 0 TimeRanges Until Fully Loaded

I have an HTML5 audio player and I'm trying to monitor it's buffering progress until it reaches 100% so I can run it.
music = document.querySelector("#music");
progress = document.querySelector("#progress");
music.addEventListener("progress", function(){
var z = (music.buffered.end(0) / music.duration)*100;
progress.value = z;
}, false);
(Where #progress is an HTML <progress> element.)
When I attempt the above code, I get this error.
Uncaught IndexSizeError: Failed to execute 'end' on 'TimeRanges': The index provided (0) is greater than or equal to the maximum bound (0).
This error has been discussed here, but the code I am using is similar to the code used in the answer, and I am still getting the error.
Here's the weird part: I monitored music.buffered.length, and it only reached 1 when the audio was fully loaded, so for some reason I cannot monitor the buffered length of my audio element.
Here's a rather robust JS Bin to show the problem.
Has anyone else run into this problem? Am I doing something completely wrong here?
You can get buffered value in loadeddata listener:
audio.addEventListener ('loadeddata', function () {
if (typeof (this.buffered) !== 'undefined' && this.buffered.length > 0) {
audio.addEventListener ('progress', function () {
var z = (this.buffered.end (0) / this.duration) * 100;
progress.innerText = z;
});
}
});
keep in mind that it inherits this, not music!
Years down the line, I managed to determine the problem. #dandavis was correct. My server was not supplying metadata about audio files, so the browser had no clue how long an audio file was (or even how big the file was in size) until it was completely downloaded. There was no timerange because there was no information about time supplied at all.
If you're having this problem, make sure your server is supplying EVERYTHING about your audio file.
you might want to try and replace this part:
music.addEventListener("progress", function(){
var z = (music.buffered.end(0) / music.duration)*100;
progress.innerText = z;
}, false);
with this part:
if(music.onprogress){
var z = (music.buffered.end(0) / music.duration)*100;
progress.innerText = z;
};

Is it possible to define a layer which will retry to load, e.g. with exponential back-off?

I am using OpenLayers to connect to a home-grown server, and unlike professional grade servers like Google or Cloudmade that box will actually take a while to calculate the result for a specific tile. And as it is a mathematical function I am plotting, there is no big chance to accelerate the server or even pre-render the tiles.
My initial trials with Leaflet quickly came to the conclusion that Leaflet actually leaves all of the reloading and load-error handling to the browser, while OpenLayers at least has an event that is fired when the tile server does return with an error code.
The idea I am following was to basically start rendering a tile when it was requested and fire an HTTP 503 immediately, relying on the client to try again.
To try again, I implemented a simple layer like this:
var myLayer = new OpenLayers.Layer.OSM.MYLayer("mine", {
'transparent':"true",
'format':"image/png",
'isBaseLayer':false});
myLayer.events.register("tileerror", myLayer, function (param) {
// Try again:
var targetURL = param.tile.layer.getURL(param.tile.bounds);
var tile = param.tile;
tile.timeout = tile.hasOwnProperty("timeout") ? tile.timeout * 2 : 1000;
setTimeout(function (tileToLoad, url) {
if (tileToLoad.url === url) {
tileToLoad.clear();
tileToLoad.url = url;
tileToLoad.initImage();
}
}.bind(undefined, tile, targetURL), tile.timeout);
});
I figured out the code required to reload a tile from the source of OpenLayers, but maybe there is a cleaner way to accomplish this.
My problem is: The tiles themselves are reused, as are the divs in the DOM, so the reload procedure might actually try to reload a tile into a DIV that long as been successfully reused, e.g. because the user scrolled to someplace else where the server was able to provide data quickly.
The question I guess boils down to - is there an official way to use the tileerror event to simply try to reloading, or at least a simpler way in the API to trigger a reload? I spent quite a while in the source of OpenLayers itself but couldn't shed light on why it is still going wrong (the test for tileToLoad.url == url didn't really do it).
Thanks for your help!
Ok, after some more trial and error I found that I could actually add an eventListener to my Layer class, which will do what I want - try to reload the tile again after a certain wait. The trick was the consecutive call of setImgSrc() for cleanup and to draw with the true parameter, which effectively is an (undocumented) force flag. Thanks to the code!
OpenLayers.Layer.OSM.MyLayer= OpenLayers.Class(OpenLayers.Layer.OSM, {
initialize:function (name, options) {
var url = [
"xxxx"
];
options = OpenLayers.Util.extend({
"tileOptions":{
eventListeners:{
'loaderror':function (evt) {
// Later reload
window.setTimeout(function () {
console.log("Drawing ", this);
this.setImgSrc();
this.draw(true);
}.bind(this), 3000); // e.g. after 3 seconds
}
}
}
}, options);
var newArguments = [name, url, options];
OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
},
CLASS_NAME:"OpenLayers.Layer.OSM.MyLayer"
});
You should have a look at the following resources:
http://dev.openlayers.org/docs/files/OpenLayers/Util-js.html#Util.IMAGE_RELOAD_ATTEMPTS
http://dev.openlayers.org/apidocs/files/OpenLayers/Tile-js.html
http://dev.openlayers.org/docs/files/OpenLayers/Tile/Image-js.html

Titanium HTTPClient not running before results should be used

I have an odd problem that I've been beating my head into a wall over for the past few hours.
I'm working on an iPhone app in Appcelerator Titanium, and it currently wants to wait until other code has been run before getting the results of a couple of HTTPClient requests, despite the fact that I'm calling them before I try to use their results.
function getMarkers(e, miles){ //The function with the HTTPClient calls that are firing last, trimmed to have only the relevant code.
var markers = [];
Ti.API.info("Getting markers");
xhr.onload = function()
{
var data = Ti.XML.parseString(this.responseText);
var ref = data.documentElement.getElementsByTagName("reference");
for(var i =0; i < ref.length; i++){
var marker = new Object();
marker.ref = ref.item(i).text;
var request = Titanium.Network.createHTTPClient();
request.setTimeout(10000);
request.onload = function(){
var data = Ti.XML.parseString(this.responseText);
marker.address = data.documentElement.getElementsByTagName("formatted_address").item(0).text;
if(data.documentElement.getElementsByTagName("formatted_phone_number") != null){
marker.phone = data.documentElement.getElementsByTagName("formatted_phone_number").item(0).text;
} else {
marker.phone = null;
}
marker.icon = data.documentElement.getElementsByTagName("icon").item(0).text;
marker.lat = data.documentElement.getElementsByTagName("lat").item(0).text;
marker.lng = data.documentElement.getElementsByTagName("lng").item(0).text;
marker.name = data.documentElement.getElementsByTagName("name").item(0).text;
if(data.documentElement.getElementsByTagName("url") != null) {
marker.url = data.documentElement.getElementsByTagName("url").item(0).text;
} else {
marker.url = null;
}
markers.push(marker);
Ti.API.info(markers.length);
}
request.open("GET","https://maps.googleapis.com/maps/api/place/details/xml?reference=" + marker.ref + "&sensor=true&key=" + Ti.App.apiKey);
request.send();
}
};
xhr.open("GET","https://maps.googleapis.com/maps/api/place/search/xml?location=" + googleLatLng + "&radius=" + radius + "&types=" + Ti.App.types + "&sensor=true&key=" + Ti.App.apiKey);
xhr.send();
return markers;
}
// actually draw the markers on the map
function drawMap(markers, currentLoc)
{
var i;
Ti.API.info("Adding markers...");
for(i=0;i<markers.length;i++)
{
Ti.API.info("Marker " + i);
Ti.API.info(markers[i].name);
var ann = Titanium.Map.createAnnotation({
image:markers[i].icon,
animate:true,
latitude:markers[i].lat,
longitude:markers[i].lng,
title:markers[i].name,
subtitle:markers[i].address
});
if(markers[i].url != null){
ann.rightButton = markers[i].url;
}
mapview.addAnnotation(ann);
}
Ti.API.info("Markers added"); //When this block is called, markers.length == 0
}
// find the user's location and mark it on the map
function waitForLocation(e)
{
//Do stuff about finding current location and marking it on the map. This stuff works and a pin drops for the current location
drawMap(getMarkers(e), currentLoc);
}
waitForLocation gets called first, which then calls the others. Xcode outputs the following:
[INFO] Getting markers
[INFO] Adding markers...
[INFO] Markers added
[INFO] 1
[INFO] 2
[INFO] 3
[INFO] 4
This means that it's going into the getMarkers function (first line), then leaving it (next two lines), then going back to the getMarkers function to actually get the markers (last four lines, output of markers.length as each marker is added). Knowing that
I moved the .open() call from before .onload() call based on an answer I found here, but I get the same whether .open() is before or after .onload().
I found information that the httpClient call performs its task asynchronously (an important bit of information that is lacking from the API reference). Knowing this, it makes sense that it leaves the function, but it does screw with how information is being handled, as I need the markers downloaded before trying to add them.
On talking with my iPhone developer coworker, he mentioned that he handles them using a delegate and a delegate.connectionDidFinishLoading call. Is there, perhaps, either a way to hook into this, or a Titanium implementation of this that I could use?
Is there another good way that I can make sure it doesn't try to load the markers before the app has actually downloaded them? It only needs to work for iPhone, so iPhone-specific options are fine.
After a lot of searching and hair pulling, I finally managed to get it working the way I needed.
.open() has a third, boolean, parameter that forces the HTTPClient to run synchronously (another tidbit not mentioned in the documentation). Setting it to false will make it run synchronously. Doing that allowed me to test the code in the order I expected it to run.
I also found that I couldn't make an array of all the markers and load them at once, so I adjusted my addMarker() function to only take one marker, and called it inside the loop that gets the marker data. Once I got that working, I was able to make the HTTPClient calls asynchronous again.
On Android, any task started from the UI thread especially Network activity is required to be asynchronous from SDK 2.3 on.

Categories

Resources