I am new to Three.js. I was trying to load a STL model using the STLLoader from the examples. The model I am trying to load is the Eiffel Tower model. I downloaded the STL file which is in ASCII format and almost 33 MB in size. I have the following setup to display the model:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body { margin: 5% auto; }
canvas { width: 80%; height: 80% }
#progress {
margin-bottom: 2%;
min-width: 50%;
}
</style>
<script src="js/three.js"></script>
<script src="js/STLLoader.js"></script>
</head>
<body>
<div id="progress"></div>
<script>
// window properties
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// camera properties
const FOV = 35;
const ASPECT_RATIO = windowWidth / windowHeight;
const NEAR = 0.1;
const FAR = 1000;
// scene settings
const SCENE_BKG = new THREE.Color("rgb(220,220,220)");
const scene = new THREE.Scene();
scene.background = SCENE_BKG;
const camera = new THREE.PerspectiveCamera( FOV, ASPECT_RATIO, NEAR, FAR );
const STLLoader = new THREE.STLLoader();
STLLoader.load('./sample_stl/Eiffel_tower_sample.STL', function(geometry) {
console.dir(geometry);
const materials = [];
const nGeometryGroups = geometry.groups.length;
let colorMap = []; // Some logic to index colors.
let material;
// create a random colorMap
let startColor = 0x010101;
let clr = startColor;
let count = 0;
while (count++ < nGeometryGroups) {
colorMap.push(clr);
clr = ( parseInt(clr, 16) + startColor ).toString();
}
console.log(colorMap);
for (let i = 0; i < nGeometryGroups; i++) {
material = new THREE.MeshPhongMaterial({
color: colorMap[i],
wireframe: false
});
}
materials.push(material);
const mesh = new THREE.Mesh(geometry, materials);
console.dir(mesh);
scene.add(mesh);
// should i call animate here?
}, function (xhr) {
// show progress here
progressBar.innerHTML = `<span style="color: green;">${(xhr.loaded/xhr.total) * 100}%</span> have been loaded`;
}, function(err) {
console.error('[!] Fatal Error: Could not load model');
console.error(err);
});
const renderer = new THREE.WebGLRenderer();
renderer.setSize( windowWidth, windowHeight );
const progressBar = document.querySelector('#progress');
document.body.appendChild( renderer.domElement );
const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
camera.position.set(0, 0, 10);
animate();
</script>
</body>
</html>
There are two things I am not sure about. First, what is the colorMap array? I looked at the MeshPhongMaterial class documentation and figured out it was a hex color value. I copied this code directly from the STLLoader example folder here. I found a quick hack to generate some hex colors and populate the colorMap array (an empty array was throwing errors). Secondly, where should I call the animate() function? I tried calling it inside the modelLoaded handler and also outside it with the only difference being that inside the handler, it throws Violation: handler took 500ms. I checked the networks tab on both Firefox and Chromium to see that the STL file was loaded properly. I also printed the Mesh object in the console which is as follows:
Mesh
castShadow: false
children: []
drawMode: 0
frustumCulled: true
geometry: BufferGeometry
attributes: {position: Float32BufferAttribute, normal: Float32BufferAttribute}
boundingBox: null
boundingSphere: Sphere {center: Vector3, radius: 71.76963889659893}
drawRange: {start: 0, count: Infinity}
groups: [{…}]
index: null
morphAttributes: {}
morphTargetsRelative: false
name: ""
type: "BufferGeometry"
userData: {}
uuid: "28A4B269-2828-477D-9C6D-5C9A30E95A7F"
_listeners: {dispose: Array(1)}
drawcalls: (...)
id: 7
offsets: (...)
__proto__: EventDispatcher
layers: Layers
mask: 1
__proto__: Object
material: Array(1)
0: MeshPhongMaterial {uuid: "1E33986A-A979-49C0-ADF5-823CACC6F3AA", name: "", type: "MeshPhongMaterial", fog: true, blending: 1, …}
length: 1
__proto__: Array(0)
matrix: Matrix4
elements: Array(16)
0: 1
1: 0
2: 0
3: 0
4: 0
5: 1
6: 0
7: 0
8: 0
9: 0
10: 1
11: 0
12: 0
13: 0
14: 0
15: 1
length: 16
__proto__: Array(0)
__proto__: Object
matrixAutoUpdate: true
matrixWorld: Matrix4
elements: Array(16)
0: 1
1: 0
2: 0
3: 0
4: 0
5: 1
6: 0
7: 0
8: 0
9: 0
10: 1
11: 0
12: 0
13: 0
14: 0
15: 1
length: 16
__proto__: Array(0)
__proto__: Object
matrixWorldNeedsUpdate: false
name: ""
parent: Scene
autoUpdate: true
background: Color {r: 0.8627450980392157, g: 0.8627450980392157, b: 0.8627450980392157}
castShadow: false
children: [Mesh]
fog: null
frustumCulled: true
layers: Layers {mask: 1}
matrix: Matrix4 {elements: Array(16)}
matrixAutoUpdate: true
matrixWorld: Matrix4 {elements: Array(16)}
matrixWorldNeedsUpdate: false
name: ""
overrideMaterial: null
parent: null
position: Vector3 {x: 0, y: 0, z: 0}
quaternion: Quaternion {_x: 0, _y: 0, _z: 0, _w: 1, _onChangeCallback: ƒ}
receiveShadow: false
renderOrder: 0
rotation: Euler {_x: 0, _y: 0, _z: 0, _order: "XYZ", _onChangeCallback: ƒ}
scale: Vector3 {x: 1, y: 1, z: 1}
type: "Scene"
up: Vector3 {x: 0, y: 1, z: 0}
userData: {}
uuid: "3F9C4993-4CB4-4541-9C64-34FCBB12B1E3"
visible: true
_listeners: {dispose: Array(2)}
eulerOrder: (...)
id: 4
modelViewMatrix: Matrix4 {elements: Array(16)}
normalMatrix: Matrix3 {elements: Array(9)}
useQuaternion: (...)
__proto__: Object3D
position: Vector3 {x: 0, y: 0, z: 0}
quaternion: Quaternion {_x: 0, _y: 0, _z: 0, _w: 1, _onChangeCallback: ƒ}
receiveShadow: false
renderOrder: 0
rotation: Euler {_x: 0, _y: 0, _z: 0, _order: "XYZ", _onChangeCallback: ƒ}
scale: Vector3 {x: 1, y: 1, z: 1}
type: "Mesh"
up: Vector3 {x: 0, y: 1, z: 0}
userData: {}
uuid: "72BA29AF-F939-45F8-869A-D074E6696D7F"
visible: true
eulerOrder: (...)
id: 10
modelViewMatrix: Matrix4 {elements: Array(16)}
normalMatrix: Matrix3 {elements: Array(9)}
useQuaternion: (...)
__proto__: Object3D
This is the printed Mesh object. I am not sure what's wrong and would be glad if someone can explain me how to use the colorMap property and also give me a definitive answer on the animate function calling scope.
EDIT
Added a fiddle here.
First, what is the colorMap array?
The code from your example assumes that the model's geometry has multiple groups. If this is true, it's possible to assign multiple materials to a single 3D object. However, it's not mandatory to do so. If you want just a single material color, do this:
const material = new THREE.MeshPhongMaterial( { color: 0x0000ff } );
const mesh = new THREE.Mesh( geometry, material );
Secondly, where should I call the animate() function?
three.js examples normally have an init() and an animate() function. Meaning you start animating right after initializing the scene (creating camera, renderer, lights etc.). You can do this also in your application, however the STL file will pop in as soon as its loading and parsing progress is finished. As an alternative, you can also start animating in the onLoad() callback. It really depends on your use case (so there is no right or wrong).
three.js R111
Related
We have three spheres
const object1 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
new THREE.MeshBasicMaterial({ color: '#ff0000' })
)
object1.position.x = - 2
const object2 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
new THREE.MeshBasicMaterial({ color: '#ff0000' })
)
const object3 = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 16, 16),
new THREE.MeshBasicMaterial({ color: '#ff0000' })
)
object3.position.x = 2
And a raycaster
const raycaster = new THREE.Raycaster();
const rayOrigin = new THREE.Vector3(-3, 0, 0);
const rayDirection = new THREE.Vector3(1, 0, 0);
raycaster.set(rayOrigin, rayDirection);
const intersects = raycaster.intersectObjects([object1, object2, object3])
console.log(intersects)
Logging the intersects array, we get this:
0: {distance: 2.5, point: Vector3, object: Mesh, uv: Vector2, face: {…}, …}
1: {distance: 2.5, point: Vector3, object: Mesh, uv: Vector2, face: {…}, …}
2: {distance: 2.5, point: Vector3, object: Mesh, uv: Vector2, face: {…}, …}
length: 3
Why is the distance the same for all spheres? Shouldn't the distances be 0.5, 2.5 and 4.5 respectively? What am I missing?
I created a Three.js Scene where it loads a GLtf Object (.glb) inside a RGBELoader (for hdr Enviroment Texture Lighting )
I then give each Mesh inside this GLtf Object a new Material. Like this:
gltfObject.traverse((ChildGLTF) => {
ChildGLTF.children[0].material = MidMaterial;
ChildGLTF.children[1].material = TopMaterial;
ChildGLTF.children[2].material = BotMaterial;
});
Now the strange thing is, I get an Error and at the same time the materials are applied like I want them to. So it actually works.
Still I want to get rid of that error or at least understand what is causing it.
Thats the Error:
script.js:77 Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'material')
at script.js:77
at Mesh.traverse (three.module.js:6907)
at Group.traverse (three.module.js:6913)
at script.js:72
at GLTFLoader.js:175
at GLTFLoader.js:1989
So my best guess is, that there are more than one .material Attributes? But how can i specify, that I only want the material that is changeable..
Or is it, that 'gltfObject.traverse((ChildGLTF)' runs more than one time with ChildGLTF as an array? If so, "console.log( ChildGLTF[0] );" gives "undefined" ..
Here are the console logs
console.log( ChildGLTF[0] ); //'undefined'
console.log( ChildGLTF.children[0] ); //works fine
console.log( ChildGLTF.children[0].material ); //'Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'material')'
console.log( ChildGLTF.children[0] )
! Mesh {uuid: '3BE5076C-A90D-47B4-BE40-D1E77E4F4DEC', name: 'Cube', type: 'Mesh', parent: Group, children: Array(0), …}
animations: []
castShadow: false
children: []
frustumCulled: true
geometry: BufferGeometry {uuid: 'EC8A35A7-1942-4556-9D93-6C3E25F4C77A', name: '', type: 'BufferGeometry', index: BufferAttribute, attributes: {…}, …}
layers: Layers {mask: 1}
material: MeshStandardMaterial {uuid: 'ACA40D34-B763-42DA-A956-3ABBAC901D37', name: '', type: 'MeshStandardMaterial', fog: true, blending: 1, …}
matrix: Matrix4 {elements: Array(16)}
matrixAutoUpdate: true
matrixWorld: Matrix4 {elements: Array(16)}
matrixWorldNeedsUpdate: false
name: "Cube"
parent: Group {uuid: '27EE9C8E-3B31-4483-8058-CA2DA42070FC', name: 'Scene', type: 'Group', parent: Scene, children: Array(3), …}
position: Vector3 {x: 0, y: 0, z: 0}
quaternion: Quaternion {_x: 0, _y: 0, _z: 0, _w: 1, _onChangeCallback: ƒ}
receiveShadow: false
renderOrder: 0
rotation: Euler {_x: 0, _y: 0, _z: 0, _order: 'XYZ', _onChangeCallback: ƒ}
scale: Vector3 {x: 1, y: 1, z: 1}
type: "Mesh"
up: Vector3 {x: 0, y: 1, z: 0}
userData: {name: 'Cube'}
uuid: "3BE5076C-A90D-47B4-BE40-D1E77E4F4DEC"
visible: true
drawMode: (...)
eulerOrder: (...)
id: 40
modelViewMatrix: Matrix4 {elements: Array(16)}
normalMatrix: Matrix3 {elements: Array(9)}
useQuaternion: (...)
[[Prototype]]: Object3D
console.log( ChildGLTF.children[0].material );
! MeshStandardMaterial {uuid: 'FBA72EAB-93C3-4F92-B141-1BA011BB81FD', name: 'CubeMaterial', type: 'MeshStandardMaterial', fog: true, blending: 1, …}
alphaMap: null
alphaTest: 0
aoMap: null
aoMapIntensity: 1
blendDst: 205
blendDstAlpha: null
blendEquation: 100
blendEquationAlpha: null
blendSrc: 204
blendSrcAlpha: null
blending: 1
bumpMap: null
bumpScale: 1
clipIntersection: false
clipShadows: false
clippingPlanes: null
color: Color {r: 0.011123105883598328, g: 0.004119289573282003, b: 0.8000000715255737}
colorWrite: true
defines: {STANDARD: ''}
depthFunc: 3
depthTest: true
depthWrite: true
displacementBias: 0
displacementMap: null
displacementScale: 1
dithering: false
emissive: Color {r: 0, g: 0, b: 0}
emissiveIntensity: 1
emissiveMap: null
envMap: null
envMapIntensity: 1
flatShading: false
fog: true
lightMap: null
lightMapIntensity: 1
map: null
metalness: 0
metalnessMap: null
morphNormals: false
morphTargets: false
name: "CubeMaterial"
normalMap: null
normalMapType: 0
normalScale: Vector2 {x: 1, y: 1}
opacity: 1
polygonOffset: false
polygonOffsetFactor: 0
polygonOffsetUnits: 0
precision: null
premultipliedAlpha: false
refractionRatio: 0.98
roughness: 0.5
roughnessMap: null
shadowSide: null
side: 2
skinning: false
stencilFail: 7680
stencilFunc: 519
stencilFuncMask: 255
stencilRef: 0
stencilWrite: false
stencilWriteMask: 255
stencilZFail: 7680
stencilZPass: 7680
toneMapped: true
transparent: false
type: "MeshStandardMaterial"
userData: {}
uuid: "FBA72EAB-93C3-4F92-B141-1BA011BB81FD"
version: 0
vertexColors: false
vertexTangents: false
visible: true
wireframe: false
wireframeLinecap: "round"
wireframeLinejoin: "round"
wireframeLinewidth: 1
id: 7
overdraw: (...)
shading: (...)
stencilMask: (...)
wrapAround: (...)
wrapRGB: (...)
[[Prototype]]: Material
.traverse(...) is going to run that callback for every object within the model, and your code assumes each object has exactly three children. That's not possible, because the model would have to be infinitely deep, and when you eventually reach an object that doesn't have children, doing .children[0].material will throw these errors.
You'll want to check what ChildGLTFis, and only modify that one object during the loop.
gltfObject.traverse((child) => {
if (!child.isMesh) return;
if (child.name === 'MyMidMeshName') { // change this name
child.material = MidMaterial;
} else if (child.name === 'MyTopMeshName') { // change this name
// ...
} else {
// ...
}
});
I´ve been trying to enable tooltips on some imported 3D models, but it isnt working.
I already enabled tooltips in threbox, and I enabled tooltips in the options for the 3d element, as shown below.
tb = new Threebox(
map,
mbxContext,
{
realSunlight: true,
enableSelectingFeatures: true, //change this to false to disable fill-extrusion features selection
enableTooltips: true // change this to false to disable default tooltips on fill-extrusion and 3D models
}
);
var proptions = {
obj: './models/er.glb',
type: 'gltf',
scale: 10,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 }, //default rotation
anchor: 'center',
adjustment: { x: 0, y: 0, z: 0.4 },
enableToltips: true
}
When i load the object i did the following:
tb.loadObj(proptions, function (model) {
model.setCoords(place);
model.addTooltip("A radar in the middle of nowhere", true);
model.setRotation({ x: 0, y: 0, z: Math.floor(Math.random() * 100) })
tb.add(model);
});
Although the object appears in the render, when I put the mouse above or i click it nothing shows the tooltip.
What am I missing ?
EDIT:
Following #jscastro response i changed the import in the top of my html page to <link href="./threebox-plugin/examples/css/threebox.css" rel="stylesheet" /> (the path is the correct to where the file is)
I also removed the enableTooltip: true in proptions.
Despite that it still does not work, Below i will leave the code as it is:
var origin = [-8.4, 41.20, 1];
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: origin,
zoom: 11,
pitch: 30,
antialias: true
});
//Things related to dateTime ommited
window.tb = new Threebox(
map,
map.getCanvas().getContext('webgl'),
{
realSunlight: true,
enableSelectingFeatures: true, //change this to false to disable fill-extrusion features selection
enableTooltips: true // change this to false to disable default tooltips on fill-extrusion and 3D models
}
);
map.on('style.load', async function () {
await importarLinhas();
// stats
// stats = new Stats();
// map.getContainer().appendChild(stats.dom);
animate();
map.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, mbxContext) {
var eroptions = {
obj: './models/stationBus.fbx',
type: 'fbx',
scale: 0.01,
units: 'meters',
rotation: { x: 90, y: 20, z: 0 }, //default rotation
anchor: 'center',
adjustment: { x: -0.1, y: -0.1, z: 0.4 }
}
var poptions = {
obj: './models/Busstop.fbx',
type: 'fbx',
scale: 0.03,
units: 'meters',
rotation: { x: 90, y: 20, z: 0 }, //default rotation
anchor: 'center',
adjustment: { x: -0.1, y: -0.1, z: 0.1 }
}
var proptions = {
obj: './models/er.glb',
type: 'gltf',
scale: 2.7,
units: 'meters',
rotation: { x: 90, y: 0, z: 0 }, //default rotation
anchor: 'center',
adjustment: { x: 0, y: 0, z: 0.4 }
}
allNos.forEach((element) => { //For each one of a list that i fill first
//center of where the objects are
var place = [element.lng, element.lat, 0];
//cylinder as "base" for each one of the 3d Models
**//in here i cant do the Tooltip for the object**
const geometry = new THREE.CylinderGeometry(0.6, 0.6, 0.15, 32);
const material = new THREE.MeshLambertMaterial({ color: 0x5B5B5B });
const cylinder = new THREE.Mesh(geometry, material);
var baseOptions = {
obj: cylinder,
anchor: 'center',
adjustment: { x: 0, y: 0, z: -0.4 }
}
let base = tb.Object3D(baseOptions);
base.setCoords(place);
base.setRotation({ x: 90, y: 0, z: 0 })
//The text is just for the test
base.addTooltip("A radar in the middle of nowhere", true);
// base.castShadow = true;
window.tb.add(base);
//next i check what type of element it is
//it can only be one at the same time, so i use different models for each type
if (element.tipo === "p") {
window.tb.loadObj(poptions, function (model) {
model.setCoords(place);
model.addTooltip("A radar in the middle of nowhere", true);
model.setRotation({ x: 0, y: 0, z: Math.floor(Math.random() * 100) })
// model.castShadow = true;
window.tb.add(model);
});
}
if (element.tipo === "er") {
window.tb.loadObj(eroptions, function (model) {
model.setCoords(place);
model.addTooltip("A radar in the middle of nowhere", true);
model.setRotation({ x: 0, y: 0, z: Math.floor(Math.random() * 100) })
// model.castShadow = true;
window.tb.add(model);
});
}
if (element.tipo === "pr") {
window.tb.loadObj(proptions, function (model) {
model.setCoords(place);
model.addTooltip("A radar in the middle of nowhere", true);
model.setRotation({ x: 0, y: 0, z: Math.floor(Math.random() * 100) })
// model.castShadow = true;
window.tb.add(model);
});
}
});
},
render: function (gl, matrix) {
window.tb.setSunlight(date, origin.center);
window.tb.update();
}
})
map.addLayer(createCompositeLayer());
map.on('SelectedFeatureChange', onSelectedFeatureChange);
});
EDIT
I downloaded the page you shared in the chat, and I found many different issues and mistakes in your code.
1. You're using the wrong property to enable the selection of 3D objects, you use enableSelectingFeatures: true, //change this to false to disable fill-extrusion features selection, that is for Mapbox fill-extrusions features as said in the comment, but not for 3D models and objects, you have to use enableSelectingObjects: true. Only adding this, your problem with the tooltips on mouse over will be solved.
tb = new Threebox(
map,
mbxContext,
{
realSunlight: true,
enableSelectingObjects: true, //enable 3D models over/selection
enableTooltips: true // enable default tooltips on fill-extrusion and 3D models
}
);
But I have found other issues...
2. Your models scale initialization is too small, so you are hiding them below the big shapes you have created. The scale of your bus stop is scale: 0.01 and you define a place which is on the ground var place = [element.lng, element.lat, 0];, so it's hidden inside this CylinderGeometry
If you use scale: 1 you will see how your bus stops raises from the cylinder.
3. Same with the bus, you initialize them with scale: 1, which make them be hidden below the tubes and cylinders you have created. If you initialize them with scale: 10, and you elevate them 5 meters from the floor let truck = model.setCoords([lngB, latB, 4]); then you will see them raising.
4. Your models have a wrong initialization params mixing anchor and adjustment. anchor: center will center the pivotal center of your object properly, but then you apply negative values to x and y (which means decenter the object), and a z value that elevates the pivotal center adjustment: { x: -0.1, y: -0.1, z: 0.4 }. If you want your model on altitude use the 3rd coord in setCoords.
5. Your Cylinders and Tubes for the bus stops and bus lines are huge, and also they have the wrong init params, as you set them below the ground level -0.4 units adjustment: { x: 0, y: 0, z: -0.4 } (something supported by Mapbox but very bad resolved and producing weird effects. My recommendation would be to make them almost flat and at the ground level with no adjustment param. const geometry = new THREE.CylinderGeometry(0.6, 0.6, 0.01, 32);.
Summarizing, check all of these changes and let me know if it works.
How do I get the rotation of an element relative to the world coordination system?
I'm getting the information of an element by the following:
this.viewerComponent.viewer.impl.hitTest(event.layerX, event.layerY, false);
The result of this function is the following
{distance: 186476.22640731235, point: X.Vector3, face: X.Face3, faceIndex: 0, fragId: 372, …}
distance: 186476.22640731235
point: X.Vector3 {x: 70297.79662079967, y: 8922.73091035225, z: 9109.446866256267}
face: X.Face3 {a: 0, b: 1, c: 2, normal: X.Vector3, vertexNormals: Array(0), …}
faceIndex: 0
fragId: 372
dbId: 1959
object: X.Mesh
eulerOrder: (...)
useQuaternion: (...)
uuid: "A3D04442-BB20-4E0C-B371-3A987D212255"
name: ""
type: "Mesh"
parent: undefined
children: []
up: X.Vector3 {x: 0, y: 1, z: 0}
position: X.Vector3 {x: 0, y: 0, z: 0}
rotation: X.Euler {_x: 0, _y: 0, _z: 0, _order: "XYZ", onChangeCallback: ƒ}
quaternion: X.Quaternion {_x: 0, _y: 0, _z: 0, _w: 1, onChangeCallback: ƒ}
scale: X.Vector3 {x: 1, y: 1, z: 1}
rotationAutoUpdate: true
matrix: X.Matrix4
elements: Float32Array(16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
__proto__: Object
matrixWorld: X.Matrix4
elements: Float32Array(16) [10, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 79719.9609375, -6109.12646484375,
1962.4998779296875, 1]
__proto__: Object
matrixAutoUpdate: true
matrixWorldNeedsUpdate: false
visible: true
castShadow: false
receiveShadow: false
frustumCulled: true
renderOrder: 0
userData: {}
geometry: h {id: 2265, attributes: {…}, __webglInit: undefined, byteSize: 28, vb: Float32Array(6),
…}
material: X.LineBasicMaterial {uuid: "E36F7B3D-C885-475C-9AA6-A3D1024F7687", name: "", type:
"LineBasicMaterial", side: 0, opacity: 1, …}
isTemp: true
dbId: 529
modelId: 1
fragId: 2264
hide: false
isLine: true
isWideLine: false
isPoint: false
themingColor: undefined
id: 1
__proto__: X.Object3D
As seen, we have the information matrix (local coordination system which is connected to the element coordination system) and matrixWorld which should be the transformation matrix for element -> global coordination system. How do I now get the angles out of the matrixWorld to know what is the rotation by the elmenent in relation to the world coordination system.
Hope it is clear what i want, thank you in advance.
If I understand you correctly you may try:
//Getting fragment info by fragId if necessary
//const matrixWorld = new THREE.Matrix4();
//const fragProxy = NOP_VIEWER.impl.getFragmentProxy(NOP_VIEWER.model, fragId)
//fragProxy.getWorldMatrix(matrixWorld);
const position = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3();
matrixWorld.decompose( position, quaternion, scale );
See more on extracting rotation here
Thank you. It's a similar approach I've taken. Your solution would probably work, too.
public onMouseMove(event) {
var snapper = new
Autodesk.Viewing.Extensions.Snapping.Snapper(this.viewerComponent.viewer, {});
const hitTestResult = this.viewerComponent.viewer.impl.snappingHitTest(event.layerX,
event.layerY);
snapper.snapping3D(hitTestResult);
// Arrow
const geometry = new THREE.CylinderGeometry(1, 800, 2000, 100);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);
mesh.rotateZ(this.arcSinFunction(normal.x, normal.y, normal.z, 0, 1, 0)[0]);
mesh.name = 'section-mesh';
mesh.position.set(snapper.getSnapResult().intersectPoint.x,
snapper.getSnapResult().intersectPoint.y, snapper.getSnapResult().intersectPoint.z);
if (!this.viewerComponent.viewer.overlays.hasScene('section-scene')) {
this.viewerComponent.viewer.overlays.addScene('section-scene');
}
this.viewerComponent.viewer.overlays.addMesh([mesh, mesh_test], 'section-scene');
}
public arcSinFunction(n1: number, n2: number, n3: number, u1: number, u2: number,
u3:
number): Array<number> {
var zähler: number = Math.abs(n1 * u1 + n2 * u2 + n3 * u3);
// console.log(zähler);
var nenner: number = (Math.sqrt(n1 * n1 + n2 * n2 + n3 * n3)) * (Math.sqrt(u1 * u1 +
u2 * u2 + u3 * u3));
// console.log(nenner);
var resultRadian: number = Math.asin(zähler / nenner);
// console.log(resultRadian);
var pi = Math.PI;
var resultDegree = resultRadian * (180 / pi);
// console.log(resultDegree);
var res: Array<number> = [resultRadian, resultDegree];
// console.log(res);
return res;
}
Best regards
I have a ThreeJS scene with 16 objects that are supposed to end up in a 4 by 4 square grid. However, when I run the code, I only see one of the objects. I wrote a "dump" function to show me all the current XYZ values of the mesh objects's position property, which you can see below. The values all look good to me and I believe I should see a nice 4 by 4 square grid of objects given the positioning those values present.
I am using this ThreeJS Javascript file for ThreeJS:
https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js
I am using this code to create the mesh. The mesh is a cube with initially the side facing the camera is a cat image:
function makeCatCube(catImageUrl, textureBackSide, locX, locY, locZ) {
let errPrefix = '(makeCatCube) ';
// TODO: Should we use BoxBufferGeometry here for greater speed?
let cubeGeometry = new THREE.BoxGeometry(2, 0.1, 2);
let loader = new THREE.TextureLoader();
let materialArray = [
new THREE.MeshBasicMaterial( { map: loader.load('/images/cards/white-square-400x400.png') } ),
new THREE.MeshBasicMaterial( { map: loader.load('/images/cards/white-square-400x400.png') } ),
// Card face.
new THREE.MeshBasicMaterial( { map: loader.load(catImageUrl) } ),
// Card back side.
new THREE.MeshBasicMaterial(
{
map: textureBackSide
}
),
new THREE.MeshBasicMaterial( { map: loader.load('/images/cards/white-square-400x400.png') } ),
new THREE.MeshBasicMaterial( { map: loader.load('/images/cards/white-square-400x400.png') } ),
];
cube = new THREE.Mesh( cubeGeometry, materialArray );
if (g_ShowGraphicsDebugInfo) {
console.log(errPrefix + `Setting cube position to - X: ${locX}, Y: ${locY}, Z: ${locZ}`);
}
cube.position.set(locX, locZ, locY);
// TODO: Magic number to set the cube's X rotation so it looks flat facing the viewer.
cube.rotation.x = THREE.Math.radToDeg(60);
return cube;
}
Here is the main promise that builds all the game assets and shows where I add the mesh objects to the scene. The global g_aryCatCards array that contains all the cat cards that were built is prepared in a much larger module elsewhere. It contains each of the cat cards and each card has a meshThreeJS property that contains the ThreeJS mesh object (i.e. - the cube) that was built using the makeCatCube() function shown above :
function initializeGameAssets_promise(gameAreaDomElementID, threeJSCanvasAreaDomElementID, catCardWidth, catCardHeight) {
let errPrefix = '(initializeGameAssets_promise) ';
return new Promise(function(resolve, reject) {
try {
buildAllCatCards_promise(gameAreaDomElementID, threeJSCanvasAreaDomElementID, 1, catCardWidth, catCardHeight)
.then(result => {
g_Scene = new THREE.Scene();
g_Scene.background = new THREE.Color('yellow');
initCamera();
initRenderer();
for (let cardLabelKey in g_aryCatCards) {
let catCard = g_aryCatCards[cardLabelKey];
g_Scene.add(catCard.meshThreeJS);
}
let threeJSCanvasAreaDOMElement = document.getElementById(threeJSCanvasAreaDomElementID);
if (!threeJSCanvasAreaDOMElement)
throw new Error(errPrefix + `Unable to find the DOM element for the cat cards underlay table using ID: ${threeJSCanvasAreaDomElementID}`);
threeJSCanvasAreaDOMElement.appendChild(g_Renderer.domElement);
let catCardsTableElementOffset = getElementOffsetById(ELEMENT_ID_CAT_CARDS_TABLE);
threeJSCanvasAreaDOMElement.left = catCardsTableElementOffset.left;
threeJSCanvasAreaDOMElement.top = catCardsTableElementOffset.top;
// Start the rendering process.
render();
resolve(true);
})
.catch(err => {
reject(err);
});
}
catch(err) {
reject(err);
}
});
}
Here are the functions I use to initialize the camera and the renderer:
function initCamera() {
g_Camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
g_Camera.position.set(0, 3.5, 5);
g_Camera.lookAt(g_Scene.position);
}
function initRenderer() {
g_Renderer = new THREE.WebGLRenderer(
{
antialias: true
});
}
This dump shows the XYZ values of the mesh object's position property:
------------- DUMPING MESH POSITIONS -------------
[label: A1] - X: 2, Y: 0, Z: 2
[label: A2] - X: 176, Y: 0, Z: 2
[label: A3] - X: 350, Y: 0, Z: 2
[label: A4] - X: 525, Y: 0, Z: 2
[label: E1] - X: 2, Y: 0, Z: 364
[label: E2] - X: 176, Y: 0, Z: 364
[label: E3] - X: 350, Y: 0, Z: 364
[label: E4] - X: 525, Y: 0, Z: 364
[label: L1] - X: 2, Y: 0, Z: 183
[label: L2] - X: 176, Y: 0, Z: 183
[label: L3] - X: 350, Y: 0, Z: 183
[label: L4] - X: 525, Y: 0, Z: 183
[label: X1] - X: 2, Y: 0, Z: 545
[label: X2] - X: 176, Y: 0, Z: 545
[label: X3] - X: 350, Y: 0, Z: 545
[label: X4] - X: 525, Y: 0, Z: 545
I really don't know what to do at this point to debug this problem. Can anyone give me some general tips on what to inspect, or what diagnostic code I could write to try and figure this out?
Your camera can only see 9 units deep
new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, 1, 10);
but your objects are up to 545 units away
Try
const near = 1;
const far = 1000;
new THREE.PerspectiveCamera(70, WIDTH / HEIGHT, near, far);
see
You also seem to have the camera looking in the wrong direction.
g_Camera.position.set(0, 3.5, 5);
g_Camera.lookAt(g_Scene.position);
AFAIK g_scene.position is 0, 0, 0 which means the camera is at z = 5 looking toward Z = 0 but your list of objects are almost all behind the camera.
Try
g_Camera.position.set(0, 3.5, -50);
g_Camera.lookAt(g_Scene.position);