|
|
|
|
|
using System;
|
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
namespace FluffyGroomingTool {
|
|
|
|
|
|
[CustomEditor(typeof(FurRenderer))]
|
|
|
|
|
|
public class FurRendererObjectEditor : Editor {
|
|
|
|
|
|
private GeneralSettingsUI generalSettingsUI;
|
|
|
|
|
|
private MotionSettingsUI motionSettingsUI;
|
|
|
|
|
|
private WindSettingUI windSettingUI;
|
|
|
|
|
|
private CollidersUI collidersUI;
|
|
|
|
|
|
private NormalUI normalUI;
|
|
|
|
|
|
private LodUi lodUI;
|
|
|
|
|
|
private Styles styles = new Styles();
|
|
|
|
|
|
private FurRenderer furRenderer;
|
|
|
|
|
|
|
|
|
|
|
|
private void initialize() {
|
|
|
|
|
|
furRenderer = serializedObject.targetObject as FurRenderer;
|
|
|
|
|
|
generalSettingsUI = new GeneralSettingsUI(serializedObject, "Main Settings", "isMainExpanded") {styles = styles};
|
|
|
|
|
|
collidersUI = new CollidersUI(serializedObject, "Colliders", "isColliderExpanded") {
|
|
|
|
|
|
styles = styles,
|
|
|
|
|
|
furRenderer = furRenderer
|
|
|
|
|
|
};
|
|
|
|
|
|
lodUI = new LodUi(serializedObject, "LOD & Culling", "isLodExpanded") {
|
|
|
|
|
|
styles = styles,
|
|
|
|
|
|
furRenderer = furRenderer
|
|
|
|
|
|
};
|
|
|
|
|
|
motionSettingsUI = new MotionSettingsUI(serializedObject, "Movement", "isMovementExpanded") {
|
|
|
|
|
|
styles = styles,
|
|
|
|
|
|
furRenderer = furRenderer
|
|
|
|
|
|
};
|
|
|
|
|
|
windSettingUI = new WindSettingUI(serializedObject, "Wind", "isWindExpanded") {styles = styles};
|
|
|
|
|
|
normalUI = new NormalUI(serializedObject, "Normals", "isNormalExpanded") {
|
|
|
|
|
|
styles = styles,
|
|
|
|
|
|
furRenderer = furRenderer
|
|
|
|
|
|
};
|
|
|
|
|
|
EditorUtility.SetSelectedRenderState((serializedObject.targetObject as MonoBehaviour)?.GetComponent<Renderer>(),
|
|
|
|
|
|
EditorSelectedRenderState.Hidden);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void OnInspectorGUI() {
|
|
|
|
|
|
if (generalSettingsUI == null) initialize();
|
|
|
|
|
|
serializedObject.Update();
|
|
|
|
|
|
|
|
|
|
|
|
generalSettingsUI.drawUI();
|
|
|
|
|
|
if (generalSettingsUI.hasNoFurContainer()) {
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
motionSettingsUI.drawUI();
|
|
|
|
|
|
normalUI.drawUI();
|
|
|
|
|
|
windSettingUI.drawUI();
|
|
|
|
|
|
collidersUI.drawUI();
|
|
|
|
|
|
lodUI.drawUI();
|
|
|
|
|
|
generalSettingsUI.drawMaterialUI();
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
if (GUI.changed) {
|
|
|
|
|
|
furRenderer.updateLod();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class Styles {
|
|
|
|
|
|
private GUIStyle _panelStyle;
|
|
|
|
|
|
|
|
|
|
|
|
public GUIStyle PanelStyle {
|
|
|
|
|
|
get {
|
|
|
|
|
|
if (_panelStyle == null) _panelStyle = createDefaultPanelStyle();
|
|
|
|
|
|
return _panelStyle;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static GUIStyle createDefaultPanelStyle() {
|
|
|
|
|
|
return new GUIStyle(GUI.skin.label) {
|
|
|
|
|
|
border = new RectOffset(20, 20, 20, 20), padding = new RectOffset(15, 16, 15, 17),
|
|
|
|
|
|
normal = {background = Resources.Load<Texture2D>("bg_box")}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool addColapsibleHeader(bool isExpanded, string text) {
|
|
|
|
|
|
EditorGUILayout.BeginVertical();
|
|
|
|
|
|
isExpanded = EditorGUILayout.Foldout(isExpanded, text);
|
|
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
|
|
EditorGUILayout.Space(4);
|
|
|
|
|
|
return isExpanded;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class GeneralSettingsUI : HeaderUI {
|
|
|
|
|
|
readonly SerializedProperty furContainer;
|
|
|
|
|
|
readonly SerializedProperty hairContainer;
|
|
|
|
|
|
readonly SerializedProperty material;
|
|
|
|
|
|
private readonly SerializedProperty isMotionVectorEnabled;
|
|
|
|
|
|
private readonly SerializedProperty isUrp;
|
|
|
|
|
|
MaterialEditor matEditor;
|
|
|
|
|
|
private GUIStyle buttonStyle;
|
|
|
|
|
|
private string materialName;
|
|
|
|
|
|
|
|
|
|
|
|
public GeneralSettingsUI(SerializedObject serializedObject, string header, string headerProperty) : base(serializedObject, header,
|
|
|
|
|
|
headerProperty) {
|
|
|
|
|
|
furContainer = serializedObject.FindProperty("furContainer");
|
|
|
|
|
|
hairContainer = serializedObject.FindProperty("hairContainer");
|
|
|
|
|
|
material = serializedObject.FindProperty("material");
|
|
|
|
|
|
isMotionVectorEnabled = serializedObject.FindProperty("motionVectors");
|
|
|
|
|
|
isUrp = serializedObject.FindProperty("isUrp");
|
|
|
|
|
|
buttonStyle = PainterLayersUI.createButtonStyle("bg_button", "bg_button_hover");
|
|
|
|
|
|
buttonStyle.alignment = TextAnchor.MiddleCenter;
|
|
|
|
|
|
materialName = serializedObject.targetObject.name + "FluffyMaterial";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void drawContent() {
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
EditorGUILayout.PropertyField(material);
|
|
|
|
|
|
if (!AssetDatabase.Contains(material.objectReferenceValue)) {
|
|
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
|
|
if (GUILayout.Button("Save Material To Disk", buttonStyle)) {
|
|
|
|
|
|
var path = EditorUtility.SaveFilePanel("Save Material", "Assets/", materialName, "mat");
|
|
|
|
|
|
if (!string.IsNullOrEmpty(path)) {
|
|
|
|
|
|
path = FileUtil.GetProjectRelativePath(path);
|
|
|
|
|
|
AssetDatabase.CreateAsset(material.objectReferenceValue, path);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(15);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (hairContainer != null) EditorGUILayout.PropertyField(hairContainer);
|
|
|
|
|
|
if (furContainer != null) EditorGUILayout.PropertyField(furContainer);
|
|
|
|
|
|
|
|
|
|
|
|
if (!isUrp.boolValue) {
|
|
|
|
|
|
EditorGUILayout.PropertyField(isMotionVectorEnabled);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void drawMaterialUI() {
|
|
|
|
|
|
Material mat = material.objectReferenceValue as Material;
|
|
|
|
|
|
if (material.objectReferenceValue != null) {
|
|
|
|
|
|
if (matEditor == null || matEditor.target != mat) {
|
|
|
|
|
|
matEditor = (MaterialEditor) Editor.CreateEditor(mat);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
matEditor.DrawHeader();
|
|
|
|
|
|
matEditor.OnInspectorGUI();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public bool hasNoFurContainer() { return furContainer.objectReferenceValue == null; }
|
|
|
|
|
|
|
|
|
|
|
|
public bool hasNoHairContainer() { return hairContainer.objectReferenceValue == null; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MotionSettingsUI : HeaderUI {
|
|
|
|
|
|
private SerializedProperty enablePhysics;
|
|
|
|
|
|
private SerializedProperty perLayerMotionSettings;
|
|
|
|
|
|
private SerializedProperty constraintIterations;
|
|
|
|
|
|
private SerializedProperty gravity;
|
|
|
|
|
|
private SerializedProperty drag;
|
|
|
|
|
|
private SerializedProperty stiffnessRoot;
|
|
|
|
|
|
private SerializedProperty stiffnessTip;
|
|
|
|
|
|
private SerializedProperty keepShapeStrength;
|
|
|
|
|
|
private SerializedProperty isFirstNodeFixed;
|
|
|
|
|
|
internal FurRenderer furRenderer;
|
|
|
|
|
|
|
|
|
|
|
|
public MotionSettingsUI(SerializedObject serializedObject, string header, string headerProperty,
|
|
|
|
|
|
string settingsName = "furRendererSettings") :
|
|
|
|
|
|
base(serializedObject, header, headerProperty) {
|
|
|
|
|
|
enablePhysics = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.enableMovement");
|
|
|
|
|
|
perLayerMotionSettings = serializedObject.FindProperty(settingsName + ".perLayerMotionSettings");
|
|
|
|
|
|
constraintIterations = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.constraintIterations");
|
|
|
|
|
|
gravity = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.gravity");
|
|
|
|
|
|
drag = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.drag");
|
|
|
|
|
|
stiffnessRoot = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.stiffnessRoot");
|
|
|
|
|
|
stiffnessTip = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.stiffnessTip");
|
|
|
|
|
|
isFirstNodeFixed = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.isFirstNodeFixed");
|
|
|
|
|
|
if (!settingsName.Equals("furRendererSettings")) {
|
|
|
|
|
|
keepShapeStrength = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.keepShapeStrength");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private FluffyToolbar fluffyToolbar = new FluffyToolbar() {
|
|
|
|
|
|
activatedColor = new Color32(55, 210, 232, 255),
|
|
|
|
|
|
activatedColorHover = new Color32(55, 210, 232, 200)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int selectedLayerIndex;
|
|
|
|
|
|
|
|
|
|
|
|
public override void drawContent() {
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
|
|
|
|
|
|
if (furRenderer != null) {
|
|
|
|
|
|
EditorGUILayout.PropertyField(perLayerMotionSettings);
|
|
|
|
|
|
if (perLayerMotionSettings.boolValue) {
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
selectedLayerIndex = Math.Min(furRenderer.furContainer.layerStrandsList.Length - 1, selectedLayerIndex);
|
|
|
|
|
|
selectedLayerIndex = fluffyToolbar.drawToolbar(selectedLayerIndex, furRenderer.getLayersList(), null);
|
|
|
|
|
|
|
|
|
|
|
|
if (furRenderer.furContainer.layerStrandsList[selectedLayerIndex].verletSimulationSettings == null) {
|
|
|
|
|
|
initializeLayerSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var verletSettings = furRenderer.furContainer.layerStrandsList[selectedLayerIndex].verletSimulationSettings;
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(-5);
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
|
verletSettings?.also(settings => {
|
|
|
|
|
|
settings.enableMovement = EditorGUILayout.Toggle("Enable Movement", settings.enableMovement);
|
|
|
|
|
|
if (settings.enableMovement) {
|
|
|
|
|
|
settings.drag = EditorGUILayout.Slider("Drag", settings.drag, 0f, 1f);
|
|
|
|
|
|
settings.gravity = EditorGUILayout.Vector3Field("Gravity", settings.gravity);
|
|
|
|
|
|
settings.stiffnessRoot = EditorGUILayout.Slider("Stiffness Root", settings.stiffnessRoot, 0, 200);
|
|
|
|
|
|
settings.stiffnessTip = EditorGUILayout.Slider("Stiffness Tip", settings.stiffnessTip, 0, 200);
|
|
|
|
|
|
settings.constraintIterations = EditorGUILayout.IntSlider("Constraint Iterations", settings.constraintIterations, 4, 32);
|
|
|
|
|
|
settings.isFirstNodeFixed = EditorGUILayout.Toggle("First Node Is Fixed", settings.isFirstNodeFixed);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
|
|
|
|
EditorUtility.SetDirty(furRenderer.furContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
EditorGUILayout.PropertyField(enablePhysics);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (enablePhysics.boolValue && !perLayerMotionSettings.boolValue) {
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
EditorGUILayout.PropertyField(drag);
|
|
|
|
|
|
EditorGUILayout.PropertyField(gravity);
|
|
|
|
|
|
EditorGUILayout.PropertyField(stiffnessRoot);
|
|
|
|
|
|
EditorGUILayout.PropertyField(stiffnessTip);
|
|
|
|
|
|
EditorGUILayout.PropertyField(constraintIterations);
|
|
|
|
|
|
EditorGUILayout.PropertyField(isFirstNodeFixed);
|
|
|
|
|
|
if (keepShapeStrength != null) EditorGUILayout.PropertyField(keepShapeStrength);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void initializeLayerSettings() {
|
|
|
|
|
|
foreach (var layer in furRenderer.furContainer.layerStrandsList) {
|
|
|
|
|
|
layer.verletSimulationSettings = (VerletSimulationSettings) furRenderer.furRendererSettings.verletSimulationSettings.Clone();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static class Extensions {
|
|
|
|
|
|
public static string[] getLayersList(this FurRenderer furRenderer) {
|
|
|
|
|
|
var layerNames = new string[furRenderer.furContainer.layerStrandsList.Length];
|
|
|
|
|
|
for (var i = 0; i < layerNames.Length; i++) {
|
|
|
|
|
|
if (string.IsNullOrEmpty(furRenderer.furContainer.layerStrandsList[i].layerName)) {
|
|
|
|
|
|
furRenderer.furContainer.layerStrandsList[i].layerName = "Layer " + i;
|
|
|
|
|
|
EditorUtility.SetDirty(furRenderer.furContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
layerNames[i] = furRenderer.furContainer.layerStrandsList[i].layerName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return layerNames;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class WindSettingUI : HeaderUI {
|
|
|
|
|
|
private readonly SerializedProperty gustFrequency;
|
|
|
|
|
|
private readonly SerializedProperty windStrength;
|
|
|
|
|
|
private readonly SerializedProperty windDirectionDegree;
|
|
|
|
|
|
|
|
|
|
|
|
public WindSettingUI(SerializedObject serializedObject, string header, string headerProperty, string settingsName = "furRendererSettings") :
|
|
|
|
|
|
base(serializedObject, header, headerProperty) {
|
|
|
|
|
|
gustFrequency = serializedObject.FindProperty(settingsName + ".windProperties.gustFrequency");
|
|
|
|
|
|
windStrength = serializedObject.FindProperty(settingsName + ".windProperties.windStrength");
|
|
|
|
|
|
windDirectionDegree = serializedObject.FindProperty(settingsName + ".windProperties.windDirectionDegree");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void drawContent() {
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
EditorGUILayout.PropertyField(gustFrequency);
|
|
|
|
|
|
EditorGUILayout.PropertyField(windStrength);
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
EditorGUILayout.PropertyField(windDirectionDegree);
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class CollidersUI : HeaderUI {
|
|
|
|
|
|
private readonly SerializedProperty sdfColliderResolution;
|
|
|
|
|
|
private readonly SerializedProperty useForwardCollision;
|
|
|
|
|
|
private readonly SerializedProperty sphereColliders;
|
|
|
|
|
|
private readonly SerializedProperty sdfColliders;
|
|
|
|
|
|
private readonly SerializedProperty capsuleColliders;
|
|
|
|
|
|
private GUIStyle leftPaddingStyle;
|
|
|
|
|
|
private readonly SerializedProperty collideWithSourceMesh;
|
|
|
|
|
|
private readonly SerializedProperty colliderSkinWidth;
|
|
|
|
|
|
public FurRenderer furRenderer;
|
|
|
|
|
|
public HairRenderer hairRenderer;
|
|
|
|
|
|
|
|
|
|
|
|
public CollidersUI(SerializedObject serializedObject, string header, string headerProperty,
|
|
|
|
|
|
string settingsName = "furRendererSettings") : base(serializedObject, header, headerProperty) {
|
|
|
|
|
|
sphereColliders = serializedObject.FindProperty("sphereColliders");
|
|
|
|
|
|
capsuleColliders = serializedObject.FindProperty("capsuleColliders");
|
|
|
|
|
|
sdfColliders = serializedObject.FindProperty("sdfColliders");
|
|
|
|
|
|
collideWithSourceMesh = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.collideWithSourceMesh");
|
|
|
|
|
|
colliderSkinWidth = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.colliderSkinWidth");
|
|
|
|
|
|
sdfColliderResolution = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.sdfColliderResolution");
|
|
|
|
|
|
useForwardCollision = serializedObject.FindProperty(settingsName + ".verletSimulationSettings.useForwardCollision");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static GUIStyle extraLeftPadding() {
|
|
|
|
|
|
return new GUIStyle(GUI.skin.label) {
|
|
|
|
|
|
border = new RectOffset(0, 0, 0, 0), padding = new RectOffset(15, 0, 0, 0)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void drawContent() {
|
|
|
|
|
|
if (leftPaddingStyle == null) {
|
|
|
|
|
|
leftPaddingStyle = extraLeftPadding();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
|
|
|
|
|
|
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) {
|
|
|
|
|
|
EditorGUILayout.LabelField("SDF Colliders are currently not supported on Android.");
|
|
|
|
|
|
if (furRenderer != null) furRenderer.furRendererSettings.verletSimulationSettings.isUnsupportedSDFPlatform = true;
|
|
|
|
|
|
if (hairRenderer != null) hairRenderer.settings.verletSimulationSettings.isUnsupportedSDFPlatform = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
EditorGUILayout.PropertyField(collideWithSourceMesh, new GUIContent("Collide With Source Mesh(SDF)"));
|
|
|
|
|
|
if (furRenderer != null) furRenderer.furRendererSettings.verletSimulationSettings.isUnsupportedSDFPlatform = false;
|
|
|
|
|
|
if (hairRenderer != null) hairRenderer.settings.verletSimulationSettings.isUnsupportedSDFPlatform = false;
|
|
|
|
|
|
if (collideWithSourceMesh.boolValue) {
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
EditorGUILayout.PropertyField(colliderSkinWidth);
|
|
|
|
|
|
|
|
|
|
|
|
var res = sdfColliderResolution.intValue;
|
|
|
|
|
|
EditorGUILayout.PropertyField(sdfColliderResolution);
|
|
|
|
|
|
if (res != sdfColliderResolution.intValue) {
|
|
|
|
|
|
if (furRenderer != null) furRenderer.recreateSdfCollider();
|
|
|
|
|
|
if (hairRenderer != null) hairRenderer.recreateSdfCollider();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.PropertyField(useForwardCollision);
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
GUILayout.BeginVertical(leftPaddingStyle);
|
|
|
|
|
|
EditorGUILayout.PropertyField(sphereColliders);
|
|
|
|
|
|
EditorGUILayout.PropertyField(capsuleColliders);
|
|
|
|
|
|
if (furRenderer != null && !furRenderer.furRendererSettings.verletSimulationSettings.isUnsupportedSDFPlatform) {
|
|
|
|
|
|
EditorGUILayout.PropertyField(sdfColliders);
|
|
|
|
|
|
} else if (hairRenderer != null && !hairRenderer.settings.verletSimulationSettings.isUnsupportedSDFPlatform) {
|
|
|
|
|
|
EditorGUILayout.PropertyField(sdfColliders);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public abstract class HeaderUI {
|
|
|
|
|
|
readonly SerializedProperty isExpanded;
|
|
|
|
|
|
private string _header;
|
|
|
|
|
|
public Styles styles;
|
|
|
|
|
|
|
|
|
|
|
|
public HeaderUI(SerializedObject serializedObject, string header, string headerProperty) {
|
|
|
|
|
|
isExpanded = serializedObject.FindProperty("headerExpanded." + headerProperty);
|
|
|
|
|
|
if (isExpanded == null) Debug.Log("header" + headerProperty);
|
|
|
|
|
|
_header = header;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void drawUI() {
|
|
|
|
|
|
if (isExpanded == null) Debug.Log(isExpanded.displayName);
|
|
|
|
|
|
isExpanded.boolValue = styles.addColapsibleHeader(isExpanded.boolValue, _header);
|
|
|
|
|
|
if (isExpanded.boolValue) {
|
|
|
|
|
|
drawContent();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public abstract void drawContent();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class NormalUI : HeaderUI {
|
|
|
|
|
|
readonly SerializedProperty sourceMeshNormalToStrandNormalPercent;
|
|
|
|
|
|
readonly SerializedProperty perLayerNormals;
|
|
|
|
|
|
private Rect boundsRect;
|
|
|
|
|
|
public bool hasRenderer = true;
|
|
|
|
|
|
internal FurRenderer furRenderer;
|
|
|
|
|
|
|
|
|
|
|
|
public NormalUI(SerializedObject serializedObject, string header, string headerProperty, string settingsName = "furRendererSettings") : base(
|
|
|
|
|
|
serializedObject, header, headerProperty) {
|
|
|
|
|
|
sourceMeshNormalToStrandNormalPercent = serializedObject.FindProperty(settingsName + ".sourceMeshNormalToStrandNormalPercent");
|
|
|
|
|
|
perLayerNormals = serializedObject.FindProperty(settingsName + ".perLayerNormals");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private FluffyToolbar fluffyToolbar = new FluffyToolbar() {
|
|
|
|
|
|
activatedColor = new Color32(55, 210, 232, 255),
|
|
|
|
|
|
activatedColorHover = new Color32(55, 210, 232, 200)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int selectedLayerIndex;
|
|
|
|
|
|
|
|
|
|
|
|
public override void drawContent() {
|
|
|
|
|
|
if (hasRenderer) {
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
if (furRenderer != null) {
|
|
|
|
|
|
EditorGUILayout.PropertyField(perLayerNormals);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
|
|
|
|
|
|
if (perLayerNormals.boolValue) {
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
|
|
|
|
|
|
selectedLayerIndex = fluffyToolbar.drawToolbar(selectedLayerIndex, furRenderer.getLayersList(), null);
|
|
|
|
|
|
|
|
|
|
|
|
if (getCurrentLayerNormalPercent() < 0) initializeLayerNormalSettings();
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(-5);
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
|
|
|
|
|
|
|
drawNormalLabels();
|
|
|
|
|
|
furRenderer.furContainer.layerStrandsList[selectedLayerIndex].sourceMeshNormalToStrandNormalPercent =
|
|
|
|
|
|
GUI.HorizontalSlider(boundsRect, getCurrentLayerNormalPercent(), 0, 1);
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
|
|
|
|
EditorUtility.SetDirty(furRenderer.furContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
drawNormalLabels();
|
|
|
|
|
|
if (boundsRect != null) {
|
|
|
|
|
|
sourceMeshNormalToStrandNormalPercent.floatValue =
|
|
|
|
|
|
GUI.HorizontalSlider(boundsRect, sourceMeshNormalToStrandNormalPercent.floatValue, 0, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float getCurrentLayerNormalPercent() {
|
|
|
|
|
|
return furRenderer.furContainer.layerStrandsList[selectedLayerIndex].sourceMeshNormalToStrandNormalPercent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawNormalLabels() {
|
|
|
|
|
|
EditorGUILayout.LabelField("Source Mesh Normal");
|
|
|
|
|
|
var lastRect = GUILayoutUtility.GetLastRect();
|
|
|
|
|
|
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
|
|
|
|
GUI.Label(lastRect, "Card Normal");
|
|
|
|
|
|
EditorGUILayout.Space(18);
|
|
|
|
|
|
lastRect = GUILayoutUtility.GetLastRect();
|
|
|
|
|
|
if (lastRect.x != 0) {
|
|
|
|
|
|
boundsRect = lastRect;
|
|
|
|
|
|
boundsRect.height = 18;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void initializeLayerNormalSettings() {
|
|
|
|
|
|
foreach (var layer in furRenderer.furContainer.layerStrandsList) {
|
|
|
|
|
|
if (layer.sourceMeshNormalToStrandNormalPercent < 0) {
|
|
|
|
|
|
layer.sourceMeshNormalToStrandNormalPercent = furRenderer.furRendererSettings.sourceMeshNormalToStrandNormalPercent;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal class LodUi : HeaderUI {
|
|
|
|
|
|
private readonly SerializedProperty isLodEnabled;
|
|
|
|
|
|
private readonly SerializedProperty isLodEnabledInEditMode;
|
|
|
|
|
|
private readonly SerializedProperty isFrustumCullingEnabled;
|
|
|
|
|
|
private readonly SerializedProperty isAlphaSortingEnabled;
|
|
|
|
|
|
private readonly SerializedProperty isHdrp;
|
|
|
|
|
|
private readonly SerializedProperty activeLodCameraName;
|
|
|
|
|
|
private readonly SerializedProperty activeLodName;
|
|
|
|
|
|
private readonly SerializedProperty lodCamera;
|
|
|
|
|
|
private Rect boundsRect;
|
|
|
|
|
|
private readonly SerializedObject serializedObject;
|
|
|
|
|
|
private GUIStyle helpStyle;
|
|
|
|
|
|
internal FurRenderer furRenderer;
|
|
|
|
|
|
|
|
|
|
|
|
public LodUi(SerializedObject serializedObject, string header, string headerProperty) : base(serializedObject, header, headerProperty) {
|
|
|
|
|
|
this.serializedObject = serializedObject;
|
|
|
|
|
|
isLodEnabled = serializedObject.FindProperty("furRendererSettings.enableLod");
|
|
|
|
|
|
isLodEnabledInEditMode = serializedObject.FindProperty("furRendererSettings.enableLodInEditMode");
|
|
|
|
|
|
isFrustumCullingEnabled = serializedObject.FindProperty("furRendererSettings.isFrustumCullingEnabled");
|
|
|
|
|
|
activeLodCameraName = serializedObject.FindProperty("activeLodCameraName");
|
|
|
|
|
|
activeLodName = serializedObject.FindProperty("activeLodName");
|
|
|
|
|
|
isAlphaSortingEnabled = serializedObject.FindProperty("furRendererSettings.isAlphaSortingEnabled");
|
|
|
|
|
|
lodCamera = serializedObject.FindProperty("lodCamera");
|
|
|
|
|
|
isHdrp = serializedObject.FindProperty("isHdrp");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private int lodTab;
|
|
|
|
|
|
private GUIStyle infoStyle;
|
|
|
|
|
|
|
|
|
|
|
|
private FluffyToolbar fluffyToolbar = new FluffyToolbar() {
|
|
|
|
|
|
activatedColor = new Color32(55, 210, 232, 255),
|
|
|
|
|
|
activatedColorHover = new Color32(55, 210, 232, 200)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public override void drawContent() {
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
EditorGUILayout.PropertyField(isLodEnabled);
|
|
|
|
|
|
|
|
|
|
|
|
if (isLodEnabled.boolValue) {
|
|
|
|
|
|
var furContainerProperty = serializedObject.FindProperty("furContainer").objectReferenceValue;
|
|
|
|
|
|
if (furContainerProperty != null) {
|
|
|
|
|
|
EditorGUILayout.PropertyField(lodCamera);
|
|
|
|
|
|
drawLodEnabledInEditModeUi();
|
|
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
|
|
lodTab = fluffyToolbar.drawToolbar(lodTab, new[] {"Lod 1", "Lod 2", "Lod 3", "Culled"}, null);
|
|
|
|
|
|
drawLodTabContentUi(furContainerProperty as FurContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
drawAlphaSortingAndCullingUi();
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawAlphaSortingAndCullingUi() {
|
|
|
|
|
|
EditorGUILayout.PropertyField(isAlphaSortingEnabled,
|
|
|
|
|
|
new GUIContent("Alpha sorting",
|
|
|
|
|
|
" Should Alpha sorting be enabled. Needed when using transparent materials. This can have a huge " +
|
|
|
|
|
|
"negative impact on performance if the " +
|
|
|
|
|
|
"fur mesh has a high triangle count. But it works pretty well for moderate triangle counts."));
|
|
|
|
|
|
if (isAlphaSortingEnabled.boolValue && isHdrp.boolValue) {
|
|
|
|
|
|
if (helpStyle == null) {
|
|
|
|
|
|
helpStyle = BrushPropertiesUI.getHelpTextStyle();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.LabelField(
|
|
|
|
|
|
"Performance waring: Alpha sorting should only be used when using card based rendering with a transparent material. Currently not supported with ray tracing.",
|
|
|
|
|
|
helpStyle
|
|
|
|
|
|
);
|
|
|
|
|
|
GUILayout.Space(PainterResetAndSmoothUI.DEFAULT_CHILD_VERTICAL_MARGIN);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.PropertyField(isFrustumCullingEnabled,
|
|
|
|
|
|
new GUIContent("Frustum culling per triangle",
|
|
|
|
|
|
"Culls away the triangles that are outside of the camera frustum. This may improve performance since it only renders " +
|
|
|
|
|
|
"visible triangles, but can in some cases lead to shadow artifacts."));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawLodEnabledInEditModeUi() {
|
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
|
EditorGUILayout.PropertyField(isLodEnabledInEditMode, new GUIContent("LOD & Culling In Edit Mode",
|
|
|
|
|
|
"This will enable LOD in edit mode as well. Make sure you disable it whenever you want to groom some changes."));
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck() && !isLodEnabledInEditMode.boolValue) {
|
|
|
|
|
|
furRenderer.cullAndSortController.setLodIndex(0, furRenderer.furContainer);
|
|
|
|
|
|
furRenderer.cullAndSortController.dispatchFrustumAndLodOnlyKernel();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isLodEnabledInEditMode.boolValue) {
|
|
|
|
|
|
infoStyle ??= BrushPropertiesUI.getHelpTextStyle();
|
|
|
|
|
|
infoStyle.richText = true;
|
|
|
|
|
|
EditorGUILayout.LabelField(
|
|
|
|
|
|
"When the <color=#37d2e8>FurRenderer is focused in edit mode,</color> the Scene View Camera is used for LOD.\n" +
|
|
|
|
|
|
$"Active LOD Camera: <color=#37d2e8>{activeLodCameraName.stringValue}\n" +
|
|
|
|
|
|
$"</color>Active LOD: <color=#37d2e8>{activeLodName.stringValue}</color>",
|
|
|
|
|
|
infoStyle
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawLodTabContentUi(FurContainer furContainer) {
|
|
|
|
|
|
EditorGUILayout.Space(-5);
|
|
|
|
|
|
GUILayout.BeginVertical(styles.PanelStyle);
|
|
|
|
|
|
|
|
|
|
|
|
if (lodTab == 3) {
|
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
|
furContainer.culledDistance = EditorGUILayout.FloatField(
|
|
|
|
|
|
new GUIContent("Culled Distance", "Distance to camera before hiding the fur."),
|
|
|
|
|
|
furContainer.culledDistance
|
|
|
|
|
|
);
|
|
|
|
|
|
if (EditorGUI.EndChangeCheck()) {
|
|
|
|
|
|
EditorUtility.SetDirty(furContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var selectedLOD = furContainer.furLods[lodTab];
|
|
|
|
|
|
if (lodTab != 0) {
|
|
|
|
|
|
var newLODStartDistance =
|
|
|
|
|
|
EditorGUILayout.FloatField(new GUIContent("LOD Start Distance", "Distance to camera before switching to this LOD."),
|
|
|
|
|
|
selectedLOD.startDistance);
|
|
|
|
|
|
if (newLODStartDistance != selectedLOD.startDistance) {
|
|
|
|
|
|
var lod2 = furContainer.furLods[2];
|
|
|
|
|
|
if (lodTab == 1 && newLODStartDistance >= lod2.startDistance) {
|
|
|
|
|
|
lod2.startDistance = newLODStartDistance + 0.5f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
selectedLOD.startDistance = newLODStartDistance;
|
|
|
|
|
|
EditorUtility.SetDirty(furContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var newValue = EditorGUILayout.IntSlider(
|
|
|
|
|
|
new GUIContent("Draw every (x) strand", "How many strands to cull away when rendering at this LOD."),
|
|
|
|
|
|
selectedLOD.skipStrandsCount, 1, 10);
|
|
|
|
|
|
var newScale = EditorGUILayout.Slider(
|
|
|
|
|
|
new GUIContent("Strands Width Scale", "Scale of each strand when rendering at this LOD"),
|
|
|
|
|
|
selectedLOD.strandsScale, 1, 10);
|
|
|
|
|
|
|
|
|
|
|
|
if (newValue != selectedLOD.skipStrandsCount || newScale != selectedLOD.strandsScale) {
|
|
|
|
|
|
selectedLOD.skipStrandsCount = newValue;
|
|
|
|
|
|
selectedLOD.strandsScale = newScale;
|
|
|
|
|
|
EditorUtility.SetDirty(furContainer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.EndVertical();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|