xeokit / xeokit-sdk

Open source JavaScript SDK for viewing high-detail, full-precision 3D BIM and AEC models in the Web browser.
https://xeokit.io
Other
715 stars 286 forks source link

[SUPPORT] Making consecutive scene.pick calls results in some unpredictible behavior #1314

Closed hamza-hajji closed 5 months ago

hamza-hajji commented 8 months ago

Describe the bug Making consecutive calls to scene.pick leads to some unpredictible results To Reproduce Steps to reproduce the behavior:

  1. Make a scene.pick call with canvasPos
  2. Make another pick using origin/direction

I added the following code to the example xkt_vbo_Duplex.html

let trigger = false;

viewer.scene.input.on("mousedown", (coords) => {
  const hit = viewer.scene.pick({
    canvasPos: coords,
    pickSurface: true,
  });

  // trigger to mousemove to start dragging  
  if (hit) {
    trigger = true;
    viewer.cameraControl.active = false;
  }
});

viewer.scene.input.on("mouseup", () => {
  // trigger to mousemove to stop dragging
  trigger = false;
  viewer.cameraControl.active = true;
});

viewer.scene.input.on("mousemove", (coords) => {
  // first hit to determine point A
  const hit = viewer.scene.pick({
    canvasPos: coords,
    pickSurface: true,
  });

  if (!hit || !trigger) return;

  // second I use the first hit's normal as direction and worl position as origin
  // this is supposed to pick the point facing the first point A  
  const otherHit = viewer.scene.pick({
    origin: hit.worldPos,
    direction: hit.worldNormal,
    pickSurface: true,
  });

  // annotations to make the hit points clear
  annotations.createAnnotation({
    id: "myAnnotation" + Object.keys(annotations.annotations).length,
    worldPos: hit.worldPos,
    markerShown: true,
    labelShown: true,
    values: {
      glyph: "A",
      title: "My Annotation",
      description: "This is my annotation.",
    },
  });

  if (otherHit) {
    annotations.createAnnotation({
      id: "otherAnnotation" + Object.keys(annotations.annotations).length,
      worldPos: otherHit.worldPos,
      markerShown: true,
      labelShown: true,
      values: {
        glyph: "B",
        title: "My Annotation",
        description: "This is my annotation.",
      },
    });
  }
});

I made a recording of the behavior when I drag my mouse

Expected behavior I see an annotation A then another annotation B in the opposite direction.

Screenshots

First test: when nothing is facing the first hit point (A), point A is affected making its hit.worldPos null

Second test: when there is a facing wall to the first hit point (A), the first point is wrong but the second is correct (on the roof)

https://imgur.com/a/xfuhefa

Additional context I'm trying to implement moving models (Sphere in the sketch) while dragging my mouse so I pick first with canvasPos to determine where my mouse is and move the model using model.position = hit.worldPos which moves its center. Then I pick using origin/direction in order to calculate the distance between the center and surface of the model in order to snap it to nearest surface.

demo

xeolabs commented 5 months ago

I think this is because the way you're doing Scene.pick() you're reusing the same singleton PickResult that the method always uses, when you don't provide one yourself.

What was happening was that the worldPos on the singleton PickResult was getting modified on each pick. By instantiating a fresh PickResult each time, that won't happen.

Try updating to the latest release and, passing in your own PickResult instance to each pick call, as shown below.

I just exposed PickResult as a class in the latest release, to enable the usage shown below: https://github.com/xeokit/xeokit-sdk/releases/tag/v2.6.0-beta-16

let trigger = false;

    viewer.scene.input.on("mousedown", (coords) => {
        const hit = viewer.scene.pick({
            canvasPos: coords,
            pickSurface: true,
        });

        // trigger to mousemove to start dragging
        if (hit) {
            trigger = true;
            viewer.cameraControl.active = false;
        }
    });

    viewer.scene.input.on("mouseup", () => {
        // trigger to mousemove to stop dragging
        trigger = false;
        viewer.cameraControl.active = true;
    });

    viewer.scene.input.on("mousemove", (coords) => {
        // first hit to determine point A
        const hit = viewer.scene.pick({
            canvasPos: coords,
            pickSurface: true,
        }, new PickResult());

        if (!hit || !trigger || !hit.worldPos) return;

        // second I use the first hit's normal as direction and worl position as origin
        // this is supposed to pick the point facing the first point A
        const otherHit = viewer.scene.pick({
            origin: hit.worldPos,
            direction: hit.worldNormal,
            pickSurface: true,
        }, new PickResult());

        // annotations to make the hit points clear
        annotations.createAnnotation({
            id: "myAnnotation" + Object.keys(annotations.annotations).length,
            worldPos: hit.worldPos,
            markerShown: true,
            labelShown: true,
            values: {
                glyph: "A",
                title: "My Annotation",
                description: "This is my annotation.",
            },
        });

        if (otherHit) {
            annotations.createAnnotation({
                id: "otherAnnotation" + Object.keys(annotations.annotations).length,
                worldPos: otherHit.worldPos,
                markerShown: true,
                labelShown: true,
                values: {
                    glyph: "B",
                    title: "My Annotation",
                    description: "This is my annotation.",
                },
            });
        }
    });