How to get states of the gltf texture rendered with cesium - cesiumjs

I am the newbie and have a 3dsmax model that converted to gltf with lots of texture images, after loading with cesium and flyto the model, the white bone loaded first, then it'll take long time to render the texture. I want to show a loading image before all textures rendered. Is there anyway to get the texture rendering states?

If you're using the model directly as a graphics primitive (as opposed to using it on a Cesium Entity), then there's a Model.readyPromise that will tell you when the model has finished loading.
Here's a Sandcastle Demo for Cesium 1.54:
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var model;
var modelUrl = '../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb';
var height = 0.0;
var heading = 0.0, pitch = 0.0, roll = 0.0;
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
var origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, hpr);
scene.primitives.removeAll(); // Remove previous model
model = scene.primitives.add(Cesium.Model.fromGltf({
url : modelUrl,
modelMatrix: modelMatrix
}));
console.log('Model is loading...');
model.readyPromise.then(function(model) {
console.log('Model loading complete.');
// Zoom to model
var camera = viewer.camera;
var controller = scene.screenSpaceCameraController;
var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
controller.minimumZoomDistance = r * 0.5;
var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
var heading = Cesium.Math.toRadians(230.0);
var pitch = Cesium.Math.toRadians(-20.0);
camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0));
}).otherwise(function(error){
console.error(error);
});

Related

Default Navigation speed in Forge Viewer

How can the default navigation speed be changed in the Forge Viewer? The default setting is far to fast for my sample models. I should like to write code so that the speed may be changed during a session.
Copy an existing navigation tool from the viewer3D.js implementation, modify the speed parameters as you wish or expose methods to do so dynamically from your app, then set it active.
You can check the implementation of OrbitDollyPanTool L#14545 in viewer3D.js
Autodesk.Viewing.OrbitDollyPanTool = function( viewerImpl, viewerApi ){
var avp = Autodesk.Viewing.Private;
var _this = this;
var kScreenEpsilon = 0.001;
var kEpsilon = 0.00001;
var kAutoDeltaZ = 1.5; // Dolly increment
var kAutoDeltaXY = 0.01;
var kAutoScreenXY = 20;
var kDollyDragScale = 100.0;
var kDollyPinchScale = 0.5;
var kOrbitScale = 2.0;
// ...
That tool is instantiated as follow (L#40923):
Viewer3D.prototype.createControls = function( ) {
var self = this;
var impl = self.impl;
self.navigation = new av.Navigation(impl.camera);
self.__initAutoCam(impl);
self.utilities = new av.ViewingUtilities(impl, self.autocam, self.navigation);
self.clickHandler = new av.DefaultHandler(impl, self.navigation, self.utilities);
self.toolController = new av.ToolController(impl, self, self.autocam, self.utilities, self.clickHandler);
self.toolController.registerTool( new av.GestureHandler(self) );
self.toolController.registerTool( av.theHotkeyManager );
self.toolController.activateTool( av.theHotkeyManager.getName() );
self.registerUniversalHotkeys();
self.toolController.registerTool( new av.OrbitDollyPanTool(impl, self) );
self.toolController.activateTool( "gestures" );
return self.toolController;
};
I recently found out that you can use viewer.navigation.fitBounds(true, THREE.Box3) which will impact the navigation speed to match the extends defined by those bounds.

How can I get 3D object dimensions in ThreeJS?

I'm developing an AR application using WebRTC (webcam access), JSARToolKit (marker detection) and threeJS (3D library).
I want to place 3D objects (exported from Maya using threejs maya exporter) in the center of the detected marker.
This is the code where I load the 3D object using JSONLoader:
// load the model
var loader = new THREE.JSONLoader;
var object;
//var geometry = new THREE.BoxGeometry(1, 1, 1);
loader.load('js/cube.js', function(geometry, materials){
var material = new THREE.MeshFaceMaterial(materials);
object = new THREE.Mesh(geometry, material);
object.position.x -= ***3DobjectWidth/2***;
object.position.y -= ***3DobjectHeight/2***;
object.position.z -= ***3DobjectDepth/2***;
scene.add(object);
});
I need to get width, height and depth of the object to change his position (see 3DobjectWidth ecc).
Any suggestions?
The object size will be placed at geometry.boundingBox. But it has to be generated once.
try this.
geometry.computeBoundingBox();
var bb = geometry.boundingBox;
var object3DWidth = bb.max.x - bb.min.x;
var object3DHeight = bb.max.y - bb.min.y;
var object3DDepth = bb.max.z - bb.min.z;
Geometry has a .center() method. That might be more efficient and simpler if you need to center many meshes based on same geometry. It also calls computeBoundingBox for you.

How to smooth mesh triangles in STL loaded BufferGeometry

I´m trying to load some STL files using Three.js. The models are loaded correctly, but there are too many triangles that I would like to merge/smooth.
I had successfully applied smooth loading terrains in other 3D formats, but I can´t do it with the BufferGeometry that results from loading an STL file with the STLLoader.
_
var material = new THREE.MeshLambertMaterial( { ... } );
var path = "./models/budah.stl";
var loader = new THREE.STLLoader();
loader.load( path, function ( object ) {
object.computeBoundingBox();
object.computeBoundingSphere();
object.computeFaceNormals();
object.computeVertexNormals();
object.normalizeNormals();
object.center();
// Apply smooth
var modifier = new THREE.SubdivisionModifier( 1);
var smooth = smooth = object.clone();
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
modifier.modify( smooth );
scene.add( smooth );
});
This is what I tried, it throws an error: Uncaught TypeError: smooth.mergeVertices is not a function
If I comment the "mergeVertices()" line, what I get is a different error: Uncaught TypeError: Cannot read property 'length' of undefined in SubdivisionsModifier, line 156.
It seems that the sample codes I´m trying are outdated (this is happenning a lot recently due to the massive changes in the Three.JS library). Or maybe I´m forgetting something. The fact is that the vertices seems to be null..?
Thanks in advance!
It seems I was looking in the wrong direction: smoothing the triangles has nothing to do with the SubdivisionsModifier... What I needed was easier than that, just compute the vertex BEFORE applying the material, so it can use SmoothShading instead of FlatShading (did I got it right?).
The problem here was that the BufferGeometry returned by the STLLoader has not calculated vertices/vertex, so I had to do it manually. After that, apply mergeVertices() just before computeVertexNormals() and voilà! The triangles dissappear and everything is smooth:
var material = new THREE.MeshLambertMaterial( { ... } );
var path = "./models/budah.stl";
var loader = new THREE.STLLoader();
loader.load( path, function ( object ) {
object.computeBoundingBox();
object.computeVertexNormals();
object.center();
///////////////////////////////////////////////////////////////
var attrib = object.getAttribute('position');
if(attrib === undefined) {
throw new Error('a given BufferGeometry object must have a position attribute.');
}
var positions = attrib.array;
var vertices = [];
for(var i = 0, n = positions.length; i < n; i += 3) {
var x = positions[i];
var y = positions[i + 1];
var z = positions[i + 2];
vertices.push(new THREE.Vector3(x, y, z));
}
var faces = [];
for(var i = 0, n = vertices.length; i < n; i += 3) {
faces.push(new THREE.Face3(i, i + 1, i + 2));
}
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
geometry.faces = faces;
geometry.computeFaceNormals();
geometry.mergeVertices()
geometry.computeVertexNormals();
///////////////////////////////////////////////////////////////
var mesh = new THREE.Mesh(geometry, material);
scene.add( mesh );
});
Than, you can convert it back to BufferGeometry, because it's more GPU/CPU efficient for more complex models:
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
geometry.faces = faces;
geometry.computeFaceNormals();
geometry.mergeVertices();
geometry.computeVertexNormals();
var buffer_g = new THREE.BufferGeometry();
buffer_g.fromGeometry(geometry);
var mesh = new THREE.Mesh(buffer_g, material);
scene.add( mesh )
Happened this issue for me while loading an obj file. If you have a 3d software like 3dsmax:
Open the obj file,
Go to polygons selection mode and select all polygons.
Under the Surface properties panel, click 'Auto Smooth' button.
Export the model back to obj format
Now you won't have to call the functions geometry.mergeVertices() and geometry.computeVertexNormals();. Just load the obj and add to the scene, mesh will be smooth.
EDIT:
My obj files had meshphongmaterial by default and on changing the shading property to value 2 the mesh became smooth.
child.material.shading = 2
STL does not support vertex index.
That is reason it has duplicated vertex of all triangles.
Each vertex has its normal as triangle normal.
As a result, at same position( multiple very closed vertices), there is multiple normal value.
This leads to non-smooth surface of geometry when using Normal for lighting calculation.

Three.js does not show texture on sphere

There are some threads about textures which do not showing up. I have tried them all, but nothing helped.
I have spent a few hours on this now. Every time I end up looking at a black sphere.
I am working on Chrome v18 and Windows 7. I also tried Firefox, but this browser does not really support Three.js.
This is the body of the script:
<body>
<script src="../build/Three.js"></script>
<script src="js/Stats.js"></script>
<script src="../build/jquery-1.7.2.min.js"></script>
This is the script itself:
// stap1) camera, set the scene size
var WIDTH = 400,
HEIGHT = 300;
// set some camera attributes
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
var camera = new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR );
// stap2) scene:
var scene = new THREE.Scene();
// the camera starts at 0,0,0 so pull it back
scene.add(camera);
camera.position.z = +300;
// get the DOM element to attach to
// - assume we've got jQuery to hand
var container = $('#container');
// stap3)create a WebGL renderer:
var renderer = new THREE.WebGLRenderer();
// start the renderer
renderer.setSize(WIDTH, HEIGHT);
// attach the render-supplied DOM element
container.append(renderer.domElement);
// bol maken:
// create the sphere's material
// b.v: THREE.MeshBasicMaterial
var sphereMaterial = new THREE.MeshLambertMaterial(
{
map: THREE.ImageUtils.loadTexture("http://dev.root.nl/tree/examples/textures/ash_uvgrid01.jpg")
});
// set up the sphere vars
var radius = 50, segments = 16, rings = 16;
var sphereGeometry = new THREE.SphereGeometry(radius, segments, rings);
// create a new mesh with sphere geometry -
var sphere = new THREE.Mesh(
sphereGeometry,
sphereMaterial
);
sphere.position.x=0;
var s=1;
sphere.scale.set(s, s, s);
// add the sphere to the scene
scene.add(sphere);
// create a point light
var pointLight = new THREE.PointLight( 0xFFFFFF );
// set its position
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
// add to the scene
scene.add(pointLight);
// draw!
renderer.render(scene, camera);
You need to wait until the image used as texture is fully downloaded.
I have put your code on the web: http://jsfiddle.net/4Qg7K/ and just added a classic "render loop":
requestAnimationFrame(render);
function render(){
requestAnimationFrame(render);
sphere.rotation.y += 0.005; //rotation stuff, just for fun
renderer.render(scene, camera);
};
requestAnimationFrame function works like a timer, calling to the render function each time the browser is ready to update the web page.
BTW, Three.js works fine with Firefox.

Adding many circles to a google map

I'm attempting to draw many circles (around 1000) on a google map, at various locations and sizes, and then binding a click event to them. But this many calls of new google.maps.Circle(parameters); makes the page loading slow, and sometimes it just hangs forever, so I'd like to figure out if there's a better/faster way to accomplish what I'm trying to do.
I see that there's something called a kml layer, but there doesn't appear to be any easy way to draw filled circles with it and I'm not sure if I can still bind click events to each individual circle in the layer or not.
Looking at the circle workaround on the google KML faq page, I'm not sure if generating a KML file that contains thousands of circles similar to this would end up saving any time.
I also have no idea how to go about generating this kml file.
Finally, take into consideration that I'm pulling the circles I'm attempting to draw from a database, so I'd either have to generate the KML file on the fly for use or generate a new file every time a circle is removed or added from the DB so that the file stays up to date.
Of course, if there's another alternative, I'm all ears!
With the help of others via the Google Maps API v3 Group, I was able to implement a map overlay that handles 10,000 points suprisingly well. The trick is to use a canvas tile overlay, which minimizes the number of DOM elements in exchange for much simpler/lighter-weight POJsOs (plain old JavaScript objects).
Demo page w/mouse click events (API v2 only): http://notebook.kulchenko.com/maps/datamark
Demo page w/cursor swapping (API v2 and v3): http://notebook.kulchenko.com/maps/gridmark
Here is yet another example that demonstrates how to render multiple objects on Google Map using Overlay approach. Since the performance could degrade considerably while the amount of objects (e.g. google.maps.Circle) is increasing, it is proposed to render objects using canvas element instead of divone.
Example
The example demonstrates how to render 1k objects (cities)
var overlay;
USCitiesOverlay.prototype = new google.maps.OverlayView();
function USCitiesOverlay(map) {
this._map = map;
this._cities = [];
this._radius = 6;
this._container = document.createElement("div");
this._container.id = "citieslayer";
this.setMap(map);
this.addCity = function (lat, lng,population) {
this._cities.push({position: new google.maps.LatLng(lat,lng),population: population});
};
}
USCitiesOverlay.prototype.createCityIcon = function (id,pos,population) {
var cityIcon = document.createElement('canvas');
cityIcon.id = 'cityicon_' + id;
//calculate radius based on poulation
this._radius = population / 100000;
cityIcon.width = cityIcon.height = this._radius * 2;
cityIcon.style.width = cityIcon.width + 'px';
cityIcon.style.height = cityIcon.height + 'px';
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
cityIcon.style.position = "absolute";
var centerX = cityIcon.width / 2;
var centerY = cityIcon.height / 2;
var ctx = cityIcon.getContext('2d');
ctx.fillStyle = 'rgba(160,16,0,0.6)';
ctx.beginPath();
ctx.arc(centerX, centerY, this._radius, 0, Math.PI * 2, true);
ctx.fill();
return cityIcon;
};
USCitiesOverlay.prototype.ensureCityIcon = function (id,pos,population) {
var cityIcon = document.getElementById("cityicon_" + id);
if(cityIcon){
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
return cityIcon;
}
return this.createCityIcon(id,pos,population);
};
USCitiesOverlay.prototype.onAdd = function () {
var panes = this.getPanes();
panes.overlayLayer.appendChild(this._container);
};
USCitiesOverlay.prototype.draw = function () {
var zoom = this._map.getZoom();
var overlayProjection = this.getProjection();
var container = this._container;
this._cities.forEach(function(city,idx){
var xy = overlayProjection.fromLatLngToDivPixel(city.position);
var cityIcon = overlay.ensureCityIcon(idx,xy,city.population);
container.appendChild(cityIcon);
});
};
USCitiesOverlay.prototype.onRemove = function () {
this._container.parentNode.removeChild(this._container);
this._container = null;
};
function getRandomInterval(min, max) {
return Math.random() * (max - min) + min;
}
function generateCityMap(count) {
var citymap = [];
var minPos = new google.maps.LatLng(49.25, -123.1);
var maxPos = new google.maps.LatLng(34.052234, -74.005973);
for(var i = 0; i < count;i++)
{
var lat = getRandomInterval(minPos.lat(),maxPos.lat());
var lng = getRandomInterval(minPos.lng(),maxPos.lng());
var population = getRandomInterval(100000,1000000);
citymap.push({
location: new google.maps.LatLng(lat, lng),
population: population
});
}
return citymap;
}
function initialize() {
var mapOptions = {
zoom: 4,
center: new google.maps.LatLng(37.09024, -95.712891),
mapTypeId: google.maps.MapTypeId.TERRAIN
};
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
overlay = new USCitiesOverlay(map);
//overlay.addCity(40.714352, -74.005973); //chicago
//overlay.addCity(40.714352, -74.005973); //newyork
//overlay.addCity(34.052234, -118.243684); //losangeles
//overlay.addCity(49.25, -123.1); //vancouver
var citymap = generateCityMap(1000);
citymap.forEach(function(city){
overlay.addCity(city.location.lat(), city.location.lng(),city.population);
});
}
google.maps.event.addDomListener(window, 'load', initialize);
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true"></script>
<div id="map-canvas"></div>
Forget about KML, custom tiles are the way to go.
Have a look at these county maps:
http://maps.forum.nu/v3/gm_customTiles.html (check the "Density" box).
and
http://maps.forum.nu/gm_main.html?lat=31.428663&lon=-110.830078&z=4&mType=10
(click on the map to get county info)
These maps have 3000+ polygons, (not circles), and they load fast. The first link is API V3, the second is API V2.
The second map (V2) has click events. The click event handler is attached to the map itself, and it sends an AJAX call to the server with the lat/lon of the click. The server side script then looks this lat/lon up in the database to determine which county was clicked.