Непонятно, что вы пытаетесь сделать. GLTF-файлы - это набор материалов, анимаций, геометрий, сеток и т. Д. c .. поэтому вы не можете «выбрать» GLTF-файл. Вы можете «выбрать» отдельные элементы внутри. Вы могли бы написать некоторый код, который, если что-то будет выбрано, проверяет, что выбранное является одной из сеток, загруженных в сцену GLTF, а затем выбирает любую другую вещь, которая была загружена в сцене GLTF.
В любом case,
Вам необходимо предоставить RayCaster
список объектов для выбора. В исходном примере это был scene.children
, который является просто списком ящиков, добавленных к root сцены. Но при загрузке GLTF, если вы уже не знаете структуру GLTF, потому что вы сами создали сцену, вам нужно go найти вещи, которые вы хотите выбрать, и добавить их в некоторый список, который вы можете передать RayCaster.intersectObjects
Этот код получает все Mesh
объекты из загруженного файла GLTF
let pickableMeshes = [];
// this is run after loading the gLTT
// get a list of all the meshes in the scene
root.traverse((node) => {
if (node instanceof THREE.Mesh) {
pickableMeshes.push(node);
}
});
Обратите внимание, что вы также можете передать true
в качестве второго аргумента RayCaster.intersectObjects
как в rayCaster.intersectObjects(scene.children, true)
. Это, вероятно, редко, что вы хотите, хотя, скорее всего, у вас есть что-то на сцене, вы не хотите, чтобы пользователь мог выбрать. Например, если вы только хотели, чтобы пользователь мог выбирать автомобили, то что-то вроде
// get a list of all the meshes in the scene who's names start with "car"
root.traverse((node) => {
if (node instanceof THREE.Mesh && (/^car/i).test(node.name)) {
pickableMeshes.push(node);
}
});
Затем класс PickHelper, который вы использовали, менял цвет материала на каждой коробке, но это работало только потому, что каждая коробка имеет свой материал. Если ящики разделяют материалы, то изменение цвета материала изменит все ящики.
При загрузке другого GLTF большинство объектов разделяют один и тот же материал, поэтому для возможности его выделения требуется изменить материал, используемый с этим объектом, или выбрать какой-то другой способ выделить выделенную вещь.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 60;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 200;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 30;
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
// put the camera on a pole (parent it to an object)
// so we can spin the pole to move the camera around the scene
const cameraPole = new THREE.Object3D();
scene.add(cameraPole);
cameraPole.add(camera);
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
camera.add(light);
}
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
const halfFovY = THREE.Math.degToRad(camera.fov * .5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
// compute a unit vector that points in the direction the camera is now
// in the xz plane from the center of the box
const direction = (new THREE.Vector3())
.subVectors(camera.position, boxCenter)
.multiply(new THREE.Vector3(1, 0, 1))
.normalize();
// move the camera to a position distance units way from the center
// in whatever direction the camera was from the center already
camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
// pick some near and far values for the frustum that
// will contain the box.
camera.near = boxSize / 100;
camera.far = boxSize * 100;
camera.updateProjectionMatrix();
// point the camera to look at the center of the box
camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}
let pickableMeshes = [];
{
const gltfLoader = new THREE.GLTFLoader();
gltfLoader.load('https://threejsfundamentals.org/threejs/resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
const root = gltf.scene;
scene.add(root);
// compute the box that contains all the stuff
// from root and below
const box = new THREE.Box3().setFromObject(root);
const boxSize = box.getSize(new THREE.Vector3()).length();
const boxCenter = box.getCenter(new THREE.Vector3());
// set the camera to frame the box
frameArea(boxSize * 0.7, boxSize, boxCenter, camera);
// get a list of all the meshes in the scen
root.traverse((node) => {
if (node instanceof THREE.Mesh) {
pickableMeshes.push(node);
}
});
});
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
class PickHelper {
constructor() {
this.raycaster = new THREE.Raycaster();
this.pickedObject = null;
this.pickedObjectSavedMaterial = null;
this.selectMaterial = new THREE.MeshBasicMaterial();
this.infoElem = document.querySelector('#info');
}
pick(normalizedPosition, scene, camera, time) {
// restore the color if there is a picked object
if (this.pickedObject) {
this.pickedObject.material = this.pickedObjectSavedMaterial;
this.pickedObject = undefined;
this.infoElem.textContent = '';
}
// cast a ray through the frustum
this.raycaster.setFromCamera(normalizedPosition, camera);
// get the list of objects the ray intersected
const intersectedObjects = this.raycaster.intersectObjects(pickableMeshes);
if (intersectedObjects.length) {
// pick the first object. It's the closest one
this.pickedObject = intersectedObjects[0].object;
// save its color
this.pickedObjectSavedMaterial = this.pickedObject.material;
this.pickedObject.material = this.selectMaterial;
// flash select material color to flashing red/yellow
this.selectMaterial.color.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
this.infoElem.textContent = this.pickedObject.name;
}
}
}
const pickPosition = {x: 0, y: 0};
const pickHelper = new PickHelper();
clearPickPosition();
function render(time) {
time *= 0.001; // convert to seconds;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
cameraPole.rotation.y = time * .1;
pickHelper.pick(pickPosition, scene, camera, time);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function getCanvasRelativePosition(event) {
const rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
}
function setPickPosition(event) {
const pos = getCanvasRelativePosition(event);
pickPosition.x = (pos.x / canvas.clientWidth ) * 2 - 1;
pickPosition.y = (pos.y / canvas.clientHeight) * -2 + 1; // note we flip Y
}
function clearPickPosition() {
// unlike the mouse which always has a position
// if the user stops touching the screen we want
// to stop picking. For now we just pick a value
// unlikely to pick something
pickPosition.x = -100000;
pickPosition.y = -100000;
}
window.addEventListener('mousemove', setPickPosition);
window.addEventListener('mouseout', clearPickPosition);
window.addEventListener('mouseleave', clearPickPosition);
window.addEventListener('touchstart', (event) => {
// prevent the window from scrolling
event.preventDefault();
setPickPosition(event.touches[0]);
}, {passive: false});
window.addEventListener('touchmove', (event) => {
setPickPosition(event.touches[0]);
});
window.addEventListener('touchend', clearPickPosition);
}
main();
body { margin: 0; }
#c { width: 100vw; height: 100vh; display: block; }
#info { position: absolute; left: 0; top: 0; background: black; color: white; padding: 0.5em; font-family: monospace; }
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/examples/js/loaders/GLTFLoader.js"></script>
<canvas id="c"></canvas>
<div id="info"></div>