411 lines
16 KiB
C#
Executable file
411 lines
16 KiB
C#
Executable file
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Thry
|
|
{
|
|
public class DecalSceneTool
|
|
{
|
|
private MaterialProperty _propPosition;
|
|
private MaterialProperty _propRotation;
|
|
private MaterialProperty _propScale;
|
|
private MaterialProperty _propOffset;
|
|
private Material _material;
|
|
private Renderer _renderer;
|
|
private int _uvIndex;
|
|
private Mesh _mesh;
|
|
private Vector2[][] _uvTriangles;
|
|
private Vector3[][] _worldTriangles;
|
|
private bool _isActive;
|
|
private Mode _mode = Mode.None;
|
|
private HandleMode _handleMode = HandleMode.Position;
|
|
private Tool _previousTool;
|
|
|
|
public enum Mode
|
|
{
|
|
None, Raycast, Handles
|
|
}
|
|
|
|
public enum HandleMode
|
|
{
|
|
Position, Rotation, Scale, Offset
|
|
}
|
|
|
|
private DecalSceneTool() {}
|
|
|
|
public static DecalSceneTool Create(Renderer renderer, Material m, int uvIndex, MaterialProperty propPosition, MaterialProperty propRotation, MaterialProperty propScale, MaterialProperty propOffset)
|
|
{
|
|
var tool = new DecalSceneTool();
|
|
tool._material = m;
|
|
tool._uvIndex = uvIndex;
|
|
tool._propPosition = propPosition;
|
|
tool._propRotation = propRotation;
|
|
tool._propScale = propScale;
|
|
tool._propOffset = propOffset;
|
|
tool._renderer = renderer;
|
|
tool.Init();
|
|
return tool;
|
|
}
|
|
|
|
public void SetMaterialProperties(MaterialProperty propPosition, MaterialProperty propRotation, MaterialProperty propScale, MaterialProperty propOffset)
|
|
{
|
|
_propPosition = propPosition;
|
|
_propRotation = propRotation;
|
|
_propScale = propScale;
|
|
_propOffset = propOffset;
|
|
}
|
|
|
|
public void StartRaycastMode()
|
|
{
|
|
_mode = Mode.Raycast;
|
|
this.Activate();
|
|
}
|
|
|
|
public void StartHandleMode()
|
|
{
|
|
_mode = Mode.Handles;
|
|
this.Activate();
|
|
}
|
|
|
|
public void Activate()
|
|
{
|
|
if(_isActive) return;
|
|
_previousTool = Tools.current;
|
|
Tools.current = Tool.None;
|
|
SceneView.duringSceneGui += OnSceneGUI;
|
|
Selection.selectionChanged += OnSelectionChange;
|
|
_isActive = true;
|
|
}
|
|
|
|
public void Deactivate()
|
|
{
|
|
if(!_isActive) return;
|
|
SceneView.duringSceneGui -= OnSceneGUI;
|
|
Selection.selectionChanged -= OnSelectionChange;
|
|
Tools.current = _previousTool;
|
|
_isActive = false;
|
|
_mode = Mode.None;
|
|
|
|
}
|
|
|
|
public Mode GetMode()
|
|
{
|
|
return _mode;
|
|
}
|
|
|
|
void OnSelectionChange()
|
|
{
|
|
this.Deactivate();
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
// EditorUtility.DisplayProgressBar("Decal Tool", "Loading Mesh...", 0.0f);
|
|
GetMesh();
|
|
_uvTriangles = new Vector2[_mesh.triangles.Length / 3][];
|
|
_worldTriangles = new Vector3[_mesh.triangles.Length / 3][];
|
|
int[] triangles = _mesh.triangles;
|
|
Vector2[] uvs;
|
|
if(_uvIndex == 1) uvs = _mesh.uv2;
|
|
else if(_uvIndex == 2) uvs = _mesh.uv3;
|
|
else if(_uvIndex == 3) uvs = _mesh.uv4;
|
|
else uvs = _mesh.uv;
|
|
Vector3[] vertices = _mesh.vertices;
|
|
Transform root = _renderer.transform;
|
|
Vector3 inverseScale = new Vector3(1.0f / root.lossyScale.x, 1.0f / root.lossyScale.y, 1.0f / root.lossyScale.z);
|
|
bool isSMR = _renderer is SkinnedMeshRenderer;
|
|
for(int i = 0; i < triangles.Length; i += 3)
|
|
{
|
|
// if(i%100 == 0) EditorUtility.DisplayProgressBar("Decal Tool", "Loading Mesh...", (float)i / triangles.Length);
|
|
_uvTriangles[i / 3] = new Vector2[3];
|
|
_worldTriangles[i / 3] = new Vector3[3];
|
|
for(int j = 0; j < 3; j++)
|
|
{
|
|
_uvTriangles[i / 3][j] = uvs[triangles[i + j]];
|
|
if(isSMR)
|
|
_worldTriangles[i / 3][j] = root.TransformPoint(Vector3.Scale(vertices[triangles[i + j]], inverseScale));
|
|
else
|
|
_worldTriangles[i / 3][j] = root.TransformPoint(vertices[triangles[i + j]]);
|
|
// _worldTriangles[i / 3][j] = vertices[triangles[i + j]];
|
|
}
|
|
}
|
|
// EditorUtility.ClearProgressBar();
|
|
}
|
|
|
|
private void OnSceneGUI(SceneView sceneView)
|
|
{
|
|
switch(_mode)
|
|
{
|
|
case Mode.Raycast:
|
|
RaycastMode(sceneView);
|
|
break;
|
|
case Mode.Handles:
|
|
HandlesMode(sceneView);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RaycastMode(SceneView sceneView)
|
|
{
|
|
if(Tools.current != Tool.View)
|
|
{
|
|
Tools.current = Tool.View;
|
|
}
|
|
|
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
|
|
|
Vector2 uv = _propPosition.vectorValue;
|
|
if(RaycastToClosestUV(ray, ref uv))
|
|
{
|
|
_propPosition.vectorValue = uv;
|
|
}
|
|
|
|
if(Event.current.type == EventType.MouseDown && Event.current.button == 0)
|
|
{
|
|
Event.current.Use();
|
|
Deactivate();
|
|
}
|
|
}
|
|
|
|
void HandlesMode(SceneView sceneView)
|
|
{
|
|
switch(_handleMode)
|
|
{
|
|
case HandleMode.Position:
|
|
PositionMode(sceneView);
|
|
break;
|
|
case HandleMode.Rotation:
|
|
RotationMode(sceneView);
|
|
break;
|
|
case HandleMode.Scale:
|
|
ScaleMode(sceneView);
|
|
break;
|
|
case HandleMode.Offset:
|
|
OffsetMode(sceneView);
|
|
break;
|
|
}
|
|
|
|
if(Tools.current != Tool.None)
|
|
{
|
|
switch(Tools.current)
|
|
{
|
|
case Tool.Move:
|
|
_handleMode = HandleMode.Position;
|
|
break;
|
|
case Tool.Rotate:
|
|
_handleMode = HandleMode.Rotation;
|
|
break;
|
|
case Tool.Scale:
|
|
_handleMode = HandleMode.Scale;
|
|
break;
|
|
case Tool.Rect:
|
|
_handleMode = HandleMode.Offset;
|
|
break;
|
|
}
|
|
Tools.current = Tool.None;
|
|
}
|
|
}
|
|
|
|
void PositionMode(SceneView sceneView)
|
|
{
|
|
GetPivot();
|
|
Vector3 gizmoNormal = _pivotNormal;
|
|
if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0)
|
|
{
|
|
gizmoNormal = -_pivotNormal;
|
|
}
|
|
Quaternion rotation = Quaternion.LookRotation(gizmoNormal, _pivotUp);
|
|
|
|
if(Tools.pivotRotation == PivotRotation.Local)
|
|
{
|
|
rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue);
|
|
}
|
|
|
|
Vector3 moved = Handles.PositionHandle(_pivotPoint, rotation);
|
|
if(moved != _pivotPoint)
|
|
{
|
|
Vector2 uv = Vector2.zero;
|
|
Ray ray = new Ray(moved - _pivotNormal * 0.1f, _pivotNormal);
|
|
if(RaycastToClosestUV(ray, ref uv))
|
|
{
|
|
_propPosition.vectorValue = uv;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RotationMode(SceneView sceneView)
|
|
{
|
|
GetPivot();
|
|
Vector3 gizmoNormal = _pivotNormal;
|
|
if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0)
|
|
{
|
|
gizmoNormal = -_pivotNormal;
|
|
}
|
|
Quaternion rotation = Quaternion.LookRotation(gizmoNormal, _pivotUp);
|
|
rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue);
|
|
|
|
Quaternion moved = Handles.RotationHandle(rotation, _pivotPoint);
|
|
if(moved != rotation)
|
|
{
|
|
Quaternion delta = Quaternion.Inverse(rotation) * moved;
|
|
float deltaAngle = delta.eulerAngles.z;
|
|
DecalTool.SetClampedRotation(_propRotation, _propRotation.floatValue - deltaAngle);
|
|
}
|
|
}
|
|
|
|
Vector3 _initalScale;
|
|
void ScaleMode(SceneView sceneView)
|
|
{
|
|
GetPivot();
|
|
Vector3 gizmoNormal = _pivotNormal;
|
|
if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0)
|
|
{
|
|
gizmoNormal = -_pivotNormal;
|
|
}
|
|
Quaternion rotation = Quaternion.LookRotation(gizmoNormal, _pivotUp);
|
|
rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue);
|
|
|
|
if(Event.current.type == EventType.MouseDown && Event.current.button == 0)
|
|
{
|
|
_initalScale = _propScale.vectorValue;
|
|
}
|
|
|
|
Vector3 moved = Handles.ScaleHandle(Vector3.one, _pivotPoint, rotation, HandleUtility.GetHandleSize(_pivotPoint));
|
|
if(moved != Vector3.one)
|
|
{
|
|
Vector4 scale = _initalScale;
|
|
scale.x *= moved.x;
|
|
scale.y *= moved.y;
|
|
_propScale.vectorValue = scale;
|
|
}
|
|
}
|
|
|
|
Vector4 _initalOffset;
|
|
void OffsetMode(SceneView sceneView)
|
|
{
|
|
GetPivot();
|
|
Vector3 normal = _pivotNormal;
|
|
if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0)
|
|
{
|
|
_pivotNormal = -_pivotNormal;
|
|
}
|
|
Quaternion rotation = Quaternion.LookRotation(_pivotNormal, _pivotUp);
|
|
rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue);
|
|
|
|
if(Event.current.type == EventType.MouseDown && Event.current.button == 0)
|
|
{
|
|
_initalOffset = _propOffset.vectorValue;
|
|
}
|
|
|
|
float size = HandleUtility.GetHandleSize(_pivotPoint);
|
|
float left = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.left * size, rotation * Quaternion.Euler(0, -90, 0), size * 5, Handles.ArrowHandleCap, 0);
|
|
float right = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.right * size, rotation * Quaternion.Euler(0, 90, 0), size * 5, Handles.ArrowHandleCap, 0);
|
|
float down = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.down * size, rotation * Quaternion.Euler(90, 0, 0), size * 5, Handles.ArrowHandleCap, 0);
|
|
float up = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.up * size, rotation * Quaternion.Euler(-90, 0, 0), size * 5, Handles.ArrowHandleCap, 0);
|
|
if(left != 1 || right != 1 || down != 1 || up != 1)
|
|
{
|
|
Vector4 offset = _initalOffset;
|
|
offset.x -= (left - 1) * _propScale.vectorValue.x * 0.25f;
|
|
offset.y += (right - 1) * _propScale.vectorValue.x * 0.25f;
|
|
offset.z -= (down - 1) * _propScale.vectorValue.y * 0.25f;
|
|
offset.w += (up - 1) * _propScale.vectorValue.y * 0.25f;
|
|
_propOffset.vectorValue = offset;
|
|
}
|
|
}
|
|
|
|
Vector3 _pivotPoint;
|
|
Vector3 _pivotNormal;
|
|
Vector3 _pivotUp;
|
|
void GetPivot()
|
|
{
|
|
_pivotPoint = Vector3.zero;
|
|
_pivotNormal = Vector3.zero;
|
|
|
|
Vector2 uv = _propPosition.vectorValue;
|
|
Vector2 uvUp = uv + Vector2.up * 0.0001f;
|
|
// uv position to world position using renderer mesh
|
|
for(int i=0; i<_worldTriangles.Length;i++)
|
|
{
|
|
Vector2[] uvTriangle = _uvTriangles[i];
|
|
float a = TriangleArea(uvTriangle[0], uvTriangle[1], uvTriangle[2]);
|
|
if(a == 0) continue;
|
|
// check if uv is inside uvTriangle
|
|
float a1 = TriangleArea(uvTriangle[1], uvTriangle[2], uv) / a;
|
|
if(a1 < 0) continue;
|
|
float a2 = TriangleArea(uvTriangle[2], uvTriangle[0], uv) / a;
|
|
if(a2 < 0) continue;
|
|
float a3 = TriangleArea(uvTriangle[0], uvTriangle[1], uv) / a;
|
|
if(a3 < 0) continue;
|
|
// get a1, a2, a3 of uv up
|
|
float a1Up = TriangleArea(uvTriangle[1], uvTriangle[2], uvUp) / a;
|
|
float a2Up = TriangleArea(uvTriangle[2], uvTriangle[0], uvUp) / a;
|
|
float a3Up = TriangleArea(uvTriangle[0], uvTriangle[1], uvUp) / a;
|
|
// point inside the triangle - find mesh position by interpolation
|
|
Vector3[] triangle = _worldTriangles[i];
|
|
_pivotPoint = triangle[0] * a1 + triangle[1] * a2 + triangle[2] * a3;
|
|
_pivotNormal = Vector3.Cross(triangle[1] - triangle[0], triangle[2] - triangle[0]).normalized;
|
|
_pivotUp = (triangle[0] * a1Up + triangle[1] * a2Up + triangle[2] * a3Up - _pivotPoint).normalized;
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool RaycastToClosestUV(Ray ray, ref Vector2 uv)
|
|
{
|
|
Vector4 scaleOffset = _propOffset.vectorValue;
|
|
scaleOffset = new Vector4(-scaleOffset.x, scaleOffset.y, -scaleOffset.z, scaleOffset.w);
|
|
Vector2 centerOffset = new Vector2((scaleOffset.x + scaleOffset.y)/2, (scaleOffset.z + scaleOffset.w)/2);
|
|
|
|
float minDistance = float.MaxValue;
|
|
for(int i=0; i<_worldTriangles.Length;i++)
|
|
{
|
|
Vector3[] triangle = _worldTriangles[i];
|
|
// raycast to triangle
|
|
Plane plane = new Plane(triangle[0], triangle[1], triangle[2]);
|
|
float distance;
|
|
if(plane.Raycast(ray, out distance))
|
|
{
|
|
Vector3 hitPoint = ray.GetPoint(distance);
|
|
// check if hitPoint is inside triangle
|
|
float a = TriangleArea(triangle[0], triangle[1], triangle[2]);
|
|
if(a == 0) continue;
|
|
float a1 = TriangleArea(triangle[1], triangle[2], hitPoint) / a;
|
|
if(a1 < 0) continue;
|
|
float a2 = TriangleArea(triangle[2], triangle[0], hitPoint) / a;
|
|
if(a2 < 0) continue;
|
|
float a3 = TriangleArea(triangle[0], triangle[1], hitPoint) / a;
|
|
if(a3 < 0) continue;
|
|
if(distance < minDistance)
|
|
{
|
|
minDistance = distance;
|
|
// point inside the triangle - find uv by interpolation
|
|
Vector2[] uvTriangle = _uvTriangles[i];
|
|
uv = uvTriangle[0] * a1 + uvTriangle[1] * a2 + uvTriangle[2] * a3;
|
|
uv = uv - centerOffset;
|
|
}
|
|
}
|
|
}
|
|
return minDistance != float.MaxValue;
|
|
}
|
|
|
|
float TriangleArea(Vector2 a, Vector2 b, Vector2 c)
|
|
{
|
|
var v1 = a - c;
|
|
var v2 = b - c;
|
|
return (v1.x * v2.y - v1.y * v2.x) / 2;
|
|
}
|
|
|
|
void GetMesh()
|
|
{
|
|
if(_renderer is MeshRenderer)
|
|
{
|
|
_mesh = _renderer.GetComponent<MeshFilter>().sharedMesh;
|
|
}
|
|
else if(_renderer is SkinnedMeshRenderer)
|
|
{
|
|
_mesh = new Mesh();
|
|
(_renderer as SkinnedMeshRenderer).BakeMesh(_mesh);
|
|
}
|
|
}
|
|
}
|
|
} |