How to correctly resize sphere using mouse drag

Hello everyone!

I am trying to scale a sphere based on mouse drag. In my current implementation I can resize the sphere just fine. However there are some issues with the direction in which the scaling takes place. I want to enlarge the sphere when the mouse drag is away from the center of the sphere and shrink the sphere when the mouse drag is towards its center. This behavior is not always exhibited in my code.

Any help will be greatly appreciated. I am also open to suggestions on better ways to deal with resizing a 3D object using mouse click and drag events. Thanks!

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );

scene.background = new THREE.Color('skyblue');
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

const geometry = new THREE.SphereGeometry( 15, 32, 16 ); 
const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); 
const sphere = new THREE.Mesh( geometry, material ); scene.add( sphere );
scene.add( sphere );

camera.position.z = 25;

const controls = new OrbitControls( camera, renderer.domElement );
controls.update();

let isResizing = false;
let initialScale;
const mouse = new THREE.Vector2();

document.addEventListener('mousedown', onMouseDown);
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('mousemove', onMouseMove);

function onMouseDown(event) {
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);

  const intersects = raycaster.intersectObject(sphere);

  if (intersects.length > 0) {
    isResizing = true;
    initialScale = sphere.scale.clone();
  }
}

function onMouseUp() {
  isResizing = false;
}

function onMouseMove(event) {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  if (isResizing) {
    const scaleFactor = 1 + (mouse.x - initialScale.x) * 0.5;
    const delta = new THREE.Vector2(event.movementX, event.movementY);

    sphere.scale.set(
      initialScale.x * scaleFactor,
      initialScale.y * scaleFactor,
      initialScale.z * scaleFactor
    );
  }
}

function animate() {
	requestAnimationFrame( animate );

    controls.update();
	renderer.render( scene, camera );
}

animate();

This is not perfect, it uses 2d but in 3d on an infinite Plane in screen direction
The better way would be to rotate the plane via the point down the selected object and a cross vector so you are then moving the point in 3d towards the object, but you’ll get exponential deltas then

Or theres a triangle to devise to get a smoother flow or! its just a lerp, its ALWAYS a lerp somewhere

Cause in 3d space clicking on the front of it would make a near not coplannar plane making movement waaaaaay to far

2 Likes

Yup. It’s a tricky problem to solve in perspective.
Your solution looks like a decent compromise.

You can also find the 3 planes of space and project in a 2d form onto them