I have a raycaster-intersection event listener attached to my right laser controller like so:
<a-entity id="rightController" laser-controls="hand: right" raycaster="objects: .collidable; far: 20"></a-entity>
...
rightController.addEventListener("raycaster-intersection", function(e) {
console.log(e);
});
Collidable object example:
<a-sphere class="collidable" color="yellow" radius="5" position="31.617 7.159 -10.258" scale="0.1 0.1 0.1"></a-sphere>
When the raycaster intersects with a .collidable object the event is successfully emitted but I can't find any information on the intersected object from the event variable (e). I really don't want to have to attach events to each of these objects because I'd have like 20 event listeners.
Console output:
It's like the event output is incomplete...Aframe docs imply the 'els' array should contain what I'm looking for but it's empty. There's also no .getIntersection() function. Any ideas?
Using the A-Frame 1.2.0
The event you want to listen to is: raycaster-intersected
then you can get the intersection data from: evt.detail.el.components.raycaster that also has the getIntersection(el) method
Example below.
<!DOCTYPE HTML>
<head>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
</head>
<body>
<script>
AFRAME.registerComponent('cursor-listener', {
init: function () {
this.el.addEventListener('raycaster-intersected', evt => {
this.raycaster = evt.detail.el;
});
this.el.addEventListener('raycaster-intersected-cleared', evt => {
this.raycaster = null;
});
},
tick: function () {
if (!this.raycaster) { return; } // Not intersecting.
let intersection = this.raycaster.components.raycaster.getIntersection(this.el);
if (!intersection) { return; } // Not intersecting
// intersecting
console.log(intersection);
}
});
</script>
<a-scene id="scene">
<a-entity cursor="rayOrigin: mouse" raycaster="objects: .cursor-listener"></a-entity>
<a-plane
position="0 1.6 -1"
height="2"
width="1.5"
color="red"
class="cursor-listener"
cursor-listener
id="element"
></a-plane>
</a-scene>
</body>
Related
Following my previous question:Load json map into aframe by aframe-geo-projection-component created by earthlinginteractive
Now, I am trying to find a way to show different geometries (country or city) in AFrame each with a different color,
looking for something like this.
I do not mind which library or approach are used, Found this but not sure how to convert this D3.js to Aframe. However as the community bot asked me to provide more details, I have put the code that I am working on below. Not sure which part I need to modify to have such a color coded map but I am working on that. Again, any working example works for me even if someone can show what I need to modify in the below code.
<!-- https://github.com/EarthlingInteractive/aframe-geo-projection-component
https://earthlinginteractive.github.io/aframe-geo-projection-component/ -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A-Frame Geo Projection Component - U.S. Population 2017 (est.)</title>
<meta name="description" content="Visualization of estimated U.S. population by state in 2017"></meta>
<script src="https://aframe.io/releases/0.7.1/aframe.min.js"></script>
<script src="//cdn.rawgit.com/donmccurdy/aframe-extras/v3.12.4/dist/aframe-extras.min.js"></script>
<script src="https://unpkg.com/super-hands#2.1.0/dist/super-hands.min.js"></script>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-queue.v3.js"></script>
<script src="https://earthlinginteractive.github.io/aframe-geo-projection-component/dist/aframe-geo-projection-component.min.js"></script>
</head>
<body>
<script>
AFRAME.registerComponent('geo-extrude-population-renderer', {
dependencies: ['geo-projection'],
schema: {
maxExtrudeHeight: {
default: 2
}
},
init: function () {
this.system = this.el.sceneEl.systems['geo-projection'];
this.geoProjectionComponent = this.el.components['geo-projection'];
this.geoJsonReady = this.geoJsonReady.bind(this);
// Wait for geoJson to finish loading to avoid race conditions
this.el.addEventListener('geo-src-loaded', this.geoJsonReady);
},
update: function (oldData) {
if (!this.geoProjectionComponent.geoJson) {
return;
}
if (this.data.maxExtrudeHeight !== oldData.maxExtrudeHeight) {
this.geoJsonReady();
}
},
geoJsonReady: function () {
// Now kick off loading the data
d3.queue()
.defer(d3.csv, 'https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/us-population-2017.csv?v=1657704183925', function (d) {
return {
state: d.state,
population: +d.population
}
})
.defer(d3.csv, 'https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/us-state-county-geocodes-v2016.csv?v=1657704190160')
.await(this.onDataLoaded.bind(this));
},
onDataLoaded: function(error, populationData, geocodes) {
if (error) throw error;
var stateFips = geocodes.filter(function (row) {
return row['Summary_Level'] === '040'
});
var maxPopulation = d3.max(populationData, function (d) {
return d.population;
});
var populationByFipsCode = populationData.reduce(function (accum, d) {
var fipsForState = stateFips.find(function (fipsRow) { return fipsRow['Area_Name'] === d.state; });
var fipsCode = fipsForState['State_Code_FIPS'];
accum[fipsCode] = d.population;
return accum;
}, {});
this.render(populationByFipsCode, maxPopulation, this.data.maxExtrudeHeight);
},
render: function (populationByFipsCode, maxPopulation, maxExtrudeHeight) {
if (!populationByFipsCode) return;
var material = [];
var extrudeGeometry = null;
var stateOutlineVertices = [];
// Split the geoJson into features and render each one individually so that we can set a different
// extrusion height for each based on the population.
this.geoProjectionComponent.geoJson.features.forEach(function (feature) {
var population = populationByFipsCode[feature.id];
var extrudeAmount = (population / maxPopulation) * maxExtrudeHeight;
var material="#999999";
const extrudeSettings = {
amount: extrudeAmount,
bevelEnabled: false
};
//Math.floor(Math.random()*16777215).toString(16);
var mapRenderContext = this.system.renderToContext(feature, this.geoProjectionComponent.projection);
const stateShapes = mapRenderContext.toShapes();
// Gather the outline of the state and set the height of the outline to the extrude level
// so that the top of the state is outlined
stateOutlineVertices = stateOutlineVertices.concat(mapRenderContext.toVertices(extrudeAmount));
// Merge all the extruded feature geometries together for better rendering performance
// Need to use ExtrudeGeometry here instead of ExtrudeBufferGeometry because the latter doesn't merge properly
// in this version of Three.js
var extrudedFeatureGeometry = new THREE.ExtrudeGeometry(stateShapes, extrudeSettings);
if (!extrudeGeometry) {
extrudeGeometry = extrudedFeatureGeometry;
} else {
extrudeGeometry.merge(extrudedFeatureGeometry);
}
}.bind(this));
// Convert the extrude geometry into a buffer geometry for better rendering performance
var extrudeBufferGeometry = new THREE.BufferGeometry();
extrudeBufferGeometry.fromGeometry(extrudeGeometry);
var sideMaterial = new THREE.MeshStandardMaterial( { color: 0xaaafff } );
var extrudedMap = new THREE.Mesh(extrudeBufferGeometry, [material, sideMaterial]);
this.el.setObject3D('map', extrudedMap);
var stateOutlineGeometry = new THREE.BufferGeometry();
stateOutlineGeometry.addAttribute('position', new THREE.Float32BufferAttribute(stateOutlineVertices, 3));
var stateOutlineMaterial = new THREE.LineBasicMaterial( { color: 0xcccaaa } );
var stateOutlines = new THREE.LineSegments(stateOutlineGeometry, stateOutlineMaterial);
this.el.setObject3D('lines', stateOutlines);
}
});
</script>
<a-scene>
<a-assets>
<a-asset-item id="json-us" src="https://unpkg.com/us-atlas#1/us/10m.json" />
</a-assets>
<a-sky color="#ECECEC"></a-sky>
<a-entity id="map"
rotation="-90 0 0"
material="color: #123456;"
topojson-loader="src: #json-us; topologyObject: states;"
geo-projection="
projection: geoIdentity;
height: 6;
width: 10;"
geo-extrude-population-renderer
>
</a-entity>
<a-locomotor>
<a-entity hand-controls="left" super-hands></a-entity>
<a-entity hand-controls="right" super-hands></a-entity>
</a-locomotor>
</a-scene>
</body>
</html>
<!--material="color: #123456;"-->
getting this error as well if I use aframe 1.3.0:
Uncaught TypeError: extrudeBufferGeometry.fromGeometry is not a function
at i.render ((index):104:29)
at i.onDataLoaded ((index):66:12)
at Queue._call (d3-queue.v3.js:43:54)
at maybeNotify (d3-queue.v3.js:120:7)
at d3-queue.v3.js:91:12
at Object.<anonymous> (d3.v4.js:11470:86)
at Dispatch.call (d3.v4.js:792:72)
at XMLHttpRequest.respond (d3.v4.js:11395:13)
I am simply trying to load three rectangular images and want them to align horizontally adjacent to each other. I thought setting the left property in fabric.Image.fromURL method would accomplish this but the tree images load stacked on top of each other. Also even though I include selectable:true, the images are not selectable. Am I using fabric.Image.fromURL incorrectly?
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<!-- Get version 1.1.0 of Fabric.js from CDN -->
<script src="js/fabric.js"></script>
<!-- Get the highest 1.X version of jQuery from CDN. Required for ready() function. -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(function () {
var canvas = new fabric.Canvas('canvas', {
backgroundColor: 'black',
selectionColor: 'blue',
selectionLineWidth: 0
// ...
});
debugger;
var tiles = [
"images/Green.png",
"images/Red.png",
"images/Yellow.png"
];
var offset = [
"0",
"200",
"400"
];
debugger;
for (i = 0; i < tiles.length; i++) {
fabric.Image.fromURL(tiles[i], function (img) {
img.scale(1.0).set({
left: offset[i],
top: 0,
selectable:true,
});
canvas.add(img).setActiveObject(img);
});
}
function handleDragStart(e) {
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
this.classList.add('img_dragging');
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
// NOTE: comment above refers to the article (see top) -natchiketa
return false;
}
function handleDragEnter(e) {
// this / e.target is the current hover target.
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over'); // this / e.target is previous target element.
}
function handleDrop(e) {
// this / e.target is current target element.
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
var img = document.querySelector('#images img.img_dragging');
console.log('event: ', e);
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative
// to the canvas container.
left: e.layerX,
top: e.layerY
});
canvas.add(newImage);
return false;
}
function handleDragEnd(e) {
// this/e.target is the source node.
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
}
// Bind the event listeners for the image elements
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
// Bind the event listeners for the canvas
var canvasContainer = document.getElementById('canvas-container');
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
});
</script>
</head>
<body>
<div id="canvas-container">
<canvas id="canvas" width="600" height="200"></canvas>
</div>
<div id="images">
<img draggable="true" src="images/Red.png" width="50" height="50"></img>
<img draggable="true" src="images/Yellow.png" width="50" height="50"></img>
<img draggable="true" src="images/Green.png" width="50" height="50"></img>
</div>
</body>
</html>
There is a race condition between your fabric.Image.fromURL callback and the execution of the for loop, which prevents the images from showing.
If you print i and offset[i] inside your callback:
fabric.Image.fromURL(tiles[i], function (img) {
console.log(i, offset[i]);
...
});
you'll notice that i is 3 and offset[i] is undefined in all off the callbacks.
This happens because each of the callbacks retain a reference to the same instance of the i variable, and use its latest value at the time they execute. The for loop loops through its cycles, each time incrementing i, before any of your callbacks execute, leaving i with a final value of 3. When your callbacks execute, they try to set the left: value to offset[3], which is undefined, and fabric falls back to 0.
Solution
Variables in pre-ES6 javascript (unlike most languages) are only scoped to the function they are declared it, and code-blocks do not affect scope.
Place your image build logic into its own function, passing the current value of i as a parameter in each loop cycle. This will place the value into a new scope, and preserve the value.
Also, don't forget the var keyword in front of your i. Otherwise you're affecting the global namespace, which you want to avoid at all cost.
Here's what your code should look like:
for (var i = 0; i < tiles.length; i++) {
buildImage(i);
}
// creates a new scope for the passed in value of i when called:
function buildImage(i) {
fabric.Image.fromURL(tiles[i], function (img) {
img.scale(1.0).set({
left: offset[i],
top: 0,
selectable: true,
});
canvas.add(img).setActiveObject(img);
});
}
Finally, you may want to consider setting the originX and originY properties of the images, like so:
originX: 'left',
originY: 'top'
By default, fabric will use the center of the images as the point of origin when placing them, and your images will not be in full view of the canvas.
Update
Here is the fully working example. It is dependent on fabric, which needs to be placed in your js/ folder.
Look at this code:
This creates four circles on the map in a same position and it creates addListener click event for each one too but I just can click on the last one. I want to fix it in a way that I can click on all of them to make setEditable(true) for each one.
<!DOCTYPE html>
<html>
<head>
<script
src="http://maps.googleapis.com/maps/api/js?key=AIzaSyDY0kkJiTPVd2U7aTOAwhc9ySH6oHxOIYM&sensor=false">
</script>
<script>
var selectedShape;
function clearSelection()
{
if(selectedShape)
{
selectedShape.setEditable(false);
selectedShape = null;
}
}
function setSelection(shape)
{
clearSelection();
selectedShape = shape;
shape.setEditable(true);
}
</script>
<script>
var amsterdam=new google.maps.LatLng(52.395715,4.888916);
function initialize()
{
var mapProp = {center:amsterdam, zoom:7, mapTypeId:google.maps.MapTypeId.ROADMAP};
var map = new google.maps.Map(document.getElementById("googleMap"),mapProp);
var myArray = [];
var myCity;
for(var i = 0; i < 4; i++)
{
myCity = new google.maps.Circle({
center:amsterdam,
radius:20000,
strokeColor:"#0000FF",
strokeOpacity:0.8,
strokeWeight:2,
fillColor:"#0000FF",
fillOpacity:0.4
});
myArray.push(myCity);
google.maps.event.addListener(myCity, 'click', function() {setSelection(myCity)});
myArray[i].setMap(map);
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="googleMap" style="width:500px;height:380px;"></div>
</body>
</html>
Use this instead of myCity :
google.maps.event.addListener(myCity, 'click', function() {
setSelection(this)
});
Using setSelection(myCity) will refer to the last myCity created.
Does someone know if Strobe Media Playback (OSMF) has events like:
Streaming is ended
Streaming is started
Streaming error
in order to access it via JavaScript?
I have tried this but no joy.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Strobe Media Playback</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
<script type="text/javascript">
// Create a StrobeMediaPlayback configuration
// http://mediapm.edgesuite.net/strobe/content/test/AFaerysTale_sylviaApostol_640_500_short.flv
// http://osmf.org/dev/1.6-sprint-2/hello-world-jquery-plugin.html#
var parameters =
{
src: "http://stream.flowplayer.org/bauhaus/624x260.mp4"
, autoPlay: true
, controlBarAutoHide: false
, javascriptCallbackFunction: "onJavaScriptBridgeCreated"
};
// Embed the player SWF:
swfobject.embedSWF
( "StrobeMediaPlayback.swf"
, "strobeMediaPlayback"
, 640
, 480
, "10.1.0"
, {}
, parameters
, { allowFullScreen: "true"}
, { name: "strobeMediaPlayback" }
);
function completeFunc(time, playerId) {
//var player = document.getElementById(playerId);
alert("!!!");
}
function onCurrentTimeChange(time, playerId)
{
document.getElementById("currentTime").innerHTML = time;
}
function onDurationChange(time, playerId)
{
document.getElementById("duration").innerHTML = time;
}
var player = null;
function onJavaScriptBridgeCreated(playerId)
{
if (player == null) {
player = document.getElementById(playerId);
// Add event listeners that will update the
player.addEventListener("currentTimeChange", "onCurrentTimeChange");
player.addEventListener("durationChange", "onDurationChange");
player.addEventListener("complete", "completeFunc");
// Pause/Resume the playback when we click the Play/Pause link
document.getElementById("play-pause").onclick = function(){
var state = player.getState();
if (state == "ready" || state == "paused") {
player.play2();
}
else
if (state == "playing") {
player.pause();
}
return false;
};
}
}
</script>
</head>
<body>
<div>
<div>
<span id="currentTime" /> ... </span> : <span id="duration" /> ... </span>
</div>
<a href="#" id="play-pause">Play/Pause</div>
</div>
<div id="strobeMediaPlayback">
<p>Alternative content</p>
</div>
</body>
</html>
Thanks!
P.S. As I can see here we can use STATE variable to detect the end of the video.
So in the end of the video it goes to "PAUSED". Can we use it in this manner?
UPDATE:
I found that onJavaScriptBridgeCreated method doesn't triggering.
The same happens with all examples of SMP.
Any clue?
there it is.
function changeVidSrc(url, posterUrl, id, width, height, autoplay) {
var flashvars =
{
src: url,
autoPlay: autoplay,
controlBarAutoHide: true,
poster: posterUrl,
skin: 'skinPath', //i just needed skin, remove if not needed
javascriptCallbackFunction: "onJavaScriptBridgeCreated"
};
var parameters = { allowFullScreen: "true"};
var attributes = { name: id };
// Embed the player SWF:
swfobject.embedSWF
('/swfs/StrobeMediaPlayback.swf',
id, width, height,
"10.1.0",
'',
flashvars,
parameters,
attributes
);
}
Some important details:
1. i couldn't let it debug with firebug correctly. i can't understand behaviour.
2. passing name in attributes object is important for FF. otherwise callback triggers, but no events handled
i would also advise to put global var player and handler functions on top.
Streaming is ended:
var onJavaScriptBridgeCreated = function (event) {
if (event === ''onJavaScriptBridgeCreated'') {
var player = document.getElementById('the id of the object tag');
var callback = function(event){console.log(event);};
//stopped
player.addEventListener("complete", "callback");
//started
player.addEventListener("playStateChange", "callback");
//error
player.addEventListener("mediaError", "callback");
}
}
Is there an easy way to auto-resize a jqplot chart when resizing the browser? I have been looking on Google, and haven't found anything.
Resizing jqplots is discussed here.
To make it work when the browser is resized, bind the replot function up with the window.resize event:
$(window).resize(function() {
plot1.replot( { resetAxes: true } );
});
Running code:
$(document).ready(function(){
var plot1 = $.jqplot ('container', [[3,7,9,1,4,6,8,2,5]], {});
$(window).resize(function() {
plot1.replot( { resetAxes: true } );
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/jquery.jqplot.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/jquery.jqplot.min.css">
<div id="container"></div>
I've found that using replot doesn't always give consistent results if you've got a lot of options on your graphs. The only way I've found to have complex graphs cope with window resizes is to get brutal and destroy it and rebuild it.
function () {
var plot;
var renderGraph = function() {
plot = $.jqplot('chartId', yourChartData, yourChartOptions);
}
renderGraph();
var resizeGraph = function() {
if (plot)
plot.destroy();
renderGraph();
}
$(window).resize(function() {
resizeGraph();
});
}