Scale object on given axis in world space.

Hey guys. Kind of inexperienced. I’d like to make a marble game, but have the sphere deform with velocity. I need to be able to scale it on some axis perpendicular to the camera, as a tire would when spinning faster.

I found this, which seems very close thanks to Andeeeee.

There’s another similar example here.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// from unity community user "Andeeeee"
public class AndyMeshSquash : MonoBehaviour
{
    //    The direction along which the stretch/squash is performed...
    public Vector3 squashAxis = Vector3.up;

    //    ...and the scaling along that axis.
    public float scale = 0.5f;

    Mesh mesh;
    Vector3[] origVerts;
    Vector3[] verts;

    void Start()
    {
        //    Get the mesh data.
        MeshFilter mf = GetComponent<MeshFilter>();
        mesh = mf.mesh;

        //    Keep a copy of the original vertices and declare a new array of vertices.
        origVerts = mesh.vertices;
        verts = new Vector3[mesh.vertexCount];
    }

    void Update()
    {
        //    Create a new local coordinate space where one axis is the stretch/squash direction
        Vector3 r = squashAxis;
        Vector3 g = Vector3.zero;
        Vector3 b = Vector3.zero;

        //    OrthoNormalize creates two axes perpendicular to the stretch/squash axis!
        Vector3.OrthoNormalize(ref r, ref g, ref b);

        //A matrix transforms points into new coordinate space.
        Matrix4x4 mat = new Matrix4x4();
        mat.SetRow(0, r);
        mat.SetRow(1, g);
        mat.SetRow(2, b);
        mat.SetRow(3, new Vector4(0, 0, 0, 1));

        // Its inverse transforms them back again.
        Matrix4x4 inv = mat.inverse;

        for (int i = 0; i < verts.Length; i++)
        {
            //    Convert the original unaltered vertex to the new coordinate space.
            Vector3 vert = mat.MultiplyPoint3x4(origVerts[i]);

            //    Change the vertex's position in the new space.
            vert.x *= scale;

            //    Convert it back to the usual XYZ space.
            verts[i] = inv.MultiplyPoint3x4(vert);
        }

        //    Set the updated vertex array.
        mesh.vertices = verts;

        //    The normals have probably changed, so let Unity update them.
        mesh.RecalculateNormals();
    }
}

If the object is rotated the scale is “fixed” (applied locally?)

I’ve tried a bunch of different things but I’m afraid I’m just not smart enough with matrix transformations to get it right.

Should I be rotating the scaling vector, perhaps the mesh vertices?

I think I can handle most of the rest. We want to make a Metroid-like adventure similar to Nifflas’ “Within A Deep Forest” but 3D. I have a friend who is going to contribute some 2D art and another on sound, so we’d all appreciate your contribution.

Thanks!

I’m not sure i can help with your main problem, but if this is about visuals only, i would highly recommend not deforming the mesh itself, but using a shader to do this just visually. That would be a lot faster.

Without having experimented with it myself, i’d say you should try to deform the vertex positions using the direction vector of your movements. Each vertex has a normal showing what’s “up” from its perspective. You should then compare the moveDirection vector to the normal of each vertex and offset it based on that.

I think this may help you as well, since it’s about sphere deformation:

By observing relationships between the vectors and rotations I have been able to produce the desired result by rotating the scaling vector opposite the transform’s rotation! This took some trial and error.

Now I have a proof of concept that produces the expected or desired results, and as such am curious how it should be optimized. I may be somewhat clueless, but I can tell Unity is doing some double-work in the transforms here.

Anyway, like I said this will allow the sphere to “stretch” a little bit

The simplest way is to align the sphere with the direction of movement, then scale the object in the forward vector.

Aligning the sphere in any particular way isn’t feasible… It’s a free-moving physics object driven by torque on an invisible axis combined with a small amount of force. When the player moves left or right the sphere will try to roll in that direction. The different spheres will have surface textures to help illustrate this.

It’d be hard to keep that 1:1 physicality where it looks like the sphere’s rotation is always correctly relative its motions across the ground. (not sliding around or doing anything weird)

fair, I wasn’t sure if it was rolling, but just to provide an alternative FOR FUN, you could sample the texture as cubemap, and pass the rigid body angle to the sampler, such has it visually roll, but the mesh remain unaffected.

Thanks, that’s a great idea! Way better than this. Also, when I’m done I’ll have a better understanding of how it works. That’s always good.

When I get off work tomorrow I’m going to see what I can slap together. I’ll share what I can get working. This community is awesome.

Here’s the solution I came up with. This shader is placed on a mesh renderer with no rigidbody:

Shader "CubeMapRotator" {
    Properties{
        _CubeMap("Cube Map", Cube) = "white" {}
    }
        SubShader{
            Pass {
                Tags { "DisableBatching" = "True" }
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"

                samplerCUBE _CubeMap;
                uniform float4x4 _Rotation;

                struct appdata {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                };

                struct v2f {
                    float4 pos : SV_Position;
                    half3 worldUV : TEXCOORD0;
                };

                v2f vert(appdata v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    // compute world space position of the vertex
                    float3 worldPos = mul(unity_ObjectToWorld, float4(vertex.xyz, 1.0)).xyz;
                    // world space normal
                    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                    o.worldUV = worldNormal;
                    return o;
                }

                fixed4 frag(v2f i) : SV_Target {
                    float4 color = texCUBE(_CubeMap, mul(_Rotation, float4(i.worldUV, 0)));
                    return color;
                }
                ENDCG
            }
    }
}

This script is placed on the mesh, causing it to follow a rigidbody while copying its rotation to the cube map shader.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CubeMapRotateTest : MonoBehaviour
{
    public Transform target;
    Renderer r;

    private void Start()
    {
        r = GetComponent<Renderer>();
    }

    void Update()
    {
        Quaternion rot = Quaternion.Inverse(target.rotation);

        Matrix4x4 mat = new Matrix4x4();
        mat.SetTRS(Vector3.zero, rot, new Vector3(1, 1, 1));
        r.material.SetMatrix("_Rotation", mat);

        transform.position = target.position;
    }
}