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
Related
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
});
I'm making a map with OpenLayers 3, I have coordinates (EPSG:3857) in PostgreSQL and the map layer is OSM (same projection that the icons, EPSG:3857).
The problem is that when I increment the zoom, the icons disappear... But if I decrement then the icons won't disappear.
Someone told me that the projection's ICONS and LAYER must be the same.
Can someone help me, please?
I'm new in StackOverFlow,
Thank you for your time,
Enrique.
Note: My code is in JSFiddle, can see here: jsfiddle.net/y3sLksg6/
Try to set the style to each of your markers individually as in the exaple below, which is a copy from your jsfiddle:
function AddMarkers() {
//create a bunch of icons and add to source vector
var sizeY = Object.size(coordenadas);
var x = null;
var y = null;
for (var i = 0; i < sizeY; i++) {
x = coordenadas[i].Longitude;
y = coordenadas[i].Latitude;
var iconFeature = new ol.Feature({
geometry: new ol.geom.Point([x, y]),
name: 'Marker ' + i,
population: 4000,
rainfall: 500
});
markers[i] = [x, y];
var iconStyle = new ol.style.Style({
image: new ol.style.Icon(({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 0.75,
src: './img/circleRed.png'
}))
});
// This is new !
iconFeature.setStyle(iconStyle);
vectorSource.addFeature(iconFeature);
}
return vectorLayer;
}
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);
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?
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);