Skip to content

Commit

Permalink
feat(instancing): use instancing for large number of 3d objects
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony GULLIENT authored and AnthonyGlt committed Jun 21, 2023
1 parent 2af65b7 commit 619a611
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 13 deletions.
3 changes: 2 additions & 1 deletion examples/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
"misc_orthographic_camera": "Orthographic camera",
"misc_custom_controls": "Define custom controls",
"misc_custom_label": "Custom label popup",
"misc_camera_traveling": "Camera traveling"
"misc_camera_traveling": "Camera traveling",
"misc_instancing": "3D objects instancing"
},

"Widgets": {
Expand Down
222 changes: 222 additions & 0 deletions examples/misc_instancing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<html>
<head>
<title>Itowns - Instancing</title>
<meta charset="UTF-8" />
<link rel="stylesheet" type="text/css" href="css/example.css" />
<link rel="stylesheet" type="text/css" href="css/LoadingScreen.css" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
</head>
<body>
<div id="description">
<p>
<b>
Render a large number of objects with the same geometry<br/>
by importing your 3D models
</b>
</p>
</div>
<div id="viewerDiv" class="viewer"></div>
<script src="js/GUI/GuiTools.js"></script>
<script src="../dist/itowns.js"></script>
<script src="../dist/debug.js"></script>
<script src="js/GUI/LoadingScreen.js"></script>
<script type="text/javascript">
var THREE = itowns.THREE;

// Define initial camera position
var placement = {
coord: new itowns.Coordinates("EPSG:4326", -0.57918, 44.837789),
range: 1000,
tilt: 45,
};

// `viewerDiv` will contain iTowns' rendering area (`<canvas>`)
var viewerDiv = document.getElementById("viewerDiv");

// Instanciate iTowns GlobeView*
var view = new itowns.GlobeView(viewerDiv, placement);

// Setup loading screen and debug menu
setupLoadingScreen(viewerDiv, view);
const debugMenu = new GuiTools("menuDiv", view);

var ambLight = new itowns.THREE.AmbientLight(0xffffff, 0.2);
view.scene.add(ambLight);

// Add one imagery layer to the scene
itowns.Fetcher.json("./layers/JSONLayers/Ortho.json").then(
function _(config) {
config.source = new itowns.WMTSSource(config.source);
var layer = new itowns.ColorLayer("Ortho", config);
view.addLayer(layer).then(
debugMenu.addLayerGUI.bind(debugMenu)
);
}
);

//Tree
const trunkRadius = 5;
const trunkHeight = 20;
const topHeight = 10;

function makeTree() {
const root = new THREE.Object3D();

// Trunk
const geometry = new THREE.CylinderGeometry(
trunkRadius,
trunkRadius,
trunkHeight,
32
);
const material = new THREE.MeshPhongMaterial({
color: 0x8b4513,
});
const trunk = new THREE.Mesh(geometry, material);
trunk.rotateX(Math.PI / 2);
trunk.position.z = 10;
trunk.updateMatrix();
root.add(trunk);

// Canopy
const geometryCanop = new THREE.SphereGeometry(
topHeight,
topHeight,
10
);
const materialCanop = new THREE.MeshPhongMaterial({
color: 0x00aa00,
});
const top = new THREE.Mesh(geometryCanop, materialCanop);
top.position.z = trunkHeight - topHeight / 3 + 10;
top.updateMatrix();
root.add(top);

return root;
}



// ---------- DISPLAY VECTOR TILED BUILDING DATA AS 3D MESHES : ----------

// Define the source of the building data : those are vector tiled data from the geoportail.
const buildingsSource = new itowns.VectorTilesSource({
style: "https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/standard.json",
// We only want to display buildings related data.
filter: (layer) => {
return (
layer["source-layer"].includes("bati_surf") &&
layer.paint["fill-color"]
);
},
});

// Create a FeatureGeometryLayer to support building data.
var buildingsLayer = new itowns.FeatureGeometryLayer("VTBuilding", {
source: buildingsSource,
zoom: { min: 15 },
accurate: false,
style: new itowns.Style({
fill: {
base_altitude: () => 0,
extrusion_height: (p) => p.hauteur || 0,
},
}),
});

// Add the FeatureGeometryLayer to the scene and to the debug menu.
view.addLayer(buildingsLayer).then((layer) => {
const gui = debug.GeometryDebug.createGeometryDebugUI(
debugMenu.gui,
view,
layer
);
debug.GeometryDebug.addWireFrameCheckbox(gui, view, layer);
});

// Lights
var lightsSource = new itowns.FileSource({
url: "https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/geojson/points_lumineux_bordeaux.geojson",
crs: "EPSG:4326",
fetcher: itowns.Fetcher.json,
parser: itowns.GeoJsonParser.parse,
});

// Load a glTF resource
itowns.glTFLoader.load(
// resource URL
"https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/models/lampadaire/scene.gltf",

// called when the resource is loaded
(gltf) => {
var model = gltf.scene;

model.rotateX(Math.PI / 2.0);
gltf.scene.position.z = 2;
model.scale.set(6, 6, 6);

var styleModel3D = new itowns.Style({
point: {
model: {
object: model,
},
},
});

var lightsLayer = new itowns.FeatureGeometryLayer(
"lights",
{
name: "lights",
source: lightsSource,
zoom: { min: 7, max: 21 },
style: styleModel3D,
}
);

view.addLayer(lightsLayer);
},

// called while loading is progressing
() => {
},

// called when loading has errors
(error) => {
// eslint-disable-next-line no-console
console.log("An error happened :");
// eslint-disable-next-line no-console
console.log(error);
}
);

//Tree
var styleModel3D = new itowns.Style({
point: {
model: {
object: makeTree(),
},
},
});

var treesSource = new itowns.FileSource({
url: "https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/geojson/arbres_bordeaux.geojson",
crs: "EPSG:4326",
fetcher: itowns.Fetcher.json,
parser: itowns.GeoJsonParser.parse,
});

var treesLayer = new itowns.FeatureGeometryLayer("trees", {
name: "trees",
source: treesSource,
zoom: { min: 7, max: 21 },
style: styleModel3D,
});

view.addLayer(treesLayer);

debug.createTileDebugUI(debugMenu.gui, view);
</script>
</body>
</html>
92 changes: 81 additions & 11 deletions src/Converter/Feature2Mesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,20 @@ function getIntArrayFromSize(data, size) {
}
}

function separateMeshes(object3D) {
const meshes = [];
object3D.updateMatrixWorld();
object3D.traverse((element) => {
if (element instanceof THREE.Mesh) {
element.updateMatrixWorld();
element.geometry.applyMatrix4(element.matrixWorld);
meshes.push(element);
}
});

return meshes;
}

/**
* Convert coordinates to vertices positionned at a given altitude
*
Expand Down Expand Up @@ -203,7 +217,7 @@ function featureToPoint(feature, options) {
const ptsIn = feature.vertices;
const normals = feature.normals;
const colors = new Uint8Array(ptsIn.length);
const batchIds = options.batchId ? new Uint32Array(ptsIn.length / 3) : undefined;
const batchIds = options.batchId ? new Uint32Array(ptsIn.length / 3) : undefined;
let featureId = 0;

let vertices;
Expand Down Expand Up @@ -246,11 +260,11 @@ function featureToLine(feature, options) {
const colors = new Uint8Array(ptsIn.length);
const count = ptsIn.length / 3;

const batchIds = options.batchId ? new Uint32Array(count) : undefined;
const batchIds = options.batchId ? new Uint32Array(count) : undefined;
let featureId = 0;

let vertices;
const zTranslation = options.GlobalZTrans - feature.altitude.min;
const zTranslation = options.GlobalZTrans - feature.altitude.min;
if (zTranslation != 0) {
vertices = new Float32Array(ptsIn.length);
coordinatesToVertices(ptsIn, normals, vertices, zTranslation);
Expand Down Expand Up @@ -333,7 +347,7 @@ function featureToPolygon(feature, options) {
const colors = new Uint8Array(ptsIn.length);
const indices = [];

const batchIds = options.batchId ? new Uint32Array(vertices.length / 3) : undefined;
const batchIds = options.batchId ? new Uint32Array(vertices.length / 3) : undefined;
const globals = { fill: true };
let featureId = 0;

Expand Down Expand Up @@ -403,7 +417,7 @@ function featureToExtrudedPolygon(feature, options) {
const indices = [];
const totalVertices = ptsIn.length / 3;

const batchIds = options.batchId ? new Uint32Array(vertices.length / 3) : undefined;
const batchIds = options.batchId ? new Uint32Array(vertices.length / 3) : undefined;
let featureId = 0;

const z = options.GlobalZTrans - feature.altitude.min;
Expand Down Expand Up @@ -481,12 +495,59 @@ function featureToExtrudedPolygon(feature, options) {
return new THREE.Mesh(geom, options.polygonMaterial);
}

/**
* Created Instanced object from mesh
*
* @param {THREE.MESH} mesh Model 3D to instanciate
* @param {*} count number of instances to create (int)
* @param {*} ptsIn positions of instanced (array double)
* @returns {THREE.InstancedMesh} Instanced mesh
*/
function createInstancedMesh(mesh, count, ptsIn) {
const instancedMesh = new THREE.InstancedMesh(mesh.geometry, mesh.material, count);
let index = 0;
for (let i = 0; i < count * 3; i += 3) {
const mat = new THREE.Matrix4();
mat.setPosition(ptsIn[i], ptsIn[i + 1], ptsIn[i + 2]);
instancedMesh.setMatrixAt(index, mat);
index++;
}

instancedMesh.instanceMatrix.needsUpdate = true;

return instancedMesh;
}

/**
* Convert a [Feature]{@link Feature} of type POINT to a Instanced meshes
*
* @param {Object} feature
* @returns {THREE.Mesh} mesh or GROUP of THREE.InstancedMesh
*/
function pointsToInstancedMeshes(feature) {
const ptsIn = feature.vertices;
const count = feature.geometries.length;
const modelObject = feature.style.point.model.object;

if (modelObject instanceof THREE.Mesh) {
return createInstancedMesh(modelObject, count, ptsIn);
} else if (modelObject instanceof THREE.Object3D) {
const group = new THREE.Group();
// Get independent meshes from more complexe object
const meshes = separateMeshes(modelObject);
meshes.forEach(mesh => group.add(createInstancedMesh(mesh, count, ptsIn)));
return group;
} else {
throw new Error('The format of the model object provided in the feature style (feature.style.point.model.object) is not supported. Only THREE.Mesh or THREE.Object3D are supported.');
}
}

/**
* Convert a [Feature]{@link Feature} to a Mesh
*
* @param {Feature} feature - the feature to convert
* @param {Object} options - options controlling the conversion
* @return {THREE.Mesh} mesh
* @return {THREE.Mesh} mesh or GROUP of THREE.InstancedMesh
*/
function featureToMesh(feature, options) {
if (!feature.vertices) {
Expand All @@ -496,7 +557,16 @@ function featureToMesh(feature, options) {
let mesh;
switch (feature.type) {
case FEATURE_TYPES.POINT:
mesh = featureToPoint(feature, options);
if (feature.style.point?.model?.object) {
try {
mesh = pointsToInstancedMeshes(feature);
mesh.isInstancedMesh = true;
} catch (e) {
mesh = featureToPoint(feature, options);
}
} else {
mesh = featureToPoint(feature, options);
}
break;
case FEATURE_TYPES.LINE:
mesh = featureToLine(feature, options);
Expand All @@ -511,10 +581,10 @@ function featureToMesh(feature, options) {
default:
}

// set mesh material
mesh.material.vertexColors = true;
mesh.material.color = new THREE.Color(0xffffff);

if (!mesh.isInstancedMesh) {
mesh.material.vertexColors = true;
mesh.material.color = new THREE.Color(0xffffff);
}
mesh.feature = feature;
mesh.position.z = feature.altitude.min - options.GlobalZTrans;

Expand Down
Loading

0 comments on commit 619a611

Please sign in to comment.