3DObject event handler selecting nearby objects, should select self Three.js - javascript

I have been digging into the documentation, tutorials, stackoverflow questions literally all day and cannot find the solution to this problem. Please please take pity on me and help me!
I have a watering can that I put an event listener on, but on mousedown, objects near and to the left get selected ie. the tree trunk or a leaf (code not included) and then I am able to drag and drop that object around until I put it down and it is out of range of the watering can. If I drop it near the watering can, it will get selected again when I click. This is NOT the intended behavior, I want the actual watering can I clicked to be selected and then proceed to get dragged and dropped etc. There is kind of a lot of code here but I am not sure where the problem lies anymore so I am including almost all of it. It's like the watering can thinks it is elsewhere or the ray thinks it is shooting elsewhere. PLEASE TELL ME IF I CAN CLARIFY ANYTHING OR SEND MORE CODE. I just started learning three.js yesterday so there are definitely things I don't understand but I am pretty clear on what is supposed to be happening here... and this is not it :D
const WateringCan = function() {
this.mesh = new THREE.Object3D();
const mat = new THREE.MeshStandardMaterial({ metalness: 0.8, color: 0xadb2bd });
// Create the can
const geomCan = new THREE.CylinderGeometry(15, 15, 25, 10, 10);
geomCan.applyMatrix( new THREE.Matrix4().makeScale(1.1, 1.0, 0.6));
geomCan.computeBoundingBox();
geomCan.computeFaceNormals();
const can = new THREE.Mesh(geomCan, mat);
can.castShadow = true;
can.receiveShadow = true;
this.mesh.add(can);
// Create the handle
const geomHandle = new THREE.TorusGeometry( 10, 2, 8, 6, Math.PI);
geomHandle.applyMatrix( new THREE.Matrix4().makeScale(0.9, 1.1, 1.0));
const handle = new THREE.Mesh(geomHandle, mat);
handle.rotation.z = 4.5;
handle.position.x = 13.5;
handle.castShadow = true;
handle.receiveShadow = true;
this.mesh.add(handle);
// Create spout
const geomSpout = new THREE.CylinderGeometry(1, 3, 20, 5, 5);
const spout = new THREE.Mesh(geomSpout, mat);
spout.rotation.z = 1;
spout.position.x = -22;
spout.position.y = 10;
spout.position.z = 3;
spout.castShadow = true;
spout.receiveShadow = true;
this.mesh.add(spout);
const domEvents = new THREEx.DomEvents(camera, renderer.domElement);
domEvents.addEventListener(can, 'mousedown', (e) => onWateringCanMouseDown(e));
};
let wateringCan;
function createWateringCan() {
wateringCan = new WateringCan();
wateringCan.name = "wateringCan";
wateringCan.mesh.position.x = 120;
wateringCan.mesh.position.y = -30;
wateringCan.mesh.position.z = -10;
scene.add(wateringCan.mesh);
objects.push(wateringCan.mesh);
}
let plane;
function createPlane() {
plane = new THREE.Mesh(new THREE.PlaneBufferGeometry(WIDTH, HEIGHT, 8, 8), new THREE.MeshBasicMaterial({ color: 0xffffff, alphaTest: 0, visible: false }));
scene.add(plane);
}
let selection;
function onWateringCanMouseDown(e) {
const mouseX = (e.clientX / WIDTH) * 2 - 1;
const mouseY = -(e.clientY / HEIGHT) * 2 + 1;
const mouse3D = new THREE.Vector3(mouseX, mouseY, 0.5);
mouse3D.unproject(camera);
raycaster.set(camera.position, mouse3D.sub(camera.position).normalize());
const intersectedObjects = raycaster.intersectObjects(objects, true);
if (intersectedObjects.length > 0) {
selection = intersectedObjects[0].object;
const intersectPlane = raycaster.intersectObject(plane);
offset.z = selection.position.z;
offset.copy(intersectPlane[0].point).sub(plane.position);
}
}
function onDocumentMouseMove(e) {
const mouseX = (e.clientX / WIDTH) * 2 - 1;
const mouseY = -(e.clientY / HEIGHT) * 2 + 1;
const mouse3D = new THREE.Vector3(mouseX, mouseY, 0.5);
mouse3D.unproject(camera);
raycaster.set(camera.position, mouse3D.sub(camera.position).normalize());
raycaster.setFromCamera( mouse3D.clone(), camera);
if (selection) {
offset.z = selection.position.z;
const intersectPlane = raycaster.intersectObject(plane);
selection.position.copy(intersectPlane[0].point.sub(offset));
} else {
const intersectedObjects = raycaster.intersectObjects(objects);
if (intersectedObjects.length > 0) {
plane.position.copy(intersectedObjects[0].object.position);
plane.lookAt(camera.position);
}
}
}
function onDocumentMouseUp(e) {
selection = null;
}
Things I have already tried: computeBoundingBox() on everything I have applyMatrix on, refactoring mouse3D/raycaster around in slightly different ways, moving the can far away (then nothing gets selected). I also had the event listener set on the document for a while and I did not have this problem, but the watering can pieces did not stick to each other, the individual spout/can/handle would get dragged and dropped. But at least it knew then that I was clicking on them! Also having the ability to drag and drop literally everything on the page was fun but also not the point, like I don't need all the leaves on my tree to move around.
Please please help me if you can!! I have been messing around with this all day and it is killing my soul. :D

Okay, so. I did not find out what the reason behind this strange behavior was so I decided to kind of restructure what I was doing and I am no longer having this problem.
What I did is:
removed the event listener from the watering can and put it back on the document
removed all objects but the watering can from my objects array that gets looked through on raycaster.intersectObjects so now nothing but that can be dragged and dropped
things are mostly fine now

Related

Difference between BABYLON.Animation and scene.registerBeforeRender

I just started to learn more about Babylon.js (I don't know if this is a good choice between p5.js and three.js; throw some suggestions for me).
I came along with this question "which function is used more often between BABYLON.Animation and scene.registerBeforeRender(). I guess I am more used to use render() method, but I guess Animation function is good when I change the frameRates.
Which is better? which is used more often ?
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = () => {
const scene = new BABYLON.Scene(engine);
/**** Set camera and light *****/
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 10, new BABYLON.Vector3(0, 0, 0));
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));
const box = BABYLON.MeshBuilder.CreateBox("box", {});
box.position.y = 0.5;
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width:10, height:10});
// Animations
var alpha = 0;
scene.registerBeforeRender(function () {
box.rotation.y += 0.05;
});
const frameRate = 60;
const xSlide = new BABYLON.Animation("xSlide", "position.x", frameRate, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
const keyFrames = [];
keyFrames.push({
frame: 0,
value: 2
});
keyFrames.push({
frame: frameRate,
value: -2
});
keyFrames.push({
frame: 2 * frameRate,
value: 2
});
xSlide.setKeys(keyFrames);
box.animations.push(xSlide);
scene.beginAnimation(box, 0, 2 * frameRate, true);
return scene;
}
const scene = createScene();
engine.runRenderLoop(() => {
// call render method for our scene
scene.render();
});
scene.registerBeforeRender() is more flexible in changing the values. In your example, you are changing rotation by 0.05 constant value. In some cases, this may be variable, so you can assign a variable instead of constant value.
It is tricky to change this in Animation class methods, because in KeyFrame, your key and values are fixed once you set them. The only way I could change this is remove animations and add new ones. On a positive side, in Animation class methods, you can change frameRate and change what you want to do in a particular frame.

Three.js - Extrude certain vertex/face from BufferGeometry

I made a new THREE.PlaneBufferGeometry(100, 100, 100, 100); and have been able to update position of vertices to change the mesh's shape like following:
I achieved this by following this discussion: Threejs drag points
What am I looking for?
I want to be able to extrude a face (grab 4 vertices), so I achieve something like this:
I want to keep it all part of the same mesh, to keep it clean, because I will be exporting it as a single mesh with the ColladaExporter.
Edit
In order to achieve this, I would need to clone vertex and extrude them upwards. This means, adding 4 new vertex and connecting them together.
I tried this:
var geo = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
geo.rotateX(-Math.PI * 0.5);
geo.translate(0,0.5,0);
//And the merge them together
var newplane = BufferGeometryUtils.mergeBufferGeometries([plane, geo]);
newplane = BufferGeometryUtils.mergeVertices(newplane,1);
And I got this:
I was hoping all vertices merged with the plane, leaving a flat plane. I did this for testing purposes, but it only merged one corner.
I started building a "cube" with multiple and placing them in the right spot, to then apply again BufferGeometryUtils.mergeVertices, but the vertices don't seem to merge correctly:
Edit 2 / Progress
I managed to create a PlaneBufferGeometry and extrude it by manually modifying the vertexes and normals, as told in: https://threejs.org/docs/#api/en/core/BufferGeometry
Extruded plane has all vertices connected, so whenever I drag one vertex it drags a whole piece, the problem now is that I need to connect these new vertices to the original grid to avoid this:
Goal is to merge all vertices, now I need to find a way to merge the base plane with the new extruded piece.
Edit 3 / Done
I made it, I will post answer when I have some time. I spent all day long on these today, and already very tired.
Not sure if that's what you need, but here's the modified example from the answer you referred to (please notice the difference in mouseMove implementation). I've extended that for two points only, but I believe you should get the idea:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1.25, 7, 7);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.PlaneBufferGeometry(10, 10, 10, 10);
geometry.rotateX(-Math.PI * 0.5);
var plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
wireframe: true,
color: "red"
}));
scene.add(plane);
var points = new THREE.Points(geometry, new THREE.PointsMaterial({
size: 0.25,
color: "yellow"
}));
scene.add(points);
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 0.25;
var mouse = new THREE.Vector2();
var intersects = null;
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var currentIndex = null;
var planePoint = new THREE.Vector3();
var dragging = false;
window.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mousemove", mouseMove, false);
window.addEventListener("mouseup", mouseUp, false);
function mouseDown(event) {
setRaycaster(event);
getIndex();
dragging = true;
}
function mouseMove(event) {
if (dragging && currentIndex !== null) {
setRaycaster(event);
raycaster.ray.intersectPlane(plane, planePoint);
var indicesToMoveUp = [currentIndex-1, currentIndex];
var delta_x = geometry.attributes.position.getX(currentIndex) - planePoint.x;
geometry.attributes.position.setXYZ(currentIndex, planePoint.x, planePoint.y, planePoint.z);
geometry.attributes.position.needsUpdate = true;
var old_x_neighbour = geometry.attributes.position.getX(currentIndex - 1);
geometry.attributes.position.setY(currentIndex-1, planePoint.y);
geometry.attributes.position.setZ(currentIndex-1, planePoint.z);
geometry.attributes.position.setX(currentIndex-1, old_x_neighbour - delta_x);
geometry.attributes.position.needsUpdate = true;
}
}
function mouseUp(event) {
dragging = false;
currentIndex = null;
}
function getIndex() {
intersects = raycaster.intersectObject(points);
if (intersects.length === 0) {
currentIndex = null;
return;
}
currentIndex = intersects[0].index;
setPlane(intersects[0].point);
}
function setPlane(point) {
planeNormal.subVectors(camera.position, point).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, point);
}
function setRaycaster(event) {
getMouse(event);
raycaster.setFromCamera(mouse, camera);
}
function getMouse(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>

threejs getImageData video performance

Edit;
working codepen (need to provide video file to avoid cross-origin policy)
https://codepen.io/bw1984/pen/pezOXm
I am attempting to modify the excellent rutt etra example here https://airtightinteractive.com/demos/js/ruttetra/ to work for video (still using threejs) and am encountering strange issues with performance.
My code currently works as expected, and actually runs quite smoothly on chrome on my macbook pro, but seems to cause some sort of slow memory leak which i assume is to do with all the heavy lifting which is having to be done by getImageData. Strangely enough its only noticeable once i attempt to refresh the tab, so looks like it may be related to the garbage collection in chrome maybe? anyway to shunt the grunt work onto the GPU instead of killing the CPU?
I just wondered if i am missing anything obvious in terms of code optimisation or if the performance issues i am facing are to be expected given the nature of what i am trying to do.
I am only interested in WebGL / chrome functionality so dont really need to worry about browser compatibility of any kind.
<script>
var container, camera, scene, renderer, controls;
// PI
var PI = Math.PI;
var TWO_PI = PI*2;
// size
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
SCREEN_PIXEL_RATIO = window.devicePixelRatio;
// camera
var VIEW_ANGLE = 45;
var ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT;
var NEAR = 0.1;
var FAR = 20000000;
// video raster
var video;
var videoImage;
var videoImageContext;
var _imageHeight;
var _imageWidth;
// lines
var _lineGroup;
// gui
var _guiOptions = {
stageSize: 1,
scale: 1.0,
scanStep: 5,
lineThickness: 10.0,
opacity: 1.0,
depth: 50,
autoRotate: false
};
// triggered from audio.php getMediaStream
function runme()
{
console.log('runme running');
init();
animate();
}
runme();
function init()
{
container = document.createElement('div');
document.body.appendChild(container);
//----------
// scene
//----------
scene = new THREE.Scene();
//----------
// camera
//----------
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
//camera.position.set(0,0,450);
camera.position.set(0,150,300);
//----------
// objects
//----------
// create the video element
video = document.createElement('video');
// video.id = 'video';
// video.type = ' video/ogg; codecs="theora, vorbis" ';
video.src = 'data/sintel.ogv';
//video.src = 'data/az.mp4';
video.load(); // must call after setting/changing source
video.play();
videoImage = document.createElement('canvas');
//videoImage.width = 480;
//videoImage.height = 204;
videoImageContext = videoImage.getContext('2d');
_imageWidth = videoImage.width;
_imageHeight = videoImage.height;
//videoImageContext.fillStyle = '#ffffff';
//videoImageContext.fillRect(0, 0, videoImage.width, videoImage.height);
//----------
// controls
//----------
controls = new THREE.OrbitControls(camera);
//----------
// events
//----------
window.addEventListener('resize', onWindowResize, false);
//----------
// render
//----------
var args = {
//antialias: true // too slow
}
renderer = new THREE.WebGLRenderer(args);
renderer.setClearColor(0x000000, 1);
renderer.setPixelRatio(SCREEN_PIXEL_RATIO); //Set pixel aspect ratio
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
// attach to dom
container.appendChild(renderer.domElement);
//render();
}
function render()
{
if(video.readyState === video.HAVE_ENOUGH_DATA && !video.paused && !video.ended) // and video.currentTime > 0
{
//_imageWidth = videoImage.width;
//_imageHeight = videoImage.height;
videoImageContext.drawImage(video,0,0,_imageWidth,_imageHeight);
// Grab the pixel data from the backing canvas
var _data = videoImageContext.getImageData(0,0,videoImage.width,videoImage.height).data;
//log(data);
//_pixels = data;
var x = 0, y = 0;
if(_lineGroup)
{
scene.remove(_lineGroup);
//_lineGroup = null;
}
_lineGroup = new THREE.Object3D();
var _material = new THREE.LineBasicMaterial({
color: 0xffffff,
linewidth: _guiOptions.lineThickness
});
// loop through the image pixels
for(y = 0; y < _imageHeight; y+= _guiOptions.scanStep)
{
var _geometry = new THREE.Geometry();
for(x=0; x<_imageWidth; x+=_guiOptions.scanStep)
{
var color = new THREE.Color(getColor(x, y, _data));
var brightness = getBrightness(color);
var posn = new THREE.Vector3(x -_imageWidth/2,y - _imageHeight/2, -brightness * _guiOptions.depth + _guiOptions.depth/2);
//_geometry.vertices.push(new THREE.Vertex(posn));
_geometry.vertices.push(posn);
_geometry.colors.push(color);
_color = null;
_brightness = null;
_posn = null;
}
// add a line
var _line = new THREE.Line(_geometry, _material);
//log(line);
_lineGroup.add(_line);
// gc
_geometry = null;
}
scene.add(_lineGroup);
_data = null;
_line = null;
}
renderer.render(scene,camera);
}
function animate(){
requestAnimationFrame(animate);
stats.update();
render();
}
function onWindowResize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
// Returns a hexadecimal color for a given pixel in the pixel array.
function getColor(x, y, _pixels)
{
var base = (Math.floor(y) * _imageWidth + Math.floor(x)) * 4;
var c = {
r: _pixels[base + 0],
g: _pixels[base + 1],
b: _pixels[base + 2],
a: _pixels[base + 3]
};
return (c.r << 16) + (c.g << 8) + c.b;
}
// return pixel brightness between 0 and 1 based on human perceptual bias
function getBrightness(c)
{
return ( 0.34 * c.r + 0.5 * c.g + 0.16 * c.b );
}
</script>
any help anyone could provide would be much appreciated, even if its just pointing me in the right direction as i am only just beginning to experiment with this stuff and have almost given myself an aneurysm trying to wrap my tiny mind around it.
The slow memory leak is most likely due to:
// add a line
var _line = new THREE.Line(_geometry, _material);
//log(line);
_lineGroup.add(_line);
THREE.Line is an object, containing other objects and lots of data. Every time you instantiate it, it creates .matrix, .matrixWorld, .modelViewMatrix, .normalMatrix which are all arrays with a bunch of numbers. .position,.quaternion, .scale, .rotation and probably .up are vectors,quats etc. and are slightly smaller but also arrays with special constructors.
Allocating all this every 16 miliseconds only to be released the next frame is probably the cause of your "leak".
You should create a pool of THREE.Line objects, and draw that every frame instead. The number of drawn objects you can control with .visible and mutate their transformation properties.
#pailhead I took your advice about pre-rendering the lines and lineGroup in advance and then updating the vertices on each animation frame instead and now its purring like a kitten. Also needed to insert the following line to make sure updated coords are picked up;
e.geometry.verticesNeedUpdate = true;
I cant figure out how to get a hosted video to work on codepen (cross-origin policy violation issues) but i have put a version up anyway to show the working code.
https://codepen.io/bw1984/pen/pezOXm
I will try to get a self-hosted (working) version up as soon as i can
I've been trying in vain to get colour working, but that will have to be an exercise for another day.

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;

box2dweb beginner: can't simulate fixture added by clicking on canvas

I've just started playing around with box2dweb and have now run into a problem that is leaving me stumped.
I have a basic simulation with a "ground" static fixture and some code that allows me to add shapes (just circles for now) to the canvas at the point where I click on it.
My code is working in as far as I can add as many circles to the canvas as I want and have them drawn on the canvas.
I then have a button that begins the simulation by entering the step loop. I would expect that when I click the button, the circles would fall to the "ground" and eventually come to rest, thereby ending the simulation.
The problem is that as soon as I begin the simulation, the very next frame completely removes all the fixtures in the scene. However, if I start out with a circle in the scene (not added by click, but by the initialization function that creates the world and the ground), and I begin the simulation, it runs as expected: the circle drops at some rate and stops on the "ground".
I'm using the same function addCircle() to add the circle at either the initialization of the scene or when I click on the canvas, so it seems like that code is fine.
// this function is called by the canvas onclick
function addShape(e) {
var shape = $('input:radio[name=shape]:checked').val();
var offset = $("#c").offset();
if (shape == "circle") {
addCircle((e.pageX - offset.left) / SCALE,
(e.pageY - offset.top) / SCALE,
$("#dimen").val());
}
gWorld.DrawDebugData();
}
// same function is used by the above handler and also to set up a circle
// in the initial scene, however when added via click, the simulation
// breaks (all objects disappear in the next frame)
function addCircle(x, y, r) {
var fixDef = new b2FixtureDef;
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.x = x;
bodyDef.position.y = y;
fixDef.shape = new b2CircleShape(r);
gWorld.CreateBody(bodyDef).CreateFixture(fixDef);
}
// called when a button to start the simulation is clicked
function startSimulation() {
gWorld.SetGravity(new b2Vec2(0, parseInt($("#gravity").val())));
gStopped = false;
requestAnimFrame(update);
}
// this is the main step loop
function update() {
if (!gStopped) {
gWorld.Step(
1 / 60 //frame-rate
, 10 //velocity iterations
, 10 //position iterations
);
gWorld.DrawDebugData();
gWorld.ClearForces();
var keepGoing = true;
for (var b = gWorld.GetBodyList(); keepGoing && b; b = b.m_next) {
if (! b.IsAwake()) {
keepGoing = false;
}
}
if (keepGoing) {
requestAnimFrame(update);
}
}
function init() {
gWorld = new b2World(
new b2Vec2(0, parseInt($("#gravity").val())),
true);
var fixDef = new b2FixtureDef;
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;
//create ground
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_staticBody;
bodyDef.position.x = $("#c").width() / 2 / SCALE;
bodyDef.position.y = $("#c").height() / SCALE;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox((600 / SCALE) / 2, (10/SCALE) / 2);
gWorld.CreateBody(bodyDef).CreateFixture(fixDef);
//setup debug draw
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("c").getContext("2d"));
debugDraw.SetDrawScale(SCALE);
debugDraw.SetFillAlpha(0.3);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
gWorld.SetDebugDraw(debugDraw);
// this circle is simulated correctly
addCircle(1,1,1);
gWorld.DrawDebugData();
}
I've found that the problem was in lines like these:
$("#dimen").val()
When I changed this to the following, the simulation began to run as normal:
parseFloat($("#dimen").val())
Box2DWeb seriously lack documentation, Been breaking my head over it. Figured out some of the basic workflows, have blogged about it in here http://box2dinabox.blogspot.in/ . hope you will find it useful :)

Categories