I'm swapping in new position data to shape a point cloud (alternating between a tall cloud and a wide cloud) when a user makes a click. I've made this fiddle, and so far I can get the cloud to render correctly the first time (a tall cloud), but on the the click event all the point positions appear to become 0,0 instead of becoming a wide cloud. What have I missed here?
Here are a few snippets for inspection:
function makeCloud() {
var cloudWidth, cloudHeight;
if ( isTallCloud ) {
cloudHeight = 100;
cloudWidth = 25;
} else {
cloudHeight = 25;
cloudWidth = 100;
}
isTallCloud = !isTallCloud;
var positions = new Float32Array( particles * 4 );
var offsets = new Float32Array( particles * 4 );
for ( var i = 0, i4 = 0; i < particles; i ++, i4 +=4 ) {
positions[ i4 + 0 ] = ( Math.random() * 2 - 1 ) * cloudWidth; // x
positions[ i4 + 1 ] = ( Math.random() * 2 - 1 ) * cloudHeight; // y
positions[ i4 + 2 ] = 0.0; // velocity
positions[ i4 + 3 ] = 0.0; // velocity
offsets[ i4 + 0 ] = positions[ i4 + 0 ]; // width offset
offsets[ i4 + 1 ] = positions[ i4 + 1 ]; // height offset
offsets[ i4 + 2 ] = stateChange; // this will hold the change state ( 0.0 or 1.0 )
offsets[ i4 + 3 ] = 0.0; // not used
}
cloudData = {
"positions" : positions,
"offsets" : offsets
}
}
function updateStateChange() {
for ( var i = 0, i4 = 0; i < particles; i ++, i4 +=4 ) {
cloudData.offsets[ i4 + 2 ] = stateChange; // this will hold the change state ( 0.0 or 1.0 )
}
}
function updateOffsets() {
offsetsTexture = new THREE.DataTexture( cloudData.offsets, width, height, THREE.RGBAFormat, THREE.FloatType );
positionUniforms.tOffsets.value = offsetsTexture;
positionUniforms.tOffsets.needsUpdate = true;
}
function onDocumentClick() {
event.preventDefault();
stateChange = 1.0;
makeCloud();
updateOffsets();
}
function animate() {
requestAnimationFrame( animate );
update();
render();
}
function render() {
renderer.render( scene, camera );
}
function update() {
positionUniforms.uTime.value += 0.01;
gpuCompute.compute();
particleUniforms.tPositions.value = gpuCompute.getCurrentRenderTarget( positionVariable ).texture;
if ( stateChange === 1.0 ) {
stateChange = 0.0;
console.log("swapped state!");
updateStateChange();
updateOffsets();
}
}
A snippet from the shader code:
vec4 offsets = texture2D( tOffsets, uv ).xyzw;
float swapState = offsets.z;
vec4 nowPos;
vec2 velocity;
if ( swapState == 0.0 ) {
nowPos = texture2D( tPositions, uv ).xyzw;
velocity = vec2(nowPos.z, nowPos.w);
} else { // if swapState == 1.0
nowPos = vec4( offsets.x, offsets.y, 0.0, 0.0 );
velocity = vec2(0.0, 0.0);
}
Background:
I realize it would be easy enough just to swap all the data on a click and get the result I'm looking for; however, the reason I'm trying to update by passing in these offset values in a texture is that in my larger program there are many more point clouds and I only want to alter selected clouds (as indicated by the swapState
in the shader), while letting the physics shader continue to control the remaining unselected ones. Updating all the clouds would mess with the continuity of the physics controlled by the shader.
I found the bug in my code -- the flaw was in how I was setting needsUpdate
attribute for the DataTexture
. See the fixed fiddle here. This is the revision that made it finally work as expected:
function updateOffsets() {
offsetsTexture = new THREE.DataTexture( cloudData.offsets, width, height, THREE.RGBAFormat, THREE.FloatType );
offsetsTexture.needsUpdate = true; // **using this way of updating the texture produced the behavior I expected
positionUniforms.tOffsets.value = offsetsTexture;
// positionUniforms.tOffsets.needsUpdate = true; **this form of updating caused the faulty behavior
}