Hi All,
Ive searched around a bit for an answer to this one but cant make sense of the answers that have already been given in relation to the code I have used.
Basically I am tring to create a GLTF model of a building that contains other loaded GLTF models that make up a gallery. I am trying to give each of the hosted GLTF objects a unique URL reference so that the user can click on the object and go to another webpage.
I have managed to implement the Raycaster onclick function with a window.location.href = ‘my.url’ which works when the model is clicked anywhere but I cant figure where to add the url to give each GLTF its own unique address. Please can someone show me where and how I need to add the code within each individual GLTF loader?
This first bit of code shows how I have set up the GLTF loader, there are multiple GLTF’s all set up in the same way:
// Loads GLTF model
const loader = new GLTFLoader().setPath( 'models/gltf/MakerVerdeSchool/' );
loader.load( 'MakerVerde-Gallery.gltf', function ( gltf ) {
gltf.scene.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add( gltf.scene );
render();
} );
// Loads model 02
loader.load( 'Pic-Kitchen.gltf', function ( gltf ) {
gltf.scene.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add( gltf.scene );
render();
} );
And below the raycaster function:
function onClick() {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
console.log('Intersection:', intersects[0]);
window.location.href = 'https://makerverde.com/'; // Incorrect line, needs to be
specific to each individual GLTF
}
Any help is much appreciated and I’m sure it is possible and even quite a simple fix for someone who knows what they’re doing.
First thing that comes to mind is that you can rewrite the url with a rewrite rule in your server block for nginx or .htaccess for apache.
Apologies, I’m not familiar with these terms. Could you expand?
I would use the name
property to store unique URL.
When recursively raycasting a model (or a set of models), the raycaster may return intersections with some subobject of the model. In this case, I’d propose this approach:
- if there is intersection, pick the closest one (you already do this with
intersects[0]
)
- get the object that corresponds to this intersection (it is stored in
intersects[0].object
property)
- if this object has
name
with URL, then you can navigate; if not, go to the parent of the object (stored in parent
) and again check name
… if not, keep going up until you reach the scene
object or an object without a parent.
If you plan to export the building as a GLTF file, please, check whether this will preserve the name
properties of individual models. I hope it will, but I have never tested it.
Thanks Pavel, would the name property be written in the GLTF file? Maybe as a custom property in Blender before export?
As I said, I hope that the name will be written in the GLTF. You have to try it in order to be sure. As for Blender, the names of meshes are written in the GLTF file. When the file is read by Three.js GLTFLoader, these names are also read. There is only one caveat – some special symbols from the names are removed. Again, a single test can answer this question.
Looking at the GLTF file in VS Code I can see the each mesh has a name property:
{
“asset”:{
“generator”:“Khronos glTF Blender I/O v3.6.27”,
“version”:“2.0”
},
“scene”:0,
“scenes”:[
{
“name”:“Scene”,
“nodes”:[
0,
1,
2,
3,
4
]
}
],
“nodes”:[
{
“mesh”:0,
“name”:“Object.796”
},
{
“mesh”:1,
“name”:“Object.797”
},
{
“mesh”:2,
“name”:“Object.798”
},
{
“mesh”:3,
“name”:“Object.799”
},
{
“mesh”:4,
“name”:“Object.800”
}
Using console.log I see the name of the intersected object does correspond:
Is it just a case of adding the url in here?
{
"mesh":4,
"name":"https://makerverde.com/"
}
Yes, that’s the idea.
Please note, that some characters are removed from names. For example Blender name "Object.800"
becomes "Object800"
in Three.js (note the missing “.”). Maybe the best would be to encode the special characters and then decode them.
Great, it picking it up here:
But I dont know how to implement the function:
|
|
|
|
if (intersects.length > 0) { |
|
|
|
|
|
|
|
|
|
console.log(‘Intersection:’, intersects[0]); |
|
|
|
|
|
|
|
|
|
} |
How do I call it from the if statement?
Before using userData
make sure that it is recorded in the GLTF, otherwise you cannot export GLTF with URLs (but you can use them internally as much as you want).
The value of intersects
is an array, and each element of the array is a structure. One of the properties of the structure is the Object3D
where the intersection is captured. You can see the structure properties in the Three.js documentation. If you use userData
, then use:
intersects[0].object.userData.name
From this intersects[0].object
, you can go up to all its parents and check their names too – this is needed if you have compound objects with subobjects and the URL is set only to the top-mot parent.
Ah yes, I see that the object property from object3d is missing the special characters.
It does seem to be working by using the userData though and I have now successfully created the link using:
window.open(intersects[0].object.userData.name);
Thanks so much for your help Pavel, its great to have made some progress at last!
Hi Pavel,
Is it possible to follow?:
window.open(intersects[0].object.userData.name);
with a request to ignore name values that dont have the file path I have input?
e.g.
{
“mesh”:0,
“name”:“…/…/…/…/15_ImagesForGallery/index/Cumulus-Plan/” // file path I want to window to open
},
{
“mesh”:1,
“name”:“Object.792” // can we ignore name values containing “Object”?
Sorry to open it back up again but navigating the model is quite tricky now as a click anywhere without a link opens a new window, with an error…
if(!intersects[0].object.userData.name.includes('Object')){
window.open(intersects[0].object.userData.name)
}
or as one line…
intersects[0].object.userData.name.includes('Object') ? '' : window.open(intersects[0].object.userData.name)
2 Likes
Option 1:
Find what distinguishes all valid paths from invalid names and use this to check whether to open a new page or not. For example, it could be “if the name contains object than it is invalid” – although, this looks too dangerous for me, as the word ‘object’ may appear in valid paths. Another rule could be “if it contains ‘/’ then it is a valid path”.
Option 2:
When loading a model remove all names (of all objects and subobjects), then add your paths. In this case if you find a name, it must be a path, otherwise the name will be empty string.
Option 3:
When you set a name to contain a path, add some special prefix. For examples, instead of writing ‘…/…/images/Cumulus-Plan/’ write ‘URL:…/…/images/Cumulus-Plan/’. When you read the name, if it start with ‘URL:’, then it is a path → remove the prefix and use the remaining path.
2 Likes
Thanks Lawrence,
This has sorted out the problem. Now to do some camera animations and I’m pretty much there with this one
Thanks Pavel,
I will definitely bare this in mind and thanks for the detailed explanation. For now, in this instance, the project doesnt contain that many file paths and I am in complete control of them anyway, so as far as I can tell, using Object to make a mesh invalid works for now.
Many thanks for all of your help!
1 Like