Finding object being clicked in a set of overlapped objects - paperjs

There is a circle at the intersection of two lines. How can I figure out if the mouse is clicked on top of circle, line 1, line 2, or somewhere else?
I have used if-then-else ladder to test object.hitTest within view.onClick function. I am testing for lines first and then circle.
sketch link
When I click at circle anywhere within overlapping area with lines, then all the three hitTest return HitResult. What can I do so that only circle hitTest passes through when I click anywhere on the circle.

I am not sure if this is what you are trying to do, but you can hit test all items at once by calling project.hitTest().
Only the topmost item will match.
Here is a sketch demonstrating the solution.
var myLine1 = new Path.Line({
from: [50, 50],
to: [150, 50],
strokeColor: 'black',
strokeWidth: 10,
name: 'myLine1'
});
var myLine2 = new Path.Line({
from: [150, 50],
to: [150, 100],
strokeColor: 'black',
strokeWidth: 10,
name: 'myLine2'
});
var myCircle = new Path.Circle({
center: [150, 50],
radius: 10,
strokeColor: 'red',
fillColor: 'red',
name: 'myCircle'
});
view.onClick = function(event) {
var hitTest = project.hitTest(event.point);
if (hitTest) {
alert('Clicked on item: ' + hitTest.item.name);
} else {
alert('Clicked elsewhere');
}
};

Related

Openlayers 3 Reproject EPSG:4326 vector to EPSG:3857

I am needing to transform GeoJSON vector data from EPSG:4326 to EPSG:3857...
I have a map...
var olMapDiv = document.getElementById('olmap');
control.map = new ol.Map({
target: olMapDiv,
renderer: 'canvas',
layers: layers,
interactions: ol.interaction.defaults({
altShiftDragRotate: false,
dragPan: false,
rotate: false
}).extend([new ol.interaction.DragPan({ kinetic: null })]),
pixelRatio: 1,
loadTilesWhileAnimating: true,
loadTilesWhileInteracting: true,
view: view
});
and a view...
var view = new ol.View({
// make sure the view doesn't go beyond the 22 zoom levels of Google Maps
maxZoom: 21,
projection: 'EPSG:3857',
center: [0, 0],
zoom: 0
});
I define my geoJson Object...
var geoJsonObj = {
'type': 'Feature',
'geometry': JSON.parse(shape),
'name': 'V',
'id': V.vID
}
I try to read the features into a open layers Vector object and provide projection parameters...
var vectorSource = new ol.source.Vector({
features: (new ol.format.GeoJSON()).readFeatures(geoJsonObj, {defaultDataProjection:"EPSG:4326",featureProjection:"EPSG:3857"})
});
Then I use the "vectorSource" above in a new Vector layer...
vectors = new ol.layer.Vector({
title: V.vID,
source: vectorSource,
id: V.vID,
name: 'V',
label: response.VList[key].Acres,
fill: response.VList[key].Shade,
stroke: defaultStrokeHex,
style: function (feature, resolution) {
var text = resolution * 100000 < 10 ? response.VList[key].Acres : '';
if (text != "") {
styleCache[text] = [new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#319FD3',
width: 1
}),
text: new ol.style.Text({
font: '12px Calibri,sans-serif',
text: text,
fill: new ol.style.Fill({
color: '#000'
}),
stroke: new ol.style.Stroke({
color: '#fff',
width: 3
})
}),
fill: new ol.style.Fill({
color: rcisWebMapUtilities.convertHex(response.VList[key].Shade, '0.5')
})
})];
}
else if (text == "") {
styleCache[text] = [new ol.style.Style({
fill: new ol.style.Fill({
color: rcisWebMapUtilities.convertHex(response.VList[key].Shade, '0.5')
})
})
]
} return styleCache[text];
}
});
No matter what I do I either see the vector drawn...but in EPSG:4326 or nothing loads...
I've spent way too much time trying to figure out how to get OpenLayers3 to do this...Any help is greatly appreciated!!
If you use EPSG:4326 in your view then your geojson vector declaration should be
var vectorSource = new ol.source.Vector({
features: (new ol.format.GeoJSON()).readFeatures(geojsonObject, {
dataProjection: 'EPSG:4326',
featureProjection:'EPSG:4326' })
});
If you use EPSG:3857 in your view use this:
var vectorSource = new ol.source.Vector({
features: (new ol.format.GeoJSON()).readFeatures(geojsonObject, {
dataProjection: 'EPSG:4326',
featureProjection:'EPSG:3857' })
});
Just to explain dataProjection is the source coords. Means the epsg of your coordinates within the geojson file. While featureProjection is the EPSG of your view and thus the EPSG of your map. Means is the EPSG original coords should be trasformed.
So try to remember this rule: featureProjection and ol.View projection declaration should be equal.
Note that I am assuming your geojson coords are projected in EPSG:4326.

Changing color or icon to hotspots in GoogleVr View

In Google Vr (Web View) it is possible to define hotspots by providing the following information:
vrView.addHotspot('hotspot_name', {
pitch: 30, // In degrees. Up is positive.
yaw: 20, // In degrees. To the right is positive.
radius: 0.05, // Radius of the circular target in meters.
distance: 2 // Distance of target from camera in meters.
});
However I see no way to set the shape or colour of the hotspots. So all hotspots are the same.
I would like to define hotspots that allow navigation (e.g. within a list of images/videos), either by providing controls similar to the VR view in YouTube (where the video control appear if you gaze at them and you move to the next or previous video), or simply by inserting hotspots that are different in shape and/or colour (e.g. with right and left arrow to indicate direction as in StreetView).
However I have not found any way to do it.
I see that in the underlying library, hotspots are defined as three's Object3D. I am not familiar with three but I suppose there should be a way to change the shape?
You have to change the embed.js code. There you can set color for the hotspot.
var innerMaterial = new THREE.MeshBasicMaterial({
color: 0x93c01f, side: THREE.DoubleSide, transparent: true,
opacity: MAX_INNER_OPACITY, depthTest: false
});
var outerMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff, side: THREE.DoubleSide, transparent: true,
opacity: MAX_OUTER_OPACITY, depthTest: false
});
To change shape you have to edit three.js and include function BoxGeometry and dependencies from:
https://threejs.org/build/three.js
In order to apply different colors within the same scene, you can change these following lines of code in embed.js:
HotspotRenderer.prototype.add = function(pitch, yaw, radius, distance, id, color) {
var hotspot = this.createHotspot_(radius, distance, color);
}
HotspotRenderer.prototype.createHotspot_ = function(radius, distance, givenColor) {
if (!givenColor) {
givenColor = 0xffffff;
}
var innerMaterial = new THREE.MeshBasicMaterial({
color: givenColor, side: THREE.DoubleSide, transparent: true,
opacity: MAX_INNER_OPACITY, depthTest: false
});
var outerMaterial = new THREE.MeshBasicMaterial({
color: givenColor, side: THREE.DoubleSide, transparent: true,
opacity: MAX_OUTER_OPACITY, depthTest: false
});
}
function onAddHotspot(e) {
var id = e.id;
var color = e.color;
worldRenderer.hotspotRenderer.add(pitch, yaw, radius, distance, id, color);
}
… in vrview.js:
Player.prototype.addHotspot = function(hotspotId, params) {
var data = {
pitch: params.pitch,
yaw: params.yaw,
radius: params.radius,
distance: params.distance,
id: hotspotId,
color: params.color
};
this.sender.send({type: Message.ADD_HOTSPOT, data: data});
};
… and in your upper-level JavaScript for the player:
vrView.addHotspot('hotspot-1', {
pitch: 1,
yaw: 10,
radius: 0.2,
distance: 1,
color: 0xffaa00
});
vrView.addHotspot('hotspot-2', {
pitch: -1,
yaw: -20,
radius: 0.2,
distance: 1,
color: 0x22fefe
});

Using MathBox2 and A-Frame together

MathBox is great math visualization tool created on top of Three.js and ShaderGraph.js. I'm currently working on explorable explanations for college math and want to use both A-Frame and MathBox in single project (A-Frame driving webVR stuff, scenes, events, physics, roomscale for HTC Vive and MathBox for 2D/3D math visualizations, animations).
I've asked about this in MathBox's google group here: https://groups.google.com/forum/#!topic/mathbox/FwCxKeNQ0-g
Steve Wittens (creator of MathBox) answered:
"A-Frame and MathBox are both based on three.js so the compatibility is possible in theory, but nobody has made the necessary bindings. You'd probably want to look at the examples/test/context.html example to help you figure out how to connect on the MathBox side."
Code from examples/test/context.html that he mentions:
var WIDTH = 640;
var HEIGHT = 480;
// Vanilla Three.js
var renderer = new THREE.WebGLRenderer();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, WIDTH / HEIGHT, .01, 1000);
// Insert into document
document.body.appendChild(renderer.domElement);
// MathBox context
var context = new MathBox.Context(renderer, scene, camera).init();
var mathbox = context.api;
// Set size
renderer.setSize(WIDTH, HEIGHT);
context.resize({ viewWidth: WIDTH, viewHeight: HEIGHT });
// Place camera and set background
camera.position.set(0, 0, 3);
renderer.setClearColor(new THREE.Color(0xFFFFFF), 1.0);
// MathBox elements
view = mathbox
.set({
focus: 3,
})
.cartesian({
range: [[-2, 2], [-1, 1], [-1, 1]],
scale: [2, 1, 1],
});
// Initialize grid (something similar like aframe-gridhelper-component)
view.grid({
divideX: 30,
width: 1,
opacity: 0.5,
zBias: -5,
});
// Animated Math.sin() function
view.interval({
id: 'sampler',
width: 64,
expr: function (emit, x, i, t) {
y = Math.sin(x + t) * .7;
emit(x, y);
},
channels: 2,
});
How should I connect a MathBox and A-Frame?
Maybe a mathbox system?
AFRAME.registerSystem('mathbox', {
init: function () {
var sceneEl = this.el;
if (!sceneEl.renderStarted) {
return sceneEl.addEventListener('renderstart', this.init.bind(this));
}
this.context = new MathBox.Context(sceneEl.renderer, sceneEl.object3D, sceneEl.camera).init();
this.mathbox = this.context.api;
// MathBox elements
this.view = mathbox
.set({
focus: 3,
})
.cartesian({
range: [[-2, 2], [-1, 1], [-1, 1]],
scale: [2, 1, 1],
});
};
}
});
And then you can write components that talk to mathbox.
AFRAME.registerComponent('mathbox-grid', {
init: function () {
var view = this.el.sceneEl.systems.mathbox.view;
view.grid({
divideX: 30,
width: 1,
opacity: 0.5,
zBias: -5,
});
}
});
And then just:
<a-scene>
<a-entity mathbox-grid></a-entity>
</a-scene>
Though you will probably need to bind a lot more stuff like positioning, rotation, scale? You can also prefab it:
AFRAME.registerPrimitive('a-mb-grid', {
defaultComponents: {'mathbox-grid': {}}
});
<a-mb-grid></a-mb-grid>
Alternative / Quick Path
if you don't want the declarative goodness, let A-Frame create the scene/renderer/camera, and use the three.js object3Ds directly in combination with mathbox. Here is documentation about scene and how to access its object3Ds

Click Listeners in Loop - Array and Closure

I realise I'm treading on thin ice opening another closure issue, but I have searched and can't find an answer to my issue. I have a Google Maps API v3 page which generates two maps from one block of code - a small map centered on the user's current location and a larger map showing the whole area with the user's location marked where it is, center or not. On top of the map is a rectangle layer consisting of 14 rectangles. In order to generate the two maps, I have had to put the rectangles in a 2 dimensional array, rectangles[1] for 'map', and rectangles[2] for 'map2':
var rectangles = [0,1,2,3,4,5,6,7,8,9,10,11,12,13];
rectangles[1][0]=new google.maps.Rectangle({
bounds:new google.maps.LatLngBounds(new google.maps.LatLng(a, b), new google.maps.LatLng(x, y)),
map:map,
fillColor:'red',
fillOpacity: 0.3,
strokeOpacity: 0,
url: 'http://example.com',
clickable: true
});
rectangles[2][0]=new google.maps.Rectangle({
bounds:new google.maps.LatLngBounds(new google.maps.LatLng(a, b), new google.maps.LatLng(x, y)),
map:map2,
fillColor:'red',
fillOpacity: 0.3,
strokeOpacity: 0,
url: 'http://example.com',
clickable: true
});
...and so on. It all works fine and the two maps are displayed and the geolocation works. Now I want to add a click listener for each rectangle but I'm not sure who to reference the array. This is what I have now:
for ( i = 0; i < rectangles[1].length; i++ ){
google.maps.event.addListener(rectangles[1][i], 'click', function() {
window.location.href = this.url;
});
}
for ( x = 0; x < rectangles[2].length; x++ ){
google.maps.event.addListener(rectangles[2][x], 'click', function() {
window.location.href = this.url;
});
}
Which obviously won't work. I have seen various solutions to the closure issue, but I'm not sure I'm even heading in the right direction in referencing the two arrays of rectangles - or if I even need to define two different sets of click listeners. I'd be really grateful if someone could point me in the right direction - and sorry if this is just going over old ground that appears obvious. There's always a new learner coming along who is trying hard to catch up.
Thanks.
//First, set up `rectangles` as an array containing two arrays.
var rectangles = [];
rectangles[0] = [];
rectangles[1] = [];
//As `google.maps.Rectangle` doesn't accept a `url` option,
//its url needs to be defined separately from the rectangle itself,
//but in such a way that the two are associated with each other.
//For this we can use a javascript plain object.
rectangles[0][0] = {
rect: new google.maps.Rectangle({
bounds: new google.maps.LatLngBounds(new google.maps.LatLng(a, b), new google.maps.LatLng(x, y)),
map: map,
fillColor: 'red',
fillOpacity: 0.3,
strokeOpacity: 0,
clickable: true
}),
url: 'http://example.com'
};
rectangles[1][0] = new google.maps.Rectangle({
...
});
rectangles[0][1] = new google.maps.Rectangle({
...
});
rectangles[1][1] = new google.maps.Rectangle({
...
});
rectangles[0][2] = new google.maps.Rectangle({
...
});
rectangles[1][2] = new google.maps.Rectangle({
...
});
//Now to attach the click listeners.
//First we define a function that adds a click listener.
//By doing this in its own function, a closure is formed,
//trapping the formal variable `rectObj` and making `rectObj.url`
//accessible to the listener when it is called in response to future clicks.
function addClickListener(rectObj) {
google.maps.event.addListener(rectObj.rect, 'click', function() {
window.location.href = rectObj.url;
});
}
//Now, we can loop through the `rectangles` arrays, adding listeners.
for ( i = 0; i < 2; i++ ) {
for ( j = 0; j < 14; j++ ) {
if(rectangles[i][j]) {//safety
addClickListener(rectangles[i][j]);
}
}
}

Is there a way to fade out a V3 google.maps.Polygon?

Is there a way to fade out a V3 google.maps.Polygon?
Instead of just hiding / removing a standard Google Maps V3 polygon I want to fade it out.
Is this possible? Are there any plugins out there?
The following is a solution I created to address the uniform fade out of stroke and fill and I made it easily reusable by making it a function.
seconds is how long it will take the fade out to occur and callback so you could do perform another action once it completes.
In my project my callback function removes the polygon from the map and deletes the variable.
function polygon_fadeout(polygon, seconds, callback){
var
fill = (polygon.fillOpacity*50)/(seconds*999),
stroke = (polygon.strokeOpacity*50)/(seconds*999),
fadeout = setInterval(function(){
if(polygon.strokeOpacity + polygon.fillOpacity <= 0.0){
clearInterval(fadeout);
polygon.setVisible(false);
if(typeof(callback) == 'function')
callback();
return;
}
polygon.setOptions({
'fillOpacity': Math.max(0, polygon.fillOpacity-fill),
'strokeOpacity': Math.max(0, polygon.strokeOpacity-stroke)
});
}, 50);
}
Use Javascript setInterval()/clearInterval() to change the opacity of the polygon incrementally. Something like this:
var opacity = [1, 0.8]
var polygon = new google.maps.Polygon({
strokeColor: "#000099",
strokeOpacity: opacity[0],
strokeWeight: 2,
fillColor: "#0000FF",
fillOpacity: opacity[1],
paths: [ /* your points here */ ]
});
var interval = setInterval(function() {
if (opacity[0] <= 0.0 && opacity[1] <= 0.0) {
clearInterval(interval);
polygon.setVisible(false);
} else {
opacity[0] = Math.max(0.0, opacity[0] - 0.1);
opacity[1] = Math.max(0.0, opacity[1] - 0.1);
polygon.setOptions({strokeOpacity: opacity[0], fillOpacity: opacity[1]});
}
}, 50);

Resources