THREE JS - Rendering on canvas inside of loop not updating canvas - javascript

I've got the below code, which takes in a number of scans. I know this to always be 2880. The canvas should split an entire 360° into 2880 sectors. The loop in the code below will always run from 0 to 2880, and in each loop, a bunch of (maybe several hundred) 2px coloured points are rendered in that sector, emanating from the centre of the canvas outward. The loop moves fast, before I upgraded the THREE package, this loop could render in c. 15 seconds.
The picture draws correctly, but what confuses me is the fact that the call to THREE's render message happens inside of the loop, yet the picture draws nothing until the last iteration of the loop is complete and then all 2880 sectors appear at once, which isn't the effect I'm going for.
Can anyone advise what I might be missing? It's a 2-D non-interactable image.
Stuff I've tried:-
setTimeout(null, 1000) after the .render() method to make it wait before executing the next iteration of the loop
Considered making it a recursive function with the next iteration of the loop inside of the above setTimeout
Reversing the THREE upgrade as an absolute last resort.
Stuff I've considered:-
Is the loop running two fast for the frame rate or not giving the screen enough time to update?
Limitation of THREEJs?
const drawReflectivityMap = (scans, reflectivityData, azimuthData, scene, renderer, totalScans, currentScan, cameraPosition, camera, reflectivityColours) => {
currentCamera = camera;
currentRenderer = renderer;
for (let i = 0; i < scans; i++) {
console.log('Drawing Reflectivity ' + i);
var reflectivity = reflectivityData[i];
var azimuth = utils.radians(azimuthData[i]);
var sinMultiplier = Math.sin(azimuth);
var cosMultiplier = Math.cos(azimuth);
var initialRange = mikeUtilities.addRange(mikeUtilities.multiplyRange(mikeUtilities.createRange(0, reflectivity.GateCount, 1), reflectivity.GateSize), reflectivity.FirstGate);
var x = utils.multiplyRange(initialRange, sinMultiplier);
var y = utils.multiplyRange(initialRange, cosMultiplier);
var dataSet = {
x: x,
y: y,
reflectivity: reflectivity
};
var reflectivityColourScale = d3.scaleQuantize().domain([-32.0, 94.5]).range(reflectivityColours);
var pointsMaterial = new THREE.PointsMaterial({
size: 2,
vertexColors: true,
sizeAttenuation: false
});
// x co-ordinate points for each point in this arc
var x = dataSet.x;
// y co-ordinate points for each point in this arc
var y = dataSet.y;
// Reflectivity (rainfall) intensity values for each point in this arc
var reflectivity = dataSet.reflectivity;
var geometry = new THREE.BufferGeometry();
var pointsGraph = [];
var coloursGraph = [];
x.forEach(function (index, i) {
if (reflectivity.MomentDataValues[i] > -33) {
geometry = new THREE.BufferGeometry();
var dataPointColour = new THREE.Color(reflectivityColourScale(reflectivity.MomentDataValues[i]));
pointsGraph.push(x[i], y[i], 0);
coloursGraph.push(dataPointColour.r, dataPointColour.g, dataPointColour.b);
}
});
var pointsGraphArray = new Float32Array(pointsGraph);
var coloursGraphArray = new Float32Array(coloursGraph);
geometry.setAttribute('position', new THREE.BufferAttribute(pointsGraphArray, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(coloursGraphArray, 3));
var pointsMap = new THREE.Points(geometry, pointsMaterial);
scene.add(pointsMap);
renderScene(scene, cameraPosition, renderer);
}
}
function renderScene(scene, cameraPosition,renderer) {
currentCamera.position.z = cameraPosition;
currentRenderer.render(scene, currentCamera);
requestAnimationFrame(renderScene);
}

for (let i = 0; i < scans; i++) {
setTimeout(() => {
// Your code goes here
} i * 100)
}

Related

How to draw clickable line in PIXI.js using PIXI.Graphics?

I have got the following code:
const linksGraphics = new PIXI.Graphics();
const update = () => {
linksGraphics.clear();
linksGraphics.alpha = 1;
if (forceLinkActive) {
data.links.forEach(link => {
let { source, target } = link;
linksGraphics.lineStyle(2, 0x000000);
linksGraphics.moveTo(source.x, source.y);
linksGraphics.lineTo(target.x, target.y);
});
linksGraphics.endFill();
} }
app.ticker.add( () => update() );
Where data.links is an array of edge data {source: number, target: number}. If I understand right, all lines are part of the PIXI.Graphics object. But what I need:
every line should have own opacity
every line should have an event for mouse over
Any ideas how modify my code?
Thanks.
It's been a while but can make a suggestion. Lines do not react to mouse/pointer over events in pixijs.
Instead you may want to accompany a transformed rectangle with alpha value 0 and listen mouse/pointer with this rectangle.
For example lets, change the alpha value of the line when mouse/pointer hovers the accompanying rectangle.
const app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0x283230
});
document.body.appendChild(app.view);
// 1. PRELIMINARY COMPUTATIONS
// Coordinates of the end points of a line
let x0 = 100;
let y0 = 100;
let x1 = 200;
let y1 = 200;
// Find midpoint for translation
let xmid = 0.5*(x0+x1);
let ymid = 0.5*(y0+y1);
// Length of the line
let length = Math.hypot(x0-x1, y0-y1);
// Alignment angle of the line, i.e. angle with the x axis
let angle = Math.atan((y1-y0)/(x1-x0));
// 2. LINE
line = new PIXI.Graphics();
// Arbitrary line style, say we have a non-white background
line.lineStyle(8,0xffffff,1);
line.moveTo(x0,y0);
line.lineTo(x1,y1);
// 3. ACCOMPANYING RECTANGLE
line.rectangle = new PIXI.Graphics();
line.rectangle.beginFill(0xffffff);
// Since we are going to translate, think of 0,0 is the center point on the rectangle
// Width of the rectangle is selected arbitrarily as 30
const width = 30;
line.rectangle.drawRect(-length/2,-width/2,length,width);
line.rectangle.endFill();
line.rectangle.alpha = 0;
line.rectangle.interactive = true;
line.rectangle.on("pointerover", reactOver);
line.rectangle.on("pointerout", reactOut);
// Apply transformation
line.rectangle.setTransform(xmid, ymid,1,1,angle);
app.stage.addChild(line);
// Add rectangle to the stage too.
app.stage.addChild(line.rectangle);
// Let's change alpha value of the line when user hovers.
function reactOver(){
line.alpha = 0.5;
}
function reactOut(){
line.alpha = 1;
}
To the PEN, Hover a line in pixijs
We can expand this logic to a rectangle for instance. But this time you need two accompanying rectangles (with alpha=0) where one of them is wider and the other is narrower than the unfilled rectangle. For example,
const app = new PIXI.Application({
width: window.innerWidth,
height: window.innerHeight,
backgroundColor: 0x283230
});
document.body.appendChild(app.view);
const x = 100;
const y = 100;
const width = 150;
const height = 100;
const hoverWidth = 20;
const rect = new PIXI.Graphics();
rect.lineStyle(4, 0xffffff,1);
rect.drawRect(x,y,width,height);
rect.outer = new PIXI.Graphics();
rect.inner = new PIXI.Graphics();
// Fill outer
rect.outer.alpha = 0;
rect.outer.beginFill(0xffffff);
rect.outer.drawRect(x-hoverWidth/2, y-hoverWidth/2, width+hoverWidth, height+hoverWidth);
rect.outer.endFill();
// Fill inner
rect.inner.alpha = 0;
rect.inner.beginFill(0xffffff);
rect.inner.drawRect(x+hoverWidth/2, y+hoverWidth/2, width-hoverWidth, height-hoverWidth);
rect.inner.endFill();
// Add interaction and listeners
rect.outer.interactive = true;
rect.inner.interactive = true;
rect.outer.on("pointerover", pOverOuter);
rect.outer.on("pointerout", pOutOuter);
rect.inner.interaction = true;
rect.inner.on("pointerover", pOverInner);
rect.inner.on("pointerout", pOutInner);
app.stage.addChild(rect);
app.stage.addChild(rect.outer);
app.stage.addChild(rect.inner);
// Listeners
let overOuter = false;
let overInner = false;
function pOverOuter(){
overOuter = true;
changeAlpha();
// rect.alpha = 0.5;
}
function pOutOuter(){
overOuter = false;
changeAlpha();
}
function pOverInner(){
overInner = true;
changeAlpha();
// rect.alpha = 1;
}
function pOutInner(){
overInner = false;
changeAlpha();
// rect.alpha = 0.5;
}
function changeAlpha(){
rect.alpha = (overOuter && !overInner)? 0.5: 1;
}
To the PEN, Hover a rectangle in pixijs
For your first requirement, try creating separate graphics objects for drawing each line and set alpha for each line.
For your second requirement, You need to set the interactive property of graphics (linksGraphics) object to true like below,
linksGraphics.interactive = true;
and then attach a function to be executed on mouseover event like below,
var mouseOverAction = function () {
//Some code
};
linksGraphics.on('mouseover', mouseOverAction);
You can define a hitArea on a graphic. And with getBounds() you can make a line clickable. After you do that you can also assign pointerEvents to the graphic.
const linksGraphics = new PIXI.Graphics();
const update = () => {
linksGraphics.clear();
linksGraphics.alpha = 1;
if (forceLinkActive) {
data.links.forEach(link => {
let { source, target } = link;
linksGraphics.lineStyle(2, 0x000000);
linksGraphics.moveTo(source.x, source.y);
linksGraphics.lineTo(target.x, target.y);
//A line itself is not clickable
linksGraphics.hitArea = linksGraphics.getBounds();
});
linksGraphics.endFill();
}
}
app.ticker.add( () => update() );

How can I clip a drawn canvas, rotate and draw to another canvas?

I have a canvas for the game world and a canvas for the display screen. I also have a polygon with nodes V(x,y) to serve as a viewport that follows the player and his rotation. I would like to know how to clip from the game world along the polygon, rotate and draw to the smaller canvas.`
//main looping function
var requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
//joystick setup
var leftManager = null;
var rightManager = null;
//precalculated math
var twoPi = Math.PI*2;
var halfPi = Math.PI/2;
var thirdOfCircleInRadians = twoPi/3;
//game canvas setup
var gameCvs = document.getElementById('gameCanvas');
gameCvs.width = 480;
gameCvs.height = 320;
//gameCvs.width - 960;
//gameCvs.height = 640;
var gameCtx = gameCvs.getContext("2d");
//game loop
var lastTime = 0;
function main() {
var now = Date.now();
var dt = lastTime==0? 0.016 : (now - lastTime) / 1000.0;
update(dt);
render(dt);
lastTime = now;
requestAnimFrame(main);
}
//collision class shorthand
var V = SAT.Vector;
var C = SAT.Circle;
var P = SAT.Polygon;
var R = new SAT.Response();
P.prototype.draw = function (ctx,type) {
ctx.save();
switch(type){
case 'van': ctx.fillStyle = "rgba(66, 66, 66, 0.5)"; break;
case 'col': ctx.fillStyle = "rgba(0, 0, 0, 1.0)"; break;
default: ctx.fillStyle = "rgba(0, 0, 0, 1.0)"; break;
}
ctx.translate(this.pos.x, this.pos.y);
ctx.beginPath();
var points = this.calcPoints;
ctx.moveTo(points[0].x, points[0].y);
var i = points.length;
while (i--) ctx.lineTo(points[i].x, points[i].y);
ctx.closePath();
//stroke to see through camera, when camera is not drawn use fill
ctx.stroke();
//ctx.fill();
ctx.restore();
};
//first for collisions, second for vanity. first is black, second is grey
var O = function(colPolygon,vanPolygon){
this.colPolygon = colPolygon;
this.vanPolygon = vanPolygon;
this.visible = false;
};
var objectVendor = function(type,position){
switch(type){
case 'tree':
return new O(new P(position,[
new V(10.5,19.5),
new V(20.5,9.5),
new V(23,-4),
new V(15,-16.5),
new V(-4,-19.5),
new V(-18,-14.5),
new V(-23,-0.5),
new V(-18.5,14.5),
new V(-8,20)
]),new P(position,[
new V(21,39),
new V(41,19),
new V(46,-8),
new V(30,-33),
new V(-8,-39),
new V(-36,-29),
new V(-46,-1),
new V(-37,29),
new V(-16,40)]));
break;
default: return false; break;
}
return false;
}
//Camera and Player Polygons
var cameraPoly = new P(new V(0,0),[
new V(-240,-160),
new V(240,-160),
new V(240,160),
new V(-240,160)
]);
var player = new P(new V(0,0),[
new V(5,2.5),
new V(7.5,2),
new V(7.5,-2),
new V(5,-2.5),
new V(-5,-2.5),
new V(-7.5,-2),
new V(-7.5,2),
new V(-5,2.5)
]);
//players start position on the screen, and starting angle, init velocity
player.pos = new V(240,160);
player.setAngle(1);
//players velocity for movement
player.vel = new V(0,0);
var world = {
objects: [],
visibleObjects: [],
worldCvs: null,
worldCtx: null,
init: function(){
//set up world canvas
this.worldCvs = document.createElement('canvas');
this.worldCvs.width = 480;
this.worldCvs.height = 480;
this.worldCtx = this.worldCvs.getContext("2d");
//populate world with stuff
this.objects.push(objectVendor('tree',new V(100,100)));
this.objects.push(objectVendor('tree',new V(150,200)));
this.objects.push(objectVendor('tree',new V(75,300)));
},
update: function(dt){
this.visibleObjects = [];
cameraPoly.setAngle(player.angle);
//cameraPoly.pos = player.pos;
cameraPoly.pos = new V(player.pos.x+(110*Math.cos(player.angle+halfPi)),player.pos.y+(110*Math.sin(player.angle+halfPi)));
//update objects to mark if they are in view
var i = this.objects.length;
while(i--){
if(SAT.testPolygonPolygon(this.objects[i].vanPolygon, cameraPoly, R)){
this.visibleObjects.push(this.objects[i]);
}
}
//}
},
draw: function(dt){
this.worldCtx.setTransform(1,0,0,1,0,0);
this.worldCtx.clearRect(0,0,this.worldCvs.width,this.worldCvs.height);
player.draw(this.worldCtx);
var i = this.visibleObjects.length;
while(i--){
this.visibleObjects[i].colPolygon.draw(this.worldCtx,'col');
this.visibleObjects[i].vanPolygon.draw(this.worldCtx,'van');
}
//for testing
cameraPoly.draw(this.worldCtx);
/*
this.worldCtx.save();
this.worldCtx.beginPath();
var i = cameraPoly.calcPoints.length;
this.worldCtx.moveTo(cameraPoly.calcPoints[0].x,cameraPoly.calcPoints[0].y);
while(i--){
this.worldCtx.lineTo(cameraPoly.calcPoints[i].x,cameraPoly.calcPoints[i].y);
}
this.worldCtx.clip();
this.worldCtx.restore();
*/
}
}
function render(dt){
gameCtx.setTransform(1,0,0,1,0,0);
gameCtx.clearRect(0,0,gameCvs.width,gameCvs.height);
world.draw();
//gameCtx.save();
//gameCtx.translate(cameraPoly.pos.x,cameraPoly.pos.y);
//gameCtx.translate(gameCtx.width/2,gameCtx.height/2);
//gameCtx.rotate(-player.angle+halfPi);
//gameCtx.translate(-world.worldCvs.width/2,-world.worldCvs.height/2);
gameCtx.drawImage(world.worldCvs,0,0);
//gameCtx.restore();
}
function update(dt){
world.update();
}
function init(){
//joystick setup
leftManager = nipplejs.create({
zone:document.getElementById("leftJoystick"),
color:"black",
size:75,
threshold:1.0,
position:{
top:"50%",
left:"50%"
},
mode:"static",
restOpacity:0.75,
});
rightManager = nipplejs.create({
zone:document.getElementById("rightJoystick"),
color:"black",
size:75,
threshold:1.0,
position:{
top:"50%",
right:"50%"
},
mode:"static",
restOpacity:0.75,
});
//joystick event setup
leftManager.get().on('move end', function(evt,data){
//console.log(evt);
//console.log(data);
});
rightManager.get().on('move end', function(evt,data){
//console.log(evt);
//console.log(data);
});
world.init();
main();
}
init();
`
I'm using libraries SAT.js and nipplejs.js currently.
Typically this is done in a little different of a way than you seem to be thinking of it. Instead of thinking about the viewport existing somewhere in the world, you should think about the viewport being fixed and the world being transformed behind it; you don't copy part of the world to the viewport, you draw the world offset and rotated by a certain amount, and only draw the parts that are inside the viewport. Matrices are an easy and common way to represent this transformation. You may want to read more about them here.
In practice, this would just amount to changing your existing call to worldCtx.setTransform() at the beginning of each draw frame. That link has information about how to calculate a good transform matrix, and you can find similar resources all over the place since it's pretty standard math.
In particular, you'll want to multiply a rotation and a translation matrix. Translation matrices are only possible if you use a matrix with higher-order than your coordinate space; for 2D, a 3x3 matrix, and for 3D, a 4x4 matrix. You could instead choose to just add some offset to your coordinates as you draw them, but worldCtx.setTransform already takes a matrix with a 3rd column for putting flat offsets into.
Changing the render function to the following will solve the problem, just rushing myself and didn't think things through very well.
`
function render(dt){
gameCtx.setTransform(1,0,0,1,0,0);
gameCtx.clearRect(0,0,gameCvs.width,gameCvs.height);
world.draw();
gameCtx.translate(gameCvs.width/2,gameCvs.height/2);
gameCtx.rotate(-player.angle+Math.PI);
gameCtx.translate(-cameraPoly.pos.x,-cameraPoly.pos.y);
gameCtx.drawImage(world.worldCvs,0,0);
}`
What this is doing is resetting any transformations on the context, clearing it for a new redrawing, creating the world canvas, translating to display center, rotating by the proper amount for reference point, translating to reference center point on negative axis to move game canvas proper amount so that drawing at 0,0 is in the correct location. Thank you for the reference material!

trying to have a bot move randomly on a xz pane with Threejs

I started out with following code which creates a block that moves on a straight line:
const bot_geometry = new THREE.BoxGeometry(1,1,1);
const bot_material = new THREE.MeshBasicMaterial( {color: 0x7777ff, wireframe: false} );
const bot = new THREE.Mesh( bot_geometry, bot_material );
bot.position.x = 1;
bot.position.y = 0;
bot.position.z = 1;
scene.add(bot);
//create random directions for bot
let direction = new THREE.Vector3(0.001, 0, 0.002); // amount to move per frame
function animate() {
bot.position.add(direction); // add to position
requestAnimationFrame(animate); // keep looping
}
requestAnimationFrame(animate);
I am trying to make it move randomly on the plane and tried to use following two lines for the direction but now I have two blocks moving on a straight line opposite from each other. Is there a simple way to have one bot move randomly on the plane?:
let reverseDirection = Math.floor(Math.random()*2) == 1 ? 1 : -1;
let direction = new THREE.Vector3(0.001*reverseDirection, 0, 0.002*reverseDirection);
you can set random direction like this
direction = new THREE.Vector3(Math.random() * 2 - 1, 0, Math.random() * 2 - 1).normalize();
also, you can set global variables
var direction;
var speed = 5; // units per second
var clock = new THREE.Clock();
var delta;
var shift = new THREE.Vector3();
and in your animation loop
delta = clock.getDelta();
shift.copy(direction).multiplyScalar(delta * speed);
bot.position.add(shift);
jsfiddle example

Javascript and subdivision, crashing browser in for loop

I'm working on some generative visuals using paper.js, and the plan is to define a random shape, subdivide it, and then randomize those points as well (to a different degree). Right now, the browser is crashing while attempting to subdivide a polygon.
I'm relatively new to doing these things in Javascript, so maybe I am expecting too much out of it, apparently crashes within intensive for loops are just something that happen. Does anyone have some tips?
var Cloud = function(point) {
this.origin = point;
this.initBound = new Size(600,200);
this.RoM = 40;
var numsides = 6;
var scalingFactor = new Point(1,this.initBound.height/this.initBound.width);
var cloud = new Path.RegularPolygon({
center: this.origin,
sides: numsides,
radius: this.initBound.width/2,
strokeColor: 'black'
});
cloud.scaling = scalingFactor;
var initBoundBox = new Path.Rectangle({
point: new Point(point.x-this.initBound.width/2,point.y-this.initBound.height/2),
size: this.initBound,
strokeColor: 'red'
});
for(var i=0;i<numsides;i++){
var px = cloud.segments[i].point.x;
var py = cloud.segments[i].point.y;
var x = Math.floor(Math.random()*( (px+this.RoM)-(px-this.RoM)+1 ) + (px-this.RoM) );
var y = Math.floor(Math.random()*( (py+this.RoM)-(py-this.RoM)+1 ) + (py-this.RoM) );
var tmpP = new Point(x,y);
cloud.segments[i].point = tmpP;
}
for(var i=0;i<cloud.segments.length-1;i++){
var mdPnt = new Point((cloud.segments[i].point.x+cloud.segments[i+1].point.x)/2,(cloud.segments[i].point.y+cloud.segments[i+1].point.y)/2);
cloud.add(i,mdPnt); //breaking here
}
//cloud.smooth();
}
new Cloud(new Point(500,300));
In your last for-loop, you're adding a segment in each iteration, and thereby increasing cloud.segments.length by one. Your loop never ends. You can mitigate this by incrementing by 2 instead of 1, or finding a better bisection routine.
In short, try this as a starting point:
for(var i=0;i<cloud.segments.length-1;i+=2){
var mdPnt = new Point((cloud.segments[i].point.x+cloud.segments[i+1].point.x)/2,(cloud.segments[i].point.y+cloud.segments[i+1].point.y)/2);
cloud.add(i,mdPnt); //breaking here
}

Three.js Using 2D texture\sprite for animation (planeGeometry)

I'm quite new in html5 and three.js. I've been experimenting a bit with it, and basically what I want done is to have a Mesh (I'm using planeGeometry, as the tutorial I followed used it). The Mesh shows different Textures, which can change later on.
Here's what my code looks like:
angelTexture = THREE.ImageUtils.loadTexture("images/textures/chars/angel/angel.png");
angelTexture.offset.x = -0.75;
angelTexture.offset.y = -0.75;
angelMesh = new THREE.Mesh( new THREE.PlaneGeometry(79, 53, 79, 53), new THREE.MeshBasicMaterial( { map: angelTexture, wireframe: false } ));
angelMesh.position.x = 0;
angelMesh.position.y = 0;
scene.add(angelMesh);
The problem is that whenever I offset, the Mesh seems big enough to show all the other Sprites (I'm using the texture as a 2D Sprite that I offset to animate it). The result is quite disastrous and I am still figuring out how to control how big the Mesh is so that it shows only one snapshot of the Sprite. All my attempts seem only to resize the Mesh as well as the underlying Texture and still shows all the Sprites.
Can someone point me in the right direction? Thanks in advance.
...
My friend came up with a solution...
I missed the repeat property.
angelTexture = THREE.ImageUtils.loadTexture("images/textures/chars/angel/angel.png");
angelTexture.offset.x = -0.75;
angelTexture.offset.y = -0.75;
angelTexture.repeat.x = 0.25;
angelTexture.repeat.y = 0.25;
scene.add(angelMesh);
Hope this helps others having the same problem.
I had the same question a while ago, and so I have written up a complete example of animating using a spritesheet as the texture for a PlaneGeometry, and then updating the texture at regular intervals -- check out the example at
http://stemkoski.github.io/Three.js/Texture-Animation.html
and view the commented source code for additional explanation.
Update (2021):
Here is an updated version of the function I recommend using. It fixes the issue with the incorrect tile display order, it automatically updates the next frame, and it returns an object you can use to stop and re-start the animation as desired.
function TextureAnimator(texture, tilesHoriz, tilesVert, tileDispDuration)
{
let obj = {};
obj.texture = texture;
obj.tilesHorizontal = tilesHoriz;
obj.tilesVertical = tilesVert;
obj.tileDisplayDuration = tileDispDuration;
obj.numberOfTiles = tilesHoriz * tilesVert;
obj.texture.wrapS = THREE.RepeatWrapping;
obj.texture.wrapT = THREE.RepeatWrapping;
obj.texture.repeat.set( 1/tilesHoriz, 1/tilesVert );
obj.currentTile = 0;
obj.nextFrame = function()
{
obj.currentTile++;
if (obj.currentTile == obj.numberOfTiles)
obj.currentTile = 0;
let currentColumn = obj.currentTile % obj.tilesHorizontal;
obj.texture.offset.x = currentColumn / obj.tilesHorizontal;
let currentRow = Math.floor( obj.currentTile / obj.tilesHorizontal );
obj.texture.offset.y = obj.tilesVertical - currentRow / obj.tilesVertical;
}
obj.start = function()
{ obj.intervalID = setInterval(obj.nextFrame, obj.tileDisplayDuration); }
obj.stop = function()
{ clearInterval(obj.intervalID); }
obj.start();
return obj;
}
I've noted in my comment to Lee Stemkoski that spritesheets that have more than one row do not work the same when using the newer THREE.TextureLoader().
I am using the following 4x4 sprite image in my tests.
With no modification to Lee Stemkoski's TextureAnimator function, assuming you have a full 16 tile spritesheet.
var texture = new THREE.TextureLoader().load('grid-sprite.jpg');
var annie = new TextureAnimator(texture, 4, 4, 16, 150);
The animated texture runs backwards.
Codepen Demo
So I made my own which I call 🎉🎉🎉 THREE.SpriteSheetTexture 🎉🎉🎉
THREE.SpriteSheetTexture = function(imageURL, framesX, framesY, frameDelay, _endFrame) {
var timer, frameWidth, frameHeight,
x = 0, y = 0, count = 0, startFrame = 0,
endFrame = _endFrame || framesX * framesY,
CORSProxy = 'https://cors-anywhere.herokuapp.com/',
canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
canvasTexture = new THREE.CanvasTexture(canvas),
img = new Image();
img.crossOrigin = "Anonymous"
img.onload = function(){
canvas.width = frameWidth = img.width / framesX;
canvas.height = frameHeight = img.height / framesY;
timer = setInterval(nextFrame, frameDelay);
}
img.src = CORSProxy + imageURL;
function nextFrame() {
count++;
if(count >= endFrame ) {
count = 0;
};
x = (count % framesX) * frameWidth;
y = ((count / framesX)|0) * frameHeight;
ctx.clearRect(0, 0, frameWidth, frameHeight);
ctx.drawImage(img, x, y, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight);
canvasTexture.needsUpdate = true;
}
return canvasTexture;
}
And what you need to know about it
imageURL is the URL of your spritesheet
framesX is how many frames fit along the x axis (left and right)
framesY is how many frames fit along the y axis (up and down)
delay is how long it the texture waits to change to the next frame
_endFrame is optional - How many frames are there (in case it doesnt use a full row)
That all looks something like this
texture = new THREE.SpriteSheetTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/68819/grid-sprite.jpg', 4, 4, 100, 16);
var material = new THREE.MeshBasicMaterial({
map: texture
});
geometry = new THREE.BoxGeometry( 200, 200, 200 );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
And there was much rejoicing!!!
Codepen Demo Here
#Cmndo to make frames flow moves in the right order you just need to update this:
texture.offset.y = currentRow / this.tilesVertical;
to this:
texture.offset.y = this.tilesVertical - (currentRow / this.tilesVertical);
In this example:
https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/Texture-Animation.html
To make frames move in the right direction use:
texture.offset.y = (1 - currentRow / _tilesVertical) - (1 / _tilesVertical);
instead of
texture.offset.y = currentRow / this.tilesVertical;

Categories