About custom function on an AutoRotationBehavior,I have a problem

I’ve to custom an AutoRotationBehavior, and here’s the code

Here is the PG playground

import {
  ArcRotateCamera,
  AutoRotationBehavior,
  PointerEventTypes,
  Scene,
  Animation,
  AbstractMesh,
  Mesh,
  ActionManager,
  ExecuteCodeAction,
  CubicEase,
  EasingFunction
} from '@babylonjs/core';

interface TEventListeners {
  idleTimeout: any[];
  resetComplete: any[];
}

const easingFunction = new CubicEase();
easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);

class CameraController {
  public camera: ArcRotateCamera;
  public scene: Scene;
  public autoRotationBehavior: AutoRotationBehavior;
  private lastInteractionTime: number = Date.now();
  private resetInterval: NodeJS.Timer | number | undefined = undefined;
  private eventListeners: TEventListeners = {
    idleTimeout: [],
    resetComplete: []
  };
  private setupTimer: NodeJS.Timer | number | undefined = undefined;
  private once: boolean = false;
  public OVER_TIME = 50 * 1000;
  private triggerEvents = new Set();
  /**
   *
   * @param camera 
   * @param scene 
   * @param mesh mesh
   * @param delay delay time
   * @param once Whether to execute the command only once
   */
  constructor(camera: ArcRotateCamera, scene: Scene, mesh: AbstractMesh | Mesh, delay?: number, once = false) {
    this.camera = camera;
    this.scene = scene;
    this.once = once;
    this.autoRotationBehavior = new AutoRotationBehavior();
    const isDelay = Boolean(delay);
  
    if (isDelay) {
      this.setupTimer = setTimeout(() => {
        //camera.useAutoRotationBehavior =true
        this.autoRotationBehavior.attach(this.camera);
      }, delay);
    } else {
      this.autoRotationBehavior.attach(this.camera);
    }

    this.autoRotationBehavior.idleRotationSpeed = -0.2;
    this.autoRotationBehavior.idleRotationWaitTime = 5 * 1000; 
    this.autoRotationBehavior.idleRotationSpinupTime = 5000; 
    // this.SetupListeners();

    this.SetMeshAction(mesh);
    this.StartResetCheck();
  }

  SetupListeners() {
  
    this.scene.onPointerObservable.add((pointerInfo) => {
      switch (pointerInfo.type) {
        case PointerEventTypes.POINTERDOWN:
          this.autoRotationBehavior.idleRotationSpeed = 0;
          this.lastInteractionTime = Date.now();
          break;

        case PointerEventTypes.POINTERUP:
          this.autoRotationBehavior.idleRotationSpeed = -0.1;
          this.lastInteractionTime = Date.now();
          break;
        case PointerEventTypes.POINTERMOVE:
          if (pointerInfo.event.button !== 0) {
            this.lastInteractionTime = Date.now();
          }
          break;
      }
    });
  }

  SetMeshAction(mesh: AbstractMesh | Mesh) {
    mesh.actionManager = new ActionManager(this.scene);
    mesh.actionManager.registerAction(
      new ExecuteCodeAction(
        {
          trigger: ActionManager.OnPickTrigger
        },
        () => {
          this.autoRotationBehavior.idleRotationSpeed = 0;
          this.lastInteractionTime = Date.now();
        }
      )
    );
    mesh.actionManager.registerAction(
      new ExecuteCodeAction(
        {
          trigger: ActionManager.OnPickUpTrigger
        },
        () => {
          this.autoRotationBehavior.idleRotationSpeed = 0.1;
          this.lastInteractionTime = Date.now();
        }
      )
    );
    this.scene.onPointerObservable.add((pointerInfo) => {
      if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
        if (pointerInfo.pickInfo?.hit && pointerInfo.pickInfo.pickedMesh === mesh) {
          this.lastInteractionTime = Date.now();
        }
      }
    });
  }

  StartResetCheck() {
    this.resetInterval = setInterval(() => {
      const currentTime = Date.now();
      const timeDifference = currentTime - this.lastInteractionTime;
      if (timeDifference > this.OVER_TIME) {
        if (!this.once || !this.triggerEvents.has('idleTimeout')) {
          this.TriggerEvent('idleTimeout');
          this.ResetCameraRotaion();
          this.lastInteractionTime = currentTime;
        }
      }
    }, 1000);
  }
  StopResetCheck() {
    if (this.resetInterval) {
      clearInterval(this.resetInterval as number);
    }
  }

  ResetCameraRotaion = (initAplha = 1.57, duration = 1000) => {
    let currentAlpha = this.camera.alpha;

    currentAlpha %= Math.PI * 2;
    if (currentAlpha < 0) {
      currentAlpha += Math.PI * 2;
    }

    let deltaAlpha = initAplha - currentAlpha;

    if (Math.abs(deltaAlpha) > Math.PI) {
      deltaAlpha = deltaAlpha > 0 ? deltaAlpha - Math.PI * 2 : deltaAlpha + Math.PI * 2;
    }

    const ani = Animation.CreateAndStartAnimation(
      'resetCameraRotaion',
      this.camera,
      'alpha',
      60,
      duration / (1000 / 60),
      this.camera.alpha,
      this.camera.alpha + deltaAlpha,
      Animation.ANIMATIONLOOPMODE_CONSTANT
    );
    ani!.onAnimationEnd = () => {
      if (!this.once || !this.triggerEvents.has('resetComplete')) {
        this.autoRotationBehavior.idleRotationSpeed = 0;
        this.TriggerEvent('resetComplete');
      }
    };
  };

  addEventListener(eventName: keyof TEventListeners, callback: () => void) {
    if (!this.eventListeners[eventName]) {
      this.eventListeners[eventName] = [];
    }
    this.eventListeners[eventName].push(callback);
  }

  removeEventListener(eventName: keyof TEventListeners, callback: () => void) {
    if (this.eventListeners[eventName]) {
      this.eventListeners[eventName] = this.eventListeners[eventName].filter((cb) => cb !== callback);
    }
  }

  TriggerEvent(eventName: keyof TEventListeners) {
    if (this.eventListeners[eventName]) {
      this.eventListeners[eventName].forEach((callback) => callback());
      this.triggerEvents.add(eventName);
    }
  }
  Dispose() {
    this.StopResetCheck();
    this.autoRotationBehavior.detach();
    this.eventListeners = null;
    this.setupTimer = undefined;
    this.resetInterval = undefined;
    this.triggerEvents.clear();
  }
}

export { CameraController };

I have done this:

  scene.getMeshByName('Earth_Clound').isPickable = false;
  scene.getMeshByName('Earth_Hightlight').isPickable = false;

The function I want to implement is:

  1. Use the autoRotationBehavior of ArcRotateCamera, but need to reset some of its properties, such as idleRotationSpeed and idleRotationWaitTime
  2. Monitor whether there is interaction with the mesh. If there is no interaction, then after OVER_TIME seconds, reset the camera position and send events so that I can do my own things.


The question is:
I don’t want to add listening events to the scene, I just want to add them to the mesh, but no matter what I do, the mesh can’t add OnPickTrigger or anything else, but I can easily add them to the scene, I think I’ve set the mesh that blocks the mesh isPickable=false,such as Earth_Clound,Earth_Hightlight, I’ve even set them all to xxx.isenabled (false)

I’m not very good at bjs, is there another better way? Or can you help me figure out what’s wrong? Also, can the resetLastInteractionTime attribute help me?

Hello !
(I came from your ping in the other topic :slight_smile: )

I would be happy to help, but your request is not 100% clear to me

  • autoRotationBehavior allows the camera to switch between auto rotating (user being idle) or manual rotation (user interacting with the canvas).
  • So by default there is no link between any mesh, and the camera autoRotationBehavior.
  • It’s not clear what you speak about when you write “the mesh” several times.

To me, a single click on the canvas (even before mousemove rotating camera) will anyway stop the camera auto rotation. So what is exactly the mesh picking you are speaking about ?

1 Like

Thank you very much!!

Did you go into my PG? the mesh refers to earth in pg. Or you could look at line 96.
I just want this function SetMeshAction to work @Tricotou

I woud do it this easy way:

Mouse over the sphere stops the rotation. Mouse out of the sphere starts it again.

2 Likes

Your approach is surprisingly simple, but it doesn’t seem to fit the needs of my project, thank you anyway. What’s wrong with my code

Your code is too complex for an example PG so it would quite hard to spot the problem even without knowing what exactly do you want to achieve.

Could you somehow simplify the PG? No fancy meshes, animations needed. Just create some primitive meshes (sphere, box) and describe your needs precisely. I’m sure we can find a solution just need to clarify your expectations. :nerd_face:

1 Like

I think I’ve found the problem
In fact, my question is very simple, maybe I described too complicated, too much code, so sorry!! I just want to know why the earth mesh in PG can not add OnPickTrigger

If the mesh does not have the isPickable attribute, then the actionManager cannot be added??


Would you like to test this PG?

https://doc.babylonjs.com/features/featuresDeepDive/events/actions#how-to-use-actions

1 Like

Yes, you are correct, today I have found the problem, also thank you, provided another way of thinking, I replaced the pg 32,33 lines like this:

         // const actionMesh = meshes[0]

         const actionMesh = scene.getMeshByName('Earth');
         const cameraController = new CameraController(camera, scene, actionMesh , 3000, true)

It worked well!!!

What puzzles me is why root doesn’t add ActionManager, what am I missing in the babylonjs document?

1 Like

The root class is in the form of a mesh, but in reality, you can think of it as a transformnode.

So, even if you assign the evnet of the actionmanager, the actionEvent for the transformnode does not actually work.
I’m sure I’ve seen this reason somewhere in the past, but it doesn’t come up when I search for it.

1 Like

Thank you, I have learned some new knowledge from you

1 Like