ThreeJS: Is there any possible way to reduce the amount of triangles count - three.js

Hi I have some very complex models to display which comes from Revit files that my customer provides.
But sometimes the amount of details in the model is just way too much for the purpose of the website.
I would like to reduce the amount of vertexes/triangles in the model to simplify the display and enhance performance.
I have used simply modifiers where I cant able to view the model itself.
Is this possible from within ThreeJS? Or is there maybe an other solution for this?
const renderMeshFunction = async (material: any, geometry: any, id: any) => {
let count = 0;
const emptyFunction = () => { };
const onBR = () => {
const _count = (count < 3 ? count : Math.floor(Math.random() * count) + 1);
return ((renderer: WebGLRenderer, scene: Scene,
camera: Camera, geometry: Geometry | BufferGeometry,
_material: Material, group: Group) => {
_material.clippingPlanes = checkIsClipping();
});
};
count = geometry.maxInstancedCount;
const mesh = new Mesh(geometry, material);
let modifier = new SimplifyModifier();
let simplified = mesh.clone();
simplified.material = simplified.material.clone();
let countSimple = Math.floor((simplified.geometry as any).attributes.position.count * 1); // number of vertices to remove
simplified.geometry = modifier.modify(simplified.geometry, countSimple);
that.scene.add(simplified);
mesh.name = id || Date.now().toString();
mesh.matrixAutoUpdate = false;
mesh.drawMode = 0;
mesh.onBeforeRender = onBR.apply(that);
mesh.onAfterRender = emptyFn;
mesh.geometry.dispose();
mesh.material.dispose();
geometry.dispose();
material.dispose();
material = undefined;
return mesh;
};

Related

How to access the BufferGeometry of IFC items in web-ifc-three

I'm trying to get the geometry of an element
i.e. a BufferGeometry object corresponding to an expressId I have (not through picking).
Basically I'm asking how to traverse the IFC model and export each object as a separate OBJ.
I'll note I have reverse engineered code to achieve that for some version of the package, but it uses undocumented functionality, so naturally it broke in later versions (the code also colors the geometry according to the material's color so I don't need an mtl):
Don't copy this code it won't work
Object.values(bimModel.ifcManager.state.models[bimModel.modelID].items).forEach(type => {
Object.entries(type.geometries).forEach(([id, geometry]) => {
const properties = bimModel.getItemProperties(Number(id))
const numVertices = geometry.getAttribute('position').count
const color = type.material.color.toArray().map(x => x * 255)
const vertexColors = new Uint8Array(Array.from({ length: numVertices }, () => color).flat())
geometry.setAttribute('color', new BufferAttribute(vertexColors, 3, true))
})
})
This is exactly what we do to export models to glTF. The basic workflow is:
Decide what IFC categories you would like to export.
Get all the items of each category.
Reconstruct the mesh for each item.
Export the mesh using the Three.js exporter of your choice.
Let's see a basic example to get all the meshes from the walls. The process is not as straightforward as having each IFC item as a separate mesh, but that's the price for having the draw calls at minimum (otherwise, a browser wouldn't stand even medium-sized IFC files):
import { IFCWALLSTANDARDCASE } from 'web-ifc';
async function getAllWallMeshes() {
// Get all the IDs of the walls
const wallsIDs = manager.getAllItemsOfType(0, IFCWALL, false);
const meshes = [];
const customID = 'temp-gltf-subset';
for (const wallID of wallsIDs) {
const coordinates = [];
const expressIDs = [];
const newIndices = [];
const alreadySaved = new Map();
// Get the subset for the wall
const subset = viewer.IFC.loader.ifcManager.createSubset({
ids: [wallID],
modelID,
removePrevious: true,
customID
});
// Subsets have their own index, but share the BufferAttributes
// with the original geometry, so we need to rebuild a new
// geometry with this index
const positionAttr = subset.geometry.attributes.position;
const expressIDAttr = subset.geometry.attributes.expressID;
const newGroups = subset.geometry.groups
.filter((group) => group.count !== 0);
const newMaterials = [];
const prevMaterials = subset.material;
let newMaterialIndex = 0;
newGroups.forEach((group) => {
newMaterials.push(prevMaterials[group.materialIndex]);
group.materialIndex = newMaterialIndex++;
});
let newIndex = 0;
for (let i = 0; i < subset.geometry.index.count; i++) {
const index = subset.geometry.index.array[i];
if (!alreadySaved.has(index)) {
coordinates.push(positionAttr.array[3 * index]);
coordinates.push(positionAttr.array[3 * index + 1]);
coordinates.push(positionAttr.array[3 * index + 2]);
expressIDs.push(expressIDAttr.getX(index));
alreadySaved.set(index, newIndex++);
}
const saved = alreadySaved.get(index);
newIndices.push(saved);
}
const geometryToExport = new BufferGeometry();
const newVerticesAttr = new BufferAttribute(Float32Array.from(coordinates), 3);
const newExpressIDAttr = new BufferAttribute(Uint32Array.from(expressIDs), 1);
geometryToExport.setAttribute('position', newVerticesAttr);
geometryToExport.setAttribute('expressID', newExpressIDAttr);
geometryToExport.setIndex(newIndices);
geometryToExport.groups = newGroups;
geometryToExport.computeVertexNormals();
const mesh = new Mesh(geometryToExport, newMaterials);
meshes.push(mesh);
}
viewer.IFC.loader.ifcManager.removeSubset(modelID, undefined, customID);
return meshes;
}

Is there a way to use fusion algorithms like STARFM in Google Earth Engine?

Landsat and MODIS products both have their advantages. Landsat with its high spatial resolution and MODIS with its high temporal resolution. I've read a lot about downloading the files and fuse them locally with algorithms like STARFM in Python for example. Is there a way to fuse both collections directly in Google Earth Engine to save computational time?
There is Google earth engine code in GitHub. The link is below:
https://github.com/MacGallagher/GEE_STARFM_FUSION
The code I copied is below:
/**** Start of imports. If edited, may not auto-convert in the playground. ****/
var l8 = ee.ImageCollection("LANDSAT/LC08/C01/T1_SR"),
modis = ee.ImageCollection("MODIS/006/MOD09GQ"),
geometry4 = /* color: #d63000 */ee.Geometry.Polygon(
[[[-116.004638671875, 42.809506838324204],
[-115.59539794921875, 42.78129125156276],
[-115.499267578125, 43.004647127794435],
[-115.499267578125, 43.28920196020127],
[-116.00189208984375, 43.35114690203121],
[-116.19415283203125, 43.07691312608711]]]),
geometry = /* color: #98ff00 */ee.Geometry.Polygon(
[[[-115.93391418457031, 43.09346209534859],
[-115.80276489257812, 43.095718426590174],
[-115.80276489257812, 43.161366298103566],
[-115.9332275390625, 43.16086543618915]]]);
/***** End of imports. If edited, may not auto-convert in the playground. *****/
// based on script by Faye Peters, University of Louisville
// modified by Megan Gallagher, Boise State University
// for code inquiries, or adjustments please email megangallagher#u.boisestate.edu
// code exists for:
// landsat 5,7, and 8 NDVI merging with MODIS Terra daily
// MODIS Terra and Sentinel-2
// Landsat pre tier and modis daily terra - works with base R code
// Landsat 8 and Sentinel-2
////////////////////////////////////////Variables//////////////////////////////////////////////
// import Landsat 8 and MODIS daily terra surface reflectance 250m
var region = geometry; //region you want to export
var bounds = geometry4 //outer bounds of image to catch boundary effect, make larger than region
var date_begin = '2016-03-02' //start date of data collection, must be a landsat image date
var date_end ='2016-10-13' // end date of data collection, must be the day AFTER last landsat image
var csv_title = '2016_Dates_BOP' //title of csv output
var ls_title = '2016_landsat' // title of landsat tif file
var mod_title = '2016_mod' // title of modis tif file
/////////////////////////////////////////Code////////////////////////////////////////////////////////////////////////
var starfm = function(modis, l8){
//Preliminary filtering of MODIS and Landsat
var filt_mod = modis.filterDate(date_begin, date_end);
var filt_l8 = l8.filterDate(date_begin, date_end)
.filterBounds(bounds);
//add bounds to subset area
var subset_bounds = bounds.transform('EPSG:4326', 30).bounds().getInfo();
// get rid of bad pixels but keep metadata date information
var removeBadObservations = function(image){
var valid_data_mask = ee.Image(image).select('pixel_qa').bitwiseAnd(2).neq(0);
var numberBandsHaveData =
image.mask().reduce(ee.Reducer.sum());
var allOrNoBandsHaveData =
numberBandsHaveData.eq(0).or(numberBandsHaveData.gte(9));
var allBandsHaveData = allOrNoBandsHaveData;
//Make sure no band is just under zero
var allBandsGT = image.reduce(ee.Reducer.min()).gt(-0.001);
var result = ee.Image(image).mask(image.mask().and(valid_data_mask).and(allBandsHaveData).and(allBandsGT));
return result.copyProperties(ee.Image(image),['system:time_start']);
};
//NDVI functions for Landsat and MODIS
var getNDVI_mod = function(image){
return image
.addBands(image.normalizedDifference(['sur_refl_b02','sur_refl_b01']).multiply(10000).rename('NDVI'));};
var getNDVI_l8= function(image){
var ndvi = ee.Image(image).normalizedDifference(['B5','B4']);
return ndvi.copyProperties(ee.Image(image),['system:time_start']);
};
var filt2_l8= filt_l8.filterBounds(subset_bounds).aside(print).map(removeBadObservations).map(getNDVI_l8);
// update mask for different areas
var subset_mask = ee.Image().byte().paint(geometry4, "id").add(1);
var filtered_modis = filt_mod.filterBounds(subset_bounds).aside(print).map(getNDVI_mod);
// Pull out the date:
var extract_modis_date = function(row) {
var d = ee.Date(row.get('system:time_start'));
var d2 = ee.Date.fromYMD(d.get('year'), d.get('month'), d.get('day'));
var result = ee.Feature(null, {'date': d2});
result = result.set({'date': d2});
return result;
};
var getQABits = function(image, start, end, newName) {
//// Compute the bits we need to extract.
var pattern = 0;
for (var i = start; i <= end; i++) {
pattern += Math.pow(2, i);
}
//// Return a single band image of the extracted QA bits, giving the band a new name.
return image.select([0], [newName])
.bitwiseAnd(pattern)
.rightShift(start); };
print(filtered_modis);
filtered_modis = filtered_modis.map(function(image){
var quality = getQABits(image.select(2), 4, 5, 'QAMask');
quality = quality.eq(3).not();
return image.clip(subset_bounds).mask(image.mask().multiply(subset_mask).multiply(quality));
});
filtered_modis = filtered_modis.select('NDVI'); //Take only NDVI band
var modis_multiband = ee.Image(filtered_modis.filterDate(date_begin, date_end).iterate( function(x, modis_multiband) {
return ee.Image(modis_multiband).addBands(ee.Image(x));
}, filtered_modis.first()));
var dates_modis = filtered_modis.map(extract_modis_date);
print(dates_modis.getInfo());
var filt2_l8_ndvi = filt2_l8.map(function(image) {
return ee.Image(image)
.clip(subset_bounds)
.mask(ee.Image(image).mask().multiply(subset_mask));
});
//use this to choose the range of days, and check for overlap
var day_expand = 1;
var reduceLandsatNDVI = function(MODISdate) {
MODISdate = ee.Date(MODISdate.get('date'));
var ndvi_subset = ee.ImageCollection(filt2_l8_ndvi).filterDate( MODISdate,
MODISdate.advance(day_expand, 'day') );
ndvi_subset = ndvi_subset.map(function (image) {
var diff = MODISdate.difference(ee.Date(ee.Image(image).get('system:time_start')), 'day').abs();
return ee.Image(image).set('diff', diff);
});
ndvi_subset = ndvi_subset.sort('diff');
var ndvi_first = ndvi_subset.reduce('first');
var ndvi_mean = ndvi_subset.reduce('mean');
return ee.Algorithms.If(
ndvi_first.bandNames(),
ndvi_first.eq(0).multiply(ndvi_mean).add(ndvi_first),
ee.Image(0)
);
};
var extract_landsat_date = function(MODISdate) {
MODISdate = ee.Date(MODISdate.get('date'));
var ndvi_subset =
ee.ImageCollection(filt2_l8_ndvi).filterDate( MODISdate,
MODISdate.advance(day_expand, 'day') );
ndvi_subset = ndvi_subset.map(function (image) {
var diff = MODISdate.difference(ee.Date(ee.Image(image).get('system:time_start')), 'day').abs();
return ee.Image(image).set('diff', diff);
});
ndvi_subset = ndvi_subset.sort('diff');
var d = ndvi_subset.aggregate_first('system:time_start');
var count = ndvi_subset.aggregate_count('system:time_start');
d = ee.Algorithms.If(
ee.Number(count).gt(0),
ee.Date(d),
ee.Date('1971-01-01')
);
d = ee.Date(d);
var d2 = ee.Date.fromYMD(d.get('year'), d.get('month'),
d.get('day'));
var result = ee.Feature(null, {'LSdate': d2, 'MODISdate':
MODISdate, 'CountLSScenes': count});
result = result.set({'LSdate': d2, 'MODISdate': MODISdate,
'CountLSScenes': count});
return result;
};
var ls_collection = dates_modis.map(reduceLandsatNDVI);
print(ls_collection,'ls collection');
var dates_landsat = dates_modis.map(extract_landsat_date);
Export.table(dates_landsat, csv_title);
var ls_multiband = ls_collection.iterate( function(x,
ls_multiband)
{ return ee.Image(ls_multiband).addBands(ee.Image(x));
}, ls_collection.first());
ls_multiband = ee.Image(ls_multiband).multiply(10000).int16();
ls_multiband = ls_multiband.mask(ls_multiband.mask().multiply(ls_multiband.neq(0)));
//remove repeated first layer
var ls_multiband2=ee.Image(ls_multiband.slice(1))
var modis_multiband2=ee.Image(modis_multiband.slice(1))
Export.image.toDrive({
image: modis_multiband2,
description: mod_title,
crs:'EPSG:4326 ',
region:region,
scale:30
});
Export.image.toDrive({
image: ls_multiband2,
description: ls_title,
crs:'EPSG:4326',
region:region,
scale:30
});
return 'Done!'
}
var running = starfm(modis,l8)
print(running)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Updating geometric value

I'm using 'dat.gui' for user input and want the tableBoardGeometry and the tableLegsGeometry to update in my render method with the values from the input.
Geometric declarations
const boxWidth = 2;
const boxHeight = 0.1;
const boxDepth = 1;
const tableBoardGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const tableLegPosition = (boxWidth * 0.45);
const tableLegHeight = 1.6
const tableLegHeightPosition = (tableLegHeight/2)
const tableLegsGeometry = new THREE.BoxGeometry(0.05, tableLegHeight, 0.05);
Dat.Gui
var gui = new dat.GUI();
var controls = function() {
this.TableWidth = 1;
this.TableHeight = 1;
this.LegsWidth = 1;
this.LegsHeight = 1;
this.RotationSpeed = 0.005;
}
var title = new controls();
gui.add(title, 'RotationSpeed', 0.005, 0.1);
Render Method
const render = () => {
requestAnimationFrame(render);
tableBoard.rotation.y -= title.RotationSpeed;
/* I'd like to update the Geomtrics here depending on user input */
renderer.render(scene, camera);
orbitCamera()
}
I've tried using:
tableBoard.scale.x = title.TableWidth;
and it doesnt work properly because of the variable dependencies for getting the right number. The variable that i'd like to update is tableLegHeightPosition
edit:
The issue is that the sub mesh follows the parent mesh on scaling

Cocos Creator js animate sprite

I have animated a sprite using a sprite sheet and the update function,
like so:
Note: I have dragged the plist into the atlas field of the sprite node (the same node the monster.js script is attached to) in the Ccos Creator UI.
//monster.js
onLoad: function(){
// change monsters face
this.faces['1'] = 'monster1';
this.faces['2'] = 'monster2';
this.faces['3'] = 'Rmonster1';
this.faces['4'] = 'Rmonster2';
}
update: function (dt) {
this.timekeep += dt;
if(this.timekeep > 0.1){
var self = this;
cc.loader.loadRes('monsters', cc.SpriteAtlas, function (err, atlas) {
self.getComponent(cc.Sprite).spriteFrame = atlas.getSpriteFrame(self.faces[self.monstersN]);
});
this.timekeep = 0;
this.monstersN++;
if(this.monstersN > 4){
this.monstersN = 1;
}
}
It actually works fine. I have already thought I should export the cc.loader.loaderRes into the onLoad function and save the atlas as a global var instead of loading every time the update is called.
However…seeing that there are built in animation functions, this can’t be the correct solution. So I tried this:
onLoad: function () {
// change monster face
this.faces['1'] = 'monster1';
this.faces['2'] = 'monster2';
this.faces['3'] = 'Rmonster1';
this.faces['4'] = 'Rmonster2';
var self = this;
cc.loader.loadRes('monsters', cc.SpriteAtlas, function (err, atlas) {
var sprite = self.getComponent(cc.Sprite);
var animFrames = [];
for (var i = 1; i < 4; i++) {
var spriteFrame = atlas.getSpriteFrame(self.faces[i]);
var animFrame = new cc.AnimationFrame();
animFrame.initWithSpriteFrame(spriteFrame, 1, null);
animFrames.push(animFrame);
}
var animation = sprite.Animation.create(animFrames, 0.2, 100);
var animate = sprite.Animate.create(animation);
sprite.runAction(animate);
});
},
I get this error:
cc.AnimationFrame is not a constructor
So then I tried this:
onLoad: function () {
// change monster face
this.faces['1'] = 'monster1';
this.faces['2'] = 'monster2';
this.faces['3'] = 'Rmonster1';
this.faces['4'] = 'Rmonster2';
var self = this;
cc.loader.loadRes('monsters', cc.SpriteAtlas, function (err, atlas) {
self.atlasA = atlas;
});
var sprite = this.getComponent(cc.Sprite);
var animFrames = [];
for (var i = 1; i < 4; i++) {
var spriteFrame = this.atlasA.getSpriteFrame(this.faces[i]);
var animFrame = new cc.AnimationFrame();
animFrame.initWithSpriteFrame(spriteFrame, 1, null);
animFrames.push(animFrame);
}
var animation = sprite.Animation.create(animFrames, 0.2, 100);
var animate = sprite.Animate.create(animation);
sprite.runAction(animate);
},
I get this error:
Cannot read property ‘getSpriteFrame’ of undefined
How can I use cc.animate to change the sprite using the spritesheet I have. All I want to achieve is to move through the plist in the order the images are in the plist, repeated until the monster is put back into the pool it came from.
Here is the solution for anyone who may still be looking.....
cc.AnimationClip.createWithSpriteFrames([sf1, sf2, ...], fps)

How to change the wall color or image in Three js?

i have created wall using path in three js. I want change the color option buttion automatically need to change the color onclick event.
Below my color any one help me.
// wall 1 inside
item_count = 1;
var wall_x1 = 69;
var wall_y1 = 55;
var wall_x2 = 366;
var wall_y2 = 52;
var wall_z = 0;
var wall_width = 5;
var wall_height = default_height;
var wall_wall_width = 5;
if(!wall_wall_width) { wall_wall_width = 5; }
var wall_wall_elevation = planner_default_height;
if(!wall_wall_elevation) { wall_wall_elevation = default_height; }
var wall_top_filltype = 'texture';
var wall_top_fill = 'default_wall.jpg';
var wall_side_filltype = 'texture';
var wall_side_fill = 'wall5.jpg';
if ( item_count == 0 )
{
starting_x_value = wall_x1;
starting_y_value = wall_y1;
}
path = generate_path_byline(wall_x1,wall_y1,wall_x2,wall_y2,default_depth);
path_type ="wall";
x=starting_x_value-wall_x1; y=starting_y_value-wall_y1;
// z=default_height/2;
path_transform = transformSVGPath(path);
create_surface(1,1,path_type,path_transform,starting_x_value,starting_y_value,x,y,z,default_height,wall_wall_elevation,wall_top_filltype,wall_top_fill,wall_side_filltype,wall_side_fill);
I see no clear way of defining the material at your sample code, or at least the way three.js does it:
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
If you use as a material a texture with an image, you have many ways to change its color... one easy trick could be to change the light color that lightens the scene or this object.

Resources