139 lines
5.4 KiB
C#
139 lines
5.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using VF.Feature.Base;
|
|
using VF.Inspector;
|
|
using VF.Model;
|
|
using VF.Model.Feature;
|
|
|
|
namespace VF.Feature {
|
|
|
|
public class BoundingBoxFixBuilder : FeatureBuilder<BoundingBoxFix2> {
|
|
|
|
[FeatureBuilderAction(FeatureOrder.BoundingBoxFix)]
|
|
public void Apply() {
|
|
var skins = avatarObject.GetComponentsInChildren<SkinnedMeshRenderer>(true);
|
|
var startBounds = CalculateFullBounds();
|
|
|
|
float maxLinear = 0;
|
|
Renderer maxRenderer = null;
|
|
foreach (var renderer in avatarObject.GetComponentsInChildren<Renderer>(true)) {
|
|
var bounds = renderer.bounds;
|
|
bounds.Encapsulate(avatarObject.transform.position);
|
|
var extents = bounds.extents;
|
|
var linear = extents.x + extents.y + extents.z;
|
|
if (linear > maxLinear) {
|
|
maxLinear = linear;
|
|
maxRenderer = renderer;
|
|
}
|
|
}
|
|
|
|
if (maxRenderer != null) {
|
|
Debug.Log($"Largest renderer is {clipBuilder.GetPath(maxRenderer.transform)} with linear size of {maxLinear}");
|
|
}
|
|
|
|
foreach (var skin in skins) {
|
|
if (model.singleRenderer && model.singleRenderer != skin) {
|
|
continue;
|
|
}
|
|
//var debug = skin.gameObject.name == "Body";
|
|
var root = OGBUtils.GetMeshRoot(skin);
|
|
|
|
bool ModifyBounds(float sizeX = 0, float sizeY = 0, float sizeZ = 0, float centerX = 0, float centerY = 0, float centerZ = 0) {
|
|
var b = skin.localBounds;
|
|
var extents = b.extents;
|
|
extents.x += sizeX;
|
|
extents.y += sizeY;
|
|
extents.z += sizeZ;
|
|
b.extents = extents;
|
|
var center = b.center;
|
|
center.x += centerX;
|
|
center.y += centerY;
|
|
center.z += centerZ;
|
|
b.center = center;
|
|
|
|
var fullBak = startBounds;
|
|
var updatedBounds = GetUpdatedBounds(skin, b);
|
|
var fullNew = fullBak;
|
|
fullNew.Encapsulate(updatedBounds);
|
|
//if (debug) Debug.Log("Expanding to " + b + " updated world bounds: " + updatedBounds);
|
|
if (fullNew != fullBak) {
|
|
//if (debug) Debug.LogError("FAILED");
|
|
return false;
|
|
}
|
|
skin.localBounds = b;
|
|
return true;
|
|
}
|
|
|
|
var stepSizeInMeters = 0.05f;
|
|
var maxSteps = 20;
|
|
var stepSize = stepSizeInMeters / root.transform.lossyScale.x;
|
|
for (var i = 0; i < maxSteps; i++) {
|
|
ModifyBounds(sizeX: stepSize, centerX: -stepSize);
|
|
ModifyBounds(sizeX: stepSize, centerX: stepSize);
|
|
ModifyBounds(sizeY: stepSize, centerY: -stepSize);
|
|
ModifyBounds(sizeY: stepSize, centerY: stepSize);
|
|
ModifyBounds(sizeZ: stepSize, centerZ: -stepSize);
|
|
ModifyBounds(sizeZ: stepSize, centerZ: stepSize);
|
|
}
|
|
|
|
EditorUtility.SetDirty(skin);
|
|
}
|
|
}
|
|
|
|
Bounds GetUpdatedBounds(SkinnedMeshRenderer skin, Bounds newBounds) {
|
|
var root = OGBUtils.GetMeshRoot(skin);
|
|
|
|
List<Vector3> GetLocalCorners(Bounds obj) {
|
|
var result = new List<Vector3>();
|
|
for (var x = -1; x <= 1; x += 2)
|
|
for (var y = -1; y <= 1; y += 2)
|
|
for (var z = -1; z <= 1; z += 2)
|
|
result.Add(obj.center + Times(obj.extents, new Vector3(x, y, z)));
|
|
return result;
|
|
}
|
|
|
|
Vector3 Times(Vector3 self, Vector3 other) =>
|
|
new Vector3(self.x * other.x, self.y * other.y, self.z * other.z);
|
|
|
|
var corners = GetLocalCorners(newBounds);
|
|
var b = new Bounds(root.TransformPoint(newBounds.center), Vector3.zero);
|
|
foreach (var corner in corners) {
|
|
b.Encapsulate(root.TransformPoint(corner));
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
private Bounds CalculateFullBounds() {
|
|
var bounds = new Bounds(avatarObject.transform.position, Vector3.zero);
|
|
foreach (Renderer renderer in avatarObject.GetComponentsInChildren<Renderer>(true)) {
|
|
if (renderer is MeshRenderer || renderer is SkinnedMeshRenderer) {
|
|
bounds.Encapsulate(renderer.bounds);
|
|
}
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
public override bool AvailableOnProps() {
|
|
return false;
|
|
}
|
|
|
|
public override string GetEditorTitle() {
|
|
return "Bounding Box Fix";
|
|
}
|
|
|
|
public override VisualElement CreateEditor(SerializedProperty prop) {
|
|
var content = new VisualElement();
|
|
content.Add(VRCFuryEditorUtils.Info(
|
|
"This feature will ensure a minimum size of your avatar's bounding boxes. " +
|
|
"This will prevent small objects on your avatar from disappearing when near the camera."
|
|
));
|
|
return content;
|
|
}
|
|
}
|
|
}
|