using UnityEngine; namespace SLZ.VRMK { public partial class Avatar : MonoBehaviour { [System.Serializable] public struct SoftEllipse { public struct Constants { public const float RadiusMin = 0.01f; public const float RadiusMax = 0.1f; public const float RadiusDefault = 0.03f; public const float BiasMin = -1f; public const float BiasMax = 1f; public const float BiasDefault = 0f; } [Range(Constants.RadiusMin, Constants.RadiusMax)] public float XRadius; [Range(Constants.BiasMin, Constants.BiasMax)] public float XBias; [Range(Constants.RadiusMin, Constants.RadiusMax)] public float ZRadius; [Range(Constants.BiasMin, Constants.BiasMax)] public float ZBias; public SoftEllipse(float xRadius = Constants.RadiusDefault, float xBias = Constants.BiasDefault, float zRadius = Constants.RadiusDefault, float zBias = Constants.BiasDefault) { XRadius = xRadius; XBias = xBias; ZRadius = zRadius; ZBias = zBias; } } [System.Serializable] public class SoftBulge { [Range(0f, 90f)] public float apexX; [Range(.01f, .99f)] public float apexY; [Range(0f, .08f)] public float apexZ; [Range(0f, 1f)] public float upperY; [Range(0f, 1f)] public float lowerY; [Range(0f, 80f)] public float innerX; [Range(0f, 80f)] public float outerX; [Range(0f, 1f)] public float roundUpperInner; [Range(0f, 1f)] public float roundUpperOuter; [Range(0f, 1f)] public float roundLowerInner; [Range(0f, 1f)] public float roundLowerOuter; [Range(0f, .5f)] public float swellUpper; [Range(0f, .5f)] public float swellLower; [Range(0f, .5f)] public float swellInner; [Range(0f, .5f)] public float swellOuter; [Range(0f, 1f)] public float rigidity; public Transform primaryRt; public Transform secondaryLf; public Vector3 ComputeBulge(float yPerc, Vector2 sin, float bulgeYTop, float bulgeYBot, bool onBack) { float apexYAbs = Mathf.LerpUnclamped(bulgeYTop, bulgeYBot, apexY); float topToBotDiff = bulgeYBot - bulgeYTop; float minY = apexYAbs - (topToBotDiff * apexY) * upperY; float maxY = apexYAbs + (topToBotDiff * (1f - apexY)) * lowerY; if (onBack) sin.y = -sin.y; if (yPerc > maxY || yPerc < minY) return Vector3.zero; float yMultInv = 0f; float xMultInv = 0f; float xOffset = 0f; float yOffset = 0f; float zOffset = 0f; float innerRadians = (apexX - innerX) * Mathf.Deg2Rad; float apexRadians = apexX * Mathf.Deg2Rad; float outerRadians = (apexX + outerX) * Mathf.Deg2Rad; float innerSin = Mathf.Sin(innerRadians); float apexCos = Mathf.Cos(apexRadians); float apexSin = Mathf.Sin(apexRadians); float outerCos = Mathf.Cos(outerRadians); if (yPerc < apexYAbs) { float yMult = Mathf.InverseLerp(apexYAbs, minY, yPerc); yMultInv = 1f - yMult; if (sin.y < apexCos) { float xMult = Mathf.InverseLerp(apexCos, Mathf.LerpUnclamped(outerCos, apexCos, yMult * yMult * roundUpperOuter), sin.y); xMultInv = 1f - xMult; float swellXMult = Mathf.Abs(xMult - .5f) * 2f; swellXMult = 1f - (swellXMult * swellXMult); float swellX = swellOuter * apexZ * swellXMult * yMultInv; zOffset += swellX; xOffset += swellX; } else { float xMult = Mathf.InverseLerp(apexSin, Mathf.LerpUnclamped(innerSin, apexSin, yMult * yMult * roundUpperInner), Mathf.Abs(sin.x)); xMultInv = 1f - xMult; float swellXMult = Mathf.Abs(xMult - .5f) * 2f; swellXMult = 1f - (swellXMult * swellXMult); float swellX = swellInner * apexZ * swellXMult * yMultInv; zOffset += swellX; xOffset -= swellX; } float swellYMult = Mathf.Abs(yMult - .5f) * 2f; swellYMult = 1f - (swellYMult * swellYMult); float swellY = swellUpper * apexZ * swellYMult * xMultInv; zOffset += swellY; yOffset += swellY; } else { float yMult = Mathf.InverseLerp(apexYAbs, maxY, yPerc); yMultInv = 1f - yMult; if (sin.y < apexCos) { float xMult = Mathf.InverseLerp(apexCos, Mathf.LerpUnclamped(outerCos, apexCos, yMult * yMult * roundLowerOuter), sin.y); xMultInv = 1f - xMult; float swellXMult = Mathf.Abs(xMult - .5f) * 2f; swellXMult = 1f - (swellXMult * swellXMult); float swellX = swellOuter * apexZ * swellXMult * yMultInv; zOffset += swellX; xOffset += swellX; } else { float xMult = Mathf.InverseLerp(apexSin, Mathf.LerpUnclamped(innerSin, apexSin, yMult * yMult * roundLowerInner), Mathf.Abs(sin.x)); xMultInv = 1f - xMult; float swellXMult = Mathf.Abs(xMult - .5f) * 2f; swellXMult = 1f - (swellXMult * swellXMult); float swellX = swellInner * apexZ * swellXMult * yMultInv; zOffset += swellX; xOffset -= swellX; } float swellYMult = Mathf.Abs(yMult - .5f) * 2f; swellYMult = 1f - (swellYMult * swellYMult); float swellY = swellLower * apexZ * swellYMult * xMultInv; zOffset += swellY; yOffset -= swellY; } Vector3 softOffset = new Vector3((apexZ * yMultInv * xMultInv + zOffset) * sin.x, yOffset, (apexZ * yMultInv * xMultInv + zOffset) * sin.y); softOffset.x += xOffset * sin.y * Mathf.Sign(sin.x); softOffset.z += xOffset * sin.x * Mathf.Sign(sin.x); if (onBack) softOffset.z = -softOffset.z; return softOffset; } public SoftBulge() { this.apexX = 26f; this.apexY = .5f; this.apexZ = 0f; this.upperY = 1f; this.lowerY = 1f; this.innerX = 26f; this.outerX = 32f; this.roundUpperInner = .1f; this.roundUpperOuter = 0f; this.roundLowerInner = .9f; this.roundLowerOuter = .4f; this.swellUpper = .1f; this.swellLower = .2f; this.swellInner = .2f; this.swellOuter = .1f; this.rigidity = .5f; } public void Copy(SoftBulge bulge) { apexX = bulge.apexX; apexY = bulge.apexY; apexZ = bulge.apexZ; upperY = bulge.upperY; lowerY = bulge.lowerY; innerX = bulge.innerX; outerX = bulge.outerX; roundUpperInner = bulge.roundUpperInner; roundUpperOuter = bulge.roundUpperOuter; roundLowerInner = bulge.roundLowerInner; roundLowerOuter = bulge.roundLowerOuter; swellUpper = bulge.swellUpper; swellLower = bulge.swellLower; swellInner = bulge.swellInner; swellOuter = bulge.swellOuter; rigidity = bulge.rigidity; } } public Vector3 GetSoftTorso(float yPerc, Vector2 sin, AnimationCurve ellipseX, AnimationCurve ellipseZ, AnimationCurve ellipseNegZ, out Vector3 softDisplacement) { float x = ellipseX.Evaluate(yPerc); float xDeriv = Derivative(ellipseX, yPerc); float z; float zDeriv; if (sin.y >= 0f) { z = ellipseZ.Evaluate(yPerc); zDeriv = Derivative(ellipseZ, yPerc); } else { z = ellipseNegZ.Evaluate(yPerc); zDeriv = Derivative(ellipseNegZ, yPerc); } Vector3 torsoCoords = new Vector3(x * sin.x, 0f, z * sin.y); Vector3 breastDisplacement = bulgeBreast.ComputeBulge(yPerc, sin, 0f, UnderbustY, false); torsoCoords += breastDisplacement * bulgeBreast.rigidity; softDisplacement = breastDisplacement * (1f - bulgeBreast.rigidity); Vector3 upperBackDisplacement = bulgeUpperBack.ComputeBulge(yPerc, sin, 0f, UnderbustY, true); torsoCoords += upperBackDisplacement * bulgeUpperBack.rigidity; softDisplacement += upperBackDisplacement * (1f - bulgeUpperBack.rigidity); Vector3 abdomenDisplacement = bulgeAbdomen.ComputeBulge(yPerc, sin, UnderbustY, HighHipsY, false); torsoCoords += abdomenDisplacement * bulgeAbdomen.rigidity; softDisplacement += abdomenDisplacement * (1f - bulgeAbdomen.rigidity); Vector3 lowBackDisplacement = bulgeLowerBack.ComputeBulge(yPerc, sin, UnderbustY, HighHipsY, true); torsoCoords += lowBackDisplacement * bulgeLowerBack.rigidity; softDisplacement += lowBackDisplacement * (1f - bulgeLowerBack.rigidity); Vector3 groinDisplacement = bulgeGroin.ComputeBulge(yPerc, sin, HighHipsY, 2f, false); torsoCoords += groinDisplacement * bulgeGroin.rigidity; softDisplacement += groinDisplacement * (1f - bulgeGroin.rigidity); Vector3 buttDisplacement = bulgeButt.ComputeBulge(yPerc, sin, HighHipsY, 2f, true); torsoCoords += buttDisplacement * bulgeButt.rigidity; softDisplacement += buttDisplacement * (1f - bulgeButt.rigidity); return torsoCoords; } public AnimationCurve GenerateSpineCurve(float eyeHeight, Vector3 t7Local, Vector3 l1Local, Vector3 l3Local, Vector3 sacrumLocal, Vector3 sternumOffsetPerc, Vector3 hipsOffsetPerc, float t1HeightPerc) { float neckT = Mathf.Lerp(0f, -1f, -sternumOffsetPerc.y / (1f - (t1HeightPerc + sternumOffsetPerc.y))); neckT = Mathf.Min(neckT, -.15f); float jawT = Mathf.Lerp(-1f, 0f, _chinY * .6f / (1f - (t1HeightPerc + sternumOffsetPerc.y))); jawT = Mathf.Min(jawT, neckT - .05f); float divByEyeHeight = 1f / eyeHeight; Vector3 t7Perc = t7Local * divByEyeHeight; Vector3 l1Perc = l1Local * divByEyeHeight; Vector3 l3Perc = l3Local * divByEyeHeight; Vector3 sacrumPerc = sacrumLocal * divByEyeHeight; Vector3 t7PercLessSacrum = -sternumOffsetPerc + t7Perc; Vector3 totalThorLumbar = t7Perc + l1Perc + l3Perc + sacrumPerc; Vector3 totalSternumToHip = -sternumOffsetPerc + totalThorLumbar + hipsOffsetPerc; float upperChestT = t7PercLessSacrum.y / totalSternumToHip.y; Vector3 upperChestOffset = t7PercLessSacrum - (totalSternumToHip * upperChestT); float chestT = (t7PercLessSacrum.y + l1Perc.y) / totalSternumToHip.y; Vector3 chestOffset = t7PercLessSacrum + l1Perc - (totalSternumToHip * chestT); float spineT = (t7PercLessSacrum.y + l1Perc.y + l3Perc.y) / totalSternumToHip.y; Vector3 spineOffset = t7PercLessSacrum + l1Perc + l3Perc - (totalSternumToHip * spineT); _t7Y = upperChestT; _t7OffsetZ = upperChestOffset.z; _l1Y = chestT; _l1OffsetZ = chestOffset.z; _l3Y = spineT; _l3OffsetZ = spineOffset.z; float underbustZOffset = (_chestEllipseZ - _chestEllipseNegZ) * .2f; float waistZOffset = (_waistEllipseZ - _waistEllipseNegZ) * .4f; float highHipsZOffset = (_highHipsEllipseZ - _highHipsEllipseNegZ) * .4f; AnimationCurve spine = new AnimationCurve(new Keyframe(-2f, 0f, 0f, 0f, 0f, 0f), new Keyframe(-1.4f, 0f, 0f, 0f, 0f, 0f), new Keyframe(jawT, 0f, 0f, 0f, 0f, 0f), new Keyframe(neckT, 0f, 0f, 0f, 0f, 0f), new Keyframe(0f, 0f, 0f, 0f, 0f, 0f), new Keyframe(_underbustY, underbustZOffset, 0f, 0f, 0f, 0f), new Keyframe(_waistY, waistZOffset, 0f, 0f, 0f, 0f), new Keyframe(_highHipY, highHipsZOffset, 0f, 0f, 0f, 0f), new Keyframe(1f, 0f, 0f, 0f, 0f, 0f), new Keyframe(2f, 0f, 0f, 0f, 0f, 0f)); return spine; } public AnimationCurve GenerateTorsoZCurve(Vector3 sternumOffsetPerc, float t1HeightPerc, AnimationCurve spineZ) { (float neckT, float jawT) = GetNeckAndJawT(sternumOffsetPerc, t1HeightPerc); var ellipseZ = new AnimationCurve(new Keyframe(-2f, .003f, _headEllipseZ * 3.141f, _headEllipseZ * 3.141f), new Keyframe(-1.4f, _headEllipseZ - spineZ.Evaluate(-1.4f), 0f, 0f), new Keyframe(jawT, _jawEllipseZ - spineZ.Evaluate(jawT), 0f, 0f), new Keyframe(neckT, _neckEllipseZ - spineZ.Evaluate(neckT), 0f, 0f), new Keyframe(0f, _sternumEllipseZ - spineZ.Evaluate(0f), (_neckEllipseZ - _sternumEllipseZ) / neckT, (_chestEllipseZ - _sternumEllipseZ) / _underbustY), new Keyframe(_underbustY, _chestEllipseZ - spineZ.Evaluate(_underbustY), 0f, 0f), new Keyframe(_waistY, _waistEllipseZ - spineZ.Evaluate(_waistY), 0f, 0f), new Keyframe(_highHipY, _highHipsEllipseZ - spineZ.Evaluate(_highHipY), 0f, 0f), new Keyframe(1f, _hipsEllipseZ - spineZ.Evaluate(1f), (_hipsEllipseZ - _highHipsEllipseZ) * (1f - _highHipY) * 4f, (_hipsEllipseZ - _highHipsEllipseZ) * (2f - 1f)), new Keyframe(2f, .003f, _hipsEllipseZ * -3.141f, _hipsEllipseZ * -3.141f)); return ellipseZ; } public AnimationCurve GenerateTorsoNegZCurve(Vector3 sternumOffsetPerc, float t1HeightPerc, AnimationCurve spineZ) { (float neckT, float jawT) = GetNeckAndJawT(sternumOffsetPerc, t1HeightPerc); var ellipseNegZ = new AnimationCurve(new Keyframe(-2f, .003f, _headEllipseNegZ * 3.141f, _headEllipseNegZ * 3.141f), new Keyframe(-1.4f, _headEllipseNegZ + spineZ.Evaluate(-1.4f), 0f, 0f), new Keyframe(jawT, _jawEllipseNegZ + spineZ.Evaluate(jawT), 0f, 0f), new Keyframe(neckT, _neckEllipseNegZ + spineZ.Evaluate(neckT), 0f, 0f), new Keyframe(0f, _sternumEllipseNegZ + spineZ.Evaluate(0f), 0f, 0f), new Keyframe(_underbustY, _chestEllipseNegZ + spineZ.Evaluate(_underbustY), 0f, 0f), new Keyframe(_waistY, _waistEllipseNegZ + spineZ.Evaluate(_waistY), 0f, 0f), new Keyframe(_highHipY, _highHipsEllipseNegZ + spineZ.Evaluate(_highHipY), 0f, 0f), new Keyframe(1f, _hipsEllipseNegZ + spineZ.Evaluate(1f), 0f, 0f), new Keyframe(2f, .003f, _hipsEllipseNegZ * -3.141f, _hipsEllipseNegZ * -3.141f)); return ellipseNegZ; } public AnimationCurve GenerateTorsoXCurve(float sternumEllipseX, float hipEllipseX, Vector3 sternumOffsetPerc, float t1HeightPerc) { (float neckT, float jawT) = GetNeckAndJawT(sternumOffsetPerc, t1HeightPerc); var ellipseX = new AnimationCurve(new Keyframe(-2f, .003f, _headEllipseX * 3.141f, _headEllipseX * 3.141f), new Keyframe(-1.4f, _headEllipseX, 0f, 0f), new Keyframe(jawT, _jawEllipseX, 0f, 0f), new Keyframe(neckT, _neckEllipseX, 0f, 0f), new Keyframe(0f, sternumEllipseX, 0f, 0f), new Keyframe(_underbustY, _chestEllipseX, 0f, 0f), new Keyframe(_waistY, _waistEllipseX, 0f, 0f), new Keyframe(_highHipY, _highHipsEllipseX, 0f, 0f), new Keyframe(1f, _hipsEllipseX, 0f, 0f), new Keyframe(2f, hipEllipseX, (_hipsEllipseX - hipEllipseX) * -3.141f, (_hipsEllipseX - hipEllipseX) * -3.141f)); return ellipseX; } private (float, float) GetNeckAndJawT(Vector3 sternumOffsetPerc, float t1HeightPerc) { float neckT = Mathf.Lerp(0f, -1f, -sternumOffsetPerc.y / (1f - (t1HeightPerc + sternumOffsetPerc.y))); neckT = Mathf.Min(neckT, -.15f); float jawT = Mathf.Lerp(-1f, 0f, _chinY * .6f / (1f - (t1HeightPerc + sternumOffsetPerc.y))); jawT = Mathf.Min(jawT, neckT - .05f); return (neckT, jawT); } public float Derivative(AnimationCurve self, float time) { if (self == null) return 0.0f; for (int i = 0; i < self.length - 1; i++) { if (time < self[i].time) continue; if (time > self[i + 1].time) continue; return Derivative(self[i], self[i + 1], (time - self[i].time) / (self[i + 1].time - self[i].time)); } return 0.0f; } private float Derivative(Keyframe from, Keyframe to, float lerp) { float dt = to.time - from.time; float m0 = from.outTangent * dt; float m1 = to.inTangent * dt; float lerp2 = lerp * lerp; float a = 6.0f * lerp2 - 6.0f * lerp; float b = 3.0f * lerp2 - 4.0f * lerp + 1.0f; float c = 3.0f * lerp2 - 2.0f * lerp; float d = -a; return a * from.value + b * m0 + c * m1 + d * to.value; } } }