In my scene I'm using a script to create an entity with a raycaster component. This works great, and the event listeners are working fine colliding with my ground object. But my ground is uneven, and I need to know the height of the ground where the raycaster hits. As far as I know, this isn't possible with just what A-Frame provides. I'd like to call THREE's raycaster.intersectObject, but I'm not able to get a working reference to the THREE raycaster. The A-Frame documentation mentions a member called raycaster, but it's undefined for me. I've tried a few different things, but to no avail. My next plan was to make a new component, but I figured I'd ask here first.
var ray = document.createElement('a-entity');
ray.setAttribute('id', 'raycaster');
ray.setAttribute('raycaster', 'objects: #ground');
ray.setAttribute('position', pos);
ray.setAttribute('rotation', '-90 0 0');
scene.appendChild(ray);
var ground = document.querySelector('#ground');
ray.addEventListener('raycaster-intersection', function() {
console.log(ray.raycaster); //undefined
console.log(ray.intersectedEls); //undefined
console.log(ray.objects); //undefined
console.log(ray.object3D.id); //gives me the THREE id
console.log(ray.object3D.intersectObject(ground.object3D, false)); //intersectObject not a function
});
Any clues? I'm sure I'm doing something stupid.
It's a member of the component, which you'll have to dig for.
ray.components.raycaster.raycaster
ray - entity
.components - the entity's components
.raycaster - the raycaster component
.raycaster - the raycaster object as a member of the raycaster component
Here is a complete component in case anyone is interested. Just add "set-to-ground" to your entity. If you want objects to be standing on the ground, you can add them as children to the entity and set their relative y position to half the object height.
Should still be enhanced with a "target" selector though as currently the ground needs to be with id 'ground'
AFRAME.registerComponent('set-to-ground', {
schema: {
type: 'string'
},
init: function () {
var data = this.data;
var el = this.el;
var scene = document.getElementById('ascene');
var ray = document.createElement('a-entity');
ray.setAttribute('id', 'raycaster');
ray.setAttribute('raycaster', 'objects: #ground');
ray.setAttribute('rotation', '-90 0 0');
el.appendChild(ray);
var ground = document.querySelector('#ground');
ray.addEventListener('raycaster-intersection', function getY() {
console.log(ray.components.raycaster.raycaster.intersectObject(ground.object3D, true)[0].point.y); //groundPos to log
var groundPos = ray.components.raycaster.raycaster.intersectObject(ground.object3D, true)[0].point.y;
ray.removeEventListener('raycaster-intersection', getY);
el.setAttribute('position', {y: groundPos});
});
}
});
Related
I'm trying to create a local clipping plane in AFrame, essentially this effect
https://threejs.org/examples/#webgl_clipping_intersection
To enable the clipping, you must have a reference to the renderer, and enable localClippingEnabled, like this:
renderer.localClippingEnabled = true;
In AFrame, the webGLRenderer is exposed through a the component renderer, but localClippingEnabled property is not supported. ie
<a-scene renderer="localClippingEnabled:true">
core:schema:warn Unknown property localClippingEnabled for component/system undefined.
AFRAME.registerComponent('matclipplane', {
schema:{
clipHeight:{type: 'number', default: 1}
},
init: function () {
let el = this.el;
let self = this;
self.scene = el.sceneEl.object3D;
let renderer = THREE.WebGLRenderer; // How to refernence the renderer?
renderer.localClippingEnabled = true;
I've attempted to reference it in a custom component, like this
let renderer = THREE.WebGLRenderer;
renderer.localClippingEnabled = true;
But it doesn't seem to be working. I'm not convinced this is a proper reference to the WebGLRenderer, as logging it in the console does not reveal the property localClippingEnabled.
From what I have seen in THREEjs examples, reference is always from a newly constructed renderer. So I tried that , and in the console, it looks right, with the localClippingEnable=true in the console, but still doesn't work, because (I don't think) this new renderer is doing the rendering.
How can I make this work?
Here is my glitch in progress.
https://glitch.com/~clipping-plane
According to the docs, you can access the renderer object as a scene property (sceneEl.renderer).
For example:
AFRAME.registerComponent('foo', {
init: function() {
console.log(this.el.sceneEl.renderer.localClippingEnabled)
this.el.sceneEl.renderer.localClippingEnabled = true
}
})
Check it out in this fiddle.
I can create infobubbles using the HERE maps Javascript API - eg from their documentation:
function addInfoBubble(map) {
map.set('center', new nokia.maps.geo.Coordinate(53.430, -2.961));
map.setZoomLevel(7);
var infoBubbles = new nokia.maps.map.component.InfoBubbles(),
TOUCH = nokia.maps.dom.Page.browser.touch,
CLICK = TOUCH ? 'tap' : 'click',
container = new nokia.maps.map.Container();
container.addListener(CLICK, function (evt) {
infoBubbles.openBubble(evt.target.html, evt.target.coordinate);
}, false);
map.components.add(infoBubbles);
map.objects.add(container);
addMarkerToContainer(container, [53.439, -2.221],
'<div><a href=\'http://www.mcfc.co.uk\' >Manchester City</a>' +
'</div><div >City of Manchester Stadium<br>Capacity: 48,000</div>');
addMarkerToContainer(container, [53.430, -2.961],
'<div ><a href=\'http://www.liverpoolfc.tv\' >Liverpool</a>' +
'</div><div >Anfield<br>Capacity: 45,362</div>');
}
function addMarkerToContainer(container, coordinate, html) {
var marker = new nokia.maps.map.StandardMarker(
coordinate,
{html: html}
);
container.objects.add(marker);
}
I would like to hook into the close event of the infobubble. I realise I could use jQuery to find the span that contains the close button (examining the markup, I believe it has the class nm_bubble_control_close) and do something when this is clicked. However I thought there would be a built-in event that I could use.
Does anyone know if there is a built-in event that fires when the infobubble is closed? I can't find anything in the documentation.
The InfoBubbles component has a property "openBubbleHandles". It's an OList you can observe to see when bubbles are opened (i.e. added to the list) or closed (removed from the list).
AFAIK there is no built-in event, there's also no related property which may be observed.
Possible workaround:
override the built-in close-method with a custom function:
container.addListener(CLICK, function (evt) {
var b = infoBubbles.openBubble(evt.target.html, evt.target.coordinate),
c = b.close;
b.close = function(){
//do what you want to
alert('bubble will be closed');
//execute the original close-method
c.apply(b)
}
}, false);
For the current HERE Javascript API version (3.x) the working solution can be found here:
HERE Maps - Infobubble close event / hook
My site consists of a Leaflet map with the leaflet.markerclusters plugin. I am also using Flowplayer to play a video that opens in a JQuery Tools overlay using the selector id "#video1".
Currently, when I click on any marker on the map it fires my test video in an overlay. My goal is to create a click event unique to each individual marker in the cluster. Eventually, I would like every marker to have a click event that fires a video unique to that marker.
I am a beginner, and have been doing okay using these well documented libraries up until now. However, I don't have the skills to bridge this current gap. Would someone please give me a push in the right direction? Below is a link to my JS Fiddle. My issue begins on JavaScript line 2098.
var markers = new L.MarkerClusterGroup();
var addressPoints = [
[40.634902, -73.965054, "Video1"],
[40.660897, -73.961082, "Video2"],
[40.693353, -73.970413, "Video3"],
[40.693289, -73.966289, "Video4"],
[40.68973, -73.971007, "Video5"],
[40.718423, -73.957428, "Video6"],
[40.71817, -73.956918, "Video7"],
[40.681427, -73.993959, "Video8"]
];
for (var i = 0; i < addressPoints.length; i++) {
var a = addressPoints[i];
var title = a[2];
var marker = new L.Marker(new L.LatLng(a[0], a[1]), {
title: title
});
marker.bindPopup(title);
markers.addLayer(marker);
}
map.addLayer(markers);
//assign video div ID to overlay
$('#video1').overlay({
load: false,
top: "center",
left: "center"
});
//bind marker click event to overylay
markers.on('click', function () {
$("#video1").data("overlay").load();
});
Thank you,
Joey
http://jsfiddle.net/Joey84/nM458/26/
You are headed in the right direction with the markers.on("click"... function. You just need to make a few edits. Just as you added the event listener to the entire "markers" layer, you can add it to individual markers in your for loop.
...
for (var i = 0; i < addressPoints.length; i++) {
var a = addressPoints[i];
var title = a[2];
var marker = new L.Marker(new L.LatLng(a[0], a[1]), {
title: title
});
if (title=="Video1") {
marker.on('click', function () {
$("#video1").data("overlay").load();
});
}
marker.bindPopup(title);
markers.addLayer(marker);
}
...
Likewise - and probably the better solution - you can access details about the marker you clicked in the on("click"... you are currently using by passing a variable to the function. This would be useful if you have multiple videos and don't want to check with an if statement when creating markers.
markers.on('click', function (d) {
// Grab marker title and make it look like an ID
var marker_title = "#" + d.layer.options.title.toLowerCase();
// Make sure the jQuery object exists
if ( $(marker_title) ){
// Call up the video.
$(marker_title).data("overlay").load();
}
});
Note that I used toLowerCase() because your data has the title capitalized and the video id is all lowercase.
Here it is in action:
http://jsfiddle.net/nM458/44/
I might be missing something, but it seems that Meteor's "magic" revolves around binding data to DOM elements, and updating text and HTML fragments via handlebars: http://docs.meteor.com/#reactivity
This is great, however, when trying to write a meteor app that displays live data in a <canvas> element, I cannot figure out the "meteor way" to update my canvas when the live data changes, since the canvas is populated via JS code like:
var g = canvas.getContext('2d')
g.fillRect(x, y, w, h)
and not data-backed text in the HTML template.
I am trying to draw on the canvas using data from a Meteor.Collection.
My only thought was to embed canvas-drawing JS code in the HTML template in a script tag populated by handlebar vars, but this seems wrong since meteor's events and data-binding code is already client-side JS.
Is there some way listen for live data changes, which triggers drawing on the canvas via JS instead of HTML elements/text?
Please let me know if I can clarify the question in some way
Update:
Tom's answer below made me notice Meteor.deps, which look to allow executing arbitrary code in a reactive context:
http://docs.meteor.com/#on_invalidate
I will try this out and update here if it works.
Perhaps the answer to your question is to use Collection.observe (http://docs.meteor.com/#observe) and trigger the relevant redrawing code in the various callbacks.
For instance, something like:
Rectangles.observe({
added: function(rect) {
var g = canvas.getContext('2d');
g.fillRect(rect.x, rect.y, rect.w, rect.h);
},
// etc
})
This works:
var Shapes = new Meteor.Collection('shapes')
if (Meteor.is_client) {
// Function that redraws the entire canvas from shapes in Meteor.Collection
function drawShapes() {
var shapes = Shapes.find({})
shapes.forEach(function(shape) {
// draw each on canvas
})
}
var startUpdateListener = function() {
// Function called each time 'Shapes' is updated.
var redrawCanvas = function() {
var context = new Meteor.deps.Context()
context.on_invalidate(redrawCanvas) // Ensures this is recalled for each update
context.run(function() {
drawShapes()
})
}
redrawCanvas()
}
Meteor.startup(function() {
startUpdateListener()
})
}
I had some trouble with updating the canvas so I created this simple game demo:
https://github.com/randompast/Meteor-SimpleGame
I am a Backbone.js n00b and trying to get my head around it. I know how to render a model using a view and the built-in underscore.js templating engine. Now I'm trying to render a collection and that's where I get stuck. There is no server here, so I'm not fetching anything remotely, just a simple HTML page with some JavaScript.
ContinentModel = Backbone.Model.extend({});
ContinentsCollection = Backbone.Collection.extend({
model: ContinentModel,
initialize: function () {
this.continentsView = new ContinentsView;
this.bind("reset", this.continentsView.render);
}
});
ContinentsView = Backbone.View.extend({
el: '#continents',
template: _.template($('#continents-template').html()),
render: function() {
var renderedContent = this.template(this.collection.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
$(function() {
var continentsCollection = new ContinentsCollection();
continentsCollection.reset([{name: "Asia"}, {name: "Africa"}]);
});
It breaks on the template attribute line in the view but I'm not sure that's where I need to look. Am I supposed to render a collection or do I miss the point completely here (maybe collections are just grouping objects and I shouldn't look at it as a list I can render)?
Thanks for helping...
The problem is that when you define ContinentsView, the template is evaluated and it uses $('#continents-template') - but the DOM is not ready yet, so it does not find the template.
To solve it, simply move the template assignment in the initialize function:
ContinentsView = Backbone.View.extend({
el: '#continents',
initialize: function() {
this.template = _.template($('#continents-template').html());
}
...
Regarding collections, yes, they are grouping objects, specifically sets of models.
You should make the code so the models (and collections) do NOT know about the views, only the views know about models.
ContinentModel = Backbone.Model.extend({});
ContinentsCollection = Backbone.Collection.extend({
model: ContinentModel,
// no reference to any view here
});
ContinentsView = Backbone.View.extend({
el: '#continents',
initialize: function() {
this.template = _.template($('#continents-template').html());
// in the view, listen for events on the model / collection
this.collection.bind("reset", this.render, this);
},
render: function() {
var renderedContent = this.template(this.collection.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
$(function() {
var continentsCollection = new ContinentsCollection();
continentsCollection.reset([{name: "Asia"}, {name: "Africa"}]);
// initialize the view and pass the collection
var continentsView = new ContinentsView({collection: continentsCollection});
});
It is also worth noting there are additional complexities that quickly rear their heads when rendering a collection in a view. For instance, the view generally needs to be re-rendered when models are added or removed from the collection. It isn't rocket science to implement your own solution, but it is probably worth looking into existing solutions since there are quite a few tried and tested ones out there.
Backbone.CollectionView is a robust collection view class that handles selecting models in response to mouse clicks, reordering the collection based on drag and drop, filtering visible models, etc.
Several popular frameworks built on top of backbone also provide simple collection view classes, like Backbone.Marionette, Chaplin, and Layout Manager.
Even though Backbone itself does not provide any structure for rendering a collection, it is a non-trivial problem and lots of people have different opinions on how it should be done. Luckily it is such a common need that there are quite a few good options already in the eco system.