How do I make a object rotate 180 or 360 deegres smoothly in the Y angle?

I’m trying to make an application where, when the user hit the ground/screen, one object is projected in the position of the touch. The object appears from the ground and moves to the 0,0 (origin) point in the ground while going from 0 to 3 in the scale. It should also rotate 180 degrees when moving, but the rotation doesn’t work. Here is where I need help!!

The ground has a Tap Placer and when spawning the object I apply a ScaleAnimation, a PositionAnimation and a RotateAnimation. But only the ScaleAnimation and the PositionAnimation are working properly. But the RotateAnimation is not. Why?

All 3 functions should work the same, but in my case it is not happening. The object never rotates. I can see it moving, I can see it scaling, but the rotation never happens.

In my code you can see they all have loop set to false. When I set loop to true in the RotateAnimation, it works partially. The rotation keeps going forever and never stop.

It is rotating, but not smoothly, like it is not respecting the duration parameter.

What I’m missing??

Why the rotation is not happening in the same way that the scaling and the position ??
Here is my full Tap Place code

import * as ecs from '@8thwall/ecs'

const {THREE} = (window as any)

// Components used by the Tap Place spawner
const {BoxGeometry, Material, Position, Quaternion, Scale, Shadow, ScaleAnimation, RotateAnimation, PositionAnimation} = ecs
const {vec3, quat} = ecs.math

const rgb = (color: number) => ({
  r: (color >> 16) % 256,  // eslint-disable-line no-bitwise
  g: (color >> 8) % 256,   // eslint-disable-line no-bitwise
  b: color % 256,
})

const componentsForClone = [
  Position, Quaternion, Scale, Shadow, BoxGeometry, Material, ScaleAnimation,
  RotateAnimation, PositionAnimation, ecs.PositionAnimation, ecs.RotateAnimation, ecs.CustomPropertyAnimation, ecs.CustomVec3Animation,
  ecs.FollowAnimation, ecs.LookAtAnimation, ecs.GltfModel, ecs.Collider, ecs.ParticleEmitter,
  ecs.Ui, ecs.Audio,
]

const cloneComponents = (sourceEid, targetEid, world) => {
  componentsForClone.forEach((component) => {
    if (component.has(world, sourceEid)) {
      const properties = component.get(world, sourceEid)
      component.set(world, targetEid, {...properties})
    }
  })
}

const q = quat.zero()
const v = vec3.zero()
const touchPoint = vec3.zero()

ecs.registerComponent({
  name: 'Tap Place',
  schema: {
    entityToSpawn: ecs.eid,  // Entity ID for the entity to spawn
    groundEntity: ecs.eid,  // Entity ID for the ground object
    minScale: ecs.f32,  // Minimum scale for the spawned entity
    maxScale: ecs.f32,  // Maximum scale for the spawned entity
  },
  schemaDefaults: {
    minScale: 1.0,  // Default minimum scale is 1.0
    maxScale: 1.0,  // Default maximum scale is 3.0
  },
  add: (world, component) => {
    const {groundEntity, entityToSpawn, minScale, maxScale} = component.schema

    if (!groundEntity) {
      console.error('Ground entity not set in schema')
      return
    }

    const raycaster = new THREE.Raycaster()
    const mouse = new THREE.Vector2()
    let lastInteractionTime = 0

    function handleInteraction(event) {
      const currentTime = Date.now()
      if (currentTime - lastInteractionTime < 500) {
        return
      }
      lastInteractionTime = currentTime

      if (event.touches) {
        mouse.x = (event.touches[0].clientX / window.innerWidth) * 2 - 1
        mouse.y = -(event.touches[0].clientY / window.innerHeight) * 2 + 1
      } else {
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
      }

      raycaster.setFromCamera(mouse, world.three.activeCamera)

      const groundObject = world.three.entityToObject.get(groundEntity)

      if (!groundObject) {
        console.error('Ground object not found for entity ID:', groundEntity)
        return
      }

      const intersects = raycaster.intersectObject(groundObject, true)

      if (intersects.length > 0) {
        touchPoint.setFrom(intersects[0].point)

        const prompt = document.getElementById('promptText')
        if (prompt) {
          prompt.style.display = 'none'
        }

        const newEid = world.createEntity()

        const randomScale = Math.random() * (maxScale - minScale) + minScale

        if (entityToSpawn) {
          // Clone an entity at the target position, with an initial scale of zero.
          cloneComponents(entityToSpawn, newEid, world)
          Position.set(world, newEid, touchPoint)
          Scale.set(world, newEid, v.makeZero())
        } else {
          // If no clone entity is indicated, spawn a box instead.
          Position.set(world, newEid, v.setXyz(touchPoint.x, randomScale / 2, touchPoint.z))
          Quaternion.set(world, newEid, q.makeYDegrees(Math.random() * 360))
          Scale.set(world, newEid, v.makeZero())
          Shadow.set(world, newEid, {castShadow: true, receiveShadow: false})
          BoxGeometry.set(world, newEid, {width: 1, height: 1, depth: 1})
          Material.set(world, newEid, rgb(0xAE00FF))
        }

        ScaleAnimation.set(world, newEid, {
          fromX: 0,
          fromY: 0,
          fromZ: 0,
          toX: 5,
          toY: 5,
          toZ: 5,
          duration: 500,
          loop: false,
          easeIn: true,
          easeOut: true,
        })

        PositionAnimation.set(world, newEid, {
          fromX: touchPoint.x,
          fromY: touchPoint.y,
          fromZ: touchPoint.z,
          toX: 0,
          toY: 0,
          toZ: 0,
          duration: 5000,
          loop: false,
          easeIn: true,
          easeOut: true,
        })

        RotateAnimation.set(world, newEid, {
          fromX: 0,
          fromY: 0,
          fromZ: 0,
          toX: 0,
          toY: 180,
          toZ: 0,
          duration: 1000,
          loop: false,
          easeIn: true,
          easeOut: true,
        })

      }
    }

    window.addEventListener('click', handleInteraction)
    window.addEventListener('touchstart', handleInteraction)
  },
})

Is it here the right place to ask questions? Is there a Discord channel, a place where the developers are really active?

It seems the forum is not really that active. Now I have a question about particle systems and how can I ask if my question of a week ago didn’t have an interaction yet. I would love to connect with the community to start creating on 8th Wall. But I can’t find other developers to interact with.

Can someone send me the link for the community channel please? A channel where the community is active.

Hi Gabriel,

I’d look into a JS tween library for smooth animations.

As for particle systems, feel free to ask your question and I’ll happily take a look.

Thanks in advance. I seem to be having issues when the rotation register strange numbers after the object completes a full 360 degree rotation.
I mean when the object has 0 on the y-axis and I rotate it 360 degrees. The new value on the y-axis instead of 360 is another value that I can’t figure out how to work with. So when it’s supposed to rotate 360 ​​degrees a second time, it never happens. Because of this new (now) incompatible value.

I have created another topic related to the issue that I have with particles and AFRAME, if you could give a look, I would really appreciate it.

You could be experiencing Gimbal Lock, I would clamp the rotational values between 0 and 359, and if they go over or under the values loop back to their nearest min or max.