awawawaw
awawawaw

Reputation: 195

There are five elements in my Three.js Group children but I can't access the meshes. How to access the meshes in a three.js group?

enter image description here

I'm trying to access elements in this array, but for some reason it's saying "undefined" and giving back a length of 0, but there's clearly a length of 5. The picture above is the result of this code.

console.log(scene.children[1].children);

The result of this code below is 0

console.log(scene.children[1].children.length);

The result of this code below is undefined

console.log(scene.children[1].children[0]);

Here's the previous paths if it helps with the context:

console.log(scene);

enter image description here

console.log(scene.children[1]);

enter image description here

Per comments: here is full code:

import "./styles.css";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import gsap from "gsap";

import blueHouseBaked from "./static/Bluehouse.png";
import blueHouse from "./static/Bluehouse.glb";
import greenHouseBaked from "./static/Greenhouse.png";
import greenHouse from "./static/Greenhouse.glb";
import redHouseBaked from "./static/Redhouse.png";
import redHouse from "./static/Redhouse.glb";
import purpleHouseBaked from "./static/Purplehouse.png";
import purpleHouse from "./static/Purplehouse.glb";
import groundBaked from "./static/Ground.png";
import ground from "./static/Ground.glb";

import "regenerator-runtime/runtime";

const canvas = document.querySelector("canvas.experience");

// Scene
const scene = new THREE.Scene();

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight,
};

window.addEventListener("resize", () => {
    // Update sizes
    sizes.width = window.innerWidth;
    sizes.height = window.innerHeight;

    // Update camera
    camera.aspect = sizes.width / sizes.height;
    camera.updateProjectionMatrix();

    // Update renderer
    renderer.setSize(sizes.width, sizes.height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(
    75,
    sizes.width / sizes.height,
    0.1,
    100
);
camera.position.x = 0;
camera.position.y = 17;
camera.position.z = 22;
camera.rotation.x = -Math.PI / 8;

scene.add(camera);

//Controls -------------------
//Three.js vector lerping
let cameraTargetPosition = new THREE.Vector3(0, 17, 22);

window.addEventListener("mousedown", () => {
    window.addEventListener("mousemove", updateCamera);
});

window.addEventListener("mouseup", () => {
    window.removeEventListener("mousemove", updateCamera);
});

// Controls
function updateCamera(ev) {
    ev.preventDefault();
    if (ev.movementX > 0) {
        cameraTargetPosition.x -= ev.movementX * 0.015;
    } else {
        cameraTargetPosition.x -= ev.movementX * 0.015;
    }
    if (ev.movementY > 0) {
        cameraTargetPosition.z -= ev.movementY * 0.015;
    } else {
        cameraTargetPosition.z -= ev.movementY * 0.015;
    }
}

/**
 * Everything load
 */
let group = new THREE.Group();
async function loadModels(model, texture) {
    const gltfLoader = new GLTFLoader();
    const textureLoader = new THREE.TextureLoader();

    const mesh = await gltfLoader.loadAsync(model);

    const bakedTexture = textureLoader.load(texture);
    const bakedMaterial = new THREE.MeshBasicMaterial({
        map: bakedTexture,
    });

    bakedTexture.flipY = false;
    bakedTexture.encoding = THREE.sRGBEncoding;
    mesh.scene.children[0].material = bakedMaterial;
    group.add(mesh.scene.children[0]);
}

loadModels(blueHouse, blueHouseBaked);
loadModels(greenHouse, greenHouseBaked);
loadModels(redHouse, redHouseBaked);
loadModels(purpleHouse, purpleHouseBaked);
loadModels(ground, groundBaked);

scene.add(group);

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: true,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

/**
 * Animate
 */
const clock = new THREE.Clock();
let lastElapsedTime = 0;

// Ray Casting stuff
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let currentIntersect = null;

window.addEventListener("mousemove", (event) => {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});

console.log(scene.children[1].children[0]);


const tick = () => {
    camera.position.lerp(cameraTargetPosition, 0.1);
    const elapsedTime = clock.getElapsedTime();
    const deltaTime = elapsedTime - lastElapsedTime;
    lastElapsedTime = elapsedTime;

    // Raycaster stuff
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects([scene.children[1].children]);
    if (intersects.length) {
        if (!currentIntersect) {
            console.log("mouse enter");
        }
        currentIntersect = intersects[0];
    } else {
        if (currentIntersect) {
            console.log("mouse leave");
        }
        currentIntersect = null;
    }
    // Render
    renderer.render(scene, camera);

    // Call tick again on the next frame
    window.requestAnimationFrame(tick);
};

tick();

Upvotes: 1

Views: 1227

Answers (2)

Sanil Khurana
Sanil Khurana

Reputation: 1169

The problem you are facing is due to asynchronous code. The loadModels function loads model asynchronously which means that the rest of the code finishes first before that asynchronous method is finished for all of its calls.

Here is a great video of the event loop and how it works to better understand why this happens - https://www.youtube.com/watch?v=8aGhZQkoFbQ.

Loading GLTF models can also take a bit of time depending on their size so it may be a few ms before they are available in the group.

You can try logging on every tick, so changing you tick method to -

const tick = () => {
    camera.position.lerp(cameraTargetPosition, 0.1);
    const elapsedTime = clock.getElapsedTime();
    const deltaTime = elapsedTime - lastElapsedTime;
    lastElapsedTime = elapsedTime;

    // Raycaster stuff
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects([scene.children[1].children]);
    if (intersects.length) {
        if (!currentIntersect) {
            console.log("mouse enter");
        }
        currentIntersect = intersects[0];
    } else {
        if (currentIntersect) {
            console.log("mouse leave");
        }
        currentIntersect = null;
    }
    // Render
    renderer.render(scene, camera);

    console.log(scene.children[0].children[1])

    // Call tick again on the next frame
    window.requestAnimationFrame(tick);
};

However, when we worked with threejs, it was much easier to have the scene assigned to the global window for easy access in chrome.

Simply add the following lines in your code after creating the scene

window.scene = scene

import "./styles.css";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import gsap from "gsap";

import blueHouseBaked from "./static/Bluehouse.png";
import blueHouse from "./static/Bluehouse.glb";
import greenHouseBaked from "./static/Greenhouse.png";
import greenHouse from "./static/Greenhouse.glb";
import redHouseBaked from "./static/Redhouse.png";
import redHouse from "./static/Redhouse.glb";
import purpleHouseBaked from "./static/Purplehouse.png";
import purpleHouse from "./static/Purplehouse.glb";
import groundBaked from "./static/Ground.png";
import ground from "./static/Ground.glb";

import "regenerator-runtime/runtime";

const canvas = document.querySelector("canvas.experience");

// Scene
const scene = new THREE.Scene();

window.scene = scene

.......

Then you can access the group from the chrome console once the GLTF models are loaded since scene is now accessible from the devtools. You can just write scene there and you would be able to access it, or you can write scene.children[0].children[1] and you would see your group.

Edit: Meant this just for debugging.

Upvotes: 2

awawawaw
awawawaw

Reputation: 195

So I found a solution, but I still don't know why the fundamental problem still exists. If I put the console.log(scene.children[1].children[0]); INSIDE of the tick It outputs this:

undefined
mesh
mesh
mesh

So the first loop through is undefined. Therefore the solution is just to check if it's undefined and now I get no errors and everything works:

// Raycaster stuff
raycaster.setFromCamera(mouse, camera);
if (
    typeof scene.children[1].children[0] !== "undefined" &&
    typeof scene.children[1].children[1] !== "undefined" &&
    typeof scene.children[1].children[2] !== "undefined" &&
    typeof scene.children[1].children[3] !== "undefined"
) {
    const intersects = raycaster.intersectObjects([
        scene.children[1].children[0],
        scene.children[1].children[1],
        scene.children[1].children[2],
        scene.children[1].children[3],
    ]);
    if (intersects.length) {
        if (!currentIntersect) {
            console.log("mouse enter");
        }
        currentIntersect = intersects[0];
    } else {
        if (currentIntersect) {
            console.log("mouse leave");
        }
        currentIntersect = null;
    }
}

But again, I do NOT understand why it's undefined in the first few loops.

Upvotes: 0

Related Questions