Initial Commit
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
using UnityEngine;
|
||||
|
||||
/*
|
||||
* This script creates a custom mesh, specifically for hand masking in the Passthrough SDK:
|
||||
*
|
||||
* 1. Created with 2D screen space in mind, since it's 2D triangles facing the camera.
|
||||
* Not advised to use this mesh in any other way.
|
||||
* 2. The look of it should be coupled with maskMaterial, which defines the falloff of the fade and
|
||||
* also how it blends Passthrough.
|
||||
* 3. The mesh has UV.x of 0 where the hand is fully opaque, and UV.x of 1 where faded out.
|
||||
* This way, the gradient curve can be tuned in a user-friendly way (instead of in shader).
|
||||
* There may be an optimization of avoiding a texture read, if the fade is all in shader (remapping UV.x)
|
||||
* 4. Requires knowledge of the hand bones and indices
|
||||
*/
|
||||
|
||||
public class HandMeshMask : MonoBehaviour
|
||||
{
|
||||
public OVRSkeleton referenceHand;
|
||||
public Material maskMaterial;
|
||||
|
||||
// these feel like good defaults
|
||||
[Tooltip("The segments around the tip of a finger")]
|
||||
public int radialDivisions = 9;
|
||||
|
||||
[Tooltip("The fade range (finger width is 2x this)")]
|
||||
public float borderSize = 0.2f;
|
||||
|
||||
[Tooltip("Along the fingers, each knuckle scales down by this amount. " +
|
||||
"Default is zero for uniform width along entire finger.")]
|
||||
public float fingerTaper = 0.13f;
|
||||
|
||||
[Tooltip("Shorten the last bone of each finger; need this to account for bone structure " +
|
||||
"(end bone is at finger tip instead of center). Default is 1.")]
|
||||
public float fingerTipLength = 0.8f;
|
||||
|
||||
[Tooltip("Move the base of the 4 main fingers towards the tips, to avoid a visible mesh crack " +
|
||||
"between finger webbing. Default is 0.")]
|
||||
public float webOffset = 0.25f;
|
||||
|
||||
// retrieved by OVRHands at runtime
|
||||
float handScale = 1.0f;
|
||||
|
||||
// mesh information
|
||||
GameObject maskMeshObject;
|
||||
Mesh maskMesh;
|
||||
Vector3[] handVertices;
|
||||
Vector2[] handUVs;
|
||||
Color32[] handColors;
|
||||
int[] handTriangles;
|
||||
int vertCounter = 0;
|
||||
int triCounter = 0;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// this object must be at the origin for the vertex positions to work
|
||||
transform.position = Vector3.zero;
|
||||
|
||||
maskMesh = new Mesh();
|
||||
maskMeshObject = new GameObject("MeshMask");
|
||||
maskMeshObject.transform.parent = this.transform;
|
||||
maskMeshObject.transform.localPosition = Vector3.zero;
|
||||
maskMeshObject.AddComponent<MeshFilter>();
|
||||
maskMeshObject.AddComponent<MeshRenderer>();
|
||||
maskMeshObject.GetComponent<MeshFilter>().mesh = maskMesh;
|
||||
maskMeshObject.GetComponent<MeshRenderer>().material = maskMaterial;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// must have a minimum amount for math to work out
|
||||
radialDivisions = Mathf.Max(2, radialDivisions);
|
||||
|
||||
if (referenceHand)
|
||||
{
|
||||
// make sure all math accounts for hand scale
|
||||
handScale = referenceHand.GetComponent<OVRHand>().HandScale;
|
||||
CreateHandMesh();
|
||||
}
|
||||
|
||||
bool handsActive = (
|
||||
OVRInput.GetActiveController() == OVRInput.Controller.Hands ||
|
||||
OVRInput.GetActiveController() == OVRInput.Controller.LHand ||
|
||||
OVRInput.GetActiveController() == OVRInput.Controller.RHand);
|
||||
maskMeshObject.SetActive(handsActive);
|
||||
}
|
||||
|
||||
// do not edit any numbers below unless you like pain
|
||||
// (it's EASY to break mesh creation and HARD to debug it)
|
||||
// if you do, you need to become intimately aware of the OVRSkeleton.Bone numbers
|
||||
void CreateHandMesh()
|
||||
{
|
||||
int knuckleVerts = 8 + (radialDivisions - 2) * 2;
|
||||
int knuckleCount = 25; // five fingers, three knuckles per finger, then 10 more as palm borders
|
||||
|
||||
int palmVerts = 12;
|
||||
int palmTriIndices = 66; // 22 triangles, each triangle has three verts
|
||||
|
||||
handVertices = new Vector3[knuckleVerts * knuckleCount + palmVerts];
|
||||
handUVs = new Vector2[handVertices.Length];
|
||||
handColors = new Color32[handVertices.Length];
|
||||
handTriangles = new int[handVertices.Length * 3 + palmTriIndices];
|
||||
// these counters are incremented during mesh construction, at each step, to ensure a valid mesh
|
||||
vertCounter = 0;
|
||||
triCounter = 0;
|
||||
|
||||
// make knuckle meshes for each finger
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
int pinkyOffset = i < 4 ? 0 : 1; // pinky bone numbering is slightly different than other fingers
|
||||
int baseId = 3 + i * 3 + pinkyOffset;
|
||||
int tipId = 19 + i;
|
||||
float k0taper = 1.0f;
|
||||
float k1taper = 1.0f - fingerTaper;
|
||||
float k2taper = 1.0f - (fingerTaper * 2);
|
||||
float k3taper = 1.0f - (fingerTaper * 3);
|
||||
// thumb also gets a bit wider thickness at the base
|
||||
if (i == 0)
|
||||
{
|
||||
k0taper *= 1.2f;
|
||||
k1taper *= 1.1f;
|
||||
}
|
||||
|
||||
AddKnuckleMesh(knuckleVerts, k0taper, k1taper, referenceHand.Bones[baseId].Transform.position,
|
||||
referenceHand.Bones[baseId + 1].Transform.position);
|
||||
AddKnuckleMesh(knuckleVerts, k1taper, k2taper, referenceHand.Bones[baseId + 1].Transform.position,
|
||||
referenceHand.Bones[baseId + 2].Transform.position);
|
||||
|
||||
// for the tip of the finger, the mask needs to be a bit different:
|
||||
// the final joint of the skeleton's finger is at the tip, but
|
||||
// we need the final joint to be somewhat inside the finger tip, so the radial mask matches
|
||||
Vector3 lastKnucklePos = referenceHand.Bones[baseId + 2].Transform.position;
|
||||
Vector3 tipPos = referenceHand.Bones[tipId].Transform.position;
|
||||
Vector3 offsetTipPos = (tipPos - lastKnucklePos) * fingerTipLength + lastKnucklePos;
|
||||
AddKnuckleMesh(knuckleVerts, k2taper, k3taper, lastKnucklePos, offsetTipPos);
|
||||
}
|
||||
|
||||
// add palm mesh, which is very different than fingers
|
||||
// it uses the same concept and parameters ("fade out from center")
|
||||
AddPalmMesh(knuckleVerts);
|
||||
|
||||
// final step: combine all the arrays to make the mesh object
|
||||
maskMesh.Clear();
|
||||
maskMesh.name = "HandMeshMask";
|
||||
maskMesh.vertices = handVertices;
|
||||
maskMesh.uv = handUVs;
|
||||
maskMesh.colors32 = handColors;
|
||||
maskMesh.triangles = handTriangles;
|
||||
}
|
||||
|
||||
// a "knuckle" is a camera-facing mesh from two bones
|
||||
// the first two verts are at the bones, the rest border them like a 2D capsule
|
||||
void AddKnuckleMesh(int knuckleVerts, float point1scale, float point2scale, Vector3 point1, Vector3 point2)
|
||||
{
|
||||
int baseVertId = vertCounter;
|
||||
|
||||
Vector3 camPos = Camera.main.transform.position;
|
||||
Vector3 fwdVec = (point1 + point2) * 0.5f - camPos; // use the center of the two points
|
||||
Vector3 upVec = point2 - point1;
|
||||
Vector3.OrthoNormalize(ref fwdVec, ref upVec);
|
||||
Vector3 rightVec = Vector3.Cross(upVec, fwdVec);
|
||||
|
||||
AddVertex(point2, Vector2.zero, Color.black);
|
||||
AddVertex(point1, Vector2.zero, Color.black);
|
||||
|
||||
int fanVerts = radialDivisions + 1;
|
||||
|
||||
// rotate this vector counter clockwise, making verts along the way
|
||||
Vector3 windingVec = rightVec;
|
||||
|
||||
for (int i = 0; i < fanVerts * 2; i++)
|
||||
{
|
||||
int basePoint = (i / fanVerts) + baseVertId;
|
||||
Vector3 vertPos = handVertices[basePoint] + windingVec * borderSize * handScale *
|
||||
(basePoint != baseVertId ? point1scale : point2scale);
|
||||
AddVertex(vertPos, new Vector2(1, 0), Color.black);
|
||||
if (i != radialDivisions) // after making the first fan, don't wind for one vert
|
||||
{
|
||||
windingVec = Quaternion.AngleAxis(180.0f / radialDivisions, fwdVec) * windingVec;
|
||||
}
|
||||
}
|
||||
|
||||
// after vertices have been made, assign their indices to create triangles
|
||||
handTriangles[triCounter++] = baseVertId + 0;
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
handTriangles[triCounter++] = baseVertId + radialDivisions + 3;
|
||||
handTriangles[triCounter++] = baseVertId + 0;
|
||||
handTriangles[triCounter++] = baseVertId + radialDivisions + 3;
|
||||
handTriangles[triCounter++] = baseVertId + radialDivisions + 2;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 2;
|
||||
handTriangles[triCounter++] = baseVertId + knuckleVerts - 1;
|
||||
handTriangles[triCounter++] = baseVertId + 0;
|
||||
handTriangles[triCounter++] = baseVertId + 0;
|
||||
handTriangles[triCounter++] = baseVertId + knuckleVerts - 1;
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
|
||||
for (int i = 0; i < radialDivisions; i++)
|
||||
{
|
||||
handTriangles[triCounter++] = baseVertId + 2 + i;
|
||||
handTriangles[triCounter++] = baseVertId + 0;
|
||||
handTriangles[triCounter++] = baseVertId + 3 + i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < radialDivisions; i++)
|
||||
{
|
||||
handTriangles[triCounter++] = baseVertId + fanVerts + 2 + i;
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
handTriangles[triCounter++] = baseVertId + fanVerts + 3 + i;
|
||||
}
|
||||
}
|
||||
|
||||
// make the palm section, append it to the mesh
|
||||
// study the bone indices and locations in OVRSkeleton.Bones to understand this
|
||||
void AddPalmMesh(int knuckleVerts)
|
||||
{
|
||||
int baseVertId = vertCounter;
|
||||
// make a few vertices that aren't bone positions
|
||||
|
||||
// vertex between middle and ring fingers
|
||||
Vector3 customVert1 = (referenceHand.Bones[9].Transform.position + referenceHand.Bones[12].Transform.position) *
|
||||
0.5f;
|
||||
// vertex at "saddle" between thumb and index
|
||||
Vector3 customVert2 = (referenceHand.Bones[4].Transform.position + referenceHand.Bones[6].Transform.position) *
|
||||
0.5f;
|
||||
customVert2 = (customVert2 - referenceHand.Bones[15].Transform.position) * 0.9f +
|
||||
referenceHand.Bones[15].Transform.position;
|
||||
// vertex further up thumb, between bones 4 and 5
|
||||
Vector3 thumbPos = (referenceHand.Bones[5].Transform.position - referenceHand.Bones[4].Transform.position) *
|
||||
webOffset;
|
||||
thumbPos += referenceHand.Bones[4].Transform.position;
|
||||
// at knuckles - move the mesh down the fingers to avoid the ugly mesh gap at finger-webs
|
||||
Vector3 indexPos = (referenceHand.Bones[7].Transform.position - referenceHand.Bones[6].Transform.position) *
|
||||
webOffset;
|
||||
indexPos += referenceHand.Bones[6].Transform.position;
|
||||
Vector3 pinkyPos = (referenceHand.Bones[17].Transform.position - referenceHand.Bones[16].Transform.position) *
|
||||
webOffset;
|
||||
pinkyPos += referenceHand.Bones[16].Transform.position;
|
||||
Vector3 middlePos = (referenceHand.Bones[10].Transform.position - referenceHand.Bones[9].Transform.position) +
|
||||
(referenceHand.Bones[13].Transform.position - referenceHand.Bones[12].Transform.position);
|
||||
middlePos *= 0.5f * webOffset;
|
||||
middlePos += customVert1;
|
||||
|
||||
// first, make solid low-poly palm
|
||||
AddVertex(referenceHand.Bones[0].Transform.position, Vector2.zero, Color.black); // baseVertId + 0
|
||||
AddVertex(referenceHand.Bones[3].Transform.position, Vector2.zero, Color.black); // +1
|
||||
AddVertex(referenceHand.Bones[4].Transform.position, Vector2.zero, Color.black); // +2
|
||||
AddVertex(thumbPos, Vector2.zero, Color.black); // +3
|
||||
AddVertex(customVert2, Vector2.zero, Color.black); // +4
|
||||
AddVertex(referenceHand.Bones[6].Transform.position, Vector2.zero, Color.black); // +5
|
||||
AddVertex(customVert1, Vector2.zero, Color.black); // +6
|
||||
AddVertex(referenceHand.Bones[15].Transform.position, Vector2.zero, Color.black); // +7
|
||||
AddVertex(referenceHand.Bones[16].Transform.position, Vector2.zero, Color.black); // +8
|
||||
AddVertex(indexPos, Vector2.zero, Color.black); // +9
|
||||
AddVertex(middlePos, Vector2.zero, Color.black); // +10
|
||||
AddVertex(pinkyPos, Vector2.zero, Color.black); // +11
|
||||
|
||||
// then, assign triangles
|
||||
// unfortunately there's no elegant way to do this
|
||||
// this is literally making a low-poly mesh in code (YUCK)
|
||||
|
||||
// palm side (on left hand)
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
handTriangles[triCounter++] = baseVertId + 2;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 2;
|
||||
handTriangles[triCounter++] = baseVertId + 3;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
handTriangles[triCounter++] = baseVertId + 5;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 5;
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 8;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 8;
|
||||
handTriangles[triCounter++] = baseVertId + 7;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 5;
|
||||
handTriangles[triCounter++] = baseVertId + 9;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 9;
|
||||
handTriangles[triCounter++] = baseVertId + 10;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 10;
|
||||
handTriangles[triCounter++] = baseVertId + 11;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 11;
|
||||
handTriangles[triCounter++] = baseVertId + 8;
|
||||
|
||||
// back side - to make triangulation easier, it's the same as palm side but reversing the last two verts
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 1;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
handTriangles[triCounter++] = baseVertId + 2;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 2;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
handTriangles[triCounter++] = baseVertId + 3;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 5;
|
||||
handTriangles[triCounter++] = baseVertId + 4;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 5;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 8;
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId;
|
||||
handTriangles[triCounter++] = baseVertId + 7;
|
||||
handTriangles[triCounter++] = baseVertId + 8;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 9;
|
||||
handTriangles[triCounter++] = baseVertId + 5;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 10;
|
||||
handTriangles[triCounter++] = baseVertId + 9;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 11;
|
||||
handTriangles[triCounter++] = baseVertId + 10;
|
||||
|
||||
handTriangles[triCounter++] = baseVertId + 6;
|
||||
handTriangles[triCounter++] = baseVertId + 8;
|
||||
handTriangles[triCounter++] = baseVertId + 11;
|
||||
|
||||
// then, make camera-facing "fins" for the outer fade
|
||||
// fortunately these can be knuckle capsules
|
||||
// as an optimization, they could just be fins on silhouette edges
|
||||
|
||||
// add fading border to palm mesh
|
||||
// between thumb and index
|
||||
AddKnuckleMesh(knuckleVerts, 1.0f, 1.0f, customVert2, referenceHand.Bones[6].Transform.position);
|
||||
AddKnuckleMesh(knuckleVerts, 1.0f - fingerTaper, 1.0f, thumbPos, customVert2);
|
||||
|
||||
|
||||
AddKnuckleMesh(knuckleVerts, 1.0f, 1.0f, indexPos, middlePos);
|
||||
AddKnuckleMesh(knuckleVerts, 1.0f, 1.0f, middlePos, pinkyPos);
|
||||
|
||||
AddKnuckleMesh(knuckleVerts, 1.0f, 1.0f, referenceHand.Bones[6].Transform.position, customVert1);
|
||||
AddKnuckleMesh(knuckleVerts, 1.0f, 1.0f, customVert1, referenceHand.Bones[16].Transform.position);
|
||||
|
||||
AddKnuckleMesh(knuckleVerts, 1.2f, 1.0f, referenceHand.Bones[15].Transform.position,
|
||||
referenceHand.Bones[16].Transform.position);
|
||||
AddKnuckleMesh(knuckleVerts, 1.3f, 1.2f, referenceHand.Bones[0].Transform.position,
|
||||
referenceHand.Bones[15].Transform.position);
|
||||
AddKnuckleMesh(knuckleVerts, 1.3f, 1.2f, referenceHand.Bones[0].Transform.position,
|
||||
referenceHand.Bones[3].Transform.position);
|
||||
AddKnuckleMesh(knuckleVerts, 1.3f, 1.0f, referenceHand.Bones[0].Transform.position,
|
||||
referenceHand.Bones[6].Transform.position);
|
||||
}
|
||||
|
||||
void AddVertex(Vector3 position, Vector2 uv, Color color)
|
||||
{
|
||||
handVertices[vertCounter] = position;
|
||||
// UV.x will be the distance from center
|
||||
// this way, the shader could decide how best to remap: either with a gradient texture, or directly on UV.x
|
||||
handUVs[vertCounter] = uv;
|
||||
// using vertex alpha could also be an option for fading, if the UVs are needed elsewhere
|
||||
handColors[vertCounter] = color;
|
||||
vertCounter++;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user