aframe-spritesheet-component freezing webvr environment in browser? - animation

This is my first attempt at building an a-frame experience, and I'm doing so by cobbling together multiple community components.
I'm currently attempting to implement a spritesheet animation using aframe-spritesheet-component ( https://github.com/EkoLabs/aframe-spritesheet-component ) and having an issue getting it to play well with others.
My build here: https://afantasy.github.io/spritetest/
So far, I've managed to get the spritesheet animation to loop through once, after which it causes a total freeze of the entire environment.
I am not sure whether it's a bad interaction with the other components I've installed, or with the aframe framework itself.
From what I gather from the console, something is up with the update / tick functions.
I've managed to isolate the direct cause to this bit of code I grabbed directly from the aframe-spritesheet-component example on github ( https://ekolabs.github.io/aframe-spritesheet-component/examples/rowscols/ )
<!-- SPRITE ANIMATION LOOP -->
<script type="text/javascript">
var animation = { progress: 0 };
var tween = new TWEEN.Tween(animation)
.to({ progress: 1 }, 1000)
.onUpdate(function(){
document.querySelector('a-image').setAttribute('sprite-sheet', 'progress', animation.progress);
});
tween.onComplete(function() { animation.progress = 0; });
tween.chain(tween);
tween.start();
</script>
Super stumped and sure how to proceed from here, any hints would be appreciated.

Here is the code for an alternative spritesheet animation component that might work for you, without requiring the external TWEEN.js dependency:
AFRAME.registerComponent('spritesheet-animation', {
schema:
{
rows: {type: 'number', default: 1},
columns: {type: 'number', default: 1},
// set these values to play a (consecutive) subset of frames from spritesheet
firstFrameIndex: {type: 'number', default: 0},
lastFrameIndex: {type: 'number', default: -1}, // index is inclusive
// goes from top-left to bottom-right.
frameDuration: {type: 'number', default: 1}, // seconds to display each frame
loop: {type: 'boolean', default: true},
},
init: function()
{
this.repeatX = 1 / this.data.columns;
this.repeatY = 1 / this.data.rows;
if (this.data.lastFrameIndex == -1) // indicates value not set; default to full sheet
this.data.lastFrameIndex = this.data.columns * this.data.rows - 1;
this.mesh = this.el.getObject3D("mesh");
this.frameTimer = 0;
this.currentFrameIndex = this.data.firstFrameIndex;
this.animationFinished = false;
},
tick: function (time, timeDelta)
{
// return if animation finished.
if (this.animationFinished)
return;
this.frameTimer += timeDelta / 1000;
while (this.frameTimer > this.data.frameDuration)
{
this.currentFrameIndex += 1;
this.frameTimer -= this.data.frameDuration;
if (this.currentFrameIndex > this.data.lastFrameIndex)
{
if (this.data.loop)
{
this.currentFrameIndex = this.data.firstFrameIndex;
}
else
{
this.animationFinished = true;
return;
}
}
}
let rowNumber = Math.floor(this.currentFrameIndex / this.data.columns);
let columnNumber = this.currentFrameIndex % this.data.columns;
let offsetY = (this.data.rows - rowNumber - 1) / this.data.rows;
let offsetX = columnNumber / this.data.columns;
if ( this.mesh.material.map )
{
this.mesh.material.map.repeat.set(this.repeatX, this.repeatY);
this.mesh.material.map.offset.set(offsetX, offsetY);
}
}
});
A complete example of the component in code:
https://github.com/stemkoski/A-Frame-Examples/blob/master/spritesheet.html
Live preview of example:
https://stemkoski.github.io/A-Frame-Examples/spritesheet.html

Your app.js is minified, so it's hard to trace the issue. However, I replaced the Tween library in your version with the exact Tween library used on their version and it seemed to work:
https://stackoverflow-51620935.glitch.me
My guess is that either you may be forgetting to configure Tween (I have never used this library before) or are using an unsupported version (the console also shows several warnings about deprecated methods).
I copy/pasted your code into that glitch above, and changed line 96 to include the overwrite:
<!-- SPRITE ANIMATION LOOP -->
<script src="tween-rewrite.js"></script>
<script type="text/javascript">
var animation = { progress: 0 };
var tween = new TWEEN.Tween(animation)
.to({ progress: 1 }, 1000)
.onUpdate(function(){
document.querySelector('a-image').setAttribute('sprite-sheet', 'progress', animation.progress);
});
tween.onComplete(function() { animation.progress = 0; });
tween.chain(tween);
tween.start();
</script>

Ran into the same problem. Looping the animation like this works for me:
<script>
function smokeLoop(){
var animation = { progress: 0 };
var tween = new TWEEN.Tween(animation)
.to({ progress: 1 }, 4000)
.onUpdate(function(){
document.querySelector('a-image').setAttribute('sprite-sheet', 'progress', animation.progress);
});
tween.onComplete(function() { animation.progress = 0; smokeLoop();});
tween.start();
}
smokeLoop();
</script>

Related

ChartJs linechart adddata animation

I am trying to make a 'Movie-Chart' with ChartJs v3.9.1 where I add and remove data:
https://codepen.io/ipax77/pen/YzLrNEM
<...>
let chartdata = { x: label, y: stepdata.Winrate };
chart.data.labels.push(label);
chart.data.datasets.forEach(dataset => {
if (dataset.label == stepdata.Commander) {
dataset.data.push(chartdata);
dataadded = true;
}
});
chart.update();
<...>
if (chart.data.labels.length > 6) {
let removelabel = chart.data.labels[0];
chart.data.labels.splice(0, 1);
chart.data.datasets.forEach(dataset => {
const index = dataset.data.findIndex(obj => obj.x == removelabel);
if (index > -1) {
dataset.data.splice(index, 1);
}
});
Somehow the animation for new datapoints starts from the xAxis rather than the previouse datapoint. Is there a way to fix this?
I tried working with timescale axis and chartjs-adapter-date-fns which fixes the problem, but then the animation for lines that don't start with the first label are messed up.
You could disable animation on the y-axis by defining options.animation as follows:
options: {
responsive: true,
animation: {
y: {
duration: 0
}
},

A-Frame Scene Using OffscreenCanvas

I am seeking advice on implementing an A-Frame scene using OffscreenCanvas. I am using https://github.com/spite/ccapture.js to record my VR scene and the result is very janky and slow when FPS is above 30.
The video and the code samples within the Google link below provide examples of modifying Three.js to leverage OffscreenCanvas. Thus, I believe it is possible to modify A-Frame to include an OffscreenCanvas option.
https://developers.google.com/web/updates/2018/08/offscreen-canvas
https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
I have experimented with modifying the A-Frame a-scene setupRenderer function to use an OffscreenCanvas within the rendererConfig, but I am running into getContext issues.
I am not sure what the best approach is to offload rendering (and getting blob/bitmap image) to a Web Worker.
I want to take advantage of rendering in the Worker without conflicting with the built in requestAnimationFrame functionality of A-Frame.
Any advice is greatly appreciated. Thank you.
https://github.com/aframevr/aframe/blob/master/src/core/scene/a-scene.js#L561
setupRenderer: {
value: function () {
var self = this;
var renderer;
var rendererAttr;
var rendererAttrString;
var rendererConfig;
const offscreenCanvas = this.canvas.transferControlToOffscreen();
const worker = new Worker('./canvasworker.js');
worker.postMessage({ msg: 'init', canvas: offscreenCanvas }, [offscreenCanvas]);
rendererConfig = {
alpha: true,
antialias: !isMobile,
canvas: offscreenCanvas,
logarithmicDepthBuffer: false
};
this.maxCanvasSize = {height: 1920, width: 1920};
if (this.hasAttribute('renderer')) {
rendererAttrString = this.getAttribute('renderer');
rendererAttr = utils.styleParser.parse(rendererAttrString);
if (rendererAttr.precision) {
rendererConfig.precision = rendererAttr.precision + 'p';
}
if (rendererAttr.antialias && rendererAttr.antialias !== 'auto') {
rendererConfig.antialias = rendererAttr.antialias === 'true';
}
if (rendererAttr.logarithmicDepthBuffer && rendererAttr.logarithmicDepthBuffer !== 'auto') {
rendererConfig.logarithmicDepthBuffer = rendererAttr.logarithmicDepthBuffer === 'true';
}
this.maxCanvasSize = {
width: rendererAttr.maxCanvasWidth
? parseInt(rendererAttr.maxCanvasWidth)
: this.maxCanvasSize.width,
height: rendererAttr.maxCanvasHeight
? parseInt(rendererAttr.maxCanvasHeight)
: this.maxCanvasSize.height
};
}
renderer = this.renderer = new THREE.WebGLRenderer(rendererConfig);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.sortObjects = false;
if (this.camera) { renderer.vr.setPoseTarget(this.camera.el.object3D); }
this.addEventListener('camera-set-active', function () {
renderer.vr.setPoseTarget(self.camera.el.object3D);
});
loadingScreen.setup(this, getCanvasSize);
},
writable: window.debug
},
I would like to take advantage of OffscreenCanvas and rendering on a Web Worker to maintain a consistent 60 fps.

Aframe: size of model

When using a model as a source to an entity, say gltf, is there a way we know the original size? Since the scale attribute works on relative size, it seems to be a trial an error to fit the model to our desired size. I tried using the geometry.getComputingBox() of the mesh of the model but it returns null. Wondering if there is a component that is available that lets us specify the scale in absolute terms.
Ah, figured it out.
var model = this.el.object3D;
var box = new THREE.Box3().setFromObject( model );
var size = box.getSize();
gives you the size. then using the above any desired size can be set.
Created a simple component that can be conveniently used
AFRAME.registerComponent('resize', {
schema: {
axis: {
type: 'string',
default: 'x'
},
value: {
type: 'number',
default: 1
}
},
init: function() {
var el = this.el;
var data = this.data;
var model = el.object3D;
el.addEventListener('model-loaded', function(e) {
var box = new THREE.Box3().setFromObject( model );
var size = box.getSize();
var x = size.x;
var y = size.y;
var z = size.z;
if(data.axis === 'x') {
var scale = data.value / x;
}
else if(data.axis === 'y') {
var scale = data.value / y;
}
else {
var scale = data.value / z;
}
el.setAttribute('scale', scale + ' ' + scale + ' ' + scale);
});
}
});
And it can be used as to proportionately resize the model with x axis length as 0.5
<a-entity resize='axis:x; value:0.5' gltf-model='#model`></a-entity>
(This would have come as a comment but as I don't have enough rep points this is coming as an answer.)
I found that the model doesn't have a size directly after the model-loaded event listener so I trigger the rescale from the update method. Funnily enough though if you don't have the model-loaded event listener then the size of the model will be 0 even after the first update is fired.
This is my variant of the above code with the difference being that the dimension is set in meters:
/**
* Scales the object proportionally to a set value given in meters.
*/
AFRAME.registerComponent("natural-size", {
schema: {
width: {
type: "number",
default: undefined, // meters
},
height: {
type: "number",
default: undefined, // meters
},
depth: {
type: "number",
default: undefined, // meters
},
},
init() {
this.el.addEventListener("model-loaded", this.rescale.bind(this));
},
update() {
this.rescale();
},
rescale() {
const el = this.el;
const data = this.data;
const model = el.object3D;
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize();
if (!size.x && !size.y && !size.z) {
return;
}
let scale = 1;
if (data.width) {
scale = data.width / size.x;
} else if (data.height) {
scale = data.height(size.y);
} else if (data.depth) {
scale = data.depth / size.y;
}
el.setAttribute("scale", `${scale} ${scale} ${scale}`);
},
remove() {
this.el.removeEventListener("model-loaded", this.rescale);
},
});
Then:
<a-entity natural-size='width:0.72' gltf-model='#model`></a-entity>
box.getSize has changed, I combined what I found here with what I found in another answer and noticed in the console to produce a more minimalist answer just to determine the size itself of a model:
getDimensions(object3d) {
// e.g., object3d = document.querySelector('#goban').object3D
var box = new THREE.Box3().setFromObject( object3d );
var x = box.max.x - box.min.x
var y = box.max.y - box.min.y
var z = box.max.z - box.min.z
return {x,y,z}
}

How to integrate KendoUI chart with SignalR

i want to create a real time chart, using Kendo ui chart and signalr. I see this example, but has no code. So i try alone.
A little demonstration of my code:
At first I created a kendo chart
function queueActivityChart() {
$("#queueActivityChart").kendoChart({
legend: {
visible: true
},
seriesDefaults: {
labels: {
visible: true,
format: "{0}",
background: "transparent"
}
},
series: [{
type: "line",
field: "Incoming",
categoryField: "DateTime",
}],
valueAxis: {
labels: {
format: "{0}"
},
line: {
visible: false
}
},
categoryAxis: {
labels:
{
rotation: -90,
dateFormats:
{
seconds: "ss",
minutes: "HH:mm:ss",
hours: "HH:mm",
days: "dd/MM",
months: "MMM 'yy",
years: "yyyy"
}
}, type: "Date", field: "DateTime", baseUnit: "seconds"
}
});
var chart = $("#queueActivityChart").data("kendoChart");
chart.options.transitions = false;
}
$(document).ready(queueActivityChart);
$(document).bind("kendo:skinChange", queueActivityChart);
Then I have this part of code, that get from server data
$scope.signalRData = [];
$scope.signalR.on('receiveCounters', function (data) {
$scope.queueData = data;
for (var i = 0; i < data.length; i++) {
$scope.signalRData.push(data[i]);
}
while ($scope.signalRData.length > 12) {
$scope.signalRData.splice(0, 1);
}
$("#queueActivityChart").data("kendoChart").setDataSource(
new kendo.data.DataSource({
group: {
field: "Name"
},
data: $scope.signalRData
}));
});
This works! And I get a picture of the latest updated items.
But the problem is that this chart is like to put one picture in front of other. I mean that this is the first time that load Data Source; that creates a chart of my data, the second time my data has changed, some values are still in my array some others has move out, the third too.
It seems like it puts a picture of my current data in front of the
previous data. It's not smoothie and cannot use chart's legend
property because I initialize my Data Source everytime.
Can someone help me how can create a smoothie kendo chart with real time data like the kendo official example? Also can somehow to add scroller to bottom?
I looked at the code for the benchmark and I think you may be missing in your chart which is renderAs: "canvas"
Also, in the example, the data is kept locally (saved) and then moved so it creates that "smooth" effect you may be talking about.
Here is the code that you can be of interest:
function step() {
addPoint();
$("#chart").data("kendoChart").refresh();
frames++;
if (playing) {
kendo.animationFrame(step);
}
}
function addPoint() {
var stockData,
change,
lastValue;
// Shift existing categories to the left and add the next date at the end
lastDate = new Date(lastDate.getTime() + TICKS_PER_DAY);
categoryList.push((lastDate.getMonth() + 1) + "/" + (lastDate.getDay() + 1));
if (categoryList.length > POINTS) {
categoryList.shift();
}
for (var i = 0; i < stocks.length; i++) {
stockData = stocks[i];
change = (Math.random() > 0.5 ? 1 : - 1) * Math.random() * 10;
lastValue = stockData[stockData.length - 1] || Math.random() * 10;
// Add a new pseudo-random data point
stockData.push(Math.min((i + 1) * 20, Math.max((i + 1) * 10, lastValue + change)));
// Shift the data points of each series to the left
if (stockData.length > POINTS) {
stockData.shift();
}
}
}
Check out the source code of your example for the full source code and use the dojo to test our their code and play around with it easily

Highslide, Auto follow scroll

I was looking for a fix/solution for that and found this topic:
http://highslide.com/forum/viewtopic.php?t=3030
function fixElement(el) {
var stl = el.style;
stl.position = 'fixed';
stl.top = (parseInt(stl.top) - hs.page.scrollTop) +'px';
stl.left = (parseInt(stl.left) - hs.page.scrollLeft) +'px';
}
function unfixElement(el) {
var stl = el.style;
stl.position = 'absolute';
stl.top = (parseInt(stl.top) + hs.page.scrollTop) +'px';
stl.left = (parseInt(stl.left) + hs.page.scrollLeft) +'px';
}
if (!hs.ie || hs.ieVersion() > 6) {
hs.Expander.prototype.onAfterExpand = function() {
fixElement (this.wrapper);
if (this.outline) fixElement(this.outline.table);
};
hs.Expander.prototype.onBeforeClose = function() {
unfixElement (this.wrapper);
if (this.outline) unfixElement(this.outline.table);
};
}
Got a small hack on the forum topic, but the hack does not work on any Internet Explorer that I tried (IE7, IE8 and IE9).
Does anyone has a "fix" on the hack to make it work on IE?
I think it's something related on this part of the code, this condition:
if (!hs.ie || hs.ieVersion() > 6)
I removed and it worked, but maybe this code could be changed.
Thank you.
The below code is what you need. Live demo: http://www.highslide.com/studies/position-fixed.html
Note: requires highslide-full.js
// Highslide fixed popup mod. Requires the "Events" component.
if (!hs.ie || hs.uaVersion > 6) hs.extend ( hs.Expander.prototype, {
fix: function(on) {
var sign = on ? -1 : 1,
stl = this.wrapper.style;
if (!on) hs.getPageSize(); // recalculate scroll positions
hs.setStyles (this.wrapper, {
position: on ? 'fixed' : 'absolute',
zoom: 1, // IE7 hasLayout bug,
left: (parseInt(stl.left) + sign * hs.page.scrollLeft) +'px',
top: (parseInt(stl.top) + sign * hs.page.scrollTop) +'px'
});
if (this.outline) {
stl = this.outline.table.style;
hs.setStyles (this.outline.table, {
position: on ? 'fixed' : 'absolute',
zoom: 1, // IE7 hasLayout bug,
left: (parseInt(stl.left) + sign * hs.page.scrollLeft) +'px',
top: (parseInt(stl.top) + sign * hs.page.scrollTop) +'px'
});
}
this.fixed = on; // flag for use on dragging
},
onAfterExpand: function() {
this.fix(true); // fix the popup to viewport coordinates
},
onBeforeClose: function() {
this.fix(false); // unfix to get the animation right
},
onDrop: function() {
this.fix(true); // fix it again after dragging
},
onDrag: function(sender, args) {
//if (this.fixed) { // only unfix it on the first drag event
this.fix(true);
//}
}
});

Resources