Reputation: 200
I made some experiments with the great threejs lib: https://maki-mikkelson.com/time
But the performance is very bad. Does somebody know what I could do better?
First there's my timeFont class from where I create every second (or by input) an instance of it. Below I initialize the camera, the scene, the light and the renderer. I delete the instance of it when it flies out of the scene.
I don't know what's eating more resources. Is it the fonts, is it that I update the text too much or did I just go over the limits of this lib.
Here's the source:
// timeFont class
class timeFont {
constructor(scene = undefined, timeType = 'currentTimeString', startZPos = 0) {
this.scene = scene;
this.timeType = timeType;
this.startZPos = startZPos;
this.group, this.textMesh1, this.textGeo, this.materials;
// set random speed
this.speed = this.getRandomInt(10, 100);
// set random y
this.yOffset = this.getRandomArbitrary(-1.0, 1.0);
// time strings
this.timeStrings = {
0: 'currentTimeString',
1: 'currentMilliseconds',
2: 'currentSeconds',
3: 'currentMinutes',
4: 'currentHours',
5: 'currentDateString',
6: 'currentDay',
7: 'currentMonth',
8: 'currentYear',
9: 'currentTimestamp',
10: 'mid'
};
this.currentTimeString,
this.currentMilliseconds,
this.currentSeconds,
this.currentMinutes,
this.currentHours,
this.currentDateString,
this.currentDay,
this.currentMonth,
this.currentYear,
this.currentTimestamp,
this.mid;
// font settings
this.fontMap = {
0: "Alfa_Slab_One_Regular.json",
1: "Comfortaa_Regular.json",
2: "Freehand_Regular.json",
3: "Mandali_Regular.json",
4: "Monofett_Regular.json",
5: "Raleway_Dots_Regular.json"
};
// update the clock initially
this.updateClock();
this.timeType = this.randomProperty(this.timeStrings);
this.text = this.currentMilliseconds,
this.height = this.getRandomInt(10, 100),
this.size = this.getRandomInt(10, 100),
this.hover = 0,
this.curveSegments = 4,
this.bevelThickness = 1.0,
this.bevelSize = 1.5,
this.font = undefined,
this.fontName = this.randomProperty(this.fontMap);
}
randomProperty(obj) {
var keys = Object.keys(obj);
return obj[keys[ keys.length * Math.random() << 0]];
};
getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
initClass() {
this.materials = [
new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true, opacity: 0.5, depthWrite: false, transparent: true } ), // front
new THREE.MeshPhongMaterial( { color: 0xffffff, opacity: 0.5, depthWrite: false, transparent: true } ) // side
];
this.group = new THREE.Group();
this.group.position.y = 0;
// load font
this.loadFont();
// add to scene
if (this.scene) {
this.scene.add(this.group);
}
}
loadFont() {
this.fontName = this.randomProperty(this.fontMap);
var loader = new THREE.FontLoader();
loader.load('assets/content/font/' + this.fontName, (response) => {
this.font = response;
});
};
createText() {
this.textGeo = new THREE.TextGeometry(String(this.text), {
font: this.font,
size: this.size,
height: this.height,
curveSegments: this.curveSegments,
bevelThickness: this.bevelThickness,
bevelSize: this.bevelSize
});
this.textGeo.computeBoundingBox();
this.textGeo.computeVertexNormals();
var centerOffset = - 0.5 * ( this.textGeo.boundingBox.max.x - this.textGeo.boundingBox.min.x );
this.textGeo = new THREE.BufferGeometry().fromGeometry(this.textGeo);
this.textMesh1 = new THREE.Mesh(this.textGeo, this.materials);
this.textMesh1.position.x = centerOffset;
this.textMesh1.position.y = this.hover;
this.textMesh1.position.z = 0;
this.textMesh1.rotation.x = 0;
this.textMesh1.rotation.y = 0;
this.textMesh1.rotation.z = 0;
this.group.add(this.textMesh1);
};
updateText() {
// remove old mesh
this.group.remove(this.textMesh1);
// create it again
this.createText();
}
// time function
updateClock() {
var currentTime = new Date();
this.currentDay = currentTime.getDate();
this.currentMonth = currentTime.getMonth() + 1;
this.currentYear = currentTime.getFullYear();
this.currentTimestamp = Date.now();
this.currentHours = currentTime.getHours();
this.currentMinutes = currentTime.getMinutes();
this.currentSeconds = currentTime.getSeconds();
this.currentMilliseconds = currentTime.getMilliseconds();
// get AM / PM
var hours = (this.currentHours+24-2)%24;
this.mid='am';
if (hours==0) {
hours=12;
} else if (hours>12) {
hours=hours%12;
this.mid='pm';
}
// Pad the minutes with leading zeros, if required
//currentMinutes = ( currentMinutes == 12 ) ? currentHours - 12 : currentHours;
if (this.currentMinutes < 10) {
this.currentMinutes = '0' + this.currentMinutes;
}
// Pad the seconds with leading zeros, if required
if (this.currentSeconds < 10) {
this.currentSeconds = '0' + this.currentSeconds;
}
// Compose the string for display time
this.currentTimeString = this.currentHours + ":" + this.currentMinutes + ":" + this.currentSeconds;
// Compose the string for display date
this.currentDateString = this.currentDay + "-" + this.currentMonth + "-" + this.currentYear;
// set text
switch(this.timeType) {
case 'currentTimeString':
this.text = this.currentTimeString;
break;
case 'currentMilliseconds':
this.text = this.currentMilliseconds;
break;
case 'currentSeconds':
this.text = this.currentSeconds;
break;
case 'currentMinutes':
this.text = this.currentMinutes;
break;
case 'currentHours':
this.text = this.currentHours;
break;
case 'currentDateString':
this.text = this.currentDateString;
break;
case 'currentDay':
this.text = this.currentDay;
break;
case 'currentMonth':
this.text = this.currentMonth;
break;
case 'currentYear':
this.text = this.currentYear;
break;
case 'currentTimestamp':
this.text = this.currentTimestamp;
break;
case 'mid':
this.text = this.mid;
break;
default:
this.text = this.currentTimestamp;
}
};
get timeFontZ() {
return this.group.position.z;
}
updatePosition() {
//group.rotation.y += ( targetRotation - group.rotation.y ) * 0.05;
// move the object towards the camera
this.group.position.z += this.speed;
this.group.position.y += this.yOffset;
// reset the object, if it's behind the camera
if (this.group.position.z > 4080) {
this.scene.remove(this.group);
}
}
update() {
// update font, time and position
this.updateClock();
this.updatePosition();
if (this.font) {
this.updateText();
}
}
};
// global vars
var container;
var camera, cameraTarget, scene, renderer;
var windowHalfX = window.innerWidth / 2;
var instances = [];
var frameRate = 5;
document.getElementById("setFrameRate-Input").value = frameRate;
var instanceCreationTime = 1;
document.getElementById("setInstanceCreation-Input").value = instanceCreationTime;
// start
initGlobal();
animate();
document.getElementById("setFrameRate").addEventListener('click', function () {
frameRate = document.getElementById("setFrameRate-Input").value;
}, false);
document.getElementById("setInstanceCreation").addEventListener('click', function () {
instanceCreationTime = document.getElementById("setInstanceCreation-Input").value;
}, false);
function setDeceleratingTimeout(callback, factor, times) {
var internalCallback = function(tick, counter) {
return function() {
if (--tick >= 0) {
window.setTimeout(internalCallback, ++counter * factor);
callback();
}
}
} (times, 0);
window.setTimeout(internalCallback, factor);
};
function initGlobal() {
// container
container = document.createElement('div');
document.body.appendChild(container);
// camera
var cameraZ = 4000;
camera = new THREE.PerspectiveCamera(500, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 0, cameraZ);
cameraTarget = new THREE.Vector3(0, 0, 0);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// lights
var dirLight = new THREE.DirectionalLight(0xffffff, 0.125);
dirLight.position.set(0, 0, 1).normalize();
scene.add(dirLight);
// renderer
renderer = new THREE.WebGLRenderer( {antialias: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// add resize listener
window.addEventListener('resize', onWindowResize, false);
// create first instance
createInstance();
};
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
function createInstance() {
var newInstance = new timeFont(scene);
newInstance.initClass();
instances.push(newInstance);
setDeceleratingTimeout(function() {
createInstance();
//console.log('create instace');
}, 1000 / instanceCreationTime, 1);
}
function animate() {
//requestAnimationFrame(animate);
setTimeout(function() {
requestAnimationFrame(animate);
}, 1000 / frameRate);
render();
// update test instance
instances.forEach(instance => {
instance.update();
if (instance.timeFontZ > 4120) {
delete instance;
}
});
}
function render() {
camera.lookAt(cameraTarget);
renderer.clear();
renderer.render(scene, camera);
}
PS: I updated the script on the link on my website. I only update the text, if there are seconds shown now. But it's still bad performance.
Upvotes: 0
Views: 204
Reputation: 31026
One problem in your app is the amount of geometries you allocate over time. When removing objects from the scene, you miss to call dispose()
in order to free internal resources.
You remove objects at two places:
updateText()
via this.group.remove(this.textMesh1);
updatePosition()
via this.scene.remove(this.group);
In updateText()
use this code to free the material and geometry:
this.textMesh1.geometry.dispose();
if ( Array.isArray( this.textMesh1.material ) === true ) {
for ( let material of this.textMesh1.material ) material.dispose();
} else {
this.textMesh1.material.dispose();
}
In updatePosition()
, use this generic code:
this.group.traverse( function( object ) {
if ( object.isMesh ) {
object.geometry.dispose();
// release material like above
}
} );
You can read more about memory management in this official guide:
Besides, try to load your fonts only once at the beginning and then reuse them. This will also avoid big frametimes since you avoid parsing overhead during your animation.
Upvotes: 1