Stretching a primitive between two points

I’ve created a game with multiple points. Currently, I’m moving a primitive between these points (based on player input, which is arbitrary).

Right now I’m using Vector3.Lerp to move the primitive’s transform from point to point. What I’d like to do is stretch the primitive to the end point, and then return the primitive to its original shape at the end (like a rubber band motion).

Graphically, I want this (I hope this makes sense):

o         x
ooooo     x
ooooooooooo
x     ooooo
x         o

I know that there are ways to perform stuff like this by parenting the object to an empty transform to change the pivot point, but because I want a rubber band motion, I feel like that would be too complex.

Is there a way to accomplish this without using bones? I’m okay with going the bone route if necessary (as they should stretch the mesh accordingly without much setup, I believe) but if there’s a better way…

Edit:

Note that I want my primitive to stretch completely from point to point;, and I’m using coroutines for movement right now.

Bones would be soo much easier but this will work for a stretching cube I think.

IEnumerator Stretch(GameObject cube, float stretchTime, float snapTime, Vector3 goal)
{
	Vector3 start = cube.transform.position;
	Vector3 halfGoal = Vector3.Lerp(start, goal, 0.5f);
	float distance = Vector3.Distance(start, goal);
	
	//change x to the axis you need
	float startX = cube.transform.localScale.x;
	float wantedX = distance;
	//change it in the localScale assign as well
	
	float t = 0;
	while(t < stretchTime)
	{
		t += Time.deltaTime;
		
		cube.transform.position = Vector3.Lerp(start, halfGoal, t/stretchTime);
		//in order to get it to 'stick' to the goal, we cant use a lerp for this part
		//float x = Mathf.Lerp(startX, wantedX, t/stretchTime);
		float distanceToStart = Vector3.Distance(cube.transform.position, start);
		float x = distanceToStart * 2;
		if(x < startX)
			X = startX;
		cube.transform.localScale = new Vector3(x, cube.transform.localScale.y, cube.transform.localScale.z);
		
		yield return null;
	}
	
	t = 0;
	while(t < snapTime)
	{
		t += Time.deltaTime;
		
		cube.transform.position = Vector3.Lerp(halfGoal, goal, t/snapTime);
		//in order to get it to 'stick' to the goal, we cant use a lerp for this part
		//float x = Mathf.Lerp(wantedX, startX, t/snapTime);
		float distanceToEnd = Vector3.Distance(cube.transform.position, goal);
		float x = distanceToEnd * 2;
		cube.transform.localScale = new Vector3(x, cube.transform.localScale.y, cube.transform.localScale.z);
		
		yield return null;
	}
}

That’s certainly possible, but it’s going to involve some tricky math.

Something like this might be a place to start:

float percent = 0.25f;
float leftPerc = Mathf.Clamp01(Mathf.InverseLerp(0.5f, 1f, percent);
float rightPerc = Mathf.Clamp01(Mathf.InverseLerp(0f, 0.5f, percent);

See the Mathf documentation if that’s confusing. Basically, I see how far we are through 0-50%, then through 50-100%.

That’ll tell you how far across the left and right edges of your primitive should be. You can lerp between the start and end positions to convert those into 3D points:

Vector3 leftPos = Vector3.Lerp(startPos, endPos, leftPerc);
Vector3 rightPos = Vector3.Lerp(startPos, endPos, rightPerc);
float width = Vector3.Distance(leftPos, rightPos);

From there, you can solve two problems:

  • How far do you need to stretch the primitive so that it fills width space?
  • Where do you need to place the primitive so that its left edge is at leftPos?

Given some diagrams, those problems should be interesting but possible to solve.

If you’re not sure how wide the primitive is, you could use its renderer bounds to find out.

Recommend keeping the pivot point of your object on its far left or far right side, to keep things simple.

Here is my take on the question. Whenever I see this kind of mechanic (spring, rubber band, etc.), I think curve. As a programmer, I think code, not Animation. So this code does two two things beyond what you asked for. First, it solves the problem for an arbitrary direction. Second it computes time through a sine curve, so the object has more of a snapping feel. I hesitated to post it with @KMKxJOEY1’s excellent answer that directly addresses your question, but maybe some future person will benefit from my additions. Start a new scene, put the code on a cube, and hit the spacebar. Hit the spacebar again when the cycle completes.

#pragma strict

public var moveTime : float = 1.0;
public var objectWidth : float = 1.0;

private var startMarker : Transform;
private var endMarker : Transform;

private var stretching = false;


function Start () {
	startMarker = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
	endMarker = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
	
	startMarker.position = transform.position;
	endMarker.position = transform.position;
	
	startMarker.localScale = Vector3(.2, .2, .2);
	endMarker.localScale = Vector3(.2, .2, .2);	
}

function Update () {
	if (!stretching && Input.GetKeyDown(KeyCode.Space)) {
		endMarker.transform.position = Quaternion.AngleAxis(Random.Range(0,360), Vector3.forward) * Vector3.up * 5.5;
		DoTheBand(moveTime);
	}
}

function DoTheBand(time : float) {
	stretching = true;
	var dir : Vector3 = endMarker.position - startMarker.position;
	var maxWidth : float = dir.magnitude + objectWidth;
	
	var timer = 0.0;
	
	transform.rotation = Quaternion.FromToRotation(transform.right, dir) * transform.rotation;
	
	while (timer <= time) {
		timer += Time.deltaTime;
		var t = timer / time;
		var _t = Mathf.Sin(t * Mathf.PI);
		var __t = (t < 0.5) ? _t * 0.5 : 1.0 - 0.5 * _t;
		
		transform.position = Vector3.Lerp(startMarker.position, endMarker.position, __t);
		transform.localScale.x = Mathf.Lerp(objectWidth, maxWidth, _t);
		Debug.Log(_t); 
		yield;
	} 
	
	transform.position = endMarker.position;
	startMarker.position = transform.position;
	transform.localScale.x = objectWidth;
	stretching = false;
}