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.
Related
I'm working on my scrim component for A-Frame and want it to "turn off" when VR Mode is entered. Where in the component can I listen for the enter-vr event so I can remove animations?
I add animation settings for opacity on init of the component. I don't know where add a listener for enter-vr to remove the added animation.
init: function () {
var thisEl = this.el;
var data = this.data
var properties = 'property: material.opacity; from: 0; to: 1; dir: alternate; loop: true'
properties = properties.concat(properties,'; dur: ', data.durration, '; easing: ', data.easing)
thisEl.setAttribute('animation', properties)
document.querySelector('a-scene').addEventListener('enter-vr', remove())
},
remove: function () {
var thisEl = this.el;
thisEl.setAttribute('animation', '')
},
I expected the listener in the init function to call the remove function when enter-vr fires.
The method is not correctly attached. Try this instead:
document.querySelector('a-scene').addEventListener('enter-vr', this.remove.bind(this));
Your code is calling the function and passing the result to addEventListener not a reference to the function itself. I recommend reading about function declarations, statements and scoping in JavaScript for better understanding.
Also to remove a component use removeAttribute:
thisEl.removeAttribute(“animation”);
With setAttribute you are setting the properties to the default values not removing the component. Additional info in docs: https://aframe.io/docs/0.9.0/introduction/javascript-events-dom-apis.html#removing-a-component-with-removeattribute
I have a use-case where I need to programmatically add/remove the onClick event associated with a panel.
I have tried the following solution but receive a cijCell.addEventListener is not a function error.
function cij_enabled(){
var cijCell = app.pages.Home.descendants.cellFour;
var index = cijCell.styles.indexOf('disabled-card');
if (Report.riskOfLoss === 'High') {
cijCell.styles.splice(index, 1);
cijCell.addEventListener("click", function() {
app.popups.Customer.visible = true;
});
} else {
if (index === -1){
cijCell.styles.push('disabled-card');
cijCell.removeEventListener("click", function() {
app.popups.Customer.visible = true;
});
}
}
}
How can I achieve the desired outcome? Is adding eventlisteners possible in this fashion through app maker?
You can definitely do so and you got it almost right. The only thing you need to understand is that the appmaker widget is not a native html element hence the error:
cijCell.addEventListener is not a function
Fortunately, AppMaker has a way of getting the native html elements associated to a widget. You need to use the getElement() method and then you can use the add/remove event listeners methods. So you should change your code from cijCell.addEventListener... to cijCell.getElement().addEventListener...
Reference: https://developers.google.com/appmaker/scripting/api/widgets#Panel
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});
});
}
});
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
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