You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
18 KiB
C#
416 lines
18 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace FluffyGroomingTool {
|
|
public class FluffyWindow : EditorWindow {
|
|
private bool isChangingBrushValue;
|
|
private bool isPainting;
|
|
private bool isPaintingDisabledDueToAltDrag;
|
|
|
|
internal float brushMoveDistance;
|
|
|
|
private Vector2 mousePosition = Vector2.zero;
|
|
private Vector2 lastMousePosition = Vector2.zero;
|
|
internal Vector2 scroll;
|
|
|
|
internal MeshProperties mirroredRayCastHitMp;
|
|
internal MeshProperties rayCastHitMp;
|
|
internal Vector3 previousRayHit;
|
|
internal Vector3 previousMirrorRayHit;
|
|
|
|
internal Vector3? clickStartHitPoint;
|
|
internal Vector3? mirrorClickStartHitPoint;
|
|
|
|
internal FurCreator furCreator;
|
|
internal GameObject activeObject;
|
|
|
|
internal readonly PainterBrushTypeUI brushTypeUI = new PainterBrushTypeUI();
|
|
private readonly PainterResetAndSmoothUI painterResetAndSmoothUI = new PainterResetAndSmoothUI();
|
|
private readonly PainterMagnitudeUI painterMagnitudeUI = new PainterMagnitudeUI();
|
|
private readonly PainterClumpMaskUI painterPainterClumpMaskUI = new PainterClumpMaskUI();
|
|
private readonly PainterTwistUI painterTwistUI = new PainterTwistUI();
|
|
|
|
private readonly PainterMaskUI painterMaskUI = new PainterMaskUI();
|
|
internal readonly PainterAddSpacingUI painterAddSpacingUI = new PainterAddSpacingUI();
|
|
internal readonly BrushPropertiesUI brushPropertiesUI = new BrushPropertiesUI();
|
|
readonly FirstLoadSpinnerUI firstLoadUI = new FirstLoadSpinnerUI();
|
|
readonly ClumpModifierUI clumpModifierUI = new ClumpModifierUI();
|
|
internal readonly PainterLayersUI painterLayersUI = new PainterLayersUI();
|
|
private readonly PainterColorOverrideUI painterColorOverrideUI = new PainterColorOverrideUI();
|
|
private readonly RateAndReviewUI rateAndReviewUi = new RateAndReviewUI();
|
|
|
|
internal readonly LayerPropertiesUI layerPropertiesUI = new LayerPropertiesUI();
|
|
readonly MeshCardPropertiesUI meshCardPropertiesUI = new MeshCardPropertiesUI();
|
|
readonly ExportMeshUI exportMeshUI = new ExportMeshUI();
|
|
private readonly ExportFurContainerUI _exportFurContainerUI = new ExportFurContainerUI();
|
|
private readonly AddFurCreatorUI addFurCreatorUI = new AddFurCreatorUI();
|
|
private WelcomeTextUI welcomeTextUI = new WelcomeTextUI();
|
|
|
|
private bool hasMeshFilterOrSkinnedMesh;
|
|
|
|
[MenuItem("Tools/Fluffy Grooming Tool/Launch Fluffy Window", false, 1)]
|
|
public static FluffyWindow launchFurPainter() {
|
|
var window = GetWindow<FluffyWindow>();
|
|
window.titleContent = new GUIContent("Fluffy Grooming Tool");
|
|
window.Show();
|
|
window.OnSelectionChange();
|
|
return window;
|
|
}
|
|
|
|
public void showWelcomeTextUI() {
|
|
welcomeTextUI.isEnabled = true;
|
|
welcomeTextUI.startTimeStamp = (float) EditorApplication.timeSinceStartup;
|
|
}
|
|
|
|
private void OnEnable() {
|
|
brushPropertiesUI.init();
|
|
SceneView.duringSceneGui -= OnSceneGUI;
|
|
SceneView.duringSceneGui += OnSceneGUI;
|
|
EditorApplication.playModeStateChanged -= playModeStateChanged;
|
|
EditorApplication.playModeStateChanged += playModeStateChanged;
|
|
sceneDisk = SceneDisk.createDisk("SceneDisk");
|
|
mirroredSceneDisk = SceneDisk.createDisk("MirroredSceneDisk");
|
|
OnSelectionChange();
|
|
}
|
|
|
|
private SceneDisk sceneDisk;
|
|
private SceneDisk mirroredSceneDisk;
|
|
|
|
private void playModeStateChanged(PlayModeStateChange state) {
|
|
OnSelectionChange();
|
|
}
|
|
|
|
private void OnDisable() {
|
|
SceneView.duringSceneGui -= OnSceneGUI;
|
|
EditorApplication.playModeStateChanged -= playModeStateChanged;
|
|
if (sceneDisk != null) DestroyImmediate(sceneDisk.gameObject);
|
|
if (mirroredSceneDisk != null) DestroyImmediate(mirroredSceneDisk.gameObject);
|
|
}
|
|
|
|
internal void OnSelectionChange() {
|
|
if (shouldLockSelectionToCurrent()) {
|
|
if (!furCreator.gameObject.activeInHierarchy) {
|
|
furCreator.painterProperties.isSelectionLocked = false;
|
|
}
|
|
else {
|
|
Selection.activeGameObject = furCreator.gameObject;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (furCreator != null) furCreator.showToastMessage -= showTaost;
|
|
furCreator = null;
|
|
activeObject = null;
|
|
hasMeshFilterOrSkinnedMesh = false;
|
|
if (Selection.activeGameObject != null) {
|
|
furCreator = Selection.activeGameObject.GetComponent<FurCreator>();
|
|
if (furCreator != null) furCreator.showToastMessage += showTaost;
|
|
var activeGameObject = Selection.activeGameObject;
|
|
hasMeshFilterOrSkinnedMesh = activeGameObject.GetComponent<MeshFilter>() != null ||
|
|
activeGameObject.GetComponent<SkinnedMeshRenderer>() != null;
|
|
if (hasMeshFilterOrSkinnedMesh) {
|
|
activeObject = activeGameObject;
|
|
}
|
|
}
|
|
|
|
Repaint();
|
|
}
|
|
|
|
private void showTaost(string message) {
|
|
toastMessages.Add(
|
|
new ToastMessage {
|
|
messageText = message,
|
|
show = true
|
|
}.autoHide()
|
|
);
|
|
}
|
|
|
|
private bool shouldLockSelectionToCurrent() {
|
|
return activeObject != null && furCreator != null && furCreator.getPainterProperties().isSelectionLocked &&
|
|
Selection.activeGameObject != activeObject;
|
|
}
|
|
|
|
private void OnGUI() {
|
|
drawToastMessages();
|
|
|
|
if (hasFurCreator() && furCreator.IsFirstLoad) {
|
|
firstLoadUI.drawFirstLoadProgressbar(this);
|
|
refreshAll();
|
|
}
|
|
else {
|
|
firstLoadUI.resetIndex();
|
|
scroll = GUILayout.BeginScrollView(scroll, false, false);
|
|
GUILayout.BeginVertical(new GUIStyle() {padding = new RectOffset(4, 4, 0, 0)});
|
|
EditorGUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
if (furCreator != null) {
|
|
if (!furCreator.isActiveAndEnabled || !furCreator.FurRenderer.isActiveAndEnabled) {
|
|
drawFurCreatorDisabledUI();
|
|
}
|
|
else {
|
|
drawPainterUI();
|
|
}
|
|
}
|
|
else {
|
|
addFurCreatorUI.drawFurCreatorUI(this, hasMeshFilterOrSkinnedMesh);
|
|
}
|
|
|
|
GUILayout.EndVertical();
|
|
GUILayout.EndScrollView();
|
|
}
|
|
|
|
welcomeTextUI.drawWelcomeText(this);
|
|
}
|
|
|
|
private void drawToastMessages() {
|
|
foreach (var message in toastMessages) {
|
|
message.drawMessage(position.width);
|
|
}
|
|
|
|
if (Event.current.type == EventType.Layout) {
|
|
toastMessages = toastMessages.Where(tm => !tm.isFinished).ToList();
|
|
}
|
|
|
|
var isSelectionLocked = furCreator != null && furCreator.getPainterProperties().isSelectionLocked;
|
|
if (isSelectionLocked) selectionLockedMessage.messageText = "Selection is locked to (" + activeObject.name + ")";
|
|
selectionLockedMessage.drawFixedMessage(isSelectionLocked, position.width);
|
|
var isGroomAllLayerAtOnce = furCreator != null && furCreator.getPainterProperties().isGroomAllLayerAtOnce;
|
|
allLayerModeMessage.drawFixedMessage(isGroomAllLayerAtOnce, position.width);
|
|
}
|
|
|
|
private ToastMessage selectionLockedMessage = new ToastMessage();
|
|
private ToastMessage allLayerModeMessage = new ToastMessage {messageText = "Groom all layers simultaneously is enabled"};
|
|
private List<ToastMessage> toastMessages = new List<ToastMessage>();
|
|
|
|
private bool hasFurCreator() {
|
|
return furCreator != null && furCreator.isActiveAndEnabled && furCreator.FurRenderer.isActiveAndEnabled;
|
|
}
|
|
|
|
private void drawPainterUI() {
|
|
Tools.current = Tool.None;
|
|
brushTypeUI.drawBrushTypeUI(this, furCreator);
|
|
painterResetAndSmoothUI.drawResetAndSmoothUI(brushTypeUI, furCreator);
|
|
painterMagnitudeUI.drawMagnitudeUI(brushTypeUI, furCreator);
|
|
painterPainterClumpMaskUI.drawClumpMaskUI(brushTypeUI, furCreator);
|
|
painterTwistUI.drawTwistUI(brushTypeUI, furCreator);
|
|
painterMaskUI.drawMaskUI(this, painterLayersUI.buttonStyle, furCreator);
|
|
painterColorOverrideUI.drawColorOverrideUI(brushTypeUI, furCreator);
|
|
painterAddSpacingUI.drawAddFurSpacingUI(brushTypeUI, furCreator.getPainterProperties());
|
|
brushTypeUI.endLayout();
|
|
brushPropertiesUI.drawBrushPropertiesUI(furCreator, brushTypeUI, painterLayersUI.buttonStyle);
|
|
|
|
if (painterLayersUI.isDrawHeader()) {
|
|
GUILayout.BeginVertical(brushPropertiesUI.PanelStyle);
|
|
|
|
for (int i = 0; i < furCreator.groomContainer.layers.Length; i++) {
|
|
painterLayersUI.drawLayerHeadingUI(furCreator, i, position.width);
|
|
|
|
if (i == furCreator.groomContainer.activeLayerIndex) {
|
|
layerPropertiesUI.drawFurPropertiesUI(furCreator, brushPropertiesUI.PanelStyle);
|
|
clumpModifierUI.drawClumpingUI(furCreator, brushPropertiesUI.PanelStyle, painterLayersUI);
|
|
meshCardPropertiesUI.drawCardMeshPropertiesUI(furCreator, brushPropertiesUI.PanelStyle, painterLayersUI);
|
|
painterLayersUI.endLayout();
|
|
}
|
|
}
|
|
|
|
painterLayersUI.drawAddLayerButton(furCreator);
|
|
GUILayout.EndVertical();
|
|
EditorGUILayout.Space(PainterResetAndSmoothUI.DEFAULT_MARGIN_TOP);
|
|
}
|
|
|
|
_exportFurContainerUI.drawExportFurContainerUI(furCreator, brushPropertiesUI.PanelStyle, this);
|
|
exportMeshUI.drawExportMeshUI(furCreator, this);
|
|
rateAndReviewUi.drawRateAndReviewUI(this);
|
|
refreshAll();
|
|
if (GUI.changed) furCreator.updateAllLayerStrands();
|
|
}
|
|
|
|
private void refreshAll() {
|
|
updateSceneView();
|
|
Repaint();
|
|
}
|
|
|
|
private void drawFurCreatorDisabledUI() {
|
|
GUILayout.BeginVertical(brushTypeUI.BrushDetailsStyle);
|
|
var furCreatorOrRenderer = furCreator.isActiveAndEnabled ? "(FurRenderer)" : "(FurCreator)";
|
|
EditorGUILayout.LabelField("Please enable " + furCreator.name + furCreatorOrRenderer + " to edit the fur.");
|
|
GUILayout.Space(5);
|
|
if (GUILayout.Button("Enable", painterLayersUI.buttonStyle)) {
|
|
furCreator.enabled = true;
|
|
GameObject gameObject;
|
|
(gameObject = furCreator.gameObject).SetActive(true);
|
|
furCreator.FurRenderer.enabled = true;
|
|
var existingObject = PainterUtils.findExistingFurObject(
|
|
FurMeshCreator.getFurObjectName(gameObject),
|
|
furCreator.transform.parent
|
|
);
|
|
if (existingObject != null) existingObject.gameObject.SetActive(false);
|
|
}
|
|
|
|
GUILayout.EndVertical();
|
|
}
|
|
|
|
|
|
void OnSceneGUI(SceneView sceneView) {
|
|
#if UNITY_EDITOR
|
|
if (BuildPipeline.isBuildingPlayer) return;
|
|
#endif
|
|
Ray worldRay = HandleUtility.GUIPointToWorldRay(mousePosition);
|
|
if (hasFurCreator() && furCreator.FurRenderer.meshBaker.sourceMesh != null) {
|
|
if (sceneDisk == null || sceneDisk.gameObject == null) sceneDisk = SceneDisk.createDisk("SceneDisk");
|
|
if (mirroredSceneDisk == null || mirroredSceneDisk.gameObject == null) mirroredSceneDisk = SceneDisk.createDisk("MirroredSceneDisk");
|
|
var hitMp = furCreator.FurRenderer.meshBaker.rayCast(worldRay);
|
|
|
|
var isHit = hitMp != null;
|
|
if (isHit && !isChangingBrushValue) {
|
|
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
|
|
|
|
rayCastHitMp = (MeshProperties) hitMp;
|
|
mirroredRayCastHitMp = createInverseHit(rayCastHitMp);
|
|
|
|
if (isPainting && furCreator.isActiveAndEnabled && !isChangingBrushValue) {
|
|
this.paintVertex();
|
|
updateSceneView();
|
|
}
|
|
}
|
|
|
|
if (isHit || isChangingBrushValue) {
|
|
sceneDisk.gameObject.SetActive(true);
|
|
drawSceneDisk(rayCastHitMp, sceneDisk);
|
|
if (furCreator.getPainterProperties().isMirrorMode) {
|
|
mirroredSceneDisk.gameObject.SetActive(true);
|
|
Handles.zTest = CompareFunction.LessEqual;
|
|
drawSceneDisk(mirroredRayCastHitMp, mirroredSceneDisk);
|
|
Handles.zTest = CompareFunction.Greater;
|
|
}
|
|
else {
|
|
mirroredSceneDisk.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
else {
|
|
sceneDisk.gameObject.SetActive(false);
|
|
mirroredSceneDisk.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
|
|
processInputs();
|
|
sceneView.Repaint();
|
|
if (GUI.changed) furCreator.updateAllLayerStrands();
|
|
}
|
|
|
|
|
|
private MeshProperties createInverseHit(MeshProperties currentHit) {
|
|
var thisTransform = furCreator.transform;
|
|
var targetTransform = getTargetMirrorTransform(thisTransform);
|
|
|
|
var inverseTransformPoint = targetTransform.InverseTransformPoint(currentHit.sourceVertex);
|
|
var inverseNormalDirection = targetTransform.InverseTransformDirection(currentHit.sourceNormal);
|
|
bool mirrorOnXAxis = furCreator.getPainterProperties().mirrorAxisTab == 0;
|
|
|
|
Vector3 mirrorNormal;
|
|
if (mirrorOnXAxis) {
|
|
inverseTransformPoint.x *= -1f;
|
|
mirrorNormal = new Vector3(-inverseNormalDirection.x, inverseNormalDirection.y, inverseNormalDirection.z);
|
|
}
|
|
else {
|
|
inverseTransformPoint.z *= -1f;
|
|
mirrorNormal = new Vector3(inverseNormalDirection.x, inverseNormalDirection.y, -inverseNormalDirection.z);
|
|
}
|
|
|
|
var mirroredPoint = targetTransform.TransformPoint(inverseTransformPoint);
|
|
mirrorNormal = targetTransform.TransformDirection(mirrorNormal);
|
|
|
|
return new MeshProperties {
|
|
sourceNormal = mirrorNormal,
|
|
sourceVertex = mirroredPoint,
|
|
sourceTangent = currentHit.sourceTangent
|
|
};
|
|
}
|
|
|
|
private Transform getTargetMirrorTransform(Transform thisTransform) {
|
|
Transform transform;
|
|
return furCreator.FurRenderer.meshBaker.isSkinnedMesh() &&
|
|
(transform = furCreator.transform).parent != null
|
|
// ReSharper disable once Unity.InefficientPropertyAccess
|
|
? transform.parent
|
|
: thisTransform;
|
|
}
|
|
|
|
private static readonly float OUTLINE_THICKNESS = 1.5f;
|
|
|
|
private void drawSceneDisk(MeshProperties raycastHit, SceneDisk disk) {
|
|
var type = furCreator.getPainterProperties().type;
|
|
Handles.color = Color.white;
|
|
Handles.DrawWireDisc(raycastHit.sourceVertex, raycastHit.sourceNormal,
|
|
brushPropertiesUI.getBrushDiscSize(type, painterAddSpacingUI, furCreator.getPainterProperties()), OUTLINE_THICKNESS);
|
|
Handles.DrawWireDisc(raycastHit.sourceVertex, raycastHit.sourceNormal,
|
|
brushPropertiesUI.getFalloffBrushDiscSize(type, furCreator.getPainterProperties()), OUTLINE_THICKNESS);
|
|
disk.setup(
|
|
raycastHit,
|
|
brushPropertiesUI.getBrushDiscSize(type, painterAddSpacingUI, furCreator.getPainterProperties()),
|
|
brushPropertiesUI.getFalloffBrushDiscSize(type, furCreator.getPainterProperties()),
|
|
brushPropertiesUI.getCircleFillColor(furCreator.getPainterProperties())
|
|
);
|
|
}
|
|
|
|
private void updateSceneView() {
|
|
if (furCreator != null) {
|
|
furCreator.FurRenderer.drawWindContribution = furCreator.getPainterProperties().type == (int) PaintType.WIND_MAX_DISTANCE;
|
|
EditorApplication.QueuePlayerLoopUpdate();
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
|
|
void processInputs() {
|
|
if (hasFurCreator()) {
|
|
Event e = Event.current;
|
|
|
|
mousePosition = e.mousePosition;
|
|
|
|
if (e.rawType == EventType.MouseUp) onMouseUp();
|
|
|
|
if (lastMousePosition == mousePosition) isPainting = false;
|
|
|
|
if (e.type == EventType.MouseDown) {
|
|
reset(e.alt);
|
|
brushPropertiesUI.resetMouseMove(e.button == 0);
|
|
}
|
|
|
|
if (!isPaintingDisabledDueToAltDrag) {
|
|
isChangingBrushValue = brushPropertiesUI.handleBrushShortcuts(e, furCreator.getPainterProperties()) || isChangingBrushValue;
|
|
handleRegularPainting(e);
|
|
}
|
|
|
|
if (e.rawType == EventType.MouseEnterWindow &&
|
|
!brushPropertiesUI.handleBrushShortcuts(e, furCreator.getPainterProperties())) onMouseUp();
|
|
lastMousePosition = mousePosition;
|
|
}
|
|
}
|
|
|
|
private void handleRegularPainting(Event e) {
|
|
if ((e.type == EventType.MouseDrag || e.type == EventType.MouseDown) && !e.getControlButton() && e.button == 0 && !e.shift) {
|
|
isPainting = true;
|
|
if (e.type == EventType.MouseDown) brushMoveDistance = 0;
|
|
}
|
|
}
|
|
|
|
private void onMouseUp() {
|
|
if (previousRayHit != Vector3.zero) furCreator.updateSerializedFurProperties();
|
|
reset(false);
|
|
brushPropertiesUI.isLeftMousePressed = false;
|
|
}
|
|
|
|
private void reset(bool eAlt) {
|
|
isChangingBrushValue = false;
|
|
isPainting = false;
|
|
previousRayHit = Vector3.zero;
|
|
previousMirrorRayHit = Vector3.zero;
|
|
isPaintingDisabledDueToAltDrag = eAlt;
|
|
clickStartHitPoint = null;
|
|
mirrorClickStartHitPoint = null;
|
|
}
|
|
}
|
|
} |