I want my raycaster to look for intersections only when the trigger of my vive controller is pressed.
I thought to initialize the raycaster with a fake class in objects, change it to the real one .prop and gather the intersectedEls
let rightHand = document.getElementById('rightController');
rightHand.setAttribute('line', 'color: purple; opacity: 1;');
rightHand.setAttribute('raycaster', { showLine: true, objects: '.none' });
rightHand.setAttribute('cursor', { downEvents: ['triggerdown'], upEvents: ['triggerup'], rayOrigin: 'entity', fuse: false });
let scene = document.getElementById('scene');
scene.addEventListener('triggerdown', this.myTriggerDown);
scene.addEventListener('triggerup', this.myTriggerUp);
myTriggerDown() {
let rightHand = document.getElementById('rightController');
rightHand.setAttribute('raycaster', { showLine: true, objects: '.prop' });
rightHand.components['raycaster'].refreshObjects();
let raycaster = rightHand.components['raycaster'];
let intersectedEls = raycaster.intersectedEls;
if (typeof intersectedEls !== 'undefined' && intersectedEls.length > 0) {
scene.components['resize'].enableResize(intersectedEls[0]);
} else {
console.log('1234 no intersections')
}
}
myTriggerUp() {
let rightHand = document.getElementById('rightController');
rightHand.setAttribute('raycaster', { showLine: true, objects: '.none' });
}
Unfortunately I keep getting the console.log('1234 no intersections') message.
I tried adding the refreshObjects() line with no effects.
I tried toggling the enabled property instead of changing the objects, but had still the same result.
Any help would be appreciated. Thanks
edit:
if I look for intersections in the triggerup part it works. but this is a workaround which also deprives me of using the intersected element and doing things while keeping the trigger pressed . I'd still like to know why it does not work to enable the ray/change target objects and immediately look for intersections.
I recommend using the raycaster.enabled property versus swapping to a dummy class.
The raycaster checks for intersections once per frame (or whatever the raycaster.interval is). On trigger down, you enable the raycaster, but you have to wait until the next frame for it to pick up intersections.
You can manually invoke intersection check via raycaster.checkIntersections() or run a setTimeout before checking.
Related
I have a scene including an Object3D representing a globe and multiple mesh elements representing points on this globe. I use OrbitControls to allow interaction. Additionally I attach HTMLElements to the points on the globe. Since a globe basically is a sphere, points might not be visible for the camera when placed on the back.
How can I detect whether or not such a point is visible for the camera/hidden by the object? Doing so I want to hide the HTMLElement in relation to the mesh's visibility. The HTMLElement's position is updated on render, hence this check should happen on render as well I assume:
private render() {
this.renderer.render(this.scene, this.camera);
this.points.forEach(({ label, mesh }) => {
const screen = this.toScreenPosition(mesh);
label.style.transform = `translate3d(${screen.x - 15}px, ${screen.y}px, 0)`;
});
this.requestId = window.requestAnimationFrame(this.render.bind(this));
}
Working code within render:
this.points.forEach(({ label, mesh }) => {
const screen = this.toScreenPosition(mesh);
label.style.transform = `translate3d(${screen.x - 15}px, ${screen.y}px, 0)`;
const direction = new Vector3();
direction.copy(mesh.position).sub(this.camera.position).normalize();
this.raycaster.set(this.camera.position, direction);
const intersections = this.raycaster.intersectObject(this.scene, true);
const intersected = intersections.length > 0 ? intersections[0].object.uuid === mesh.uuid : false;
if (intersected && label.style.opacity === "0") {
label.style.opacity = "1";
} else if (!intersected && label.style.opacity === "1") {
label.style.opacity = "0";
}
});
I recommend a simple algorithm with two steps:
First, check if the given point is in the view frustum at all. The code for implementing this feature is shared in: three.js - check if object is still in view of the camera.
If the test passes, you have to verify whether the point is occluded by a 3D object or not. A typical way for checking this is a line-of-sight test. Meaning you setup a raycaster from your camera's position and the direction that points from your camera to the given point. You then test if 3D objects in your scene intersect with this ray. If there is no intersection, the point is not occluded. Otherwise it is and you can hide the respective label.
I'm using ChartJS (v 2.9.4) and I want the hover interaction / tooltip system (they work the same way from what I can tell) to select the point nearest (relative to the x axis) to the mouse pointer on all datasets.
Now, if the x-axis of the chart is operating in the default category mode (i.e.: data points with the same index in the dataset have the same position on the x-axis) this is easily achieved with this configuration:
options: {
hover: {
mode: 'index',
intersect: false
}
//other options...
}
However, if the x-axis is of type linear (i.e.: each data point in each dataset has a specific x-value) this doesn't work properly, since the decision on which point to select is based on its index in the dataset, rather than its x-axis value.
I've created an example here:
https://jsfiddle.net/parhwv73/5/
as you can see by hovering the mouse around, you can easily find yourself in situations like this, where the points selected in the two datasets are very far apart:
while what I would like is this:
in other words: I would like to select the point in each dataset nearest to the mouse pointer (relative to the x-axis).
I've tried playing around with the mode and intersect options, but I found no combination that works, mainly because most other modes only select a point in one single dataset, rather than all of them, index mode is the closest one to being correct, but not quite as I've explained.
Is there a way to achieve this natively?
Or if there isn't, can anyone give some pointers on how to implement a plugin of some sort that can achieve this?
If someone has the same problem in a more recent version (like 3.7.0), you can just modify the interaction mode: instead of 'index', use this:
options: {
interaction: {
intersect: false,
mode: 'nearest',
axis: 'x'
},
}
found on the docs
Well, in the end I had to modify the ChartJS source code to achieve this. Fortunately, it wasn't too hard. All I had to do was add this function to the core.interaction.js file:
function xPositionMode(chart, e, options) {
var position = getRelativePosition(e, chart);
// Default axis for index mode is 'x' to match old behaviour
options.axis = options.axis || 'x';
var distanceMetric = getDistanceMetricForAxis(options.axis);
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
var elements = [];
if (!items.length) {
return [];
}
const findClosestByX = function(array, element) {
let minDiff = -1;
let ans;
for (let i = 0; i < array.length; i++) {
var m = Math.abs(element._view.x - array[i]._view.x);
if (minDiff === -1 || m < minDiff) {
minDiff = m;
ans = array[i];
}
}
return ans;
}
chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
var element = findClosestByX(meta.data, items[0]);
// don't count items that are skipped (null data)
if (element && !element._view.skip) {
elements.push(element);
}
});
return elements;
}
This is basically a modified version of the indexMode function present in the same file, but instead of searching items by their index in the dataset it searches the closest items by their horizontal position on the canvas (see the findClosestByX inner function for the search algorithm)
To make this usable, you also have to add the new mode in the list of exports (in the same file):
module.exports = {
// Helper function for different modes
modes: {
xPosition: xPositionMode,
//Rest of the original code...
}
}
Once this is done, recompile the ChartJS library and use it instead of the original one. Now you can use the new mode like this:
options: {
hover: {
mode: 'xPosition',
intersect: false
}
//other options...
}
Note: all code in this answer refers to the 2.9.4 version of the ChartJS library, it might not work the same for 3.x versions.
I started this project just for fun (inspired by the Conway's Game of Life). In my project, I have a file for the cell (defined as an object) and other for the game (universe) where the rules are set so the cell can play. The cell object is defined like this:
const cell = {
x_birth: 0,
y_birth: 0,
alive: false,
live: function() {
fill(100);
square((WIDTH/F)*x, (HIGH/F)*y, (WIDTH/F), (HIGH/F));
},
die: function() {
fill(255);
square((WIDTH/F)*x, (HIGH/F)*y, (WIDTH/F), (HIGH/F));
},
move: function(x_var, y_var) {
if (this.alive == false){
x = this.x_birth;
y = this.y_birth;
this.alive = true; // Bug?
this.live();
} else { //... }}}
WIDTH, HIGH and F are global constants from the other main file.
The problem I have is that when I create two objects with 'Object.create(cell)' in the main file, just one of the cells moves. It appears that when one of the cells executes its function 'move()', it does it correctly, setting it's property 'alive' to 'true' when the other cell (object) doesn't. I though that this may be the problem, but then I searched and I didn't find why this should be the issue.
If you want, you can download the files from my repo: https://github.com/Atotem/Game.git . There you will understand what i mean. I'm open to all suggestions.
PD: I'm using p5.js libraries.
I have a problem in Javascript: I have an array which changes his elements in each step when I´m clicking with the mouse. My array has for example 5 elements. Now I want to draw 5 rectangles and I want each of the rectangles to have the height of an element of this array. Then I click with the mouse, so the elements change and so the heights change too. How can I realize this?
Thank you for your help!
Maybe you can use a Getter and a Setter. Here an example:
const data = {
_arr: [],
set arr(value) {
this._arr = value;
//inform me
console.log("changed");
},
get arr() {
return this._arr;
}
};
data.arr = [1, 3];
Openlayers3 can display a map at a fractional zoom level programmatically by specifying a fractional number in setZoom().
However, I want my users to be able to get a fractional zoom level on a mobile touch-driven device (ie, a smart phone). Pinch or reverse pinch zooms out/in by jumping to the nearest whole zoom level when the fingers are removed from the screen.
How can a touch/mobile user get Openlayers3 to stay at the exact extent (fractional zoom level) that the user has pinched to?
Is there something I can add to the javascript for my Openlayers3 map or view to get this to work as desired?
In particular, I note that Openlayers3 has a ol.interaction.PinchZoom() class ( https://openlayers.org/en/latest/apidoc/ol.interaction.PinchZoom.html ). This is a subclass of ol.interaction.Pointer which has a handleUpEvent and a handleEvent (which should be a function). So in theory, I should be able to either replace the handleUpEvent in PinchZoom OR replace the default PinchZoom interaction with one that has a custom event handler function. But with both of these approaches, I can't get my new handleUpEvent function to be called.
(While trawling the OL code for PinchZoom I have found that the default PinchZoom does what I want if one finger is lifted from the touch-screen before the other finger, but I still want to get this working when both fingers are lifted simultaneously.)
Here is what I've tried so far...
FIRST ATTEMPT - This just attempts to replace the standard PinchZoom's handleUpEvent with a custom one, and set this as the only interaction for the map. However, the event function is never called (never logs anything).
function handleUpEvent(evt) {
console.log("Up event handler");
return true; // Stop drag
}
map = new ol.Map({
layers: [],
target: 'map',
controls: controls,
interactions: [new ol.interaction.PinchZoom({handleEvent: handleUpEvent})],
view: new ol.View({projection: projCode})
});
SECOND ATTEMPT - This attempt is based on the actual OL code for creating the standard PinchZoom interaction. In this case, all my event handler functions DO get called, the number of touches ( targetPointers ) is always zero (as logged). I'm no javascript guru, but I suspect that this is because the symbols in ol.js are different to in ol-debug.js which I'm basing this on. In fact I had to declare targetPointers myself to even get this to run, even though it is declared by OL itself already (but presumably using a different symbol name in the non-debug version).
function handleDownEvent(mapBrowserEvent) {
console.log("DOWN event handler");
this.anchor_ = null;
this.lastDistance_ = undefined;
this.lastScaleDelta_ = 1;
mapBrowserEvent.map.render();
return true; // Start drag
}
function handleDragEvent(mapBrowserEvent) {
if ( this.targetPointers.length < 2 ) {
console.log("DRAG event ignored - touches ", this.targetPointers.length);
} else {
console.log("DRAG event handled");
var scaleDelta = 1.0;
var touch0 = this.targetPointers[0];
var touch1 = this.targetPointers[1];
var dx = touch0.clientX - touch1.clientX;
var dy = touch0.clientY - touch1.clientY;
// distance between touches
var distance = Math.sqrt(dx * dx + dy * dy);
if (this.lastDistance_ !== undefined) {
scaleDelta = this.lastDistance_ / distance;
}
this.lastDistance_ = distance;
if (scaleDelta != 1.0) {
this.lastScaleDelta_ = scaleDelta;
}
var map = mapBrowserEvent.map;
var view = map.getView();
var resolution = view.getResolution();
// scale anchor point.
var viewportPosition = map.getViewport().getBoundingClientRect();
var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
centroid[0] -= viewportPosition.left;
centroid[1] -= viewportPosition.top;
this.anchor_ = map.getCoordinateFromPixel(centroid);
// scale, bypass the resolution constraint
map.render();
ol.interaction.Interaction.zoomWithoutConstraints(
map, view, resolution * scaleDelta, this.anchor_);
}
}
function handleUpEvent(mapBrowserEvent) {
console.log("UP event handler");
return true; // Stop drag
}
var pinchZoom = new ol.interaction.Pointer({
handleDownEvent: handleDownEvent,
handleDragEvent: handleDragEvent,
handleUpEvent: handleUpEvent
});
pinchZoom.targetPointers = [];
map = new ol.Map({
interactions: ol.interaction.defaults().extend([pinchZoom]),
layers: [
new ol.layer.Tile({
source: new ol.source.TileJSON({url: 'https://api.tiles.mapbox.com/v3/mapbox.geography-class.json?secure'})
})
],
target: 'map',
view: new ol.View({center: [0, 0], zoom: 3})
});
The problems with your attempts:
First attempt
The option handleEvent for ol.interaction.PinchZoom does not exists. That is why your callback is not executed.
Second attempt
As you already noted you try to use openlayers internals. This might work with the debug build but will not work in the release build as the names are different than.
Solution
Add the option to keep a fractional resolution at the pinch zoom end in the openlayers sources.
Issue: https://github.com/openlayers/ol3/issues/6223
Pull request: https://github.com/openlayers/ol3/issues/6224
The pinch zoom will by default keep the fractional zoom level as chosen by the user .. starting with OpenLayers 3.20
An earlier idea:
As an example I added the option keepFractionalZoomLevel to ol.interaction.PinchZoom: https://github.com/aAXEe/ol3/commit/7639cb20d17858492652896bcd4a6ff7992a9bb0
See this fiddle for a working example: https://jsfiddle.net/wm78prro/5/
Note: The fiddle uses a custom openlayers build: https://github.com/aAXEe/ol3/pull/1
The deploy url is: https://deploy-preview-1--pump-attendant-rhinoceros-42285.netlify.com/ol-debug.js
Does this example behave as you want?
As this may be usefull for others we can try to integrate it into openlayers.