Changing color or icon to hotspots in GoogleVr View - google-vr

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
});

Related

Finding object being clicked in a set of overlapped objects

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');
}
};

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

Moving a shape in kineticJS towards mouse click

I am experimenting with Meteor and KineticJS. What I'm trying to do is to create a shape, and move it towards a mouse click on the stage. The position should be saved to the mongoDB so that all connected clients can be updated.
I haven't gotten far yet, but this is what I've got. I basically need a way to do two things:
How can I make a shape move towards the mouse click on the stage
and stop when it gets there?
Is there a better way of checking
the current position of a shape, other than the gameLoop that I
tried below? It works, but it feels wrong.
Thank you!
//client.js code
var player = null;
var playerAnim = null;
Template.container.rendered = function () {
var myCanvas = document.getElementById('myCanvas');
var stage = new Kinetic.Stage({
container: myCanvas,
width: 1024,
height: 1024
});
var layer = new Kinetic.Layer();
// add the layer to the stage
stage.add(layer);
// add click listener for the stage
$(stage.getContent()).on('click', function(evt) {
//stage was clicked
//Find the player shape in the database
Players.find().forEach(function (dbShape) {
player = new Kinetic.RegularPolygon(dbShape);
// setup an animation to move the player to the right
playerAnim = new Kinetic.Animation(function(frame) {
var velocity = 50;
var dist = velocity * (frame.timeDiff / 1000);
player.move(dist, 0);
Players.update(dbShape._id, {$set: {x: player.attrs.x, y: player.attrs.y}});
}, layer);
playerAnim.start();
layer.add(player);
layer.draw();
});
});
//make a gameLoop to check the position and stop the animation
Meteor.setInterval(function(gameLoop){
if(player.attrs.x > 500){
playerAnim.stop();
}
}, 500);
Meteor.autosubscribe(function () {
// clear the canvas
if (layer) {
layer.removeChildren();
layer.clear();
}
// populate the canvas with the Shapes collection
Players.find().forEach(function (dbShape) {
var player = new Kinetic.RegularPolygon(dbShape);
layer.add(player);
layer.draw();
});
});
}
I would use a tween to do this. Grab your object, get mouse position, and on mousedown or click create a Tween for your node to that mouse position.
layer.on('mousedown', function() {
var mousePos = stage.getMousePosition();
var tween = new Kinetic.Tween({
node: rect,
duration: 1,
x: mousePos.x,
y: mousePos.y,
opacity: 1,
strokeWidth: 6,
scaleX: 1.5
}).play();
});
JSFiddle
Note: To make the layer clickable, we need to add a transparent rectangle with the size of the width and height of the stage to the layer. See in the jsfiddle the Kinetic.Rect I made named var bg
Would putting the check inside the animation work for you?
playerAnim = new Kinetic.Animation(function(frame) {
var velocity = 50;
var dist = velocity * (frame.timeDiff / 1000);
player.move(dist, 0);
Players.update(dbShape._id, {$set: {x: player.attrs.x, y: player.attrs.y}});
if(player.getX() > 500){
this.stop();
}
}, layer);

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