loading multiple collada objects at the same time Three.js - three.js

i have to load the same collada object multiple times.
Here is the code
var ai = [];
function setupAI() {
var c = getMapSector(cam.position);
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('models/monster.DAE',function colladaReady( collada ){
var op = collada.scene;
skin = collada.skins [ 0 ];
for (var i = 0; i < NUMAI; i++) {
var o = THREE.SceneUtils.cloneObject(op);
o.scale.x = o.scale.y = o.scale.z = 0.006;
o.updateMatrix();
do {
var x = getRandBetween(0, mapW-1);
var z = getRandBetween(0, mapH-1);
} while (map[x][z] > 0 || (x == c.x && z == c.z));
x = Math.floor(x - mapW/2) * UNITSIZE;
z = Math.floor(z - mapW/2) * UNITSIZE;
o.position.set(x, UNITSIZE * 0.15, z);
o.health = 100;
//o.path = getAIpath(o);
o.pathPos = 1;
o.lastRandomX = Math.random();
o.lastRandomZ = Math.random();
o.lastShot = Date.now(); // Higher-fidelity timers aren't a big deal here.
ai.push(o);
scene.add(o);
}
});
}
but this gives error in this line
var o = THREE.SceneUtils.cloneObject(op);
but if i put loader inside the loop it becomes heavy and not load in browser.

Related

I have a google script that creates QR codes and Barcode. the QR Codes show up twice. Once where its suppose to and then at the end of the document

The code below generates a label that has a QR Code and barcode. Currently I am trying to get both to work. They both show up in the label but when it does the QR code will be added in the correct spot but also will add extra qr codes on the last label generated. The two qr codes at the end are itemnum qr codes and they are not suppose to be there. It normally puts them ontop of each other i moved them so people could see. The barcodes generate just fine but they don't scan
TEST SCRIPT here is a link to test it.. This is a test script and not part of a business so its easy to do. Basically what needs done is click on My Custom Menu and select reset labels. Once the labels are created you can go to the labels tab and select a few labels with serial numbers then go back to the menu and select create labels for selected items.
//####################################### Create Zebra Label #########################
function createZebraLabel(monthlySheet, newSheetName) {
var parentFolder = DriveApp.getFolderById('1mUPLw1PSi-guH19uJPCPWmogkpdckLcW');
var createdoc = DocumentApp.create('Zebra Label').getId();
doc = DocumentApp.openById(createdoc);
var id = createdoc;
var docFile = DriveApp.getFileById(doc.getId());
var target = parentFolder;
var file1 = DriveApp.getFileById(id);
var mkfile1 = file1.makeCopy(newSheetName, target).getId();
doc = DocumentApp.openById(mkfile1);
docFile.setTrashed(true);
//325
var body = doc.getBody().setPageWidth(325).setPageHeight(180).setMarginTop(.2).setMarginBottom(.2);
// var body = doc.getBody().setPageWidth(144).setPageHeight(144).setMarginTop(.2).setMarginBottom(.2);
var sourceSpreadSheet = SpreadsheetApp.openById(monthlySheet);
var ss = sourceSpreadSheet.getSheetByName('Sheet1');
var startRow = 1; // First row of data to process
var numRows = 250; // Number of rows to process
var startColumn = 1; // A=1 B=2
var numColumns = 5; // Number of columns to process
var dataRange = ss.getRange(startRow, startColumn, numRows, numColumns);
var data = dataRange.getValues();
for (var i = 1; i < data.length; ++i) {
var row = data[i];
var actualrow = i;
var itemnum = row[0]; // A column
var desc = row[1]; // B column
var serial = row[2]; // D column
var bin = row[3]; // D column
var qty = row[4];
var styleLeft = {};
styleLeft[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleLeft[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleLeft[DocumentApp.Attribute.FONT_SIZE] = 14;
styleLeft[DocumentApp.Attribute.BOLD] = true;
var styleRight = {};
styleRight[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
styleRight[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleRight[DocumentApp.Attribute.FONT_SIZE] = 14;
styleRight[DocumentApp.Attribute.BOLD] = true;
var itemBarcode = {};
itemBarcode[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
itemBarcode[DocumentApp.Attribute.FONT_FAMILY] = 'Libre Barcode 39 Extended Text';
itemBarcode[DocumentApp.Attribute.FONT_SIZE] = 25;
itemBarcode[DocumentApp.Attribute.BOLD] = false;
var serialBarcode = {};
serialBarcode[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER;
serialBarcode[DocumentApp.Attribute.FONT_FAMILY] = 'Libre Barcode 39 Extended Text';
serialBarcode[DocumentApp.Attribute.FONT_SIZE] = 20;
serialBarcode[DocumentApp.Attribute.BOLD] = false;
var nospace = {};
nospace[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
nospace[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
nospace[DocumentApp.Attribute.FONT_SIZE] = 1;
nospace[DocumentApp.Attribute.BOLD] = true;
var text = body.editAsText();
var itemqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=70x70&cht=qr&chl=' + itemnum);
var serialqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=70x70&cht=qr&chl=' + serial);
if (itemnum != '') {
//#################### SERIAL ###################
if (serial != '') { // SERIAL NUMBER
body.appendParagraph('').setAttributes(nospace);
var paragraph = body.appendParagraph('Item Number: ' + itemnum).setAttributes(styleLeft);
paragraph.addPositionedImage(itemqrcode.getBlob()).setLayout(DocumentApp.PositionedLayout.WRAP_TEXT)
body.appendParagraph('*' + itemnum + '*').setAttributes(itemBarcode);
body.appendParagraph('\n\n').setAttributes(nospace).appendHorizontalRule();
//description
body.appendParagraph(desc).setAttributes(styleLeft);
body.appendParagraph('').setAttributes(nospace);
//serials
body.appendParagraph('').setAttributes(nospace).appendHorizontalRule();
var paragraph = body.appendParagraph('Serial Number: ' + serial).setAttributes(styleRight);
body.appendParagraph('*' + serial + '*').setAttributes(serialBarcode);
paragraph.addPositionedImage(serialqrcode.getBlob()).setLayout(DocumentApp.PositionedLayout.WRAP_TEXT)
body.appendParagraph('').setAttributes(nospace);
} else if (serial == '') { // NO SERIAL NUMBER
var styleCenter = {};
styleCenter[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER;
styleCenter[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleCenter[DocumentApp.Attribute.FONT_SIZE] = 18;
styleCenter[DocumentApp.Attribute.BOLD] = true;
var itemBarcode = {};
itemBarcode[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER;
itemBarcode[DocumentApp.Attribute.FONT_FAMILY] = 'Libre Barcode 39 Extended Text';
itemBarcode[DocumentApp.Attribute.FONT_SIZE] = 35;
itemBarcode[DocumentApp.Attribute.BOLD] = false;
var styleLocation = {};
styleLocation[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleLocation[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleLocation[DocumentApp.Attribute.FONT_SIZE] = 12;
styleLocation[DocumentApp.Attribute.BOLD] = true;
body.appendParagraph('').setAttributes(nospace);
var paragraph = body.appendParagraph('Item Number: ' + itemnum).setAttributes(styleLeft);
body.appendParagraph('*' + itemnum + '*').setAttributes(itemBarcode);
body.appendParagraph('\n').setAttributes(nospace).appendHorizontalRule();
//description
body.appendParagraph(desc).setAttributes(styleLeft);
body.appendParagraph('').setAttributes(nospace).appendHorizontalRule();
body.appendParagraph('Location: ' + bin + ' Qty: ' + qty).setAttributes(styleLocation);
body.appendParagraph('\n').setAttributes(nospace);
}
body.appendPageBreak();
}
}
var googledoc = doc.getUrl();
FinishedGoogleDoc(googledoc);
}
Below is my solution that does work.
/
/####################################### Create Zebra Label #########################
function createZebraLabel(monthlySheet,newSheetName){
var parentFolder=DriveApp.getFolderById('1mUPLw1PSi-guH19uJPCPWmogkpdckLcW');
var createdoc = DocumentApp.create('Zebra Label').getId();
doc = DocumentApp.openById(createdoc);
var id = createdoc;
var docFile = DriveApp.getFileById(doc.getId());
var target = parentFolder;
var file1 = DriveApp.getFileById(id);
var mkfile1 = file1.makeCopy(newSheetName, target).getId();
doc = DocumentApp.openById(mkfile1);
docFile.setTrashed(true);
//325
var body = doc.getBody().setPageWidth(325).setPageHeight(180).setMarginTop(.02).setMarginBottom(.02);
// var body = doc.getBody().setPageWidth(144).setPageHeight(200).setMarginTop(.01).setMarginBottom(.01);
var sourceSpreadSheet = SpreadsheetApp.openById(monthlySheet);
var ss = sourceSpreadSheet.getSheetByName('Sheet1');
var startRow = 1; // First row of data to process
var numRows = 250; // Number of rows to process
var startColumn = 1; // A=1 B=2
var numColumns = 5; // Number of columns to process
var dataRange = ss.getRange(startRow, startColumn, numRows, numColumns);
var data = dataRange.getValues();
for (var i = 1; i < data.length; ++i) {
var row = data[i];
var actualrow=i;
var itemnum = row[0]; // A column
var desc = row[1]; // B column
var serial = row[2]; // D column
var bin = row[3]; // D column
var qty =row[4];
if (itemnum != ''){
var styleITEMNUM = {};
styleITEMNUM[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
styleITEMNUM[DocumentApp.Attribute.VERTICAL_ALIGNMENT] =DocumentApp.VerticalAlignment.CENTER;
styleITEMNUM[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleITEMNUM[DocumentApp.Attribute.FONT_SIZE] = 14;
styleITEMNUM[DocumentApp.Attribute.BOLD] = true;
var styleDESC = {};
styleDESC[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleDESC[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleDESC[DocumentApp.Attribute.FONT_SIZE] = 10;
styleDESC[DocumentApp.Attribute.BOLD] = true;
var styleSERIAL = {};
styleSERIAL[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
styleSERIAL[DocumentApp.Attribute.VERTICAL_ALIGNMENT] =DocumentApp.VerticalAlignment.CENTER;
styleSERIAL[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleSERIAL[DocumentApp.Attribute.FONT_SIZE] = 12;
styleSERIAL[DocumentApp.Attribute.BOLD] = true;
var nospace = {};
nospace[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT;
nospace[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
nospace[DocumentApp.Attribute.FONT_SIZE] =-1;
nospace[DocumentApp.Attribute.BOLD] = true;
var text = body.editAsText();
var paraStyle = {};
paraStyle[DocumentApp.Attribute.PADDING_BOTTOM] = 0;
paraStyle[DocumentApp.Attribute.PADDING_TOP] = 0;
paraStyle[DocumentApp.Attribute.MARGIN_TOP] = 0;
paraStyle[DocumentApp.Attribute.SPACING_BEFORE] = 0;
paraStyle[DocumentApp.Attribute.SPACING_AFTER] = 0;
paraStyle[DocumentApp.Attribute.LINE_SPACING] = 0;
var itemqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=80x80&cht=qr&chl='+itemnum);
var serialqrcode = UrlFetchApp.fetch('https://chart.googleapis.com/chart?chs=80x80&cht=qr&chl='+serial);
//#################### ITEM WITH SERIAL NUMBER ###################
if (itemnum != '' && serial != ''){ // SERIAL NUMBE
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td = tr.appendTableCell('').setWidth(80).setPaddingTop(0).setPaddingBottom(0).appendImage(itemqrcode.getBlob()).setAttributes(paraStyle);
var td = tr.appendTableCell('Item Number\n' +itemnum).setPaddingTop(0).setPaddingBottom(0).setAttributes(styleITEMNUM);
body.appendHorizontalRule().setAttributes(nospace);
body.appendParagraph(desc).setAttributes(styleDESC);
body.appendParagraph('').setAttributes(nospace).appendHorizontalRule().setAttributes(nospace);
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td = tr.appendTableCell('').setWidth(80).setPaddingTop(0).setPaddingBottom(0).appendImage(serialqrcode.getBlob()).setHeight(80);
var td = tr.appendTableCell('Serial Number\n ' +serial).setPaddingTop(0).setPaddingBottom(0).setAttributes(styleSERIAL);
}
else if (itemnum != '' && serial == ''){ // NO SERIAL NUMBER
var styleLocation = {};
styleLocation[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT;
styleLocation[DocumentApp.Attribute.FONT_FAMILY] = 'Calibri';
styleLocation[DocumentApp.Attribute.FONT_SIZE] =12;
styleLocation[DocumentApp.Attribute.BOLD] = true;
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td = tr.appendTableCell('').setWidth(80).setPaddingTop(0).setPaddingBottom(0).appendImage(itemqrcode.getBlob()).setAttributes(paraStyle);
var td=tr.appendTableCell('Item Number\n' +itemnum).setAttributes(styleITEMNUM);
body.appendParagraph('\n').setAttributes(nospace).appendHorizontalRule();
body.appendParagraph(desc).setAttributes(styleDESC);
body.appendParagraph('\n').setAttributes(nospace).appendHorizontalRule();
body.appendParagraph('').setAttributes(nospace);
var table = body.appendTable().setBorderWidth(0).setAttributes(paraStyle);
var tr = table.appendTableRow().setAttributes(paraStyle);
var td=tr.appendTableCell('Location:'+bin).setWidth(200).setPaddingTop(0).setPaddingBottom(0).setAttributes(styleLocation);
var td=tr.appendTableCell('Qty: ' +qty).setAttributes(styleLocation);
}
body.appendPageBreak();
}
}
var googledoc = doc.getUrl();
FinishedGoogleDoc(googledoc);
}

AmChart- After updating fieldMappings and dataSet,Graph is not plotting

Problem Statement:
after updating fieldMappings and dataSet during runtime(After clicking on a button) for a stockgraph, validateNow() / validteData() is not plotting the graph.
Note: MACD0 is added from 25th element onward and expoSignalLine0 is added from 33rd element onward in the dataprovider and fieldMapping is also getting updated and can be verified same in console.enter code here
Following is the Code snippet:
(addMACD function is called on click of a button)
function addMACD() {
var chart = AmCharts.charts[ 0 ];
AmCharts.MACDGraphs = 0;
AmCharts.expoSignalLineGraphs = 0;
var MACDField = "MACD"+ AmCharts.MACDGraphs;
var expoSignalLineField = "expoSignalLine"+ AmCharts.expoSignalLineGraphs;
chart.dataSets[0].fieldMappings.push( {
fromField : MACDField,
toField : MACDField
},
{
fromField : expoSignalLineField,
toField : expoSignalLineField
});
var currClose;
var prevClose;
var twelveDayEMA =[];
var twentySixDayEMA =[];
var MACDarray = [];
var signalLineArray = [];
var MACDperiod = 9 ;// 9 day exponential average
for ( var i = 1; i < (chart.dataSets[0].dataProvider.length); i++) {
var dp = chart.dataSets[0].dataProvider[i - 1];
prevClose = parseFloat(dp["close"]);
var dp = chart.dataSets[0].dataProvider[i];
currClose = parseFloat(dp["close"]);
if( i==1){
twelveDayEMA[i] = (0.15*currClose) + (0.85*prevClose);
twentySixDayEMA[i] = (0.075*currClose) + (0.925*prevClose);
}
else{
twelveDayEMA[i] = (0.15*currClose) + (0.85*twelveDayEMA[i - 1]);
twentySixDayEMA[i] = (0.075*currClose) + (0.925*twentySixDayEMA[i - 1]);
}
if(i >= 25){
MACDarray[i] = twelveDayEMA[i] - twentySixDayEMA[i] ;
dp[MACDField] = MACDarray[i];
if(i == 25){
signalLineArray[i] = MACDarray[i];
}
else{
signalLineArray[i] = ( MACDarray[i]*(2/( MACDperiod + 1)) ) + ( signalLineArray[i - 1]*(1-(2/( MACDperiod + 1))) )
}
}
if(i >=33){
dp[expoSignalLineField] = signalLineArray[i];
}
}
console.log(chart);
if ( chart.panels.length == 1 || chart.panels.length == 2 || chart.panels.length == 3 || chart.panels.length == 4 || chart.panels.length == 5) {
var newPanel = new AmCharts.StockPanel();
newPanel.allowTurningOff = true;
newPanel.title = "MACD";
newPanel.showCategoryAxis = false;
graph1 = new AmCharts.StockGraph();
graph1.valueField = MACDField;
graph1.useDataSetColors = false;
graph1.lineColor="#6699FF";
graph1.title = "MACD";
newPanel.stockGraphs.push( graph1 );
graph2 = new AmCharts.StockGraph();
graph2.valueField =expoSignalLineField;
graph2.useDataSetColors = false;
graph2.lineColor = "#990000";
graph2.title = "MACD2";
newPanel.stockGraphs.push( graph2 );
var legend = new AmCharts.StockLegend();
legend.markerType = "none";
legend.markerSize = 0;
newPanel.stockLegend = legend;
chart.addPanelAt( newPanel, 1 );
chart.validateData();
chart.validateNow();
//chart.write("chartdiv");
}
}
You have to call validateNow first, then call validateData.
Alternatively, you can call validateNow(true, false) which has the same effect as calling the two functions separately.
Updated fiddle

Geometry intersection converting from direct geometry to buffergeometry

I am using Three.js. Found a really good Decal library written by Benpurdy. It's very easily modifiable and also used the techniques described here
However, the technique uses Geometry. The project I am on, uses BufferGeometry. I traced the code which does the geometry intersects and can't figure out the conversion from faces and vertices to attributes.
this.createGeometry = function(matrix, mesh) {
var geom = mesh.geometry;
var decalGeometry = new THREE.Geometry();
var projectorInverse = matrix.clone().getInverse(matrix);
var meshInverse = mesh.matrixWorld.clone().getInverse(mesh.matrixWorld);
var faces = [];
for(var i = 0; i < geom.faces.length; i++){
var verts = [geom.faces[i].a, geom.faces[i].b, geom.faces[i].c];
var pts = [];
var valid = false;
for(var v = 0; v < 3; v++) {
var vec = geom.vertices[verts[v]].clone();
vec.applyMatrix4(mesh.matrixWorld);
vec.applyMatrix4(matrix);
if((vec.z > 1) || (vec.z < -1) || (vec.x > 1) || (vec.x < -1) || (vec.y > 1) || (vec.y < -1)) {
} else {
valid = true;
}
pts.push(vec);
}
if(valid) {
var uv = [];
for(var n = 0; n < 3; n++){
uv.push(new THREE.Vector2( (pts[n].x + 1) / 2, (pts[n].y + 1) / 2));
pts[n].applyMatrix4(projectorInverse);
pts[n].applyMatrix4(meshInverse);
decalGeometry.vertices.push( pts[n] );
}
// update UV's
decalGeometry.faceVertexUvs[0].push(uv);
var newFace = geom.faces[i].clone();
newFace.a = decalGeometry.vertices.length - 3;
newFace.b = decalGeometry.vertices.length - 2;
newFace.c = decalGeometry.vertices.length - 1;
decalGeometry.faces.push(newFace);
}
}
return decalGeometry;
}
Appreciate if anyone could shed some light on how to go about pursuing this? Thanks.
I ended up solving the problem by writing another function to compute intersections with buffergeometry. Took me a while trying to understand the original buffer geometry code.
this.createGeometryFromBufferGeometry = function(matrix, mesh) {
var geom = mesh.geometry;
var decalGeometry = new THREE.Geometry();
var projectorInverse = matrix.clone().getInverse(matrix);
var meshInverse = mesh.matrixWorld.clone().getInverse(mesh.matrixWorld);
var faces = [];
for(var i = 0; i < geom.attributes.position.array.length; i+=9){
var pts = [];
var valid = false;
for(var v = 0; v < 9; v+=3) {
var vec = new THREE.Vector3(geom.attributes.position.array[i+v],geom.attributes.position.array[i+v+1],geom.attributes.position.array[i+v+2]);
console.log((i+v) + " " + (i+v+1) + " " + (i+v+2) );
console.log(vec);
vec.applyMatrix4(mesh.matrixWorld);
vec.applyMatrix4(matrix);
if((vec.z > 1) || (vec.z < -1) || (vec.x > 1) || (vec.x < -1) || (vec.y > 1) || (vec.y < -1)) {
} else {
valid = true;
}
pts.push(vec);
}
if(valid) {
var uv = [];
for(var n = 0; n < 3; n++){
uv.push(new THREE.Vector2( (pts[n].x + 1) / 2, (pts[n].y + 1) / 2));
pts[n].applyMatrix4(projectorInverse);
pts[n].applyMatrix4(meshInverse);
decalGeometry.vertices.push( pts[n] );
}
decalGeometry.faceVertexUvs[0].push(uv);
var newFace = new THREE.Face3()
newFace.a = decalGeometry.vertices.length - 3;
newFace.b = decalGeometry.vertices.length - 2;
newFace.c = decalGeometry.vertices.length - 1;
decalGeometry.faces.push(newFace);
}
}
return decalGeometry;
}
BufferGeometry() has a method .fromGeometry(). Populates this BufferGeometry with data from a Geometry object.
var geom = new THREE.BoxGeometry(1,1,1);
var bufGeom = new THREE.BufferGeometry().fromGeometry(geom);
UPD. You can use the other way round.
var bufGeom = new THREE.BoxBufferGeometry(1,1,1);
var geom = new THREE.Geometry().fromBufferGeometry(bufGeom);
Quick and dirty solution is to create geometry from bufferGeometry and after calculating dispose created geometry
this.compute = function()
{
this.geometry = mesh.geometry
if(this.geometry.attributes)
{
this.geometry = new THREE.Geometry().fromBufferGeometry(this.geometry);
this.computeDecal();
this.geometry.dispose();
}
else
{
this.computeDecal();
}
}

Is there any solution to Firefox 37's poor Canvas rendering performance

I've deliberately provided the version number in the question as this is the kind of question that will go out of date at some point.
Here is a simple animation that will run perfectly smoothly in Chrome and Safari, but will be very jerky in Firefox:
function lerp(a,b,λ) {
return a + λ*(b-a);
}
function random(a,b) {
return lerp( a, b, Math.random() );
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
$(document).ready( function()
{
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var balls = new Array(12);
for( var i=0; i<balls.length; i++ )
{
while(true)
{
var density = Math.sqrt( random(1,100) );
var r = random(5, 30);
var x = random( r+1, canvas.width-1 -(r+1) ),
y = random( r+1, canvas.height-1 -(r+1) );
var overlap = false;
for( var j=0; j<i; j++ )
{
var _x = balls[j].x - x,
_y = balls[j].y - y,
d2 = _x*_x + _y*_y,
_r = balls[j].r + r;
if( d2 < _r*_r )
overlap = true;
}
if( overlap )
continue;
balls[i] = {
color : d3.hsl( lerp(0,240,density/10), random(.3,.7), random(.3,.7) ).toString(),
x : x,
y : y,
vx : random(0, 0.2),
vy : random(0, 0.2),
r : r,
advance: function(t) {
this.x += t * this.vx;
this.y += t * this.vy;
}
};
break;
}
};
window.requestAnimationFrame(vsync);
var t_last;
function vsync(t)
{
if( t_last )
render( t - t_last );
t_last = t;
window.requestAnimationFrame(vsync);
}
function render(t_frame)
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.fillStyle="gray";
ctx.fillRect(0,0, canvas.width, canvas.height);
function advance_all(t) {
balls.forEach( function(b) {
b.advance(t);
});
}
var t_remaining = t_frame;
while(true) {
var hit = get_next_collision( balls, canvas.width, canvas.height );
if( t_remaining < hit.t )
break;
advance_all( hit.t );
t_remaining -= hit.t;
collide_wall( balls[hit.i], hit.wall );
balls[hit.i].advance(.001);
};
advance_all( t_remaining );
// draw balls
balls.forEach( function(ball) {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fillStyle = ball.color;
ctx.fill();
});
}
});
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
function get_next_collision(balls,W,H)
{
var winner;
// ball-wall
balls.forEach( function(ball,i)
{
var t = [];
t['L'] = (ball.r - ball.x) / ball.vx; // s.x + t v.x = r
t['T'] = (ball.r - ball.y) / ball.vy;
t['R'] = (W-1-ball.r - ball.x) / ball.vx; // s.x + t v.x = (W-1)-r
t['B'] = (H-1-ball.r - ball.y) / ball.vy;
// get index of smallest positive t
var LR = t['L'] >= 0 ? 'L' : 'R',
TB = t['T'] >= 0 ? 'T' : 'B',
wall = t[LR] < t[TB] ? LR : TB;
if( ! winner || ( t[wall] <= winner.t ) )
winner = {
t : t[wall],
i : i,
wall: wall
};
});
return winner;
}
function collide_wall( A, wall )
{
if( wall == 'L' || wall == 'R' )
A.vx *= -1;
else
A.vy *= -1;
}
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/1.5.1/math.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<canvas id="myCanvas">
<!-- Insert fallback content here -->
</canvas>
Why is Firefox performing significantly worse than its competitors?
If I take the number of balls to 500 Chrome is still smooth, Firefox is seriously choppy.
If I take the number of balls down to 1 Firefox is still burring it.
Another experiment I did was applying a fixed velocity increment each frame, so that the smoothness of the animation accurately reflects the evenness of the render callback. This showed Firefox to be all over the place. Chrome on the other hand was smooth.
If there is sufficient interest, I can provide a snippet for that also and maybe tidy up the first one so that it offers a slider to modify the ball count.
As far as I can see, (1) Firefox definitely isn't giving us a genuine VSYNC callback, I suspect it is just using a timer, (2) even correcting for that by manually calculating elapsed time for each frame, it burrs the animation, maybe suggesting that it sometimes the callback fires in time to catch the VSYNC and sometimes it misses the boat, (3) there is an additional compositing hit that is disproportionate compared with Chrome.
Is there anything to be done about this?
EDIT: I found this question from four years ago! Poor performance of html5 canvas under firefox -- please don't mark this question as the duplicate unless it is certain that the answer to that question is still the only relevant answer four years later. I've deliberately included the version number in the question, as the question pertains specifically to the current version.

Actionscript - randomly drop from moving plane MC

I wasn't quite sure how to describe my problem in the subject. I have a plane MC and a crate MC. The plane only flies along the y axis from the bottom of the screen to top. Along the way I want it to randomly drop the crate MC. My code is below. The problem is that the crates spontaneously keep spawning and not near the plane.
function movePlane():void
{
var tempY:Number;
var tempX:Number;
var tempCrate:MovieClip;
var tempPlane:MovieClip;
for (var j:int =planes.length-1; j>=0; j--)
{
tempPlane = planes[j];
tempPlane.y += tempPlane.planeSpeed;
tempCrate = new Crate();
tempY = Math.floor(Math.random() * tempPlane.y);
tempX = Math.floor(Math.random() * tempPlane.x);
}
tempCrate.y = tempY;
tempCrate.x = tempX;
addChild(tempCrate);
}
Edited answer:
To make a crate drop on each plane once you can create this behavior by creating a timer on each plane with a random time value. Like this:
function addRandomCreation():void{
var animationTime:Number = 5000; //The time the planes will be animating in ms
for(var i:int = 0; i < planes.length; i++){
var planeTimer:Timer = new Timer(Math.round(animationTime * Math.random()));
planeTimer.addEventListener(TimerEvent.TIMER, timerComplete(i));
planeTimer.start();
}
}
function timerComplete(planeID:int):function{
return function(event:TimerEvent):void{
event.target.stop();
event.target.removeEventListener(event.type, arguments.callee);
var tempCrate:MovieClip = new Crate();
tempY = Math.round(Math.random() * planes[planeID].y);
tempCrate.y = tempY;
tempCrate.x = planes[planeID].x;
addChild(tempCrate);
}
}
Edited answer:
This will create a crate on the same x axis as the plane it's being created by.
function movePlane():void
{
var tempY:Number;
var tempX:Number;
var tempCrate:MovieClip;
var tempPlane:MovieClip;
for (var j:int =planes.length-1; j>=0; j--)
{
tempPlane = planes[j];
tempPlane.y += tempPlane.planeSpeed;
tempCrate = new Crate();
tempY = Math.floor(Math.random() * tempPlane.y);
tempCrate.y = tempY;
tempCrate.x = tempPlane.x;
addChild(tempCrate);
}
}
You have have to use addChild each time you create a new Crate otherwise it will just create a lot of crates which only the last one will be added to the stage. To do this you have to move the addChild into the loop.
function movePlane():void
{
var tempY:Number;
var tempX:Number;
var tempCrate:MovieClip;
var tempPlane:MovieClip;
for (var j:int =planes.length-1; j>=0; j--)
{
tempPlane = planes[j];
tempPlane.y += tempPlane.planeSpeed;
tempCrate = new Crate();
tempY = Math.floor(Math.random() * tempPlane.y);
tempX = Math.floor(Math.random() * tempPlane.x);
tempCrate.y = tempY;
tempCrate.x = tempX;
addChild(tempCrate);
}
}

Resources