- Home /
How to achieve a rotation on "batch quads" ?
Hello everyone,
Let's start with a simple illustration:
I have two plans. The plane on the left is the "target". The right plane is going to translate and rotate during a time, t. After the animation, the right plane will have the same position∨ientation than the left one.
Here is the code :
public class Test : MonoBehaviour
{
public float TranslateSpeed = 2.0f;
public float RotateSpeed = 3.0f;
public Transform Target;
private Transform objectTransform;
protected virtual void Awake ()
{
this.objectTransform = this.transform;
}
protected virtual void Update ()
{
if (Time.time < 1.5f)
{
return;
}
if (this.Target == null || this.objectTransform == null)
{
return;
}
float translateStep = this.TranslateSpeed * Time.deltaTime;
float rotateStep = this.RotateSpeed * Time.deltaTime;
this.objectTransform.position = Vector3.MoveTowards(this.objectTransform.position, this.Target.position, translateStep);
this.objectTransform.rotation = Quaternion.RotateTowards(this.objectTransform.rotation, this.Target.rotation, rotateStep);
}
}
It works perfectly with the built-in unity functions.
Now, I would like to achieve this effect on many (maybe hundred, maybe thousands) quads batched inside one Unity.Mesh. So I create all the necessary buffers (vertices, triangles, etc...) and in the Update function, I want to get the same behaviour for all my quads, ie translate and rotate them to a given position and direction.
For the translation it's quite easy, but for the rotation it's tricky :/ I need to handle each quad one by one, and as the geometry is batched in one mesh, i don't have access to an "independant Transform variable"...
I copy my code here:
using UnityEngine;
using System.Collections;
/* **** INFORMATIONS ***
*
* The name "leave" refers to a simple quad.
* A PATCH contains leaves geometry.
***/
public class LeavesPatch : MonoBehaviour {
public int NumberOfLeaves = 10;
public float LeaveSize = 0.1f;
public float Radius = 2.0f; // Used to spread leaves position in a circle.
public float AnimationLength = 3.0f;
public float TranslationSpeed = 3.0f;
public float RotationSpeed = 3.0f;
public Transform TargetToReach;
public Material LeaveMaterial;
private const int IndicesPerQuad = 6;
private Mesh patchMesh;
private bool playAnimation = false;
private Vector3[] updateVertices;
protected virtual void Awake() {
// Create buffers
Vector3[] quadVertices = new Vector3[] {
new Vector3(-0.5f * this.LeaveSize, 0.0f, 0.5f * this.LeaveSize),
new Vector3(0.5f * this.LeaveSize, 0.0f, 0.5f * this.LeaveSize),
new Vector3(0.5f * this.LeaveSize, 0.0f, -0.5f * this.LeaveSize),
new Vector3(-0.5f * this.LeaveSize, 0.0f, -0.5f * this.LeaveSize),
};
Vector3[] quadNormals = new Vector3[] {
Vector3.up,
Vector3.up,
Vector3.up,
Vector3.up
};
int[] quadIndices = new int[] {
0, 1, 2,
0, 2, 3
};
Vector2[] quadUV = new Vector2[] {
new Vector2(0.0f, 0.0f),
new Vector2(1.0f, 0.0f),
new Vector2(1.0f, 1.0f),
new Vector2(0.0f, 1.0f)
};
// Create patch and fill buffers.
Vector3[] patchVertices = new Vector3[quadVertices.Length * this.NumberOfLeaves];
Vector3[] patchNormals = new Vector3[quadNormals.Length * this.NumberOfLeaves];
int[] patchIndices = new int[quadIndices.Length * this.NumberOfLeaves];
Vector2[] patchUVs = new Vector2[quadUV.Length * this.NumberOfLeaves];
for (int i = 0; i < this.NumberOfLeaves; ++i) {
// Create vertices and random position in a circle (plan XZ).
Vector3[] newVertices = new Vector3[quadVertices.Length];
float randRadius = Random.Range(0.0f, this.Radius);
float randRadian = Mathf.Deg2Rad * Random.Range(0.0f, 360.0f);
Vector3 randCirclePos = new Vector3(
randRadius * Mathf.Cos(randRadian),
0.0f,
randRadius * Mathf.Sin(randRadian));
for (int j = 0; j < quadVertices.Length; ++j) {
newVertices[j] = quadVertices[j] + randCirclePos;
}
// Create new array of indices.
int[] newIndices = new int[IndicesPerQuad];
for (int j = 0; j < IndicesPerQuad; ++j) {
newIndices[j] = quadIndices[j] + (i * 4);
}
System.Array.Copy(newVertices, 0, patchVertices, i * 4, 4);
System.Array.Copy(quadNormals, 0, patchNormals, i * 4, 4);
System.Array.Copy(newIndices, 0, patchIndices, i * IndicesPerQuad, IndicesPerQuad);
System.Array.ConstrainedCopy(quadUV, 0, patchUVs, i * 4, 4);
}
// Create the Patch GameObject and we add essentials Component.
this.patch = new GameObject("Patch");
MeshRenderer mr = this.patch.AddComponent<MeshRenderer>();
mr.material = this.LeaveMaterial;
MeshFilter patchMeshFilter = this.patch.AddComponent<MeshFilter>();
patchMeshFilter.mesh.MarkDynamic();
patchMeshFilter.mesh.vertices = patchVertices;
patchMeshFilter.mesh.normals = patchNormals;
patchMeshFilter.mesh.triangles = patchIndices;
patchMeshFilter.mesh.uv = patchUVs;
patchMeshFilter.mesh.Optimize();
this.patchMesh = patchMeshFilter.mesh;
}
protected virtual void Update() {
// Press SPACE to launch the animation.
if (Input.GetKeyDown(KeyCode.Space)) {
this.playAnimation = !this.playAnimation;
if (this.playAnimation)
Debug.Log("PLAY");
}
if (!this.playAnimation) {
return;
}
this.updateVertices = this.patchMesh.vertices;
// I work in the patch space.
Vector3 targetPositionInPatchLocalSpace = this.patch.transform.worldToLocalMatrix.MultiplyPoint3x4(this.TargetToReach.position);
Vector3 targetDirectionInPatchLocalSpace = this.patch.transform.InverseTransformDirection(this.TargetToReach.up);
Vector3 currentPatchNormal = Vector3.zero;
float translationStep = this.TranslationSpeed * Time.deltaTime;
float rotationStep = this.RotationSpeed * Time.deltaTime;
for (int i = 0; i < this.patchMesh.vertexCount; ++i) {
// MOVE TOWARDS Built-in unity function
Vector3 newPos = Vector3.MoveTowards(this.updateVertices[i], targetPositionInPatchLocalSpace, translationStep);
// ROTATE TOWARDS
// I only compute the normal of the quad only one time per quad.
if ((i % 4) == 0) {
Vector3 p0 = (this.updateVertices[i + 3] - this.updateVertices[i]);
Vector3 p1 = (this.updateVertices[i + 1] - this.updateVertices[i]);
currentPatchNormal = Vector3.Cross(p0, p1).normalized;
// Debug : Draw Normal.
Vector3 startPoint = this.transform.TransformPoint((this.updateVertices[i] + this.updateVertices[i + 1] + this.updateVertices[i + 2]) / 3.0f);
Vector3 worldSpaceNormal = this.transform.TransformDirection(currentPatchNormal);
Debug.DrawRay(startPoint, worldSpaceNormal, Color.red, float.MaxValue, true);
}
// TODO : I thought it would worked...
Vector3 newDir = Vector3.RotateTowards(currentPatchNormal, targetDirectionInPatchLocalSpace, rotationStep, 0.0f);
Quaternion currentRotation = Quaternion.LookRotation(newDir);
//Matrix4x4 m = Matrix4x4.TRS(newPos - this.updateVertices[i], Quaternion.identity, Vector3.one); // This line works, only translation !
Matrix4x4 m = Matrix4x4.TRS(newPos - this.updateVertices[i], currentRotation, Vector3.one); // and this line doesn't work ...
Vector3 pt = m.MultiplyPoint3x4(this.updateVertices[i]);
this.updateVertices[i] = pt;
}
this.patchMesh.vertices = this.updateVertices;
this.patchMesh.RecalculateNormals();
this.patchMesh.RecalculateBounds();
}
}
The quads just turn themselves and with the Debug.Draw(normals) I get a nice effect :p
I really would appreciate your mathematics tricks ;)
Thanks in advance !
C.Tsubasa
I'm having trouble seeing exactly what the problem is. Perhaps a video would help.
I'm wondering if your line:
Quaternion currentRotation = Quaternion.LookRotation(newDir);
Should be:
Quaternion currentRotation = Quaternion.LookRotation(newDir, transform.up);
Look at my loop line 48. I handle my vertices independently of each other. So I cannot use any transform as you suggest in your answer
Quaternion currentRotation = Quaternion.LookRotation(newDir, transform.up);
I'm going to update a video asap !
I took the liberty of formatting your code a bit - the array initializations in awake required a lot of horizontal scrolling.
To get the rotation going, shouldn't it be enough to just move the corners of each quad in the mesh? The tris are only based on the corners, so you shouldn't actually have to rotate anything.
Baste, you're right !! I didn't think about it :)
""To get the rotation going, shouldn't it be enough to just move the corners of each quad in the mesh? The tris are only based on the corners, so you shouldn't actually have to rotate anything." -> Thank you ;
But ok it will work for my quads, but if I handle a plane with hole(s) inside ? How I can manage this ? I forgot my basics mathematics knowledge :/
Every mesh is based on the same two structures - the corner points, and the triangles formed from those corners. If it works with a quad, it should definitely work with a plane.