Knightro63 / three_js

A dart conversion of three.js.
MIT License
21 stars 13 forks source link

Raycasting not working on glb model #24

Closed AlexanderMonneret closed 1 week ago

AlexanderMonneret commented 1 week ago

hey guys, I'm really lost here, I'm trying to use raycasting to color the model when touched with mouse, the model is working fine with Three.js on java script, now I'm trying to port it to Flutter but it's not working, with the code below I'm able to load the model correctly but Raycasting is not working, Even when I manually add InstansedMesh to the model tree, nothing works :/ I've been working on it for 2 days now x,( plz help

import 'dart:async';
import 'dart:math' as math;
import 'package:design_and_3d/statistics.dart';
import 'package:flutter/material.dart';
import 'package:three_js/three_js.dart' as three;

class WebglInstancingRaycasto extends StatefulWidget {
  const WebglInstancingRaycasto({super.key});

  @override
  createState() => _State();
}

class _State extends State<WebglInstancingRaycasto> {
  List<int> data = List.filled(60, 0, growable: true);
  late Timer timer;
  late three.ThreeJS threeJs;
  final mouse = three.Vector2(1, 1);
  final color = three.Color.fromHex32(0xffffff);
  final white = three.Color.fromHex32(0xffffff);
  bool modelLoaded = false;
  late three.Raycaster raycaster;
  three.InstancedMesh? mesh; // Change to nullable

  @override
  void initState() {
    super.initState();
    threeJs = three.ThreeJS(
      onSetupComplete: () {
        setState(() {
          threeJs.domElement
              .addEventListener(three.PeripheralType.pointerHover, onMouseMove);
          threeJs.addAnimationEvent((dt) {
            animate();
            controls.update();
          });
        });
      },
      setup: setup,
      settings: three.Settings(clearAlpha: 0, clearColor: 0xffffff),
    );
    timer = Timer.periodic(const Duration(seconds: 1), (t) {
      setState(() {
        data.removeAt(0);
        data.add(threeJs.clock.fps);
      });
    });
  }

  @override
  void dispose() {
    controls.dispose();
    timer.cancel();
    threeJs.dispose();
    three.loading.clear();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Stack(
      children: [threeJs.build(), Statistics(data: data)],
    ));
  }

  late three.OrbitControls controls;
  late three.AnimationMixer mixer;

  Future<void> setup() async {
    threeJs.camera =
        three.PerspectiveCamera(45, threeJs.width / threeJs.height, 1, 2200);
    threeJs.camera.position.setValues(3, 6, 10);
    controls = three.OrbitControls(threeJs.camera, threeJs.globalKey);
    threeJs.scene = three.Scene();
    threeJs.scene.background = three.Color.fromHex32(0xffffff);

    // Initialize raycaster
    raycaster = three.Raycaster();

    final ambientLight = three.AmbientLight(0xffffff, 0.9);
    threeJs.scene.add(ambientLight);

    final pointLight = three.PointLight(0xffffff, 0.8);
    pointLight.position.setValues(0, 0, 0);
    threeJs.camera.add(pointLight);
    threeJs.scene.add(threeJs.camera);

    threeJs.camera.lookAt(threeJs.scene.position);

    three.GLTFLoader loader = three.GLTFLoader().setPath('assets/bracelet/');
    three.GLTFData? result = await loader.fromAsset('ring_glb3.glb');

    // Find and store the InstancedMesh from the loaded model
    result!.scene.traverse((object) {
      if (object is three.InstancedMesh) {
        mesh = object;
        modelLoaded = true; // Only set to true when mesh is found
      }
    });

    threeJs.scene.add(result.scene);
    setState(() {}); // Update UI after mesh is set
  }

  void onMouseMove(event) {
    event.preventDefault();
    mouse.x = (event.clientX / threeJs.width) * 2 - 1;
    mouse.y = -(event.clientY / threeJs.height) * 2 + 1;
  }

  void animate() {
    if (!modelLoaded || mesh == null) return;

    controls.update();
    raycaster.setFromCamera(mouse, threeJs.camera);
    debugPrint("raycaster: $raycaster");
    final intersection = raycaster.intersectObject(mesh!, false);

    if (intersection.isNotEmpty) {
      final instanceId = intersection[0].instanceId;
      mesh!.getColorAt(instanceId!, color);

      mesh!.setColorAt(instanceId,
          color..setFromHex32((math.Random().nextDouble() * 0xffffff).toInt()));
      mesh!.instanceColor?.needsUpdate = true;
    }
  }
}
AlexanderMonneret commented 1 week ago

I found the solution! the problem was that my model was object of type Mesh and the animate function was setup to deal with time instancedMesh

so I modified the function to be compatible with mesh

Knightro63 commented 1 week ago

Hi @AlexanderMonneret,

I am glad you found a solution. Would we be able to close the issue?