Adding an IndexedLineSet in X3Dom - javascript

When working with X3D via X3Dom, sometimes I'd like to add objects dynamically. As an example, I might have a function like the following to add a line:
function add_line(lineString) {
var lineString = lineString || random_line();
var orbit = $("<shape></shape>");
scene.append(orbit);
var indexedLineSet = $("<IndexedLineSet></IndexedLineSet>");
orbit.append(indexedLineSet).attr('coordIndex', '0 1');
var coordinate = $("<coordinate></coordinate>");
coordinate.attr('point', lineString);
indexedLineSet.append(coordinate)
}
In this code, scene is an existing X3D scene, random_line is a function that generates a string defining a random line, and I'm manipulating the scene with JQuery. When calling the function as the page loads, it works just fine. When calling the function in response to a button push, however, the line doesn't appear, though it is added to the scene.
Notes:
The problem appears to depend on the type of object I try to add. Spheres can be added without any problem.
According to Firefox's inspector, the IndexedLineScene appears to be added to the scene correctly; it just doesn't appear in the image.

If you open up your developer console you can find:
x3dom.js:3014 Uncaught TypeError: Cannot read property 'getPoints' of null_
buildGeometry # x3dom.js:3014
nodeChanged # x3dom.js:3046
x3dom.NodeNameSpace.setupTree # x3dom.js:2744
onNodeInserted # x3dom.js:1100
(anonymous function) # jquery-1.12.4.min.js:3
Ha # jquery-1.12.4.min.js:3
append # jquery-1.12.4.min.js:3
add_line # IndexedLineSet-Test.html:76
(anonymous function) # IndexedLineSet-Test.html:90
dispatch # jquery-1.12.4.min.js:3
r.handle # jquery-1.12.4.min.js:3
After investigation line 76 of your original code, you will find that you have to change the order of adding the points and adding the indices:
var indexedLineSet = $("<IndexedLineSet></IndexedLineSet>");
var coordinate = $("<coordinate></coordinate>");
coordinate.attr('point', lineString);
indexedLineSet.append(coordinate);
orbit.append(indexedLineSet).attr('coordIndex', '0 1');
It works well for you on the initial load, because you construct the x3d before X3DOM is ready, check when this call appears (after your calls to add_line have been finished):
x3dom.runtime.ready = function() {
console.log('ready');
};
This means that all the events and listeners are not setup yet. But later when you click that button X3DOM has been initialized completely. Thus it can listen to all events and fires the onNodeInserted event right after you append orbit to scene. Another solution would be to have this call right at the end of your function:
Construct the 3D object completely
Add the object (orbit) to the scene.

Related

Any way to quickly find out from what Javascript file and code line a custom event is dispatched?

I would like to be able to find the JavaScript file and code line from which a custom event is dispatched.
Let's say we have two JavaScript files, A.js and B.js. I define and dispatch a custom event from A.js at line X. I listen for it in B.js at line Y.
I could jump into Chrome DevTools and run monitorEvents($0) to find out where the event was 'heard' (where the eventListener is in my code base, namely: line Y of B.js). But is there a similarly quick way to find out where the event was dispatched from (namely line X from A.js)?
If I understand correctly, in the event listener, create a new Error - the second line in its .stack property is the source of the event dispatch - note, second line in Firefox, third line in Chrumium based bruisers.
note: stack property of an Error is "non-standard" - however it's available in every modern browser released in the last 7 years (or older in most cases), and even in IE10
// Chrum browsers stack starts at the second line
const fixChrum = +(new Error().stack.split('\n')[0].startsWith('Error'));
window.addEventListener('custom:event', () => {
console.log('dispatched from', new Error().stack.split('\n')[1 + fixChrum]);
});
// the following is not required, it just shows the line number before the dispatch
console.log('dispatched from next line', new Error().stack.split('\n')[0 + fixChrum]);
window.dispatchEvent(new Event('custom:event'));

Why is this findOne in a template event causing a call stack size error?

I am trying to have a forEach in SpaceBars that shows some buttons that can be pressed for each thing in an array. However, when I press the button, it gives me this error:
Uncaught RangeError: Maximum call stack size exceeded underscore.js:1025
_.(anonymous function) # underscore.js:1025
EJSON.clone # ejson.js:475
(anonymous function) # ejson.js:494
.each..forEach # underscore.js:113
EJSON.clone # ejson.js:493
... same stuff for a while ...
.each..forEach # underscore.js:113
EJSON.clone # ejson.js:493
(anonymous function) # ejson.js:494
.each..forEach # underscore.js:113
EJSON.clone # ejson.js:493
This is the findOne that's causing the error:
"click .acceptFriend": function(requester){
currentUserId = Meteor.userId();
requesterUsername = requester;
requesterId = friendRequests.findOne({$and:[{target:currentUserId}, {requester:requesterUsername}]})._id;
Meteor.call("acceptRequest", requesterId, requester);
}
I know it's the friendRequests.findOne because I have done console logs on each line, and that is the line that breaks it. I have tried several different variations, but it seems that any type of find on the friendRequests collection in that context breaks it. The relevant part of the template is this:
{{#each getRequests}}
{{requester}} <button class='acceptFriend'>accept</button>
{{/each}}
Why is this error happening and how can I solve it?
If we look at the documentation about events:
The handler function receives two arguments: event, an object with
information about the event, and template, a template instance for the
template where the handler is defined. The handler also receives some
additional context data in this, depending on the context of the
current element handling the event. In a template, an element's
context is the data context where that element occurs, which is set by
block helpers such as #with and #each.
To sum up: events in Meteor do not receive their data context as an argument: they receive the event object and the template instance where the event occured. The data context is stored in this.
So what you should probably do is the following:
"click .acceptFriend": function(event, template){
currentUserId = Meteor.userId();
requesterUsername = this.requester;
requesterId = friendRequests.findOne({$and:[{target:currentUserId}, {requester:requesterUsername}]})._id;
Meteor.call("acceptRequest", requesterId, requesterUsername);
}

How to toggle visibility property (billboard.show) of a CZML entity's billboard?

According to the Cesium API, to toggle the visibility of an asset's billboard (or label) you can simply assign the billboard.show property to false. When I attempted to do this, Cesium would error with
An error occurred while rendering. Rendering has stopped.
TypeError: undefined is not a function
...
This discussion from the cesium-dev google group includes some example code to toggle billboard visibility on/off. The same code does not work if you attempt show = false on an entity from CZML (this example does not use CZML).
Here is what I tried
var asset = loadedCZML.entities.getById(id);
asset.billboard.show = false; //Error!
(loadedCZML is a Cesium.CzmlDataSource)
The API doc's don't mention that the show property of your entity might not always be a simple boolean property (as the API describes).
When working with a CzmlDataSource's entity, the show property is considered a TimeIntervalCollectionProperty (at least it was with my CZML).
All properties in Cesium must implement a getValue function, and when you go to set your show = false, the setter for the property is unable to apply false to a TimeIntervalCollectionProperty and instead replaces the entire property with a simple value of false.
The error undefined is not a function is a result of the cesium render call attempting to call getValue() on our show property. Regardless, the fix is simple:
Instead of this:
asset.billboard.show = false; //error
Do this:
asset.billboard.show = new Cesium.ConstantProperty(false);
PS: This applies for other Cesium properties, see the following example:
entity.billboard.image = pinBuilder.fromColor(Cesium.Color.CRIMSON, 48); //error
//do this instead
entity.billboard.image = new Cesium.ConstantProperty(pinBuilder.fromColor(Cesium.Color.CRIMSON, 48).toDataURL());

Object has no method 'call' when setting Backbone Model from Trigger

So, I just spent about 3 hours trying to debug this nasty thing and I'm at wits end.
What I'm trying to do is fairly simple. I'm working with a search query (string) and trying to set a global model with its attributes. However, when I'm moving to a different view, I want the model to clear out these attributes, or of course when they enter a new search query, have the event fire again. I'm trying to use the Backbone Marionette Wreqr / Event Aggregator to manage the events here.
I don't know if it's because I'm using requirejs, or not scoping things correctly, but I'm hitting this very confusing error:
Uncaught TypeError: Object #<Object> has no method 'call' backbone.amd.min.js:1
f backbone.amd.min.js:1
e.Events.trigger backbone.amd.min.js:1
i.extend.set backbone.amd.min.js:1
(anonymous function) app.js:42
f backbone.amd.min.js:1
e.Events.trigger backbone.amd.min.js:1
Application.searchRoute app.js:81
(anonymous function) backbone.amd.min.js:1
(anonymous function) backbone.amd.min.js:1
w.some.w.any underscore.amd.min.js:1
i.extend.loadUrl backbone.amd.min.js:1
i.extend.navigate backbone.amd.min.js:1
i.extend.navigate backbone.amd.min.js:1
SidebarLayout.search sidebar.js:177
(anonymous function) sidebar.js:3
x.event.dispatch jquery.min.js:5
y.handle
And here's some of my application code (my main controller). As you can see, I'm trying to set up the app.searchResult model with these other parameters from the searchRoute method.
What's so bizarre is that it works the first time the page loads, but when I attempt to use router navigate or do anything but refresh the page, it fails.
I tried using #searchResult instead, tried adding a , # after my trigger call, and all sorts of other things. What's stranger is that inside that scope, app.searchResult is available to me. And as soon as I remove the set call from there, the error doesn't happen anymore, so I'm thinking it must be that?
define [
'backbone.marionette',
'global/layouts/home',
'global/layouts/layout',
'search/models/search-results',
'search/collections/panels',
'global/collections/categories',
'global/collections/stores',
'global/collections/searchResults',
'search/views/panels',
'apps/router'
], (Marionette, homeLayout, subpageLayout, SearchResultModel, PanelsCollection, CategoriesCollection, StoresCollection, SearchResultsCollection, PanelsView, Router) ->
class Application extends Backbone.Marionette.Application
onInitializeAfter: =>
# Main Region
#addRegions
mainRegion: '#app'
# AppRouter
#router = new Router controller: #
# Request Response / Vent
#reqRes()
#appVent()
# Global Search Result Model
#searchResult = new SearchResultModel()
Backbone.history.start
pushState: false
root: ""
searchRoute: (query, params) ->
params = $.extend params, text: query
#vent.trigger "search:setParams", params
#subpageLayout = new subpageLayout()
#mainRegion.show #subpageLayout
appVent: ->
#vent.on "search:setParams", (params) ->
app.searchResult.set("params", params)
#vent.on "sidebar:reset", ->
app.searchResult.set("params", null)
I can send more code if you like, but the idea is to use a custom trigger like sidebar:reset to clear out these parameters. I'm also listening to some model events (all of that stuff is working great from within the context of the view and when I don't mess with the event aggregator).
Thanks in advance for your help. You have no idea how many hours I just spent on this!

canvas drawImage doesn't draw images the first time

I'm working on a simple JavaScript game using HTML5 canvas. It's a very typical game-loop setup involving:
init() function that initializes objects to be used the playfield, randomizing some start x/y positions and similar setup tasks
draw() function that draws all the game objects, including some simple canvas shapes and a few images
setInterval to call draw every 25 milliseconds
While getting this project started, I had the setInterval call at the end of the init() function, so it'd just start drawing and animating as soon as you loaded the page, and everything worked fine. Now that I'm further along, I'd like to draw a fully populated, static playfield on load and then use a button to kick off the main game loop interval. So I added a single call to draw() at the end of init(), and the issue is that when I do this, all the canvas shapes get drawn properly but none of the images render on the canvas.
They do render if I let draw() run a few times, like...
var previewDraw = setInterval(draw, 25);
var stopPreviewDraw = function() { clearInterval(previewDraw) }
setTimeout(stopPreviewDraw, 100)
...but that seems dumb. Why doesn't a single call to draw() work? I've logged the objects in Chrome's and Firefox's consoles and everything about them looks fine; they already have the appropriate img src paths and start x/y values available to pass to assign to the new Image() that gets created and then called via my canvas.2dcontext.drawImage() method.
I'm testing this in Chrome 6 and Firefox 3.6.10 as I go, and they're both puzzling me with this behavior. No errors or issues show in Chrome's console but if I try calling draw() only once the Firefox throw this:
uncaught exception: [Exception... "Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIDOMCanvasRenderingContext2D.drawImage]" nsresult: "0x80040111 (NS_ERROR_NOT_AVAILABLE)" location: "JS frame :: http://localhost/my-game-url/ :: drawItem :: line 317" data: no]
My Google searches for that error suggest a corrupt image, but they all open in Preview and Photoshop without any problems.
I assume you are calling init() after the onload event of the window object is fired. The problem is, that does not guarantee to you that the image data is actually available at that point. AFAIR it would fire after the images are fetched from the network but then they still need to be decoded.
Your best bet would be to wait for the images' onload event instead.

Categories

Resources