aframe state component: bind-for does not update entities - aframe

I'm trying to render entities based on an array in the state:
[
{ id: 'left', color: 'red', position: '-1 1 -4' },
{ id: 'right', color: 'green', position: '1 1 -4' }
]
The entities are displayed successfully according to
<a-entity bind-for="for: marker; in: markers; key: id"> <!-- ; updateInPlace: true ?? -->
<template>
<a-sphere color="{{marker.color}}" position="{{marker.position}}"></a-sphere>
<!-- <a-sphere bind-item__color="marker.color" bind-item__position="marker.position"></a-sphere> --><!-- state.marker is undefined -->
</template>
</a-entity>
I cannot get the entities to update when I alter the array (adding or removing elements) or manually setting the __dirty flag.
handlers: {
setMarkers: function (state, data) {
state.markers.length = 0;
state.markers.push(...data.markers);
},
updateMarkers: function (state, data) {
state.markers.__dirty = true;
}
}
Neither replacing the array elements nor setting the dirty flag works.
el.addEventListener('click', function () {
markers[0].color = markers[0].color === 'red' ? 'blue' : 'red';
//el.sceneEl.emit('setMarkers', {markers});
el.sceneEl.emit('updateMarkers', {});
});
Questions:
how can I make the state component update the entities when an array (or its items) has been updated?
when should I use updateInPlace?
why cannot I use bind-item__color="marker.color", as specified in the documentation? -- I get state.marker is undefined.
Glitch link: https://glitch.com/~aframe-state-bind-for-help
<html>
<head>
<title>Score</title>
<meta name="description" content="Incrementing and decrementing score"></meta>
<meta property="og:image" content="https://raw.githubusercontent.com/supermedium/superframe/master/components/state/examples/score/preview.gif"></meta>
<script src="https://aframe.io/releases/0.9.0/aframe.min.js"></script>
<script src="https://unpkg.com/aframe-state-component#6.7.0/dist/aframe-state-component.min.js"></script>
<script>
AFRAME.registerState({
initialState: {
markers: []
},
handlers: {
setMarkers: function (state, data) {
state.markers.length = 0;
state.markers.push(...data.markers);
},
updateMarkers: function (state, data) {
state.markers.__dirty = true;
}
}
});
let markers = [
{ id: 'left', color: 'red', position: '-1 1 -4' },
{ id: 'right', color: 'green', position: '1 1 -4' }
];
AFRAME.registerComponent('decrease-action', {
init: function () {
var el = this.el;
el.addEventListener('click', function () {
markers[0].color = markers[0].color === 'red' ? 'blue' : 'red';
//el.sceneEl.emit('setMarkers', {markers});
el.sceneEl.emit('updateMarkers', {});
});
el.sceneEl.addEventListener('stateupdate', function (e) {
console.log(JSON.stringify(e.detail.state.markers, null, 2));
});
el.sceneEl.emit('setMarkers', {markers});
}
});
AFRAME.registerComponent('increase-action', {
init: function () {
var el = this.el;
el.addEventListener('click', function () {
markers[1].color = markers[1].color === 'green' ? 'blue' : 'green';
//el.sceneEl.emit('setMarkers', {markers});
el.sceneEl.emit('updateMarkers', {});
});
}
});
</script>
</head>
<body>
<a-scene>
<a-entity text="value: Change left color; align: center; width: 2" geometry="primitive: plane" material="color: black" position="-1 1 -3" decrease-action></a-entity>
<a-entity text="value: Change right color; align: center; width: 2" geometry="primitive: plane" material="color: black" position="1 1 -3" increase-action></a-entity>
<a-entity bind-for="for: marker; in: markers; key: id"> <!-- ; updateInPlace: true ?? -->
<template>
<a-sphere color="{{marker.color}}" position="{{marker.position}}"></a-sphere>
<!-- <a-sphere bind-item__color="marker.color" bind-item__position="marker.position"></a-sphere> --><!-- state.marker is undefined -->
</template>
</a-entity>
<a-sky color="#24CAFF"></a-sky>
<a-camera>
<a-cursor color="#FAFAFA"></a-cursor>
</a-camera>
</a-scene>
</body>
</html>
Thank you.

Related

Font awesome icon in Vue.js does not display

I am trying to add a font-awesome arrow icon via my css code like this:
<style>
.negative {
color: red;
}
.positive {
color: green;
}
.negative::after {
content: "\f106";
}
</style>
I have font-awesome included in my html via CDN. For some reason my icon does not display properly, it just shows a square. Any ideas why and how I can fix it?
Here is the rest of my code, showing the logic behind the displaying of percentages:
<template>
<div>
<v-data-table
:headers="headers"
:items="rowsToDisplay"
:hide-default-footer="true"
class="primary"
>
<template #item.thirtyDaysDiff="{ item }">
<span :class="item.thirtyDaysDiffClass">{{ item.thirtyDaysDiff }}%</span>
</template>
<template #item.sevenDaysDifference="{ item }">
<span :class="item.sevenDaysDiffClass">{{ item.sevenDaysDiff }}%</span>
</template>
</v-data-table>
</div>
</template>
<script>
import axios from 'axios';
export default {
data () {
return {
bitcoinInfo: [],
isPositive: false,
isNegative: false,
headers: [
{
text: 'Currency',
align: 'start',
value: 'currency',
},
{ text: '30 Days Ago', value: '30d' },
{ text: '30 Day Diff %', value: 'thirtyDaysDiff'},
{ text: '7 Days Ago', value: '7d' },
{ text: '7 Day Diff %', value: 'sevenDaysDifference' },
{ text: '24 Hours Ago', value: '24h' },
],
}
},
methods: {
getBitcoinData() {
axios
.get('data.json')
.then((response => {
var convertedCollection = Object.keys(response.data).map(key => {
return {currency: key, thirtyDaysDiff: 0, sevenDaysDifference: 0, ...response.data[key]}
})
this.bitcoinInfo = convertedCollection
}))
.catch(err => console.log(err))
},
calculateDifference(a, b) {
let calculatedPercent = 100 * Math.abs((a - b) / ((a + b) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
},
getDiffClass(a, b) {
return a > b ? 'positive': a < b ? 'negative' : ''
},
calculateSevenDayDifference(item) {
let calculatedPercent = 100 * Math.abs((item['24h'] - item['7d']) / ((item['24h'] + item['7d']) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
}
},
computed: {
rowsToDisplay() {
return Object.keys(this.bitcoinInfo)
.map(key => {
return {
currency: key,
...this.bitcoinInfo[key]
}
}).map((item) => ({
...item,
thirtyDaysDiff: this.calculateDifference(item['7d'], item['30d']),
thirtyDaysDiffClass: this.getDiffClass(item['7d'], item['30d']),
sevenDaysDiff: this.calculateDifference(item['24h'], item['7d']),
sevenDaysDiffClass: this.getDiffClass(item['24h'], item['7d']),
}))
}
},
mounted() {
this.getBitcoinData()
}
}
</script>
have you tried to import your icons inside the template area with <i ...></i>?
here is an working example. Check out the cdn.fontawesome/help-page to get more information.
Vue.createApp({
data () {
return {
isPositive: false,
isNegative: true
}
}
}).mount('#demo')
.negative {
color: red;
}
.positive {
color: green;
}
.neutral {
color: #666;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#next"></script>
<div id="demo">
<i class="fas" :class="isPositive ? 'fa-angle-up positive' : isNegative ? 'fa-angle-down negative' : 'fa-minus neutral' "></i>
<br>
<br>
<button #click="isPositive = !isPositive; isNegative = !isNegative" v-text="'change pos and neg'" />
</div>
so basically you'll bind the icon classes to your own conditions. You could write the conditions for example with the tenary operator into your template area. Hope you get the idea.
Host Fontawesome yourself by following the steps in this Fontawesome documentation.
https://fontawesome.com/docs/web/setup/host-yourself/webfonts
i hope this help.

How to get aframe to only upload canvas contents to texture when needed

How do I stop aframe from uploading a canvas texture every frame?
I'm setting up this canvas just once at init time yet checking calls to texImage2D it's being uploading continuously.
const elem = document.querySelector("#ui span");
let count = 0;
WebGLRenderingContext.prototype.texImage2D = (function(origFn) {
return function(...args) {
elem.textContent = [++count, ...args].join(' ');
origFn.call(this, ...args);
};
}(WebGLRenderingContext.prototype.texImage2D));
#ui {
position: fixed;
left: 0;
top: 0;
z-index: 1000;
background: gray;
color: white;
padding: .5em;
}
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('draw-canvas-once', {
schema: {type: 'selector'},
init: function () {
const canvas = this.canvas = this.data;
const ctx = this.ctx = canvas.getContext('2d');
ctx.fillStyle = '#F00';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = "70px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#000";
ctx.fillText("X", 150, 75);
},
});
</script>
<a-scene>
<a-assets>
<canvas id="helloWorldCanvas" crossOrigin="anonymous"></canvas>
</a-assets>
<a-entity material="shader: flat; src: #helloWorldCanvas"
geometry="primitive: plane; width: 160; height: 90"
position="0 60 -250" rotation="0 35 0"
draw-canvas-once="#helloWorldCanvas">
</a-entity>
</a-scene>
<div id="ui">texImage2D call count: <span></span></div>
You can also just compare perf. Here's one with 100 planes with 100 canvases. On my MPB it runs at 20fps
const elem = document.querySelector("#ui span");
let then = 0;
function check(now) {
const et = now - then;
then = now;
elem.textContent = (1 / (et * 0.001)).toFixed(2);
requestAnimationFrame(check);
}
requestAnimationFrame(check);
#ui {
position: fixed;
left: 0;
top: 0;
z-index: 1000;
background: gray;
color: white;
padding: .5em;
}
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('grid', {
schema: {
across: {type: 'int', default: 3},
down: {type: 'int', default: 3},
},
init() {
for (let y = 0; y < this.data.down; ++y) {
for (let x = 0; x < this.data.across; ++x) {
const id = `c${x}${y}`;
const elem = document.createElement('a-entity');
elem.setAttribute('draw-canvas-once', {id});
elem.setAttribute('geometry', {
primitive: 'plane',
height: 1,
width: 1,
});
elem.setAttribute('material', {
shader: 'flat', src: `#${id}`,
});
this.el.appendChild(elem);
elem.setAttribute('position', {
x: (x - this.data.across / 2) * 1.1,
y: (y - this.data.down / 2) * 1.1,
z: 0,
});
}
}
},
});
AFRAME.registerComponent('draw-canvas-once', {
schema: {id: {type: 'string'}},
init() {
const canvas = this.canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.id = this.data.id;
const ctx = this.ctx = canvas.getContext('2d');
ctx.fillStyle = '#F00';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = "70px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#000";
ctx.fillText(this.data.id, 150, 75);
},
});
</script>
<a-scene>
<a-assets>
</a-assets>
<a-entity grid="across: 10; down: 10" position="0 0 -10" />
</a-scene>
<div id="ui">fps: <span></span></div>
vs 100 planes no canvas, just an image. it runs at 60fps. The two samples should be running at the same speed.
const elem = document.querySelector("#ui span");
let then = 0;
function check(now) {
const et = now - then;
then = now;
elem.textContent = (1 / (et * 0.001)).toFixed(2);
requestAnimationFrame(check);
}
requestAnimationFrame(check);
#ui {
position: fixed;
left: 0;
top: 0;
z-index: 1000;
background: gray;
color: white;
padding: .5em;
}
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('grid', {
schema: {
across: {type: 'int', default: 3},
down: {type: 'int', default: 3},
},
init() {
for (let y = 0; y < this.data.down; ++y) {
for (let x = 0; x < this.data.across; ++x) {
const elem = document.createElement('a-entity');
elem.setAttribute('geometry', {
primitive: 'plane',
height: 1,
width: 1,
});
elem.setAttribute('material', {
shader: 'flat', src: '#img',
});
this.el.appendChild(elem);
elem.setAttribute('position', {
x: (x - this.data.across / 2) * 1.1,
y: (y - this.data.down / 2) * 1.1,
z: 0,
});
}
}
},
});
</script>
<a-scene>
<a-assets>
<img id="img" src="https://i.imgur.com/ZKMnXce.png" crossorigin="anonymous" />
</a-assets>
<a-entity grid="across: 10; down: 10" position="0 0 -10" />
</a-scene>
<div id="ui">fps: <span></span></div>
A-Frame was doing:
loadCanvas: function (src, data, cb) {
// Hack readyState and HAVE_CURRENT_DATA on canvas to work with THREE.VideoTexture
src.readyState = 2;
src.HAVE_CURRENT_DATA = 2;
this.loadVideo(src, data, cb);
},
Bad slip up when merging a PR, will fix.
In the meantime, you can create a three.js CanvasTexture manually and assign it to the material in a component.

Why isn't the a-frame click event working?

I'm trying to figure out why my console isn't recording my log statements in my click event handlers. Any help would be greatly appreciated.
Here's my JS Fiddle. https://jsfiddle.net/tznezkqo/
I'm using A-Frame version 5.0
Here's my HTML
<!-- Player -->
<a-entity camera universal-controls="movementControls: checkpoint" checkpoint-controls="mode: animate"
position="0 1.764 0">
<a-entity cursor="fuse: true; fuseTimeout: 500" position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03;" material="color: #CCC; shader: flat;">
</a-entity>
</a-entity>
<a-entity id="box" cursor-listener geometry="primitive: box" material="color: blue" position="-2 2.2 -1.5"></a-entity>
and my JS
$(document).ready(function() {
AFRAME.registerComponent('cursor-listener', {
init: function () {
this.el.addEventListener('click', function (evt) {
console.log('I was clicked at: ', evt.detail.intersection.point);
});
}
});
AFRAME.registerComponent('collider-check', {
dependencies: ['raycaster'],
init: function () {
this.el.addEventListener('raycaster-intersected', function () {
console.log('Player hit something!');
});
}
});
});
I believe you'll have to make sure the component is registered before the elements are used.
here's a modified version of your example: https://jsfiddle.net/pj3m4obt/2/
<script>
AFRAME.registerComponent('cursor-listener', {
init: function () {
this.el.addEventListener('click', function (evt) {
console.log('I was clicked at: ', evt.detail.intersection.point);
});
}
});
</script>
<body>
...
<a-entity id="box" cursor-listener geometry="primitive: box" material="color: blue" position="-2 2.2 -1.5"></a-entity>

Layer not getting removed - Google Maps

I have Google Maps:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Fusion Tables layers</title>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: {lat: 39.8282, lng: -98.5795}
});
google.maps.event.addListener(map, 'zoom_changed', function() {
var zoom_level = map.getZoom();
var layer;
var layer1;
//state level
if(zoom_level >= 5 && zoom_level < 7) {
layer = new google.maps.FusionTablesLayer({
query: {
select: '\'geometry\'',
from: '17aT9Ud-YnGiXdXEJUyycH2ocUqreOeKGbzCkUw'
},
styles: [{
polygonOptions: {
fillColor: '#000000',
fillOpacity: 0.001
}
}]
});
layer.setMap(map);
}
//county level
if(zoom_level >= 7) {
layer = null;
layer.setMap(null);
layer1 = new google.maps.FusionTablesLayer({
query: {
select: '\'geometry\'',
from: '1xdysxZ94uUFIit9eXmnw1fYc6VcQiXhceFd_CVKa'
},
styles: [{
polygonOptions: {
fillColor: '#000000',
fillOpacity: 0.001
}
}]
});
layer1.setMap(map);
}
});
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBMtoh9P3UkoxbXndKu_HOP7KsVwTRvxGU&callback=initMap">
</script>
</body>
</html>
When I zoom in to zoomlevel 7 and above, I still see state level (layer) and not layer1. How can I reset layer when zoom in?
I would suggest that if you only ever want one layer at a time, only create one, hide it before creating a new version.
google.maps.event.addListener(map, 'zoom_changed', function() {
var zoom_level = map.getZoom();
if (layer) layer.setMap(null);
//state level
if (zoom_level >= 5 && zoom_level < 7) {
layer = new google.maps.FusionTablesLayer({
query: {
select: '\'geometry\'',
from: '17aT9Ud-YnGiXdXEJUyycH2ocUqreOeKGbzCkUw'
},
styles: [{
polygonOptions: {
fillColor: '#000000',
fillOpacity: 0.001
}
}]
});
layer.setMap(map);
}
//county level
if (zoom_level >= 7) {
layer = new google.maps.FusionTablesLayer({
query: {
select: '\'geometry\'',
from: '1xdysxZ94uUFIit9eXmnw1fYc6VcQiXhceFd_CVKa'
},
styles: [{
polygonOptions: {
fillColor: '#000000',
fillOpacity: 0.001
}
}]
});
layer.setMap(map);
}
});
proof of concept fiddle
code snippet:
var layer;
var layer1;
var map;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: {
lat: 39.8282,
lng: -98.5795
}
});
google.maps.event.addListener(map, 'zoom_changed', function() {
var zoom_level = map.getZoom();
if (layer) layer.setMap(null);
//state level
if (zoom_level >= 5 && zoom_level < 7) {
layer = new google.maps.FusionTablesLayer({
query: {
select: '\'geometry\'',
from: '17aT9Ud-YnGiXdXEJUyycH2ocUqreOeKGbzCkUw'
},
styles: [{
polygonOptions: {
fillColor: '#000000',
fillOpacity: 0.001
}
}]
});
layer.setMap(map);
}
//county level
if (zoom_level >= 7) {
layer = new google.maps.FusionTablesLayer({
query: {
select: '\'geometry\'',
from: '1xdysxZ94uUFIit9eXmnw1fYc6VcQiXhceFd_CVKa'
},
styles: [{
polygonOptions: {
fillColor: '#000000',
fillOpacity: 0.001
}
}]
});
layer.setMap(map);
}
});
}
google.maps.event.addDomListener(window, "load", initMap);
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
<script src="https://maps.googleapis.com/maps/api/js"></script>
<div id="map"></div>

Google Maps API v.3 multiple markers infowindows

I've looked through a lot of the similar questions on here, but have not found anything that will answer my question. I am new to JS and GoogleMaps API v3, and have successfully followed their tutorials to get this far. However, I'd like to have an infowindow with custom content appear based on which marker is clicked, and I cannot figure out how to do this. I'd also like to have this be possible with around 100 markers, so I'd like to know the best way to do this as well without things getting too messy. To be clear, there are 3 types of icons, but there will eventually be many markers associated with each icon, so I will need content linked to each "feature". Hopefully I've got a good start here and am not way off base. I've included the code for the page. Thank you so much in advance for any help anyone can provide.
<!DOCTYPE html>
<html>
<head>
<style>
#map_canvas {
width: 800px;
height: 500px;
background-color:#CCC;
}
#legend {
font-family: Arial, sans-serif;
background: #fff;
padding: 10px;
margin: 10px;
border: 3px solid #000;
}
#legend img {
vertical-align: middle;
}
</style>
<script src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script>
function initialize() {
var map_canvas = document.getElementById('map_canvas');
var map_options = {
center: new google.maps.LatLng(33.137551,-0.703125),
zoom: 2,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(map_canvas, map_options);
map.set('styles', [
{
featureType: 'road',
elementType: 'geometry',
stylers: [
{ color: '#888888' },
{ weight: 1.0 }
]
}, {
featureType: 'landscape',
elementType: 'geometry.fill',
stylers: [
{ hue: '#008f11' },
{ gamma: 1.0 },
{ saturation: 0 },
{ lightness: -10 }
]
}, {
featureType: 'water',
elementType: 'geometry.fill',
stylers: [
{ hue: '#054d8fd' },
{ gamma: 1.0 },
{ saturation: 0 },
{ lightness: -10 }
]
}, {
featureType: 'poi',
elementType: 'geometry',
stylers: [
{ visibility: 'off' }
]
}
]);
var iconBase = 'http://i1318.photobucket.com/albums/t658/greatergoodorg/';
var icons = {
people: {
name: 'People',
icon: iconBase + 'people_zps26d13009.png',
shadow: iconBase + 'shadow-people_zps4b39eced.png'
},
pets: {
name: 'Pets',
icon: iconBase + 'pets_zps15f549f2.png',
shadow: iconBase + 'shadow-pets_zps361068aa.png'
},
planet: {
name: 'Planet',
icon: iconBase + 'planet_zps2a8572ce.png',
shadow: iconBase + 'shadow-planet_zps9912e26b.png',
}
};
var data = ["This is the first one", "This is the second one", "This is the third one"];
function addMarker(feature) {
var marker = new google.maps.Marker({
position: feature.position,
icon: icons[feature.type].icon,
shadow: {
url: icons[feature.type].shadow,
anchor: new google.maps.Point(21, 32)
},
animation: google.maps.Animation.DROP,
map: map
});
/*...
google.maps.event.addListener(marker, "click", function () {
infowindow.open(map,marker);
});
...*/
/*...
google.maps.event.addListener(marker, 'click', function() {
map.setZoom(8);
map.setCenter(marker.getPosition());
});
...*/
}
var features = [
{
position: new google.maps.LatLng(33.137551,-0.703125),
type: 'planet'
},
{
position: new google.maps.LatLng(44.234234,-0.232233),
type: 'pets'
},
{
position: new google.maps.LatLng(54.234234,-0.032233),
type: 'people'
}
];
for (var i = 0, feature; feature = features[i]; i++) {
addMarker(feature);
}
var legend = document.getElementById('legend');
for (var key in icons) {
var type = icons[key];
var name = type.name;
var icon = type.icon;
var div = document.createElement('div');
div.innerHTML = '<img src="' + icon + '"> ' + name;
legend.appendChild(div);
}
map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(
document.getElementById('legend'));
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map_canvas"></div>
<div id="legend"><strong>Project Types</strong></div>
</body>
</html>
This will open an infowindow and display the "type" string in it
var infowindow = new google.maps.InfoWindow();
function addMarker(feature) {
var marker = new google.maps.Marker({
position: feature.position,
icon: icons[feature.type].icon,
shadow: {
url: icons[feature.type].shadow,
anchor: new google.maps.Point(21, 32)
},
animation: google.maps.Animation.DROP,
map: map
});
google.maps.event.addListener(marker, "click", function () {
infowindow.setContent(feature.type);
infowindow.open(map,marker);
});
}

Resources