Clone object in Forge Viewer - javascript

I'm trying to clone an object in Forge Viewer.
I have tried using THREE.js and creating a clone but it has different structure to the base object.
sceneBuilder = viewer.loadExtension("Autodesk.Viewing.SceneBuilder");
let modelBuilder = await sceneBuilder.addNewModel({
conserveMemory: false,
modelNameOverride: `Custom model`,
});
let renderProxy = viewer.impl.getRenderProxy(viewer.model, fragId);
let geom = new THREE.Geometry();
let VE = Autodesk.Viewing.Private.VertexEnumerator;
VE.enumMeshVertices(renderProxy.geometry, (v: any, i: any) => {
geom.vertices.push(new THREE.Vector3(v.x, v.y, v.z));
});
VE.enumMeshIndices(renderProxy.geometry, (a, b, c) => {
geom.faces.push(new THREE.Face3(a, b, c));
});
geom.computeFaceNormals();
let mesh = new THREE.Mesh(
new THREE.BufferGeometry().fromGeometry(geom),
renderProxy.material
);
(mesh as any).dbId = dbId;
modelBuilder.addMesh(mesh);
I found that renderProxy is also THREE.Mesh, but when I tried let clone = renderProxy.clone(); modelBuilder.addMesh(clone), it doesn't work. Anyway to clone an object in Viewer?
Another thing, when I add a mesh by modelBuilder, I see that the created Object has added to Browser tree, but I still can't use Viewer functions with it (such as Viewer.select(dbId); Viewer.fitToView();)

Cloning the renderProxy directly probably won't work as Forge Viewer basically returns the same THREE.Mesh instance whenever you request the proxy, just with different internals (for performance reasons).
The code snippet you provided (extracting vertices and faces from the proxy) is a safer choice. Is that snippet working as expected, or is it also causing issues?

Related

How to change geometry attributes dynamically using dat GUI in three.js?

I made a sphere geometry with this function
let createBall = () => {
let geoBall = new THREE.SphereGeometry(5, 32, 16);
let mat = new THREE.MeshPhongMaterial({ color: "red", transparent: true });
ball = new THREE.Mesh(geoBall, mat);
ball.position.set(0, 5, 0);
ball.geometry.dynamic = true;
ball.geometry.verticesNeedUpdate = true;
ball.geometry.__dirtyVertices = true;
scene.add(ball);
};
and I call the function in window.onload function. I also use dat GUI to edit the geometry attribute which was the widthSegment of the ball.geometry like this
ballFolder
.add(ball.geometry.parameters, "widthSegments", 1, 64, 1)
.onChange(function () {
console.log(geoBall);
ball.geometry.dispose();
ball.geometry = geoBall.clone();
});
when I log the geoBall in the console, it turns out that the attribute has changed, but the object itself isn't changed. Anyone know how to solve this problem ??
The values in parameters are only used when the geometry is created. Think of the geometry generators (BoxGeometry, SphereGeometry etc.) as factory methods. Changing the parameters has no effect once the object is created.
So I suggest you create a new geometry in your onChange() callback and call dispose() on the previous one (what you already do).
BTW: In recent three.js version geometry objects do not have dynamic, verticesNeedUpdate and __dirtyVertices properties.

ThreeJS: Add Two Materials on an Mesh Object

In ThreeJS, it is possible to add more than one material to an Object3D/Mesh according to its documentation. We can use a single Material or use an array of Material:
Mesh typescript file class declaration and contructor (from ThreeJS src code):
export class Mesh<
TGeometry extends BufferGeometry = BufferGeometry,
TMaterial extends Material | Material[] = Material | Material[] // ### Here: Material[] ###
> extends Object3D {
constructor(geometry?: TGeometry, material?: TMaterial);
Here is my problem which I can't seem to be able to solve...
When I use a single Material, my Mesh is displayed. However, when I use two materials as an array, my Mesh won't display. I also don't have any error print out in the console when rendering the scene.
My ultimate goal here is to be able to have to separate materials for the inner and the outer part of my object.
Here is my code:
export function init(radius: number = 18.0, innerColor: number = 0xFFFFFF, outerColor: number = 0x444444) {
var obj = new Object3D();
loader.load(
objPath,
function(object){
obj = object;
const mesh = obj.children[0] as Mesh;
// WORKING:
mesh.material = new MeshPhongMaterial({color: outerColor});
// NOT WORKING: Using two materials
// mesh.material = new Array<Material>(new MeshPhongMaterial({color: outerColor}), new MeshPhongMaterial({color:innerColor}));
mesh.scale.setLength(radius)
scene.add(mesh);
},
function (error){
console.log(error)
}
);
}
Why can't I manage to see my object when using two materials ?
I known there was the MeshFaceMaterial in previous version and the material array acceptance of the contructor is supposed to be a replacement for that in some sens.
ThreeJS version: r128
Any help would be appreciated !
The easiest way is to clone your mesh and assign two separate materials, one for the inside, another for the outside:
const meshOuter = obj.children[0] as THREE.Mesh;
const meshInner = meshOuter.clone();
// Outer mesh shows front side
meshOuter.material = new THREE.MeshPhongMaterial({
color: outerColor,
side: THREE.FrontSide
});
// Inner mesh shows back side
meshInner.material = new THREE.MeshPhongMaterial({
color: innerColor,
side: THREE.BackSide
});
// Scale inner mesh down just a bit to avoid z-fighting
meshInner.scale.multiplyScalar(0.99);

Vertices of THREE.BufferGeometry

Since r125, THREE.Geometry was deprecated. We are now updating our code base and we are running into errors that we don't know how to fix.
We create a sphere and use a raycaster on the sphere to get the intesect point.
worldSphere = new THREE.SphereGeometry(
worldSize,
worldXSegments,
worldYSegments
);
...
const intersect = raycaster.intersectObjects([worldGlobe])[0];
...
if (intersect) {
let a = worldSphere.vertices[intersect.face.a];
let b = worldSphere.vertices[intersect.face.b];
let c = worldSphere.vertices[intersect.face.c];
}
Now, normally variable a would contain 3 values for every axis namely a.x, a.y, a.z, same goes for the other variables. However, this code does not work anymore.
We already know that worldSphere is of type THREE.BufferGeometry and that the vertices are stored in a position attribute, but we cannot seem to get it working.
What is the best way to fix our issue?
It should be:
const positionAttribute = worldGlobe.geometry.getAttribute( 'position' );
const a = new THREE.Vector3();
const b = new THREE.Vector3();
const c = new THREE.Vector3();
// in your raycasting routine
a.fromBufferAttribute( positionAttribute, intersect.face.a );
b.fromBufferAttribute( positionAttribute, intersect.face.b );
c.fromBufferAttribute( positionAttribute, intersect.face.c );
BTW: If you only raycast against a single object, use intersectObject() and not intersectObjects().

Animating LineSegments in THREE.js

I have a solid MorphBlendMesh that is overlayed with a LineSegments object using EdgesGeometry/LineBasicMaterial in order to create a wireframe look without the "diagonals" that result from the triangle approach in newer versions of three.js. The problem is that I cannot find a way to get LineSegments to animate along with the mesh, presumably because it isn't a mesh, its simply an Object3D.
Is there a way to animate a LineSegments object with AnimationMixer? Or replicate this same look with a mesh setup that works well with AnimationMixer?
For reference, my question is essentially an expansion of this question -- same idea, but it MUST be capable of animation with AnimationMixer.
You can attach an arbitrary property to the mixer. This property will hold the vertices.
const a: any = ((lines.geometry as THREE.BufferGeometry).attributes.position as BufferAttribute)
.array;
const p: any = (line.geometry as any).attributes.position.array;
(lines as any).value = [...a];
const keyFrame2 = new THREE.NumberKeyframeTrack(
'.value',
[0,1],
[...a, ...p],
THREE.InterpolateSmooth
);
this.lineGeometriesToUpdate.push(lines as THREE.LineSegments);
const clip2 = new THREE.AnimationClip('lines', 1, [keyFrame2]);
const mixer2 = new THREE.AnimationMixer(lines);
const ca2 = mixer2.clipAction(clip2);
this.mixer.push(mixer2);
Then, in your animation loop, you use this property to update the geometry
this.lineGeometriesToUpdate.forEach(l => {
const geom = l.geometry as THREE.BufferGeometry;
const values = (l as any).value;
geom.setAttribute('position', new THREE.BufferAttribute(new Float32Array(values), 3));
(geom.attributes.position as BufferAttribute).needsUpdate = true;
});
this.renderScene();

DoubleSided Material not working with Typescript

I am using three.js to render a street using ShapeGeometry.
This is the code:
import { CONSTANTS, MATERIALS } from '../common'
let ret = new Array<THREE.Mesh>();
let rampShape = new THREE.Shape();
rampShape.moveTo(0,0);
rampShape.lineTo(0, ramp.width);
//some more line and curve drawing, which works perfectly
let geo = new THREE.ShapeGeometry(rampShape);
geo.faces.map( (x) => { x.color.set(MATERIALS.roadMaterial.color); });
let mesh = new THREE.Mesh(geo, MATERIALS.roadMaterial);
mesh.material.side = 2;
mesh.updateMatrix();
mesh.rotateX(-Math.PI/2);
mesh.rotateY(Math.PI);
//this is imported from another file
export const MATERIALS = {
roadMaterial: new THREE.MeshBasicMaterial({
color: 0x333333,
side: THREE.DoubleSide,
opacity: 1.0
})
}
But when i rotate the shape, or look at it from "the other side", i just vanishes. I heard that this problem is fixed by using DoubleSided Materials.
I added the side = 2 because i thought maybe the typescript enum somehow messes up the THREE.DoubleSided constant, but it does not work either way.
I installed the Typings with the tsd install command, and they are up-to-date.
Anybody got any idea? If i do not get it to work i guess i could extrude the shape - which i would like to avoid for performance reasons.

Categories