Trying to 'reset' a models position is erratic

I have no idea what I’m doing, but a pressing need to do it. This is for an interactive aimed at people with additional needs - and so touchscreen was removed and on-screen html buttons used instead. These work perfectly to pan, rotate and zoom - with a reset button that returned to the original view. Now I’ve been asked to allow touchscreen. I have it all working, except the reset view is weird - and the model drifts off-screen first before snapping back to the initial view.

Edit It just one model - with pan, rotate, zoom. no other elements, nothing else in the scene etc. **

I suspect an issue with updating orbitcontrols - but I’ve tried many things to no avail. Using buttons still works perfectly, including reset - but if I use the touchscreen/mouse for anything, the reset does the ‘model drifts off (as if swiped - if I rotate left then reset it drifts right, rotate right it drifts left, rot up drift down etc.) and then snaps back’ thing. I’ve never used three.js and am not a coder - this is all held together with hope and sellotape.
Models come in various sizes, so the posIncrement and odd calculations are to adjust pan and position to account for this. chatGPT helped a lot, but I also find it makes as many mistakes as solutions. Any assistance much appreciated.

import * as THREE from './three.module.js';
import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
import { RoomEnvironment } from './jsm/environments/RoomEnvironment.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js';

const canvas = document.getElementById('canvas-container');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });

// Create and customize the controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0); // Set the target to the center of the scene
controls.enableDamping = true; // Enable smooth camera movement
controls.dampingFactor = 0.05; // Adjust damping factor for smoothness
controls.enableZoom = true; // Enable zooming
controls.enableRotate = true; // Enable rotating
controls.enablePan = true; // Enable panning

// set initial 3d World size and params
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio( window.devicePixelRatio );
// renderer.setClearColor(0x80a8ab); // if you want single color backgrounds
renderer.outputEncoding = THREE.sRGBEncoding;

canvas.appendChild(renderer.domElement);

const pmremGenerator = new THREE.PMREMGenerator( renderer );
scene.environment = pmremGenerator.fromScene( new RoomEnvironment()).texture;

// define vars of global scope
let storedLookAtPosition = new THREE.Vector3();
let isMoving = false
let boundingBox, modelSize, posIncrement, cameraDistance

// Use GLTFLoader for .glb or .gltf files
// Function to load the 3D model asynchronously
const loadModel = async () => {
  return new Promise((resolve, reject) => {
    const modelPath = sessionStorage.getItem('modelName') || "./Galley_Cannons.glb";
    const loader = new GLTFLoader();

      loader.load(modelPath, (gltf) => {
      const model = gltf.scene;

      // Determine the size of the loaded model
      boundingBox = new THREE.Box3().setFromObject(model);
      modelSize = boundingBox.getSize(new THREE.Vector3()).length();
      // Calculate the camera distance based on the model size and FOV
      cameraDistance = (modelSize * 0.4) / Math.tan((camera.fov / 2) * (Math.PI / 180));
      //calc a rough increment for position/pan based on model size
      posIncrement = ( 0.1 + (modelSize - 6) * (2 - 0.1) / (100 - 6) );
     
      scene.add(model); // no longer needed if we use box/pivot to recentre
      resolve(model); // Resolve the Promise when the model is loaded
      }, undefined, (error) => {
      reject(error); // Reject the Promise if there is an error loading the model
      });
  });
};

// Animation loop function
function animate() {
  requestAnimationFrame(animate);
  TWEEN.update(); // Update the Tween.js animations
  renderer.render(scene, camera);
}

// Function to be executed after the model is loaded
const loadingClear = (model) => {

  // Event listeners for arrow controls ROTATE
const rarrows = document.querySelectorAll('.rarrow');
rarrows.forEach((rarrow) => {
rarrow.addEventListener('click', handleRotate);
});
// Event listeners for arrow controls PAN
const parrows = document.querySelectorAll('.parrow');
parrows.forEach((parrow) => {
  parrow.addEventListener('click', handlePan);
});
// Event listeners for zoom in and out buttons
const zoomInButton = document.getElementById('zoom-in');
const zoomOutButton = document.getElementById('zoom-out');
zoomInButton.addEventListener('click', handleZoom);
zoomOutButton.addEventListener('click', handleZoom);
// reset view button
const resetter = document.getElementById('resetView');
resetter.addEventListener('click', resetView);

  // Initialize the camera position and model rotation
  camera.position.set(0, 0, cameraDistance);
  model.position.set(0, posIncrement * -10, 0);
  model.rotation.set(0, 1.5, 0);
  console.log(camera.position);

  // Render the scene once after controls are set up
  renderer.render(scene, camera);

// Function to handle rotation using arrows
function handleRotate(event) {
  if (isMoving) { return; }
  isMoving = true;

model.rotation.order = 'XYZ';
const rotateSpeed = 0.55; // Adjust the rotation speed as needed
// Create an object to hold the rotation values
const targetRotation = {
  x: model.rotation.x,
  y: model.rotation.y,
  z: model.rotation.z,
};
  switch (event.target.id) {
    case 'r-arrow-up':
      targetRotation.x -= rotateSpeed;
      break;
    case 'r-arrow-left':
      targetRotation.y -= rotateSpeed;
      break;
    case 'r-arrow-right':
      targetRotation.y += rotateSpeed;
      break;
    case 'r-arrow-down':
      targetRotation.x += rotateSpeed;
      break;
    default:
      break;
  }

  new TWEEN.Tween(model.rotation)
  .to(targetRotation, 600)
  .easing(TWEEN.Easing.Quadratic.InOut)
  .onComplete(() => {
    isMoving = false; // Reset the flag when panning is complete
  })
  .start();
  controls.update();
  renderer.render(scene, camera);
}

// Function to handle pan using arrows
function handlePan(event) {
  const panSpeed = posIncrement * 3; // Adjust the panning speed as needed
  const targetPosition = model.position.clone();

  switch (event.target.id) {
    case 'p-arrow-up':
      targetPosition.y += panSpeed;
      // Limit panning up to modelSize / 2
      targetPosition.y = Math.min(targetPosition.y, (modelSize / 3));
      break;
    case 'p-arrow-left':
      targetPosition.x -= panSpeed;
      // Limit panning left to -modelSize
      targetPosition.x = Math.max(targetPosition.x, (modelSize * -1));
      break;
    case 'p-arrow-right':
      targetPosition.x += panSpeed;
      // Limit panning right to modelSize
      targetPosition.x = Math.min(targetPosition.x, modelSize);
      break;
    case 'p-arrow-down':
      targetPosition.y -= panSpeed;
      // Limit panning down to -modelSize
      targetPosition.y = Math.max(targetPosition.y, (modelSize * -.9));
      break;
    default:
      break;
  }
      
      // Create a Tween for smooth panning
      new TWEEN.Tween(model.position)
      .to(targetPosition, 300) // Adjust the duration (in milliseconds) as needed
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onComplete(() => {
        isMoving = false; // Reset the flag when panning is complete
      })
      .start();
      controls.update();
      renderer.render(scene, camera);
}

// Function to handle zoom in and out
function handleZoom(event) {
  if (isMoving) { return; }
  isMoving = true;
  const zoomSpeed = 0.3; // Adjust the zoom speed as needed
  const zoomFactor = event.target.id === 'zoom-in' ? 1 - zoomSpeed : 1 + zoomSpeed;
  // Calculate the zoom direction vector
  const zoomDirection = camera.position.clone().sub(storedLookAtPosition).normalize();
  // Calculate the zoom amount based on the zoom factor
  const zoomAmount = camera.position.distanceTo(storedLookAtPosition) * (zoomFactor - 1);

  // Calculate the new camera position
  const newCameraPosition = camera.position.clone().add(zoomDirection.multiplyScalar(zoomAmount));
  // Animate the camera to the new position
  new TWEEN.Tween(camera.position)
    .to(newCameraPosition, 600)
    .easing(TWEEN.Easing.Quadratic.InOut)
    .onUpdate(() => {
      // Use camera.lookAt to set the look-at target
      camera.lookAt(storedLookAtPosition);
    })
    .onComplete(() => {
      isMoving = false; // Reset the flag when panning is complete
    })
    .start();
    controls.update();
    renderer.render(scene, camera);
}

function resetView() {
  if (isMoving) { return; }
  isMoving = true;

  const cameraTween = new TWEEN.Tween(camera.position)
    .to({ x: 0, y: 0, z: cameraDistance }, 600)
    .easing(TWEEN.Easing.Quadratic.InOut);

  const rotationTween = new TWEEN.Tween(model.rotation)
    .to({ x: 0, y: 1.5, z: 0 }, 600)
    .easing(TWEEN.Easing.Quadratic.InOut);

  const positionTween = new TWEEN.Tween(model.position)
    .to({ x: 0, y: posIncrement * -10, z: 0 }, 650)
    .easing(TWEEN.Easing.Quadratic.InOut)
    .onComplete(() => {
      isMoving = false;
      controls.update();
      renderer.render(scene, camera);
      controls.target.set(0, 0, 0);
    });

  cameraTween.start();
  rotationTween.start();
  positionTween.start();
  }

  animate();
  document.getElementById('progressWrapper').style.display = 'none';
};

// Load the model and execute loadingClear after loading is complete
async function init() {
try {
const loadedModel = await loadModel();
loadingClear(loadedModel);
} catch (error) {
console.error('Error loading the model:', error);
}
}

init(); // Call the initialization function to load the model and perform actions after loading  

window.onload = function() {
// Render onload
  TWEEN.update(); // Update the Tween.js animations
  renderer.render(scene, camera);
    // everything is done - remove progress bar
}

Is there any specific reason to use tween in the first place? Does the movement work if you remove the tweens and just modify values directly?

Thanks for the reply. Just moved house so been offline…
The model movement works fine for touch and html buttons.
Without tweens the model just snaps to the new position when using html buttons. It’s just a bit ugly, hence tweens.
Everything works perfectly except that when using the touchscreen, something is changed or updated about the camera or model position - I think touchscreen manipulates the model / view in a slightly different way to the html buttons. So when I reset, the model moves to the wrong place. If I immediately reset again, it goes to where it should. So it’s only the first reset that fails.
I gave up in the end and switched off touch-screen, but would love to solve this.