新插件
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11bf12df6202040dcbf711130aa677bd
|
||||
folderAsset: yes
|
||||
timeCreated: 1477604635
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
// interface class for utilities that plug into the vertex painter window
|
||||
public interface IVertexPainterUtility
|
||||
{
|
||||
string GetName();
|
||||
void OnGUI(PaintJob[] jobs);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a976d7561f2f84a049423b3b009af072
|
||||
timeCreated: 1477602428
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#if __MICROSPLAT__
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
[System.Serializable]
|
||||
public class SaveMeshes : IVertexPainterUtility
|
||||
{
|
||||
public string GetName()
|
||||
{
|
||||
return "Save Meshes";
|
||||
}
|
||||
|
||||
public void OnGUI(PaintJob[] jobs)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button("Save Mesh"))
|
||||
{
|
||||
VertexPainterUtilities.SaveMesh(jobs);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a643d1413eb8f4f96a61344346697475
|
||||
timeCreated: 1477604672
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Custom_DebugVertex
|
||||
m_Shader: {fileID: 4800000, guid: a230d27c826c844f0b9479e0388dea5c, type: 3}
|
||||
m_ShaderKeywords:
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs: []
|
||||
m_Floats:
|
||||
- _Channel: 4
|
||||
m_Colors: []
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a89e8d1f789eb411a848ae451d12801d
|
||||
timeCreated: 1581082386
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
Shader "Custom/DebugVertex" {
|
||||
Properties {
|
||||
_Channel ("Metallic", Int) = 0
|
||||
}
|
||||
SubShader {
|
||||
Tags { "RenderType"="Opaque" }
|
||||
LOD 200
|
||||
|
||||
CGPROGRAM
|
||||
// Upgrade NOTE: excluded shader from DX11 because it uses wrong array syntax (type[size] name)
|
||||
#pragma exclude_renderers d3d11
|
||||
// Physically based Standard lighting model, and enable shadows on all light types
|
||||
#pragma surface surf Standard vertex:vert fullforwardshadows
|
||||
|
||||
// Use shader model 3.0 target, to get nicer looking lighting
|
||||
#pragma target 3.0
|
||||
|
||||
int _Channel;
|
||||
|
||||
struct Input {
|
||||
fixed4 w0, w7;
|
||||
};
|
||||
|
||||
float4 DecodeToFloat4(float v)
|
||||
{
|
||||
uint vi = (uint)(v * (256.0f * 256.0f * 256.0f * 256.0f));
|
||||
int ex = (int)(vi / (256 * 256 * 256) % 256);
|
||||
int ey = (int)((vi / (256 * 256)) % 256);
|
||||
int ez = (int)((vi / (256)) % 256);
|
||||
int ew = (int)(vi % 256);
|
||||
float4 e = float4(ex / 255.0, ey / 255.0, ez / 255.0, ew / 255.0);
|
||||
return e;
|
||||
}
|
||||
|
||||
void EncodeVertex(appdata_full i, inout Input IN)
|
||||
{
|
||||
IN.w0 = DecodeToFloat4(i.color.r);
|
||||
//IN.w1 = DecodeToFloat4(i.color.g);
|
||||
//IN.w2 = DecodeToFloat4(i.color.b);
|
||||
//IN.w3 = DecodeToFloat4(i.color.a);
|
||||
//IN.w4 = DecodeToFloat4(i.texcoord1.z);
|
||||
//IN.w5 = DecodeToFloat4(i.texcoord1.w);
|
||||
//IN.w6 = DecodeToFloat4(i.texcoord2.z);
|
||||
IN.w7 = DecodeToFloat4(i.texcoord2.w);
|
||||
}
|
||||
|
||||
void vert(inout appdata_full v, out Input i)
|
||||
{
|
||||
i = (Input)0;
|
||||
EncodeVertex(v, i);
|
||||
}
|
||||
|
||||
void surf (Input i, inout SurfaceOutputStandard o)
|
||||
{
|
||||
int c = clamp(_Channel, 0, 7);
|
||||
float fs[8];
|
||||
fs[0] = i.w0.x;
|
||||
fs[1] = i.w0.y;
|
||||
fs[2] = i.w0.z;
|
||||
fs[3] = i.w0.w;
|
||||
fs[4] = i.w7.x;
|
||||
fs[5] = i.w7.y;
|
||||
fs[6] = i.w7.z;
|
||||
fs[7] = i.w7.w;
|
||||
|
||||
|
||||
float f = fs[c];
|
||||
o.Albedo = f;
|
||||
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
FallBack "Diffuse"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a230d27c826c844f0b9479e0388dea5c
|
||||
timeCreated: 1581081467
|
||||
licenseType: Store
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:90ab0119788ed4ec6b8fe54dd44f01fa"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7866f1968bfe14be5adea4ad731f211f
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
public class PaintJob
|
||||
{
|
||||
public MeshFilter meshFilter;
|
||||
public Renderer renderer;
|
||||
public VertexInstanceStream _stream;
|
||||
public MicroSplatVertexMesh vertexMesh;
|
||||
// cache of data we often need so we don't have to cross the c#->cpp bridge often
|
||||
public Vector3[] verts;
|
||||
|
||||
// getters which take stream into account
|
||||
public Vector3 GetPosition(int i)
|
||||
{
|
||||
if (stream.positions != null && stream.positions.Length == verts.Length)
|
||||
return stream.positions[i];
|
||||
return verts[i];
|
||||
}
|
||||
|
||||
public bool HasStream() { return _stream != null; }
|
||||
public bool HasData()
|
||||
{
|
||||
if (_stream == null)
|
||||
return false;
|
||||
|
||||
int vertexCount = verts.Length;
|
||||
bool hasColors = (stream.colors != null && stream.colors.Length == vertexCount);
|
||||
bool hasPositions = (stream.positions != null && stream.positions.Length == vertexCount);
|
||||
|
||||
return (hasColors || hasPositions);
|
||||
}
|
||||
|
||||
public void EnforceStream()
|
||||
{
|
||||
if (_stream == null && renderer != null && meshFilter != null)
|
||||
{
|
||||
_stream = meshFilter.gameObject.AddComponent<VertexInstanceStream>();
|
||||
}
|
||||
}
|
||||
|
||||
public VertexInstanceStream stream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stream == null)
|
||||
{
|
||||
if (meshFilter == null)
|
||||
{ // object has been deleted
|
||||
return null;
|
||||
}
|
||||
_stream = meshFilter.gameObject.GetComponent<VertexInstanceStream>();
|
||||
if (_stream == null)
|
||||
{
|
||||
_stream = meshFilter.gameObject.AddComponent<VertexInstanceStream>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_stream.Apply();
|
||||
}
|
||||
}
|
||||
return _stream;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PaintJob(MeshFilter mf, Renderer r, MicroSplatVertexMesh vmesh)
|
||||
{
|
||||
meshFilter = mf;
|
||||
renderer = r;
|
||||
vertexMesh = vmesh;
|
||||
_stream = r.gameObject.GetComponent<VertexInstanceStream>();
|
||||
verts = mf.sharedMesh.vertices;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75ee157de0d5c421385b3a223e7417ab
|
||||
timeCreated: 1477603120
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
// https://gist.github.com/MattRix/9205bc62d558fef98045
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class RXLookingGlass
|
||||
{
|
||||
public static Type type_HandleUtility;
|
||||
protected static MethodInfo meth_IntersectRayMesh;
|
||||
|
||||
static RXLookingGlass()
|
||||
{
|
||||
var editorTypes = typeof(Editor).Assembly.GetTypes();
|
||||
|
||||
type_HandleUtility = editorTypes.FirstOrDefault(t => t.Name == "HandleUtility");
|
||||
meth_IntersectRayMesh = type_HandleUtility.GetMethod("IntersectRayMesh",(BindingFlags.Static | BindingFlags.NonPublic));
|
||||
}
|
||||
|
||||
public static bool IntersectRayMesh(Ray ray, MeshFilter meshFilter, out RaycastHit hit)
|
||||
{
|
||||
return IntersectRayMesh(ray,meshFilter.sharedMesh,meshFilter.transform.localToWorldMatrix,out hit);
|
||||
}
|
||||
static object[] parameters = new object[4];
|
||||
public static bool IntersectRayMesh(Ray ray, Mesh mesh, Matrix4x4 matrix, out RaycastHit hit)
|
||||
{
|
||||
parameters[0] = ray;
|
||||
parameters[1] = mesh;
|
||||
parameters[2] = matrix;
|
||||
parameters[3] = null;
|
||||
bool result = (bool)meth_IntersectRayMesh.Invoke(null, parameters);
|
||||
hit = (RaycastHit)parameters[3];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ab295d8392724b1db773ecc02bf98bf
|
||||
timeCreated: 1447602956
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
#if __MICROSPLAT__
|
||||
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
public class VertexPainterUtilities
|
||||
{
|
||||
public static GameObject MergeMeshes(PaintJob[] jobs)
|
||||
{
|
||||
if (jobs.Length == 0)
|
||||
return null;
|
||||
List<CombineInstance> meshes = new List<CombineInstance>();
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
Mesh m = BakeDownMesh(jobs[i].meshFilter.sharedMesh, jobs[i].stream);
|
||||
CombineInstance ci = new CombineInstance();
|
||||
ci.mesh = m;
|
||||
ci.transform = jobs[i].meshFilter.transform.localToWorldMatrix;
|
||||
meshes.Add(ci);
|
||||
}
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.CombineMeshes(meshes.ToArray());
|
||||
GameObject go = new GameObject("Combined Mesh");
|
||||
go.AddComponent<MeshRenderer>();
|
||||
var mf = go.AddComponent<MeshFilter>();
|
||||
;
|
||||
mesh.RecalculateBounds();
|
||||
mesh.UploadMeshData(false);
|
||||
mf.sharedMesh = mesh;
|
||||
for (int i = 0; i < meshes.Count; ++i)
|
||||
{
|
||||
GameObject.DestroyImmediate(meshes[i].mesh);
|
||||
}
|
||||
return go;
|
||||
}
|
||||
|
||||
// copy a mesh, and bake it's vertex stream into the mesh data.
|
||||
public static Mesh BakeDownMesh(Mesh mesh, VertexInstanceStream stream)
|
||||
{
|
||||
var copy = GameObject.Instantiate(mesh);
|
||||
|
||||
if (stream.colors != null && stream.colors.Length > 0) { copy.colors = stream.colors; }
|
||||
if (stream.uv1 != null && stream.uv1.Count > 0) { copy.SetUVs(1, stream.uv1); }
|
||||
if (stream.uv2 != null && stream.uv2.Count > 0) { copy.SetUVs(2, stream.uv2); }
|
||||
if (stream.positions != null && stream.positions.Length == copy.vertexCount)
|
||||
{
|
||||
copy.vertices = stream.positions;
|
||||
}
|
||||
copy.RecalculateBounds();
|
||||
copy.UploadMeshData(false);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
public static void SaveMesh(PaintJob[] jobs)
|
||||
{
|
||||
if (jobs.Length != 0)
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanel("Save Asset", Application.dataPath, "models", "asset");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
path = FileUtil.GetProjectRelativePath(path);
|
||||
Mesh firstMesh = BakeDownMesh(jobs[0].meshFilter.sharedMesh, jobs[0].stream);
|
||||
|
||||
AssetDatabase.CreateAsset(firstMesh, path);
|
||||
|
||||
for (int i = 1; i < jobs.Length; ++i)
|
||||
{
|
||||
Mesh m = BakeDownMesh(jobs[i].meshFilter.sharedMesh, jobs[i].stream);
|
||||
AssetDatabase.AddObjectToAsset(m, firstMesh);
|
||||
}
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.ImportAsset(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cf86837ba0f741a5b7870d20c678d87
|
||||
timeCreated: 1477605099
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/* VertexPainterWindow
|
||||
* - Jason Booth
|
||||
*
|
||||
* Uses Unity 5.0+ MeshRenderer.additionalVertexStream so that you can paint per-instance vertex colors on your meshes.
|
||||
* A component is added to your mesh to serialize this data and set it at load time. This is more effecient than making
|
||||
* duplicate meshes, and certainly less painful than saving them as separate asset files on disk. However, if you only have
|
||||
* one copy of the vertex information in your game and want to burn it into the original mesh, you can use the save feature
|
||||
* to save a new version of your mesh with the data burned into the verticies, avoiding the need for the runtime component.
|
||||
*
|
||||
* In other words, bake it if you need to instance the paint job - however, if you want tons of the same instances painted
|
||||
* uniquely in your scene, keep the component version and skip the baking..
|
||||
*
|
||||
* One possible optimization is to have the component free the array after updating the mesh when in play mode..
|
||||
*
|
||||
* Also supports burning data into the UV channels, in case you want some additional channels to work with, which also
|
||||
* happen to be full 32bit floats. You can set a viewable range; so if your floats go from 0-120, it will remap it to
|
||||
* 0-1 for display in the shader. That way you can always see your values, even when they go out of color ranges.
|
||||
*
|
||||
* Note that as of this writing Unity has a bug in the additionalVertexStream function. The docs claim the data applied here
|
||||
* will supply or overwrite the data in the mesh, however, this is not true. Rather, it will only replace the data that's
|
||||
* there - if your mesh has no color information, it will not upload the color data in additionalVertexStream, which is sad
|
||||
* because the original mesh doesn't need this data. As a workaround, if your mesh does not have color channels on the verts,
|
||||
* they will be created for you.
|
||||
*
|
||||
* There is another bug in additionalVertexStream, in that the mesh keeps disapearing in edit mode. So the component
|
||||
* which holds the data caches the mesh and keeps assigning it in the Update call, but only when running in the editor
|
||||
* and not in play mode.
|
||||
*
|
||||
* Really, the additionalVertexStream mesh should be owned by the MeshRenderer and saved as part of the objects instance
|
||||
* data. That's essentially what the VertexInstaceStream component does, but it's annoying and wasteful of memory to do
|
||||
* it this way since it doesn't need to be on the CPU at all. Enlighten somehow does this with the UVs it generates
|
||||
* this way, but appears to be handled specially. Oh, Unity..
|
||||
*/
|
||||
|
||||
#if __MICROSPLAT__
|
||||
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
public partial class VertexPainterWindow : EditorWindow
|
||||
{
|
||||
[MenuItem("Window/MicroSplat/Vertex Mesh Painter")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<JBooth.MicroSplat.VertexPainter.VertexPainterWindow>();
|
||||
window.InitMeshes();
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b7e87c0d1c384ebcbd51b505e55a724
|
||||
timeCreated: 1452445638
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,354 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
/* VertexPainterWindow
|
||||
* - Jason Booth
|
||||
*
|
||||
* Uses Unity 5.0+ MeshRenderer.additionalVertexStream so that you can paint per-instance vertex colors on your meshes.
|
||||
* A component is added to your mesh to serialize this data and set it at load time. This is more effecient than making
|
||||
* duplicate meshes, and certainly less painful than saving them as separate asset files on disk. However, if you only have
|
||||
* one copy of the vertex information in your game and want to burn it into the original mesh, you can use the save feature
|
||||
* to save a new version of your mesh with the data burned into the verticies, avoiding the need for the runtime component.
|
||||
*
|
||||
* In other words, bake it if you need to instance the paint job - however, if you want tons of the same instances painted
|
||||
* uniquely in your scene, keep the component version and skip the baking..
|
||||
*
|
||||
* One possible optimization is to have the component free the array after updating the mesh when in play mode..
|
||||
*
|
||||
* Also supports burning data into the UV channels, in case you want some additional channels to work with, which also
|
||||
* happen to be full 32bit floats. You can set a viewable range; so if your floats go from 0-120, it will remap it to
|
||||
* 0-1 for display in the shader. That way you can always see your values, even when they go out of color ranges.
|
||||
*
|
||||
* Note that as of this writing Unity has a bug in the additionalVertexStream function. The docs claim the data applied here
|
||||
* will supply or overwrite the data in the mesh, however, this is not true. Rather, it will only replace the data that's
|
||||
* there - if your mesh has no color information, it will not upload the color data in additionalVertexStream, which is sad
|
||||
* because the original mesh doesn't need this data. As a workaround, if your mesh does not have color channels on the verts,
|
||||
* they will be created for you.
|
||||
*
|
||||
* There is another bug in additionalVertexStream, in that the mesh keeps disapearing in edit mode. So the component
|
||||
* which holds the data caches the mesh and keeps assigning it in the Update call, but only when running in the editor
|
||||
* and not in play mode.
|
||||
*
|
||||
* Really, the additionalVertexStream mesh should be owned by the MeshRenderer and saved as part of the objects instance
|
||||
* data. That's essentially what the VertexInstaceStream component does, but it's annoying and wasteful of memory to do
|
||||
* it this way since it doesn't need to be on the CPU at all. Enlighten somehow does this with the UVs it generates
|
||||
* this way, but appears to be handled specially. Oh, Unity..
|
||||
*/
|
||||
|
||||
#if __MICROSPLAT__
|
||||
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
public partial class VertexPainterWindow : EditorWindow
|
||||
{
|
||||
enum Tab
|
||||
{
|
||||
Paint = 0,
|
||||
#if __MICROSPLAT_STREAMS__
|
||||
Wetness,
|
||||
Puddles,
|
||||
Streams,
|
||||
Lava,
|
||||
#endif
|
||||
Utility,
|
||||
}
|
||||
|
||||
string[] tabNames =
|
||||
{
|
||||
"Paint",
|
||||
#if __MICROSPLAT_STREAMS__
|
||||
"Wetness",
|
||||
"Puddles",
|
||||
"Streams",
|
||||
"Lava",
|
||||
#endif
|
||||
"Utility",
|
||||
};
|
||||
|
||||
|
||||
Tab tab = Tab.Paint;
|
||||
|
||||
bool hideMeshWireframe = false;
|
||||
|
||||
bool DrawClearButton(string label)
|
||||
{
|
||||
if (GUILayout.Button(label, GUILayout.Width(46)))
|
||||
{
|
||||
return (EditorUtility.DisplayDialog("Confirm", "Clear " + label + " data?", "ok", "cancel"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Dictionary<string, bool> rolloutStates = new Dictionary<string, bool>();
|
||||
static GUIStyle rolloutStyle;
|
||||
public static bool DrawRollup(string text, bool defaultState = true, bool inset = false)
|
||||
{
|
||||
if (rolloutStyle == null)
|
||||
{
|
||||
rolloutStyle = GUI.skin.box;
|
||||
rolloutStyle.normal.textColor = EditorGUIUtility.isProSkin ? Color.white : Color.black;
|
||||
}
|
||||
GUI.contentColor = EditorGUIUtility.isProSkin ? Color.white : Color.black;
|
||||
if (inset == true)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.GetControlRect(GUILayout.Width(40));
|
||||
}
|
||||
|
||||
if (!rolloutStates.ContainsKey(text))
|
||||
{
|
||||
rolloutStates[text] = defaultState;
|
||||
}
|
||||
if (GUILayout.Button(text, rolloutStyle, new GUILayoutOption[]{GUILayout.ExpandWidth(true), GUILayout.Height(20)}))
|
||||
{
|
||||
rolloutStates[text] = !rolloutStates[text];
|
||||
}
|
||||
if (inset == true)
|
||||
{
|
||||
EditorGUILayout.GetControlRect(GUILayout.Width(40));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
return rolloutStates[text];
|
||||
}
|
||||
|
||||
Vector2 scroll;
|
||||
void OnGUI()
|
||||
{
|
||||
|
||||
if (Selection.activeGameObject == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No objects selected. Please select an object with a MicroSplatVertexMesh component on it");
|
||||
return;
|
||||
}
|
||||
|
||||
DrawChannelGUI();
|
||||
|
||||
tab = (Tab)GUILayout.Toolbar((int)tab, tabNames);
|
||||
if (tab != Tab.Utility)
|
||||
{
|
||||
scroll = EditorGUILayout.BeginScrollView(scroll);
|
||||
DrawPaintGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
scroll = EditorGUILayout.BeginScrollView(scroll);
|
||||
DrawUtilityGUI();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
|
||||
void DrawChannelGUI()
|
||||
{
|
||||
EditorGUILayout.Separator();
|
||||
GUI.skin.box.normal.textColor = Color.white;
|
||||
if (DrawRollup ("Vertex Painter"))
|
||||
{
|
||||
bool oldEnabled = enabled;
|
||||
enabled = GUILayout.Toggle (enabled, "Active (ESC)");
|
||||
if (enabled != oldEnabled)
|
||||
{
|
||||
InitMeshes ();
|
||||
}
|
||||
EditorGUILayout.BeginHorizontal ();
|
||||
bool emptyStreams = false;
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
if (!jobs [i].HasStream ())
|
||||
emptyStreams = true;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal ();
|
||||
if (emptyStreams)
|
||||
{
|
||||
if (GUILayout.Button ("Add Vertex Stream"))
|
||||
{
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
jobs [i].EnforceStream ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
brushVisualization = (BrushVisualization)EditorGUILayout.EnumPopup ("Brush Visualization", brushVisualization);
|
||||
EditorGUILayout.BeginHorizontal ();
|
||||
showVertexPoints = GUILayout.Toggle (showVertexPoints, "Show Brush Influence");
|
||||
showVertexSize = EditorGUILayout.Slider (showVertexSize, 0.2f, 10);
|
||||
EditorGUILayout.EndHorizontal ();
|
||||
bool oldHideMeshWireframe = hideMeshWireframe;
|
||||
hideMeshWireframe = !GUILayout.Toggle (!hideMeshWireframe, "Show Wireframe (ctrl-W)");
|
||||
|
||||
if (hideMeshWireframe != oldHideMeshWireframe)
|
||||
{
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
SetWireframeDisplay (jobs [i].renderer, hideMeshWireframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EditorGUILayout.Separator();
|
||||
GUILayout.Box("", new GUILayoutOption[]{GUILayout.ExpandWidth(true), GUILayout.Height(1)});
|
||||
EditorGUILayout.Separator();
|
||||
|
||||
}
|
||||
|
||||
void DrawBrushSettingsGUI()
|
||||
{
|
||||
if (jobs == null || jobs.Length == 0 || jobs [0] == null || jobs [0].vertexMesh == null )
|
||||
return;
|
||||
|
||||
int maxTex = 27;
|
||||
Texture2DArray ta = null;
|
||||
if (jobs [0].vertexMesh.templateMaterial != null)
|
||||
{
|
||||
ta = jobs [0].vertexMesh.templateMaterial.GetTexture ("_Diffuse") as Texture2DArray;
|
||||
if (ta != null && ta.depth > 0)
|
||||
{
|
||||
maxTex = ta.depth - 1;
|
||||
}
|
||||
}
|
||||
if (tab == Tab.Paint)
|
||||
{
|
||||
if (ta != null)
|
||||
{
|
||||
channel = MicroSplatUtilities.DrawTextureSelector (channel, ta);
|
||||
}
|
||||
else
|
||||
{
|
||||
channel = EditorGUILayout.IntSlider ("Texture Index", channel, 0, maxTex);
|
||||
}
|
||||
}
|
||||
targetValue = EditorGUILayout.Slider ("Target Value", targetValue, 0.0f, 1.0f);
|
||||
brushSize = EditorGUILayout.Slider("Brush Size", brushSize, 0.01f, 90.0f);
|
||||
brushFlow = EditorGUILayout.Slider("Brush Flow", brushFlow, 0.1f, 128.0f);
|
||||
brushFalloff = EditorGUILayout.Slider("Brush Falloff", brushFalloff, 0.1f, 3.5f);
|
||||
|
||||
EditorGUILayout.Separator();
|
||||
GUILayout.Box("", new GUILayoutOption[]{GUILayout.ExpandWidth(true), GUILayout.Height(1)});
|
||||
EditorGUILayout.Separator();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void DrawPaintGUI()
|
||||
{
|
||||
|
||||
GUILayout.Box("Brush Settings", new GUILayoutOption[]{GUILayout.ExpandWidth(true), GUILayout.Height(20)});
|
||||
|
||||
DrawBrushSettingsGUI();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Fill"))
|
||||
{
|
||||
if (OnBeginStroke != null)
|
||||
{
|
||||
OnBeginStroke(jobs);
|
||||
}
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
Undo.RecordObject(jobs[i].stream, "Vertex Painter Fill");
|
||||
FillMesh(jobs[i]);
|
||||
}
|
||||
if (OnEndStroke != null)
|
||||
{
|
||||
OnEndStroke();
|
||||
}
|
||||
Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
}
|
||||
|
||||
List<IVertexPainterUtility> utilities = new List<IVertexPainterUtility>();
|
||||
void InitPluginUtilities()
|
||||
{
|
||||
if (utilities == null || utilities.Count == 0)
|
||||
{
|
||||
var interfaceType = typeof(IVertexPainterUtility);
|
||||
var all = System.AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(x => x.GetTypes())
|
||||
.Where(x => interfaceType.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)
|
||||
.Select(x => System.Activator.CreateInstance(x));
|
||||
|
||||
|
||||
foreach (var o in all)
|
||||
{
|
||||
IVertexPainterUtility u = o as IVertexPainterUtility;
|
||||
if (u != null)
|
||||
{
|
||||
utilities.Add(u);
|
||||
}
|
||||
}
|
||||
utilities = utilities.OrderBy(o=>o.GetName()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawUtilityGUI()
|
||||
{
|
||||
InitPluginUtilities();
|
||||
for (int i = 0; i < utilities.Count; ++i)
|
||||
{
|
||||
var u = utilities[i];
|
||||
if (DrawRollup(u.GetName(), false))
|
||||
{
|
||||
u.OnGUI(jobs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnFocus()
|
||||
{
|
||||
if (painting)
|
||||
{
|
||||
EndStroke();
|
||||
}
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
SceneView.duringSceneGui -= this.OnSceneGUI;
|
||||
SceneView.duringSceneGui += this.OnSceneGUI;
|
||||
#else
|
||||
SceneView.onSceneGUIDelegate -= this.OnSceneGUI;
|
||||
SceneView.onSceneGUIDelegate += this.OnSceneGUI;
|
||||
#endif
|
||||
|
||||
Undo.undoRedoPerformed -= this.OnUndo;
|
||||
Undo.undoRedoPerformed += this.OnUndo;
|
||||
this.titleContent = new GUIContent("Vertex Paint");
|
||||
Repaint();
|
||||
|
||||
}
|
||||
|
||||
void OnInspectorUpdate()
|
||||
{
|
||||
// unfortunate...
|
||||
Repaint ();
|
||||
}
|
||||
|
||||
void OnSelectionChange()
|
||||
{
|
||||
InitMeshes();
|
||||
this.Repaint();
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
SceneView.duringSceneGui -= this.OnSceneGUI;
|
||||
#else
|
||||
SceneView.onSceneGUIDelegate -= this.OnSceneGUI;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae410e13fcd2a4249980c7bef9494a95
|
||||
timeCreated: 1447732490
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,726 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if __MICROSPLAT__
|
||||
|
||||
namespace JBooth.MicroSplat.VertexPainter
|
||||
{
|
||||
public partial class VertexPainterWindow : EditorWindow
|
||||
{
|
||||
// for external tools
|
||||
public System.Action<PaintJob[]> OnBeginStroke;
|
||||
public System.Action<PaintJob, bool> OnStokeModified; // bool is true when doing a fill or other non-bounded opperation
|
||||
public System.Action OnEndStroke;
|
||||
|
||||
|
||||
// C# doesn't have *& or **, so it's not easy to pass a reference to a value for changing.
|
||||
// instead, we wrap the setter into a templated lambda which allows us to pass a changable
|
||||
// reference around via a function which sets it. Pretty tricky sis, but I'd rather just
|
||||
// be able to pass the freaking reference already..
|
||||
// Note the ref object, which is there just to prevent boxing of Vector/Color structs. Also
|
||||
// note the complete lack of type safety, etc.. ugh..
|
||||
|
||||
// whats worse- this could also be condensed down to a macro, which would actually be MORE
|
||||
// safe in terms of potential bugs than all this; and it would be like a dozen lines to boot.
|
||||
|
||||
|
||||
public bool enabled;
|
||||
public Vector3 oldpos = Vector3.zero;
|
||||
public float brushSize = 1;
|
||||
public float brushFlow = 8;
|
||||
public float brushFalloff = 1; // linear
|
||||
public int channel = 0;
|
||||
public float targetValue = 1.0f;
|
||||
|
||||
public bool showVertexPoints = false;
|
||||
public float showVertexSize = 1;
|
||||
|
||||
public enum BrushVisualization
|
||||
{
|
||||
Sphere,
|
||||
Disk
|
||||
}
|
||||
public BrushVisualization brushVisualization = BrushVisualization.Sphere;
|
||||
public PaintJob[] jobs = new PaintJob[0];
|
||||
// bool used to know if we've registered an undo with this object or not
|
||||
public bool[] jobEdits = new bool[0];
|
||||
|
||||
|
||||
void InitMeshes()
|
||||
{
|
||||
List<PaintJob> pjs = new List<PaintJob>();
|
||||
Object[] objs = Selection.GetFiltered(typeof(GameObject), SelectionMode.Editable | SelectionMode.Deep);
|
||||
for (int i = 0; i < objs.Length; ++i)
|
||||
{
|
||||
GameObject go = objs[i] as GameObject;
|
||||
if (go != null)
|
||||
{
|
||||
MeshFilter mf = go.GetComponent<MeshFilter>();
|
||||
Renderer r = go.GetComponent<Renderer>();
|
||||
MicroSplatVertexMesh vmesh = go.GetComponent<MicroSplatVertexMesh> ();
|
||||
if (mf != null && r != null && mf.sharedMesh != null && mf.sharedMesh.isReadable && vmesh != null)
|
||||
{
|
||||
pjs.Add(new PaintJob(mf, r, vmesh));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jobs = pjs.ToArray();
|
||||
jobEdits = new bool[jobs.Length];
|
||||
}
|
||||
|
||||
void SetWireframeDisplay(Renderer r, bool hidden)
|
||||
{
|
||||
EditorUtility.SetSelectedRenderState(r, hidden ?
|
||||
EditorSelectedRenderState.Hidden : EditorSelectedRenderState.Highlight);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void OnUndo()
|
||||
{
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
if (jobs[i].stream != null)
|
||||
{
|
||||
jobs[i].stream.Apply(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFXEnabled(PaintJob j)
|
||||
{
|
||||
return (j.vertexMesh.keywordSO.IsKeywordEnabled ("_STREAMS") ||
|
||||
j.vertexMesh.keywordSO.IsKeywordEnabled ("_LAVA") ||
|
||||
j.vertexMesh.keywordSO.IsKeywordEnabled ("_WETNESS") ||
|
||||
j.vertexMesh.keywordSO.IsKeywordEnabled ("_PUDDLES"));
|
||||
}
|
||||
|
||||
int GetChannel()
|
||||
{
|
||||
int c = channel;
|
||||
#if __MICROSPLAT_STREAMS__
|
||||
if (tab == Tab.Wetness)
|
||||
{
|
||||
c = 28;
|
||||
}
|
||||
else if (tab == Tab.Puddles)
|
||||
{
|
||||
c = 29;
|
||||
}
|
||||
else if (tab == Tab.Streams)
|
||||
{
|
||||
c = 30;
|
||||
}
|
||||
else if (tab == Tab.Lava)
|
||||
{
|
||||
c = 31;
|
||||
}
|
||||
#endif
|
||||
return c;
|
||||
}
|
||||
|
||||
public void FillMesh(PaintJob job)
|
||||
{
|
||||
PrepBrushMode(job);
|
||||
int c = GetChannel ();
|
||||
|
||||
if (c >= 28)
|
||||
{
|
||||
for (int i = 0; i < job.verts.Length; ++i)
|
||||
{
|
||||
Color clr = job.stream.colors [i];
|
||||
var uv1 = job.stream.uv1 [i];
|
||||
var uv2 = job.stream.uv2 [i];
|
||||
ToTemp (clr, uv1, uv2);
|
||||
tempWeights [c] = targetValue;
|
||||
FromTemp (ref clr, ref uv1, ref uv2);
|
||||
job.stream.colors [i] = clr;
|
||||
job.stream.uv1 [i] = uv1;
|
||||
job.stream.uv2 [i] = uv2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < job.verts.Length; ++i)
|
||||
{
|
||||
Color clr = job.stream.colors [i];
|
||||
var uv1 = job.stream.uv1 [i];
|
||||
var uv2 = job.stream.uv2 [i];
|
||||
ToTemp (clr, uv1, uv2);
|
||||
|
||||
for (int x = 0; x < 28; ++x)
|
||||
{
|
||||
tempWeights [x] = 0;
|
||||
}
|
||||
tempWeights [c] = 1;
|
||||
FromTemp (ref clr, ref uv1, ref uv2);
|
||||
job.stream.colors [i] = clr;
|
||||
job.stream.uv1 [i] = uv1;
|
||||
job.stream.uv2 [i] = uv2;
|
||||
|
||||
}
|
||||
}
|
||||
job.stream.Apply();
|
||||
if (OnStokeModified != null)
|
||||
{
|
||||
OnStokeModified(job, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void InitColors(PaintJob j)
|
||||
{
|
||||
Color[] colors = j.stream.colors;
|
||||
if (colors == null || colors.Length != j.verts.Length)
|
||||
{
|
||||
Color[] orig = j.meshFilter.sharedMesh.colors;
|
||||
if (j.meshFilter.sharedMesh.colors != null && j.meshFilter.sharedMesh.colors.Length > 0)
|
||||
{
|
||||
j.stream.colors = orig;
|
||||
}
|
||||
else
|
||||
{
|
||||
j.stream.SetColor(Color.white, j.verts.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InitUV1 (PaintJob j)
|
||||
{
|
||||
var uvs = j.stream.uv1;
|
||||
if (uvs == null || uvs.Count != j.verts.Length)
|
||||
{
|
||||
if (j.meshFilter.sharedMesh.uv2 != null && j.meshFilter.sharedMesh.uv2.Length == j.verts.Length)
|
||||
{
|
||||
List<Vector4> nuv = new List<Vector4> (j.meshFilter.sharedMesh.vertices.Length);
|
||||
j.meshFilter.sharedMesh.GetUVs (1, nuv);
|
||||
j.stream.uv1 = nuv;
|
||||
}
|
||||
else
|
||||
{
|
||||
j.stream.SetUV1 (Vector2.zero, j.verts.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InitUV2 (PaintJob j)
|
||||
{
|
||||
var uvs = j.stream.uv2;
|
||||
if (uvs == null || uvs.Count != j.verts.Length)
|
||||
{
|
||||
if (j.meshFilter.sharedMesh.uv3 != null && j.meshFilter.sharedMesh.uv3.Length == j.verts.Length)
|
||||
{
|
||||
List<Vector4> nuv = new List<Vector4> (j.meshFilter.sharedMesh.vertices.Length);
|
||||
j.meshFilter.sharedMesh.GetUVs (2, nuv);
|
||||
j.stream.uv2 = nuv;
|
||||
}
|
||||
else
|
||||
{
|
||||
j.stream.SetUV2 (Vector2.zero, j.verts.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InitPositions(PaintJob j)
|
||||
{
|
||||
Vector3[] pos = j.stream.positions;
|
||||
if (pos == null || pos.Length != j.verts.Length)
|
||||
{
|
||||
int vc = j.meshFilter.sharedMesh.vertexCount;
|
||||
if (j.stream.positions == null || j.stream.positions.Length != vc)
|
||||
{
|
||||
j.stream.positions = new Vector3[j.meshFilter.sharedMesh.vertices.Length];
|
||||
j.meshFilter.sharedMesh.vertices.CopyTo(j.stream.positions, 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public void PrepBrushMode(PaintJob j)
|
||||
{
|
||||
InitColors (j);
|
||||
InitUV1 (j);
|
||||
InitUV2 (j);
|
||||
}
|
||||
|
||||
|
||||
void DrawVertexPoints(PaintJob j, Vector3 point)
|
||||
{
|
||||
if (j.HasStream() && j.HasData())
|
||||
{
|
||||
PrepBrushMode(j);
|
||||
}
|
||||
if (j.renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// convert point into local space, so we don't have to convert every point
|
||||
var mtx = j.renderer.transform.localToWorldMatrix;
|
||||
point = j.renderer.transform.worldToLocalMatrix.MultiplyPoint3x4(point);
|
||||
// for some reason this doesn't handle scale, seems like it should
|
||||
// we handle it poorly until I can find a better solution
|
||||
float scale = 1.0f / Mathf.Abs(j.renderer.transform.lossyScale.x);
|
||||
|
||||
float bz = scale * brushSize;
|
||||
bz *= bz;
|
||||
|
||||
for (int i = 0; i < j.verts.Length; ++i)
|
||||
{
|
||||
//float d = Vector3.Distance(point, j.verts[i]);
|
||||
var p = j.verts[i];
|
||||
float x = point.x - p.x;
|
||||
float y = point.y - p.y;
|
||||
float z = point.z - p.z;
|
||||
float dist = x * x + y * y + z * z;
|
||||
|
||||
if (dist < bz)
|
||||
{
|
||||
Vector3 wp = mtx.MultiplyPoint(j.verts[i]);
|
||||
Handles.SphereHandleCap(0, point, Quaternion.identity, HandleUtility.GetHandleSize(wp) * 0.02f * showVertexSize, EventType.Repaint);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Color DecodeToColor (float v)
|
||||
{
|
||||
var vi = (uint)(v * (256.0f * 256.0f * 256.0f * 256.0f));
|
||||
var ex = (int)(vi / (256 * 256 * 256) % 256);
|
||||
var ey = (int)((vi / (256 * 256)) % 256);
|
||||
var ez = (int)((vi / (256)) % 256);
|
||||
var ew = (int)(vi % 256);
|
||||
var e = new Color (ex / 255.0f, ey / 255.0f, ez / 255.0f, ew / 255.0f);
|
||||
return e;
|
||||
}
|
||||
|
||||
float EncodeToFloat (Color enc)
|
||||
{
|
||||
var ex = (uint)(enc.r * 255);
|
||||
var ey = (uint)(enc.g * 255);
|
||||
var ez = (uint)(enc.b * 255);
|
||||
var ew = (uint)(enc.a * 255);
|
||||
var v = (ex << 24) + (ey << 16) + (ez << 8) + ew;
|
||||
return v / (256.0f * 256.0f * 256.0f * 256.0f);
|
||||
}
|
||||
|
||||
float [] tempWeights = new float [32];
|
||||
void ToTemp(Color c, Vector4 uv1, Vector4 uv2)
|
||||
{
|
||||
Color s0 = DecodeToColor (c.r);
|
||||
Color s1 = DecodeToColor (c.g);
|
||||
Color s2 = DecodeToColor (c.b);
|
||||
Color s3 = DecodeToColor (c.a);
|
||||
Color s4 = DecodeToColor (uv1.z);
|
||||
Color s5 = DecodeToColor (uv1.w);
|
||||
Color s6 = DecodeToColor (uv2.z);
|
||||
Color s7 = DecodeToColor (uv2.w);
|
||||
|
||||
tempWeights [0] = s0.r;
|
||||
tempWeights [1] = s0.g;
|
||||
tempWeights [2] = s0.b;
|
||||
tempWeights [3] = s0.a;
|
||||
tempWeights [4] = s1.r;
|
||||
tempWeights [5] = s1.g;
|
||||
tempWeights [6] = s1.b;
|
||||
tempWeights [7] = s1.a;
|
||||
tempWeights [8] = s2.r;
|
||||
tempWeights [9] = s2.g;
|
||||
tempWeights [10] = s2.b;
|
||||
tempWeights [11] = s2.a;
|
||||
tempWeights [12] = s3.r;
|
||||
tempWeights [13] = s3.g;
|
||||
tempWeights [14] = s3.b;
|
||||
tempWeights [15] = s3.a;
|
||||
tempWeights [16] = s4.r;
|
||||
tempWeights [17] = s4.g;
|
||||
tempWeights [18] = s4.b;
|
||||
tempWeights [19] = s4.a;
|
||||
tempWeights [20] = s5.r;
|
||||
tempWeights [21] = s5.g;
|
||||
tempWeights [22] = s5.b;
|
||||
tempWeights [23] = s5.a;
|
||||
tempWeights [24] = s6.r;
|
||||
tempWeights [25] = s6.g;
|
||||
tempWeights [26] = s6.b;
|
||||
tempWeights [27] = s6.a;
|
||||
tempWeights [28] = s7.r;
|
||||
tempWeights [29] = s7.g;
|
||||
tempWeights [30] = s7.b;
|
||||
tempWeights [31] = s7.a;
|
||||
}
|
||||
|
||||
void FromTemp(ref Color c, ref Vector4 uv1, ref Vector4 uv2)
|
||||
{
|
||||
c.r = EncodeToFloat (new Color (tempWeights [0], tempWeights [1], tempWeights [2], tempWeights [3]));
|
||||
c.g = EncodeToFloat (new Color (tempWeights [4], tempWeights [5], tempWeights [6], tempWeights [7]));
|
||||
c.b = EncodeToFloat (new Color (tempWeights [8], tempWeights [9], tempWeights [10], tempWeights [11]));
|
||||
c.a = EncodeToFloat (new Color (tempWeights [12], tempWeights [13], tempWeights [14], tempWeights [15]));
|
||||
uv1.z = EncodeToFloat (new Color (tempWeights [16], tempWeights [17], tempWeights [18], tempWeights [19]));
|
||||
uv1.w = EncodeToFloat (new Color (tempWeights [20], tempWeights [21], tempWeights [22], tempWeights [23]));
|
||||
uv2.z = EncodeToFloat (new Color (tempWeights [24], tempWeights [25], tempWeights [26], tempWeights [27]));
|
||||
uv2.w = EncodeToFloat (new Color (tempWeights [28], tempWeights [29], tempWeights [30], tempWeights [31]));
|
||||
}
|
||||
|
||||
void NormalizeTempWeights(bool fx)
|
||||
{
|
||||
float total = 0;
|
||||
for (int i = 0; i < 28; ++i)
|
||||
{
|
||||
total += tempWeights [i];
|
||||
}
|
||||
for (int i = 0; i < 28; ++i)
|
||||
{
|
||||
tempWeights [i] /= total;
|
||||
}
|
||||
}
|
||||
|
||||
void PaintMesh(PaintJob j, Vector3 point, float value)
|
||||
{
|
||||
bool affected = false;
|
||||
PrepBrushMode(j);
|
||||
// convert point into local space, so we don't have to convert every point
|
||||
point = j.renderer.transform.worldToLocalMatrix.MultiplyPoint3x4(point);
|
||||
// for some reason this doesn't handle scale, seems like it should
|
||||
// we handle it poorly until I can find a better solution
|
||||
float scale = 1.0f / Mathf.Abs(j.renderer.transform.lossyScale.x);
|
||||
|
||||
float bz = scale * brushSize;
|
||||
bz *= bz;
|
||||
|
||||
float pressure = Event.current.pressure > 0 ? Event.current.pressure : 1.0f;
|
||||
|
||||
bool modPos = !(j.stream.positions == null || j.stream.positions.Length == 0);
|
||||
|
||||
{
|
||||
bool fx = IsFXEnabled (j);
|
||||
int cnl = GetChannel ();
|
||||
for (int i = 0; i < j.verts.Length; ++i)
|
||||
{
|
||||
Vector3 p = modPos ? j.stream.positions[i] : j.verts[i];
|
||||
float x = point.x - p.x;
|
||||
float y = point.y - p.y;
|
||||
float z = point.z - p.z;
|
||||
float dist = x * x + y * y + z * z;
|
||||
|
||||
if (dist < bz)
|
||||
{
|
||||
float str = 1.0f - dist / bz;
|
||||
str = Mathf.Pow(str, brushFalloff);
|
||||
float finalStr = str * (float)deltaTime * brushFlow * pressure;
|
||||
if (finalStr > 0)
|
||||
{
|
||||
affected = true;
|
||||
Color c = j.stream.colors [i];
|
||||
var uv1 = j.stream.uv1 [i];
|
||||
var uv2 = j.stream.uv2 [i];
|
||||
ToTemp (c, uv1, uv2);
|
||||
var twv = tempWeights [cnl];
|
||||
var after = Mathf.Clamp01(Mathf.Lerp(twv, value, finalStr));
|
||||
|
||||
tempWeights [cnl] = after;
|
||||
NormalizeTempWeights (fx);
|
||||
|
||||
FromTemp (ref c, ref uv1, ref uv2);
|
||||
j.stream.colors [i] = c;
|
||||
j.stream.uv1 [i] = uv1;
|
||||
j.stream.uv2 [i] = uv2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (affected)
|
||||
{
|
||||
j.stream.Apply();
|
||||
if (OnStokeModified != null)
|
||||
{
|
||||
OnStokeModified(j, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EndStroke()
|
||||
{
|
||||
if (OnEndStroke != null)
|
||||
{
|
||||
OnEndStroke();
|
||||
}
|
||||
painting = false;
|
||||
|
||||
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
PaintJob j = jobs[i];
|
||||
if (j.HasStream())
|
||||
{
|
||||
EditorUtility.SetDirty(j.stream);
|
||||
EditorUtility.SetDirty(j.stream.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
double deltaTime = 0;
|
||||
double lastTime = 0;
|
||||
bool painting = false;
|
||||
|
||||
void DoShortcuts()
|
||||
{
|
||||
if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape)
|
||||
{
|
||||
enabled = !enabled;
|
||||
if (enabled)
|
||||
{
|
||||
InitMeshes();
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
// brush adjustments
|
||||
const float adjustSpeed = 0.3f;
|
||||
if (Event.current.isKey && Event.current.type == EventType.KeyDown)
|
||||
{
|
||||
if (Event.current.keyCode == KeyCode.LeftBracket)
|
||||
{
|
||||
brushSize -= adjustSpeed;
|
||||
Repaint();
|
||||
}
|
||||
else if (Event.current.keyCode == KeyCode.RightBracket)
|
||||
{
|
||||
brushSize += adjustSpeed;
|
||||
Repaint();
|
||||
}
|
||||
else if (Event.current.keyCode == KeyCode.Semicolon)
|
||||
{
|
||||
brushFlow -= adjustSpeed;
|
||||
Repaint();
|
||||
}
|
||||
else if (Event.current.keyCode == KeyCode.Quote)
|
||||
{
|
||||
brushFlow += adjustSpeed;
|
||||
Repaint();
|
||||
}
|
||||
else if (Event.current.keyCode == KeyCode.Period)
|
||||
{
|
||||
brushFalloff -= adjustSpeed;
|
||||
Repaint();
|
||||
}
|
||||
else if (Event.current.keyCode == KeyCode.Slash)
|
||||
{
|
||||
brushFlow += adjustSpeed;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
DoShortcuts();
|
||||
|
||||
deltaTime = EditorApplication.timeSinceStartup - lastTime;
|
||||
lastTime = EditorApplication.timeSinceStartup;
|
||||
|
||||
if (jobs.Length == 0 && Selection.activeGameObject != null)
|
||||
{
|
||||
InitMeshes();
|
||||
}
|
||||
|
||||
if (!enabled || jobs.Length == 0 || Selection.activeGameObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab == Tab.Utility)
|
||||
{
|
||||
return;
|
||||
}
|
||||
RaycastHit hit;
|
||||
float distance = float.MaxValue;
|
||||
Vector3 mousePosition = Event.current.mousePosition;
|
||||
|
||||
// So, in 5.4, Unity added this value, which is basically a scale to mouse coordinates for retna monitors.
|
||||
// Not all monitors, just some of them.
|
||||
// What I don't get is why the fuck they don't just pass me the correct fucking value instead. I spent hours
|
||||
// finding this, and even the paid Unity support my company pays many thousands of dollars for had no idea
|
||||
// after several weeks of back and forth. If your going to fake the coordinates for some reason, please do
|
||||
// it everywhere to not just randomly break things everywhere you don't multiply some new value in.
|
||||
float mult = EditorGUIUtility.pixelsPerPoint;
|
||||
|
||||
mousePosition.y = sceneView.camera.pixelHeight - mousePosition.y * mult;
|
||||
mousePosition.x *= mult;
|
||||
Vector3 fakeMP = mousePosition;
|
||||
fakeMP.z = 20;
|
||||
Vector3 point = sceneView.camera.ScreenToWorldPoint(fakeMP);
|
||||
Vector3 normal = Vector3.forward;
|
||||
Ray ray = sceneView.camera.ScreenPointToRay(mousePosition);
|
||||
|
||||
bool registerUndo = (Event.current.type == EventType.MouseDown && Event.current.button == 0 && Event.current.alt == false);
|
||||
bool toggleWireframe = (Event.current.type == EventType.KeyUp && Event.current.control);
|
||||
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
if (jobs[i] == null || jobs[i].meshFilter == null)
|
||||
continue;
|
||||
|
||||
// Early out if we're not in the area..
|
||||
Bounds b = jobs[i].renderer.bounds;
|
||||
b.Expand(brushSize*2);
|
||||
if (!b.IntersectRay(ray))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (registerUndo)
|
||||
{
|
||||
painting = true;
|
||||
// clear job edits
|
||||
for (int x = 0; x < jobEdits.Length; ++x)
|
||||
{
|
||||
jobEdits[x] = false;
|
||||
}
|
||||
if (OnBeginStroke != null)
|
||||
{
|
||||
OnBeginStroke(jobs);
|
||||
}
|
||||
}
|
||||
if (toggleWireframe)
|
||||
{
|
||||
SetWireframeDisplay(jobs[i].renderer, hideMeshWireframe);
|
||||
}
|
||||
|
||||
Matrix4x4 mtx = jobs[i].meshFilter.transform.localToWorldMatrix;
|
||||
Mesh msh = jobs[i].meshFilter.sharedMesh;
|
||||
|
||||
if (jobs[i].HasStream())
|
||||
{
|
||||
msh = jobs[i].stream.GetModifierMesh();
|
||||
}
|
||||
if (msh == null)
|
||||
{
|
||||
msh = jobs[i].meshFilter.sharedMesh;
|
||||
}
|
||||
if (RXLookingGlass.IntersectRayMesh(ray, msh, mtx, out hit))
|
||||
{
|
||||
if (Event.current.shift == false)
|
||||
{
|
||||
if (hit.distance < distance)
|
||||
{
|
||||
distance = hit.distance;
|
||||
point = hit.point;
|
||||
oldpos = hit.point;
|
||||
normal = hit.normal;
|
||||
// if we don't have normal overrides, we have to recast against the shared mesh to get it's normal
|
||||
// This could get a little strange if you modify the mesh, then delete the normal data, but in that
|
||||
// case there's no real correct answer anyway without knowing the index of the vertex we're hitting.
|
||||
if (normal.magnitude < 0.1f)
|
||||
{
|
||||
RXLookingGlass.IntersectRayMesh(ray, jobs[i].meshFilter.sharedMesh, mtx, out hit);
|
||||
normal = hit.normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
point = oldpos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Event.current.shift == true)
|
||||
{
|
||||
point = oldpos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Event.current.type == EventType.MouseMove && Event.current.shift)
|
||||
{
|
||||
brushSize += Event.current.delta.x * (float)deltaTime * 6.0f;
|
||||
brushFalloff -= Event.current.delta.y * (float)deltaTime * 48.0f;
|
||||
}
|
||||
|
||||
if (Event.current.rawType == EventType.MouseUp)
|
||||
{
|
||||
EndStroke();
|
||||
}
|
||||
if (Event.current.type == EventType.MouseMove && Event.current.alt)
|
||||
{
|
||||
brushSize += Event.current.delta.y * (float)deltaTime;
|
||||
}
|
||||
|
||||
|
||||
Handles.color = new Color(1, 0, 0, 0.4f);
|
||||
|
||||
|
||||
if (brushVisualization == BrushVisualization.Sphere)
|
||||
{
|
||||
Handles.SphereHandleCap(0, point, Quaternion.identity, brushSize * 2, EventType.Repaint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Handles.color = new Color(0.8f, 0, 0, 1.0f);
|
||||
float r = Mathf.Pow(0.5f, brushFalloff);
|
||||
Handles.DrawWireDisc(point, normal, brushSize * r);
|
||||
Handles.color = new Color(0.9f, 0, 0, 0.8f);
|
||||
Handles.DrawWireDisc(point, normal, brushSize);
|
||||
}
|
||||
// eat current event if mouse event and we're painting
|
||||
if (Event.current.isMouse && painting)
|
||||
{
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(GetHashCode(), FocusType.Passive));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (jobs.Length > 0 && painting)
|
||||
{
|
||||
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
Bounds b = jobs[i].renderer.bounds;
|
||||
b.Expand(brushSize*2);
|
||||
if (!b.IntersectRay(ray))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (jobEdits[i] == false)
|
||||
{
|
||||
jobEdits[i] = true;
|
||||
Undo.RegisterCompleteObjectUndo(jobs[i].stream, "Vertex Painter Stroke");
|
||||
}
|
||||
|
||||
PaintMesh(jobs[i], point, targetValue);
|
||||
Undo.RecordObject(jobs[i].stream, "Vertex Painter Stroke");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (jobs.Length > 0 && showVertexPoints)
|
||||
{
|
||||
for (int i = 0; i < jobs.Length; ++i)
|
||||
{
|
||||
DrawVertexPoints(jobs[i], point);
|
||||
}
|
||||
}
|
||||
|
||||
// update views
|
||||
sceneView.Repaint();
|
||||
HandleUtility.Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbee0de5b71e84e9989cb94b2521e6bc
|
||||
timeCreated: 1447602377
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user