0
\$\begingroup\$

TL;DR: How could one fix scaling skew caused by a parent object assigned at run-time where the parent object's scaling perpendicular to its surface is not a 1:1 ratio, thereby turning the circular child into an oval, and the child's rotation is not guaranteed to line up with any axis other than the surface normal?

The Problem:

I am making a projectile that, when it strikes a collider, sticks to that collider. Originally, this was being done by setting the object to be Kinematic, and attaching a joint between it and the struck object if that object had a rigidbody.

The joints began causing issues when multiple projectiles were attached to one object, so I opted to change the system over to remove the projectile's rigidbody and set the projectile's parent to be the struck object. This worked wonderfully, except that some objects had strange scaling after "sticking."

I found the issue to be caused by non-1:1 ratios of the axes perpendicular to the axis of "sticking" of the projectile, and I have only observed this for box colliders. To elaborate, if a projectile strikes at an angle where its Z or Y axis line up with the Z or X axis of the target, there is no skew. If the projectile hits at an angle, I experience skew.

I am now stuck, so I come to the SE again hoping for other approaches. Please note that this uses the Unity3D engine and C#.


Assumptions:

  1. The surface normal of the box collider is always (approximately) parallel to one of its axes (usually Y but not always), and is always (approximately) parallel to the X-axis of the projectile.
  2. I cannot resize the parent object, as the environment is full of these objects and there is no way for me to change them all to use unit dimensions with the colliders resized. (I could, but it could take days or even weeks.)
  3. Multiple projectiles can and will stick to one target.
  4. Performance is important here as the game is fairly heavy on a system as is.
  5. Assume objects can be moved by the player. If a solution cannot be found that allows the projectiles to follow the object's position and rotation (I've tried, but Quaternions are hard), I can detach them.

Previous Attempts:

  1. Setting the parent of the projectile to an intermediate game object with (1,1,1) scale and setting its parent to the target. Also doing this with two such intermediate game objects.
  2. Attempting to directly set the object's scale to the inverse of the parent's.
  3. Using Transform.SetParent(other.transform, false) and setting the position and/or scale directly.
  4. Rotating the projectile with two quaternions, to line up the surface normal as described in assumptions, and to line up the other axes. This caused problems with the surface normal line up, which is essential to the solution.
  5. Determining which axis is parallel to the surface normal and trying to rotate appropriately, as shown below. This attempt was less than successful for the desired surfaces, and actually caused problems with the surface normal detection for mesh colliders, of which there are many.

Current rotation & parenting code for the projectiles:

private void RotateToSurface(RaycastHit hitInfo, Vector3 contactPoint, GameObject other)
{
    Vector3 surfaceNormal = hitInfo.normal;
    Vector3 thisVector;
    Vector3 targetVector;
    if (Mathf.Abs(Vector3.Dot(surfaceNormal, other.transform.up)) == 1f)
    {
        thisVector = Vector3.Cross(RightVector, ForwardVector);
        targetVector = Vector3.Cross(other.transform.forward, surfaceNormal);
    }
    else if (Mathf.Abs(Vector3.Dot(surfaceNormal, other.transform.right)) == 1f)
    {
        thisVector = Vector3.Cross(UpVector, ForwardVector);
        targetVector = Vector3.Cross(other.transform.up, surfaceNormal);
    }
    else if (Mathf.Abs(Vector3.Dot(surfaceNormal, other.transform.forward)) == 1f)
    {
        thisVector = Vector3.Cross(RightVector, ForwardVector);
        targetVector = Vector3.Cross(other.transform.right, surfaceNormal);
    }
    else
    {
        thisVector = ForwardVector;
        targetVector = -surfaceNormal;
    }
    Quaternion q = Quaternion.FromToRotation(thisVector, targetVector);
    transform.rotation = q * transform.rotation;
    transform.position = q * (transform.position - contactPoint) + contactPoint;
}

{...}

Destroy(thisRB);
this.transform.SetParent(other.transform, true);

Note: ForwardVector and the like are my sanity getters, as the darts are rotated to have their Transform.right vectors pointing backwards for some reason.

\$\endgroup\$
  • \$\begingroup\$ You could create a new GameObject Root. Then instead of putting the projectiles as the child of your current gameobject, make them share this common Root Gameobject. Essentially, the change would be to make the projectiles not be a child of the object you are rotating. \$\endgroup\$ – jgallant Oct 25 '16 at 14:13
  • \$\begingroup\$ @jgallant My only concern with that approach is creating a nest of objects and then cleaning them up correctly afterwards if multiple projectiles stick to one object. Because of that, I've been staying away from that solution for now. Also, the player can interact with and move around some of the possible targets, so that may also be a concern. \$\endgroup\$ – Crabgor Oct 25 '16 at 14:16
  • \$\begingroup\$ I would suggest you Pool them and manage the object pool independently from all of the other game objects. \$\endgroup\$ – jgallant Oct 25 '16 at 14:18
  • \$\begingroup\$ Projectiles already exist in a pool. I'm not sure I understand what you are suggesting. \$\endgroup\$ – Crabgor Oct 25 '16 at 14:24
  • 1
    \$\begingroup\$ I think this was basically suggested already but maybe said another way will make something click. My suggestion is to make them be siblings as opposed to parent/child. Then you can skew the one object independent of the other and then instead of moving the skewed object you would move its parent which would also move its sibling (the thing stuck to it). I think this is the "correct" way to do it. \$\endgroup\$ – Neal Davis Oct 25 '16 at 19:34
0
\$\begingroup\$

Due to the complexities of the system I was working in, since the player could interact with objects, but only those with rigidbodies attached, I had to get a bit creative.

First of all, I started with the object to attach and the projectile:

> Target
> Projectile

A pool of Projectile Parent objects was created, one for each projectile. These objects were grabbed by projectiles on impact if and only if there did not exist already such a parent on the target object. Then, the target and projectile were both parented to this object.

> Projectile Parent (1)
    > Target (1)
    > Projectile (1)
> Projectile Parent (2)
    > Target (2)
    > Projectile (2)
    > Projectile (3)

Once a projectile reached the end of its life time, it was returned to the object pool, and the hierarchy was reset if no projectiles remained attached.

> Target (1)
> Projectile Parent (2)
    > Target (2)
    > Projectile (3)

Finally, for the player-manipulated objects, since these were set to be kinematic and had their transforms modified directly, the Projectile Parent objects modified their own transforms in Update to match the parent's world-space. The projectiles had their rigidbodies removed entirely, and were set to exist entirely in the Ignore Raycast layer to prevent funny effects from the overlapping colliders between the projectile and the target object.

The solution wasn't ideal, but it's what I had to work with to accomplish the desired effect in a VR game where player's can pick up a large portion of the scene.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.