Change length of cylinder or extrudedHeight of circle - javascript

I'm trying to change the length of a cylinder or the extrudedHeight of a circle when it has been added to the primitives and is shown in the cesium widget/viewer. For example this cylinder:
var length = 100;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(
Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
var primitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
widget.scene.primitives.add(primitive);
Because it's added to the primitives array it will be shown in the widget, but after 2 seconds for example I get a notification that the length should be halved (that means set to 50). Is there any way to do this? Simply changing it in cylinderGeometry doesn't seem to do the job.
I kind of have it working by creating a new cylinder with the new height, adding it and removing the old one. This however tends to flicker the cylinder (it's gone for a fraction of a second) before the new one is shown. I fixed this problem by removing the old instance after a set time after the new one is added. This whole solution isn't very elegant and doesn't work very well on devices with a small amount of computing power, hence my search for a better solution.
I don't care if this is achieved using cylinders or extruded circles. If you need any more information don't hesitate to ask in the comments below the question.
EDIT
I implemented the second solution Matthew suggested but after a while of it running perfectly the cylinders stop changing height (which didn't occur when I used my solution. The callback in the interval does get called. Here is some code showing what my new solution is (not working):
primitives.add(prim);
window.nodeValuesInterval = setInterval(function () {
if (prim._state == Cesium.PrimitiveState.COMPLETE) {
clearInterval(window.nodeValuesInterval);
clearTimeout(window.nodeValuesTimeout);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}
}, cylindervalueinterval);
window.nodeValuesTimeout = setTimeout(function () {
clearInterval(window.nodeValuesInterval);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}, cylindervaluedelay);

Cesium's geometry is currently optimized for static data. Some attributes, such as visibility, color, and material can be changed on the fly, but items that actually modify the geometry (like cylinder height) require you to remove the primitive and recompute the geometry. The flickering your seeing is the result of asynchronous primitive creation being on by default. There are two ways to do want you want.
Disable asynchronous primitive create by passing [options.asynchronous: false to the Primitive constructor. This means that when you add a new primitive, Cesium will not render until it is ready. For one or two objects, you won't notice anything. For lots of objects it will lock up the browser until everything is ready. This does guarantee that you can remove old/add new primitives without any flicker.
The second option is to add your new primitive (without removing the old one) and then every frame, check the _state property of your new Primitive (I thought this was part of the public API but apparently it's not). When the _state is equal to Cesium.PrimitiveState.COMPLETE you can safely remove the old primitive and your guaranteed the new one will render (hence no flicker).
I think we have a bug/feature request to expose the state variable publicly or otherwise notify when the Primitive is ready; but using _state should be fine for the forseeable future. I'll update this issue if we add an official way sometime soon.
Hope that helps.
EDIT: Since more help was requested; here's a complete example. You can copy and paste the below code into Sandcastle using this link.
Basically it uses the scene.preRender event instead of a timeout (preRender is almost always the better answer here). Also, if you receive a new update before the old one is finished processing, it's important to remove that one before computing the new one. Let me know if you are still having problems.
require(['Cesium'], function(Cesium) {
"use strict";
var widget = new Cesium.CesiumWidget('cesiumContainer');
var ellipsoid = widget.scene.globe.ellipsoid;
var lon = 0;
var lat = 0;
var cylinderradius = 30000;
var length = 10000000;
var cylinderslices = 32;
var newPrimitive;
var currentPrimitive;
//This function creates a new cylinder that is half the length of the old one.
function decreaseLength() {
//If there's a pending primitive already, remove it.
if(Cesium.defined(newPrimitive)){
widget.scene.primitives.remove(newPrimitive);
}
length /= 2;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
newPrimitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
//We add the new cylinder immediately, but don't remove the old one yet.
widget.scene.primitives.add(newPrimitive);
}
//Create the initial cylinder.
decreaseLength();
//Subscribe to the preRender event so we can check the primitive every frame.
widget.scene.preRender.addEventListener(function(scene, time) {
//Remove the old cylinder once the new one is ready.
if(Cesium.defined(newPrimitive) && newPrimitive._state === Cesium.PrimitiveState.COMPLETE){
if(Cesium.defined(currentPrimitive)){
widget.scene.primitives.remove(currentPrimitive);
}
currentPrimitive = newPrimitive;
newPrimitive = undefined;
}
});
Sandcastle.addToolbarButton('Decrease Length', decreaseLength);
Sandcastle.finishedLoading();
});

Related

Cesium JS - Accessing primitive attributes without using id

So essentially, I have a function that draws a colored rectangle at every globe coordinate point via coordinate and color arrays. (Frequency means new rectangle every x coordinates)
//Given an array of coordinates, respective colors, and level of detail,
//Draws heatmap on the globe
function DrawMapGivenArrays(CoordinateArray, Colors, frequency)
{
var instances = [];
for(var i = 0; i < CoordinateArray.length; i++)
{
var Cartesian1 = new Cesium.Cartesian3.fromDegrees(CoordinateArray[i].lon,
CoordinateArray[i].lat);
var Cartesian2 = new Cesium.Cartesian3.fromDegrees(CoordinateArray[i].lon+frequency,
CoordinateArray[i].lat-frequency);
var CartesianArray = Array();
CartesianArray.push(Cartesian1);
CartesianArray.push(Cartesian2);
var newPrim = new Cesium.GeometryInstance
({
geometry : new Cesium.RectangleGeometry
({
rectangle : Cesium.Rectangle.fromCartesianArray(CartesianArray),
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
}),
attributes :
{
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Colors[i])
},
id: "Rectangle" + i,
});
instances.push(newPrim);
numberOfRectangles++;
}
var primitive = new Cesium.Primitive
({
releaseGeometryInstances : false,
geometryInstances : instances,
appearance : new Cesium.PerInstanceColorAppearance(),
});
scene.primitives._primitives[1] = primitive;
}
That works fine.. After I draw the map, I'm using this small function below to individually edit the color of one rectangle. (I call this for every rectangle to change all of them).
//Changes the color of a rectangle primitive given its unique id and a color value
function setPrimitiveRectangle(id, color)
{
var CesiumColor = Cesium.ColorGeometryInstanceAttribute.toValue(color);
scene.primitives._primitives[1].getGeometryInstanceAttributes(id).color = CesiumColor; //this line is 10x slower for every instance after it runs the 1st time
//".getGeometryInstanceAttributes(id)" This specific phrase runs 10x slower after 1st instance
}
That works as well. But, for some reason, it has issues.
For example, when I re-color all of the rectangles the first time, it runs very fast. However, every time I re-fun that function again after the first time, it's 10x slower. I narrowed it down to the phrase that was giving me problems( ".getGeometryInstanceAttributes(id)" ).
I tried to circumvent calling the get function by modifying color values directly with this: (Where i is iterating over every rectangle).
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[0] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[1] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[2] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[3] = 0;
Once I do this, I can check in chrome and see that the values located at those areas changed, however, the colors of the rectangles do not update.
I don't understand why ".getGeometryInstanceAttributes(id)" runs 10x slower after it's called the first time, and why I cannot directly modify viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[0].
Thanks

Three.js: Looking to spawn objects and animate the objects on a curve

I am trying to spawn a set of objects on a setInterval and give each of these objects their own animation on a path (currently using requestAnimationFrame to do so). I managed to add one object and animate this on a path. With this code:
var psGeometry = new THREE.PlaneGeometry(3,2,10,1);
var psPlane = new THREE.Mesh(psGeometry, new THREE.MeshBasicMaterial({color:0x0000ff}));
scene.add(psPlane);
function animatePaper(obj = psPlane, offset= 0.007)
{
if(counter <=( 1-obj.geometry.vertices.length/2 *offset))
{
for (var i=0; i < obj.geometry.vertices.length/2; i++)
{
obj.geometry.vertices[i].y = curvePath.getPoint(counter + i * offset).y;
obj.geometry.vertices[i].z = -0.5;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].y = curvePath.getPoint(counter + i * offset).y;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].z = -2.5;
obj.geometry.vertices[i].x = curvePath.getPoint(counter + i * offset).x;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].x = curvePath.getPoint(counter + i * offset).x;
}
obj.geometry.verticesNeedUpdate = true;
counter += 0.005;
}
else{
console.log("Removing...");
scene.remove(obj);
}
}
function animate() {
requestAnimationFrame(animate);
animatePaper(psPlane, 0.007);
render();
}
Example can be found here: jsfiddle.net.
Since this animates the object along the curvePath (see jsfiddle example), I figured that spawning these objects on an interval and applying the above code should work. Wrong!.
I tried: creating a function spawning objects and applying the above code:
setInterval(drawSheets, 1000);
function drawSheets()
{
var psGeometry = new THREE.PlaneGeometry(3,2,10,1);
var psPlane = new THREE.Mesh(psGeometry, new THREE.MeshBasicMaterial({color:0x0000ff}));
scene.add(psPlane);
setInterval(function(){animatePaper(psPlane, 0.007);}, 30);
}
I also tried on the basis of this answer:
setInterval(objArray.forEach(function(obj){setInterval(function(){animatePaper(obj);},300);}), 3000);
Expected:
Spawning multiple objects on an interval and animate each of these objects seperately over a curve.
Hopefully anyone could help me out! Cheers.
Version: Three.js r82
** EDIT: ** Small refinement. After another small test (jsfiddle). I found out that when I use setInterval on a function, it shares the same variable (thus speeding up the animation). Since this is part of the problem I would like to ask if someone knows how to make these variables local to an object.
Consider creating an array containing each of your Path and Plane objects (or perhaps one array for Paths and one array for Planes) along with their distinctive offsets or other values, then loop though these in an update function in your animation loop, running each through your animatePaper function.
In pseudo code:
var planesAndMeshesArray = [
{ path1 : (your plane here), plane1 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
{ path2 : (your plane here), plane2 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
{ path3 : (your plane here), plane3 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
...]
- create a loop to write the above array with random values in an appropriate range to suit the effects you're looking for
- loop through the above array to add each of the meshes and planes to the scene
function update() {
- update each object by looping through the above array through your `animatePaper` function. It works as a handy set of pointers to each of the objects in your scene - if you change them in the array, they will change in your scene.
- also update your controls
}
function animate() {
requestAnimationFrame(animate);
update();
render();
}
Going one step further, you can write object-oriented Javascript to create each of your curve-and-paper objects. I'd recommend starting with the array first and adding further complexity as needed.

THREE.js line drawn with BufferGeometry not rendering if the origin of the line isn't in the camera's view

I am writing a trace-line function for a visualization project that requires jumping between time step values. My issue is that during rendering, the line created using THREE.js's BufferGeometry and the setDrawRange method, will only be visible if the origin of the line is in the camera's view. Panning away will result in the line disappearing and panning toward the origin of the line (usually 0,0,0) will make it appear again. Is there a reason for this and a way around it? I have tried playing around with render settings.
The code I have included is being used in testing and draws the trace of the object as time progresses.
var traceHandle = {
/* setup() returns trace-line */
setup : function (MAX_POINTS) {
var lineGeo = new THREE.BufferGeometry();
//var MAX_POINTS = 500*10;
var positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
lineGeo.addAttribute('position', new THREE.BufferAttribute(positions, 3));
var lineMaterial = new THREE.LineBasicMaterial({color:0x00ff00 });
var traceLine = new THREE.Line(lineGeo, lineMaterial);
scene.add(traceLine);
return traceLine;
},
/****
* updateTrace() updates and draws trace line
* Need 'index' saved globally for this
****/
updateTrace : function (traceLine, obj, timeStep, index) {
traceLine.geometry.setDrawRange( 0, timeStep );
traceLine.geometry.dynamic = true;
var positions = traceLine.geometry.attributes.position.array;
positions[index++]=obj.position.x;
positions[index++]=obj.position.y;
positions[index++]=obj.position.z;
// required after the first render
traceLine.geometry.attributes.position.needsUpdate = true;
return index;
}
};
Thanks a lot!
Likely, the bounding sphere is not defined or has radius zero. Since you are adding points dynamically, you can set:
traceLine.frustumCulled = false;
The other option is to make sure the bounding sphere is current, but given your use case, that seems too computationally expensive.
three.js r.73

Place a PolygonGeometry in the air in Cesium

I'm trying to position a PolygonGeometry in the air in Cesium. In short, I'd like to use height to create an offset from the ground, and extrudedHeight to give the object a certain thickness. However when I set extrudedHeight, the height setting itself is ignored and the extrusion goes down all the way to the ground. So I can layer planes on top of each other, but no three-dimensional objects. What's the correct way to achieve this?
Here's what I'm doing so far:
polygonGeometry = Cesium.PolygonGeometry.fromPositions(
positions: pos,
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
extrudedHeight: #options.extrudedHeight,
height:#options.height
)
geometryInstance = new Cesium.GeometryInstance
geometry: polygonGeometry
primitive = new Cesium.Primitive
geometryInstances: [geoInstance]
It's hard to say exactly what the problem is, since your example is incomplete and also seems to be using a form of data binding that I'm not familiar with. But here's a complete example that can be copied/pasted into Sandcastle so that you can compare.
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var positions = Cesium.Cartesian3.fromDegreesArray([
-88.0, 35.0,
-80.0, 35.0,
-80.0, 40.0,
-88.0, 40.0
]);
var geometryInstance = new Cesium.GeometryInstance({
geometry : Cesium.PolygonGeometry.fromPositions({
positions : positions,
height: 1000000,
extrudedHeight: 1500000,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.ORANGE)
}
});
scene.primitives.add(new Cesium.Primitive({
geometryInstances : geometryInstance,
appearance : new Cesium.PerInstanceColorAppearance({
closed : true,
translucent : false
})
}));

unable to applyMatrix in three.js

I am trying to run an animation from a JSON file. I am using a custom JSON loader, (i.e. not the one included with three.js).
So I have an object named frames, which contain many frames, all of them have shape information, and a simulation_matrix, which contains data required for animation in the form of a 4by4 transformation matrix(generated from a python script).
So I am using this code for animation ..
and this is a sample JSON script to load.
// This method is for adding static shapes
// This works perfectly fine ..
parent.add_shape = function(frame)
{
var material = new THREE.MeshLambertMaterial({
color: frame.shape.color,
wireframe: true,
wireframeLinewidth: 0.1,
opacity: 0.5
})
var geometry = new THREE.CylinderGeometry(frame.shape.radius,frame.shape.radius,frame.shape.height,50,50);
// mesh_dict dictionary maps a mesh(shape) to its frame
parent.mesh_dict[frame] = new THREE.Mesh(geometry,material);
var init_orientation = frame.simulation_matrix[0];
var orienter = new THREE.Matrix4();
orienter.elements = [];
//Since simulation_matrix is generated from python, it is a
// list of lists, We need to push it to the elemens of Matrix4 manually ..
for(var i in init_orientation)
{
for(var j in init_orientation[i])
{
orienter.elements.push(init_orientation[i][j]) ;
}
}
parent.mesh_dict[frame].applyMatrix(new THREE.Matrix4());
parent.mesh_dict[frame].applyMatrix(orienter);
parent.scene.add(parent.mesh_dict[frame]);
parent.renderer.render(parent.scene,parent.camera);
}
// This method basically takes the meshes defined in add_shape, and
// applies simulation matrix to it, and requests animation frame for
// animation.
parent.animate = function()
{
for(var frame in JSONObj.frames)
{
// defining simulation_matrix in a var.
var matrix = JSONObj.frames[frame].simulation_matrix[parent.animation_counter];
var animation_matrix = new THREE.Matrix4();
animation_matrix.elements = [];
// pushing it to a Matrix4
for(var i in matrix)
{
for(var j in matrix[i])
{
animation_matrix.elements.push(matrix[i][j]) ;
}
}
console.log(animation_matrix);
console.log(animation_matrix.elements);
// Making sure we are not applying matrix to the earlier transform
//mesh_dict is a dictionary of meshes, used in creating shapes,mapped to the
//frame which contains them
parent.mesh_dict[JSONObj.frames[frame]].applyMatrix(new THREE.Matrix4());
// now applying transform, after setting to identity matrix ...
parent.mesh_dict[JSONObj.frames[frame]].applyMatrix(animation_matrix);
}
console.log(parent.animation_counter);
//update timestep ...
parent.animation_counter++;
// This is to loop over again and again ...
// assuming 10 animations frames
if(parent.animation_counter == 10){ parent.animation_counter = 0; }
requestAnimationFrame(parent.animate);
}
The problem is that I am able to create the multiple shapes, but when I apply simulation matrix to them in the loop, only one of them is animating, that too in very unexpected manner.
Well I have figured out what was wrong. Somehow, all the dictionary parent.mesh_dict[] keys were mapped to a same single object, instead of all objects as required. Now I debugged it, and it is working like a charm. Also your point is valid #WestLangley, as I now use mesh.matrix.identity() to get things done. Thanks, I will close this question now.

Categories

Resources