// this is the delegate we're going to return to apply a brush stamp. Your passed a paint job, // which contains important cached information to make painting fast, and hold the actual stream data. // the index is the index into the vertex array, the val is the brush data we supplied in GetBrushObject, // and r is a 0 to 1 with how far to tween the value towards the brush (brush pressure * deltatime) void LerpFunc(PaintJob j, int idx, ref object val, float r) { // retrieve our brush data and get the stream we're painting into BrushData bd = val as BrushData; var s = j.stream; // use our vertex position to generate noise. We use the get position function because it will // return a vert position from the original meshes cached verticies or modified verticies if // we've modified them. This makes it compatible with deformations, etc. Vector3 pos = j.GetPosition(idx); // convert into world space pos = j.renderer.localToWorldMatrix.MultiplyPoint(pos); // scale by frequency pos.x *= bd.frequency; pos.y *= bd.frequency; pos.z *= bd.frequency; float noise = 0.5f * (0.5f * JBooth.VertexPainterPro.SimplexNoise.Noise.Generate(pos.x, pos.y, pos.z) + 0.5f); noise += 0.25f * (0.5f * JBooth.VertexPainterPro.SimplexNoise.Noise.Generate(pos.y * 2.031f, pos.z * 2.031f, pos.x * 2.031f) + 0.5f); noise += 0.25f * (0.5f * JBooth.VertexPainterPro.SimplexNoise.Noise.Generate(pos.z * 4.01f, pos.x * 4.01f, pos.y * 4.01f) + 0.5f); noise *= bd.amplitude; // lerp the noise in Color c = s.colors[idx]; c.r = Mathf.Lerp(c.r, noise, r); c.g = Mathf.Lerp(c.g, noise, r); c.b = Mathf.Lerp(c.b, noise, r); s.colors[idx] = c; }
// this is the delegate we're going to return to apply a brush stamp. Your passed a paint job, // which contains important cached information to make painting fast, and hold the actual stream data. // the index is the index into the vertex array, the val is the brush data we supplied in GetBrushObject, // and r is a 0 to 1 with how far to tween the value towards the brush (brush pressure * deltatime) void LerpFunc(PaintJob j, int idx, ref object val, float r) { // retrieve our brush data and get the stream we're painting into BrushData bd = val as BrushData; var s = j.stream; // use our vertex position to generate noise. We use the get position function because it will // return a vert position from the original meshes cached verticies or modified verticies if // we've modified them. This makes it compatible with deformations, etc. Vector3 pos = j.GetPosition(idx); // convert into world space pos = j.renderer.localToWorldMatrix.MultiplyPoint(pos); // scale by frequency pos.x *= bd.frequency; pos.y *= bd.frequency; pos.z *= bd.frequency; float noise = 0.5f * (0.5f * JBooth.VertexPainterPro.SimplexNoise.Noise.Generate(pos.x, pos.y, pos.z) + 0.5f); noise += 0.25f * (0.5f * JBooth.VertexPainterPro.SimplexNoise.Noise.Generate(pos.y * 2.031f, pos.z * 2.031f, pos.x * 2.031f) + 0.5f); noise += 0.25f * (0.5f * JBooth.VertexPainterPro.SimplexNoise.Noise.Generate(pos.z * 4.01f, pos.x * 4.01f, pos.y * 4.01f) + 0.5f); noise *= bd.amplitude; // lerp the noise in Color c = s.colors[idx]; c.r = Mathf.Lerp(c.r, noise, r); c.g = Mathf.Lerp(c.g, noise, r); c.b = Mathf.Lerp(c.b, noise, r); s.colors[idx] = c; }
public void OnGUI(PaintJob[] jobs) { bakingTex = EditorGUILayout.ObjectField("Texture", bakingTex, typeof(Texture2D), false) as Texture2D; bakeSourceUV = (BakeSourceUV)EditorGUILayout.EnumPopup("Source UVs", bakeSourceUV); bakeChannel = (BakeChannel)EditorGUILayout.EnumPopup("Bake To", bakeChannel); if (bakeSourceUV == BakeSourceUV.WorldSpaceXY || bakeSourceUV == BakeSourceUV.WorldSpaceXZ || bakeSourceUV == BakeSourceUV.WorldSpaceYZ) { worldSpaceLower = EditorGUILayout.Vector2Field("Lower world position", worldSpaceLower); worldSpaceUpper = EditorGUILayout.Vector2Field("Upper world position", worldSpaceUpper); } EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); if (GUILayout.Button("Bake")) { if (bakingTex != null) { BakeFromTexture(jobs); } else { EditorUtility.DisplayDialog("Error", "Baking texture is not set", "ok"); } } EditorGUILayout.Space(); EditorGUILayout.EndHorizontal(); }
void BakeColor(PaintJob job, BakeChannel bc, Vector4 val, int i) { switch (bc) { case BakeChannel.Color: { job.stream.colors[i] = new Color(val.x, val.y, val.z, val.w); break; } case BakeChannel.UV0: { job.stream.uv0[i] = val; break; } case BakeChannel.UV1: { job.stream.uv1[i] = val; break; } case BakeChannel.UV2: { job.stream.uv2[i] = val; break; } case BakeChannel.UV3: { job.stream.uv3[i] = val; break; } } }
void BakeColor(PaintJob job, BakeChannel bc, Vector4 val, int i) { switch (bc) { case BakeChannel.Color: { job.stream.colors[i] = new Color(val.x, val.y, val.z, val.w); break; } case BakeChannel.UV0: { job.stream.uv0[i] = val; break; } case BakeChannel.UV1: { job.stream.uv1[i] = val; break; } case BakeChannel.UV2: { job.stream.uv2[i] = val; break; } case BakeChannel.UV3: { job.stream.uv3[i] = val; break; } } }
public void OnGUI(PaintJob[] jobs) { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Combine Meshes")) { VertexPainterUtilities.MergeMeshes(jobs); } if (GUILayout.Button("Combine and Save")) { if (jobs.Length != 0) { string path = EditorUtility.SaveFilePanel("Save Asset", Application.dataPath, "models", "asset"); if (!string.IsNullOrEmpty(path)) { path = FileUtil.GetProjectRelativePath(path); GameObject go = VertexPainterUtilities.MergeMeshes(jobs); Mesh m = go.GetComponent<MeshFilter>().sharedMesh; AssetDatabase.CreateAsset(m, path); AssetDatabase.SaveAssets(); AssetDatabase.ImportAsset(path); GameObject.DestroyImmediate(go); } } } EditorGUILayout.EndHorizontal(); }
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.Optimize(); mesh.RecalculateBounds(); mesh.UploadMeshData(false); mf.sharedMesh = mesh; for (int i = 0; i < meshes.Count; ++i) { GameObject.DestroyImmediate(meshes[i].mesh); } return go; }
void LerpFunc(PaintJob j, int idx, ref object val, float r) { if (didHit && j.stream.gameObject != target && target != null) { // convert from world space to local space Vector3 norm = j.stream.GetSafeNormal(idx); Vector4 tang = j.stream.GetSafeTangent(idx); var mtx = j.stream.transform.worldToLocalMatrix; var t = tangent; t = mtx.MultiplyVector(tangent); t.w = tangent.w; j.stream.normals[idx] = Vector3.Lerp(norm, mtx.MultiplyVector(norm), r); j.stream.tangents[idx] = Vector4.Lerp(tang, t, r); } if (didHit && terrainTarget != null) { // retrieve our brush data and get the stream we're painting into Vector3 n = j.stream.normals[idx]; Vector4 t = j.stream.tangents[idx]; Vector3 pos = j.GetPosition(idx); Vector3 iNormal = terrainTarget.terrainData.GetInterpolatedNormal(pos.x, pos.z); iNormal = j.stream.transform.InverseTransformDirection(iNormal); j.stream.normals[idx] = Vector3.Lerp(n, iNormal, r); Vector3 tangentXYZ = Vector3.Cross(j.stream.normals[idx], new Vector3(0, 0, 1)); Vector4 tangent = new Vector4(tangentXYZ.x, tangentXYZ.y, tangentXYZ.z, -1); j.stream.tangents[idx] = Vector4.Lerp(t, tangent, r); } }
void LerpFunc(PaintJob j, int idx, ref object val, float r) { if (didHit) { j.stream.normals[idx] = Vector3.Lerp(j.stream.normals[idx], normal, r); j.stream.tangents[idx] = Vector4.Lerp(j.stream.tangents[idx], tangent, r); } }
static void FlowColorRG(PaintJob j, int idx, ref object v, float r) { Vector2 vv = (Vector2)v; var s = j.stream; Color c = s.colors[idx]; s.colors[idx].r = Mathf.Lerp(c.r, vv.x, r); s.colors[idx].g = Mathf.Lerp(c.g, vv.y, r); }
static void FlowColorBA(PaintJob j, int idx, ref object v, float r) { Vector2 vv = (Vector2)v; var s = j.stream; Color c = s.colors[idx]; s.colors[idx].b = Mathf.Lerp(c.b, vv.x, r); s.colors[idx].a = Mathf.Lerp(c.a, vv.y, r); }
static void FlowUV1_XY(PaintJob j, int idx, ref object v, float r) { var s = j.stream; Vector4 o = s.uv1[idx]; Vector2 t = (Vector2)v; o.x = Mathf.Lerp(o.x, t.x, r); o.y = Mathf.Lerp(o.y, t.y, r); s.uv1[idx] = o; }
static void FlowUV2_ZW(PaintJob j, int idx, ref object v, float r) { var s = j.stream; Vector4 o = s.uv2[idx]; Vector2 t = (Vector2)v; o.z = Mathf.Lerp(o.z, t.x, r); o.w = Mathf.Lerp(o.w, t.y, r); s.uv2[idx] = o; }
void DoBakePivot(PaintJob[] jobs) { switch (pivotTarget) { case PivotTarget.UV0: { InitBakeChannel(BakeChannel.UV0, jobs); foreach (PaintJob job in jobs) { Vector3 lp = bakePivotUseLocal ? job.meshFilter.transform.localPosition : job.meshFilter.transform.position; job.stream.SetUV0(new Vector4(lp.x, lp.y, lp.z, UnityEngine.Random.Range(0.0f, 1.0f)), job.verts.Length); EditorUtility.SetDirty(job.stream); EditorUtility.SetDirty(job.stream.gameObject); } break; } case PivotTarget.UV1: { InitBakeChannel(BakeChannel.UV1, jobs); foreach (PaintJob job in jobs) { Vector3 lp = bakePivotUseLocal ? job.meshFilter.transform.localPosition : job.meshFilter.transform.position; job.stream.SetUV1(new Vector4(lp.x, lp.y, lp.z, UnityEngine.Random.Range(0.0f, 1.0f)), job.verts.Length); EditorUtility.SetDirty(job.stream); EditorUtility.SetDirty(job.stream.gameObject); } break; } case PivotTarget.UV2: { InitBakeChannel(BakeChannel.UV2, jobs); foreach (PaintJob job in jobs) { Vector3 lp = bakePivotUseLocal ? job.meshFilter.transform.localPosition : job.meshFilter.transform.position; job.stream.SetUV2(new Vector4(lp.x, lp.y, lp.z, UnityEngine.Random.Range(0.0f, 1.0f)), job.verts.Length); EditorUtility.SetDirty(job.stream); EditorUtility.SetDirty(job.stream.gameObject); } break; } case PivotTarget.UV3: { InitBakeChannel(BakeChannel.UV3, jobs); foreach (PaintJob job in jobs) { Vector3 lp = bakePivotUseLocal ? job.meshFilter.transform.localPosition : job.meshFilter.transform.position; job.stream.SetUV3(new Vector4(lp.x, lp.y, lp.z, UnityEngine.Random.Range(0.0f, 1.0f)), job.verts.Length); EditorUtility.SetDirty(job.stream); EditorUtility.SetDirty(job.stream.gameObject); } break; } } }
public void OnGUI(PaintJob[] jobs) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); if (GUILayout.Button("Save Mesh")) { VertexPainterUtilities.SaveMesh(jobs); } EditorGUILayout.Space(); EditorGUILayout.EndHorizontal(); }
public void OnGUI(PaintJob[] jobs) { EditorGUILayout.HelpBox("Multiply a channel by a value. Useful for scaling UVs, objects, etc", MessageType.Info); bakeChannel = (BakeChannel)EditorGUILayout.EnumPopup("Channel", bakeChannel); multValue = EditorGUILayout.FloatField("Multiply By", multValue); EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); if (GUILayout.Button("Multiply It")) { Bake(jobs); } EditorGUILayout.Space(); EditorGUILayout.EndHorizontal(); }
public void OnGUI(PaintJob[] jobs) { pivotTarget = (PivotTarget)EditorGUILayout.EnumPopup("Store in", pivotTarget); bakePivotUseLocal = EditorGUILayout.Toggle("Use Local Space", bakePivotUseLocal); EditorGUILayout.BeginHorizontal(); EditorGUILayout.Space(); if (GUILayout.Button("Bake Pivot")) { DoBakePivot(jobs); } if (GUILayout.Button("Bake Rotation")) { DoBakeRotation(jobs); } EditorGUILayout.Space(); EditorGUILayout.EndHorizontal(); }
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); } } }
public void OnGUI(PaintJob[] jobs) { var window = VertexPainterWindow.GetWindow<VertexPainterWindow>(); window.brushMode = (VertexPainterWindow.BrushTarget)EditorGUILayout.EnumPopup("Target Channel", window.brushMode); aoSamples = EditorGUILayout.IntSlider("Samples", aoSamples, 64, 1024); EditorGUILayout.BeginHorizontal(); aoRange = EditorGUILayout.Vector2Field("Range (Min, Max)", aoRange); aoRange.x = Mathf.Max(aoRange.x, 0.0001f); EditorGUILayout.EndHorizontal(); aoIntensity = EditorGUILayout.Slider("Intensity", aoIntensity, 0.25f, 4.0f); bakeLighting = EditorGUILayout.Toggle("Bake Lighting", bakeLighting); if (bakeLighting) { aoLightAmbient = EditorGUILayout.ColorField("Light Ambient", aoLightAmbient); } aoBakeMode = (AOBakeMode)EditorGUILayout.EnumPopup("Mode", aoBakeMode); EditorGUILayout.Space(); if (GUILayout.Button("Bake")) { DoBakeAO(jobs, window); } }
void BakeAO() { Light[] aoLights = null; if (bakeLighting) { aoLights = GameObject.FindObjectsOfType <Light>(); } int sample = 0; int numVerts = 0; for (int i = 0; i < jobs.Length; ++i) { numVerts += jobs[i].verts.Length; } int numSamples = numVerts * aoSamples; float oldFloat = floatBrushValue; Color oldColor = brushColor; int oldVal = brushValue; // add temp colliders if needed bool[] tempCollider = new bool[jobs.Length]; for (int jIdx = 0; jIdx < jobs.Length; ++jIdx) { PaintJob job = jobs[jIdx]; if (job.meshFilter.GetComponent <Collider>() == null) { job.meshFilter.gameObject.AddComponent <MeshCollider>(); tempCollider[jIdx] = true; } } // do AO for (int jIdx = 0; jIdx < jobs.Length; ++jIdx) { PaintJob job = jobs[jIdx]; PrepBrushMode(job); // bake down the mesh so we take instance positions into account.. Mesh mesh = BakeDownMesh(job.meshFilter.sharedMesh, job.stream); Vector3[] verts = mesh.vertices; if (mesh.normals == null || mesh.normals.Length == 0) { mesh.RecalculateNormals(); } Vector3[] normals = mesh.normals; brushValue = 255; floatBrushValue = 1.0f; brushColor = Color.white; var val = GetBrushValue(); var setter = GetSetter(job.stream); for (int i = 0; i < job.verts.Length; ++i) { setter.Invoke(i, ref val); } for (int i = 0; i < verts.Length; i++) { Vector3 norm = normals[i]; // to world space! Vector3 v = job.meshFilter.transform.TransformPoint(verts[i]); Vector3 n = job.meshFilter.transform.TransformPoint(verts[i] + norm); Vector3 worldSpaceNormal = (n - v).normalized; float totalOcclusion = 0; // the slow part.. for (int j = 0; j < aoSamples; j++) { // random rotate around hemisphere float rot = 180.0f; float rot2 = rot / 2.0f; float rotx = ((rot * Random.value) - rot2); float roty = ((rot * Random.value) - rot2); float rotz = ((rot * Random.value) - rot2); Vector3 dir = Quaternion.Euler(rotx, roty, rotz) * Vector3.up; Quaternion dirq = Quaternion.FromToRotation(Vector3.up, worldSpaceNormal); Vector3 ray = dirq * dir; Vector3 offset = Vector3.Reflect(ray, worldSpaceNormal); // raycast ray = ray * (aoRange.y / ray.magnitude); if (Physics.Linecast(v - (offset * 0.1f), v + ray, out hit)) { if (hit.distance > aoRange.x) { totalOcclusion += Mathf.Clamp01(1 - (hit.distance / aoRange.y)); } } sample++; if (sample % 500 == 0) { EditorUtility.DisplayProgressBar("Baking AO...", "Baking...", (float)sample / (float)numSamples); } } totalOcclusion = Mathf.Clamp01(1 - ((totalOcclusion * aoIntensity) / aoSamples)); if (aoLights != null && aoLights.Length > 0) { Color c = aoLightAmbient; for (int l = 0; l < aoLights.Length; ++l) { Light light = aoLights[l]; ApplyAOLight(ref c, light, v, n); } c.r *= totalOcclusion; c.g *= totalOcclusion; c.b *= totalOcclusion; c.a = totalOcclusion; brushColor = c; // if we're lit and targeting a channel other than color, bake max intensity.. floatBrushValue = Mathf.Max(Mathf.Max(c.r, c.g), c.b) * totalOcclusion; brushValue = (int)(floatBrushValue * 255); } else { brushColor.r = totalOcclusion; brushColor.g = totalOcclusion; brushColor.b = totalOcclusion; brushColor.a = totalOcclusion; floatBrushValue = totalOcclusion; brushValue = (int)(totalOcclusion * 255); } val = GetBrushValue(); setter.Invoke(i, ref val); } job.stream.Apply(); DestroyImmediate(mesh); brushValue = oldVal; floatBrushValue = oldFloat; brushColor = oldColor; } // remove temp colliders for (int jIdx = 0; jIdx < jobs.Length; ++jIdx) { if (tempCollider[jIdx] == true) { Collider c = jobs[jIdx].meshFilter.GetComponent <Collider>(); if (c != null) { DestroyImmediate(c); } } } EditorUtility.ClearProgressBar(); SceneView.RepaintAll(); }
void DoBakeAO(PaintJob[] jobs, VertexPainterWindow window) { Light[] aoLights = null; if (bakeLighting) { aoLights = GameObject.FindObjectsOfType<Light>(); } int sample = 0; int numVerts = 0; for (int i = 0; i < jobs.Length; ++i) { numVerts += jobs[i].verts.Length; } int numSamples = numVerts * aoSamples; float oldFloat = window.floatBrushValue; Color oldColor = window.brushColor; int oldVal = window.brushValue; // add temp colliders if needed bool[] tempCollider = new bool[jobs.Length]; for (int jIdx = 0; jIdx < jobs.Length; ++jIdx) { PaintJob job = jobs[jIdx]; if (job.meshFilter.GetComponent<Collider>() == null) { job.meshFilter.gameObject.AddComponent<MeshCollider>(); tempCollider[jIdx] = true; } } // do AO for (int jIdx = 0; jIdx < jobs.Length; ++jIdx) { PaintJob job = jobs[jIdx]; window.PrepBrushMode(job); // bake down the mesh so we take instance positions into account.. Mesh mesh = VertexPainterUtilities.BakeDownMesh(job.meshFilter.sharedMesh, job.stream); Vector3[] verts = mesh.vertices; if (mesh.normals == null || mesh.normals.Length == 0) { mesh.RecalculateNormals(); } Vector3[] normals = mesh.normals; window.brushValue = 255; window.floatBrushValue = 1.0f; window.brushColor = Color.white; var val = window.GetBrushValue(); VertexPainterWindow.Lerper lerper = null; VertexPainterWindow.Multiplier mult = null; if (aoBakeMode == AOBakeMode.Replace) { lerper = window.GetLerper(); for (int i = 0; i < job.verts.Length; ++i) { lerper.Invoke(job, i, ref val, 1); } } else { mult = window.GetMultiplier(); } for (int i = 0; i<verts.Length; i++) { Vector3 norm = normals[i]; // to world space! Vector3 v = job.meshFilter.transform.TransformPoint(verts[i]); Vector3 n = job.meshFilter.transform.TransformPoint(verts[i] + norm); Vector3 worldSpaceNormal = (n-v).normalized; float totalOcclusion = 0; // the slow part.. for (int j = 0; j < aoSamples; j++) { // random rotate around hemisphere float rot = 180.0f; float rot2 = rot / 2.0f; float rotx = (( rot * Random.value ) - rot2); float roty = (( rot * Random.value ) - rot2); float rotz = (( rot * Random.value ) - rot2); Vector3 dir = Quaternion.Euler( rotx, roty, rotz ) * Vector3.up; Quaternion dirq = Quaternion.FromToRotation(Vector3.up, worldSpaceNormal); Vector3 ray = dirq * dir; Vector3 offset = Vector3.Reflect( ray, worldSpaceNormal ); // raycast ray = ray * (aoRange.y/ray.magnitude); if ( Physics.Linecast( v-(offset*0.1f), v + ray, out hit ) ) { if ( hit.distance > aoRange.x ) { totalOcclusion += Mathf.Clamp01( 1 - ( hit.distance / aoRange.y ) ); } } sample++; if (sample % 500 == 0) { EditorUtility.DisplayProgressBar("Baking AO...", "Baking...", (float)sample / (float)numSamples); } } totalOcclusion = Mathf.Clamp01( 1 - ((totalOcclusion*aoIntensity)/aoSamples) ); if (aoLights != null && aoLights.Length > 0) { Color c = aoLightAmbient; for (int l = 0; l < aoLights.Length; ++l) { Light light = aoLights[l]; ApplyAOLight(ref c, light, v, n); } c.r *= totalOcclusion; c.g *= totalOcclusion; c.b *= totalOcclusion; c.a = totalOcclusion; window.brushColor = c; // if we're lit and targeting a channel other than color, bake max intensity.. window.floatBrushValue = Mathf.Max(Mathf.Max(c.r, c.g), c.b) * totalOcclusion; window.brushValue = (int)(window.floatBrushValue * 255); } else { window.brushColor.r = totalOcclusion; window.brushColor.g = totalOcclusion; window.brushColor.b = totalOcclusion; window.brushColor.a = totalOcclusion; window.floatBrushValue = totalOcclusion; window.brushValue = (int)(totalOcclusion * 255); } val = window.GetBrushValue(); if (aoBakeMode == AOBakeMode.Replace) { lerper.Invoke(job, i, ref val, 1); } else { mult.Invoke(job.stream, i, ref val); } } job.stream.Apply(); EditorUtility.SetDirty(job.stream); EditorUtility.SetDirty(job.stream.gameObject); GameObject.DestroyImmediate(mesh); window.brushValue = oldVal; window.floatBrushValue = oldFloat; window.brushColor = oldColor; } // remove temp colliders for (int jIdx = 0; jIdx < jobs.Length; ++jIdx) { if (tempCollider[jIdx] == true) { Collider c = jobs[jIdx].meshFilter.GetComponent<Collider>(); if (c != null) { GameObject.DestroyImmediate(c); } } } EditorUtility.ClearProgressBar(); SceneView.RepaintAll(); }
void InitUV0(PaintJob j) { List<Vector4> uvs = j.stream.uv0; if (uvs == null || uvs.Count != j.verts.Length) { if (j.meshFilter.sharedMesh.uv != null && j.meshFilter.sharedMesh.uv.Length == j.verts.Length) { List<Vector4> nuv = new List<Vector4>(j.meshFilter.sharedMesh.vertices.Length); j.meshFilter.sharedMesh.GetUVs(0, nuv); j.stream.uv0 = nuv; } else { j.stream.SetUV0(Vector4.zero, j.verts.Length); } } }
static void ColorRGBAOverlay(PaintJob j, int idx, ref object v, float r) { var st = j.stream; Color c0 = st.colors[idx]; Color t = (Color)v; c0.r = Mathf.Lerp(c0.r, c0.r < 0.5f ? (2.0f * c0.r * t.r) : (1.0f - 2.0f * (1.0f - c0.r) * (1.0f - t.r)), r); c0.g = Mathf.Lerp(c0.g, c0.g < 0.5f ? (2.0f * c0.g * t.g) : (1.0f - 2.0f * (1.0f - c0.g) * (1.0f - t.g)), r); c0.b = Mathf.Lerp(c0.b, c0.b < 0.5f ? (2.0f * c0.b * t.b) : (1.0f - 2.0f * (1.0f - c0.b) * (1.0f - t.b)), r); st.colors[idx] = c0; }
void InitUV3(PaintJob j) { var uvs = j.stream.uv3; if (uvs == null || uvs.Count != j.verts.Length) { if (j.meshFilter.sharedMesh.uv4 != null && j.meshFilter.sharedMesh.uv4.Length == j.verts.Length) { List<Vector4> nuv = new List<Vector4>(j.meshFilter.sharedMesh.vertices.Length); j.meshFilter.sharedMesh.GetUVs(3, nuv); j.stream.uv3 = nuv; } else { j.stream.SetUV3(Vector2.zero, j.verts.Length); } } }
void InitNormalTangent(PaintJob j) { Vector3[] norms = j.stream.normals; if (norms == null || norms.Length != j.verts.Length) { int vc = j.meshFilter.sharedMesh.vertexCount; if (j.stream.normals == null || j.stream.normals.Length != vc) { j.stream.normals = new Vector3[j.meshFilter.sharedMesh.vertices.Length]; j.meshFilter.sharedMesh.normals.CopyTo(j.stream.normals, 0); } if (j.stream.tangents == null || j.stream.tangents.Length != vc) { j.stream.tangents = new Vector4[j.meshFilter.sharedMesh.vertices.Length]; j.meshFilter.sharedMesh.tangents.CopyTo(j.stream.tangents, 0); } } return; }
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; }
void DrawVertexPoints(PaintJob j, Vector3 point) { Profiler.BeginSample("Draw Vertex Points"); 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; for (int i = 0; i < j.verts.Length; ++i) { float d = Vector3.Distance(point, j.verts[i]); if (d < bz) { Handles.color = Color.white; Vector3 wp = j.meshFilter.transform.localToWorldMatrix.MultiplyPoint(j.verts[i]); Handles.SphereCap(0, wp, Quaternion.identity, HandleUtility.GetHandleSize(wp) * 0.02f); } } Profiler.EndSample(); }
void PrepBrushMode(PaintJob j) { if (tab == Tab.Custom) { if (customBrush == null) { Debug.Log("Custom Brush not set"); return; } var channels = customBrush.GetChannels(); if ((channels & VertexPainterCustomBrush.Channels.Colors) != 0) { InitColors(j); } if ((channels & VertexPainterCustomBrush.Channels.UV0) != 0) { InitUV0(j); } if ((channels & VertexPainterCustomBrush.Channels.UV1) != 0) { InitUV1(j); } if ((channels & VertexPainterCustomBrush.Channels.UV2) != 0) { InitUV2(j); } if ((channels & VertexPainterCustomBrush.Channels.UV3) != 0) { InitUV3(j); } if ((channels & VertexPainterCustomBrush.Channels.Positions) != 0) { InitPositions(j); } if ((channels & VertexPainterCustomBrush.Channels.Normals) != 0) { InitNormalTangent(j); } } else if (tab == Tab.Deform) { InitPositions(j); InitNormalTangent(j); return; } if (tab == Tab.Flow) { switch (flowTarget) { case FlowTarget.ColorRG: goto case FlowTarget.ColorBA; case FlowTarget.ColorBA: { InitColors(j); break; } case FlowTarget.UV0_XY: { InitUV0(j); break; } case FlowTarget.UV1_XY: { InitUV1(j); break; } case FlowTarget.UV2_XY: { InitUV2(j); break; } case FlowTarget.UV3_XY: { InitUV3(j); break; } } return; } // make sure the instance data is initialized switch (brushMode) { case BrushTarget.Color: goto case BrushTarget.ValueA; case BrushTarget.ValueR: goto case BrushTarget.ValueA; case BrushTarget.ValueG: goto case BrushTarget.ValueA; case BrushTarget.ValueB: goto case BrushTarget.ValueA; case BrushTarget.ValueA: { InitColors(j); break; } case BrushTarget.UV0_X: goto case BrushTarget.UV0_W; case BrushTarget.UV0_Y: goto case BrushTarget.UV0_W; case BrushTarget.UV0_Z: goto case BrushTarget.UV0_W; case BrushTarget.UV0_W: { InitUV0(j); break; } case BrushTarget.UV1_X: goto case BrushTarget.UV1_W; case BrushTarget.UV1_Y: goto case BrushTarget.UV1_W; case BrushTarget.UV1_Z: goto case BrushTarget.UV1_W; case BrushTarget.UV1_W: { InitUV1(j); break; } case BrushTarget.UV2_X: goto case BrushTarget.UV2_W; case BrushTarget.UV2_Y: goto case BrushTarget.UV2_W; case BrushTarget.UV2_Z: goto case BrushTarget.UV2_W; case BrushTarget.UV2_W: { InitUV2(j); break; } case BrushTarget.UV3_X: goto case BrushTarget.UV3_W; case BrushTarget.UV3_Y: goto case BrushTarget.UV3_W; case BrushTarget.UV3_Z: goto case BrushTarget.UV3_W; case BrushTarget.UV3_W: { InitUV3(j); break; } case BrushTarget.UV0_AsColor: { InitUV0(j); break; } case BrushTarget.UV1_AsColor: { InitUV1(j); break; } case BrushTarget.UV2_AsColor: { InitUV2(j); break; } case BrushTarget.UV3_AsColor: { InitUV3(j); break; } } }
static void UV3_AsColorRGBAOverlay(PaintJob j, int idx, ref object v, float r) { var st = j.stream; Vector4 vec = st.uv3[idx]; Color c0 = new Color(vec.x, vec.y, vec.z); Color t = (Color)v; c0.r = Mathf.Lerp(c0.r, c0.r < 0.5f ? (2.0f * c0.r * t.r) : (1.0f - 2.0f * (1.0f - c0.r) * (1.0f - t.r)), r); c0.g = Mathf.Lerp(c0.g, c0.g < 0.5f ? (2.0f * c0.g * t.g) : (1.0f - 2.0f * (1.0f - c0.g) * (1.0f - t.g)), r); c0.b = Mathf.Lerp(c0.b, c0.b < 0.5f ? (2.0f * c0.b * t.b) : (1.0f - 2.0f * (1.0f - c0.b) * (1.0f - t.b)), r); st.uv3[idx] = new Vector4(c0.r, c0.g, c0.b, vec.w); }
static void UV3_AsColorRGBADarken(PaintJob j, int idx, ref object v, float r) { var st = j.stream; float h, s, b; Vector4 vec = st.uv3[idx]; Color c = new Color(vec.x, vec.y, vec.z); Color.RGBToHSV(c, out h, out s, out b); b = Mathf.Lerp(b, 0.0f, r); c = Color.HSVToRGB(h, s, b); st.uv3[idx] = new Vector4(c.r, c.g, c.b, vec.w); }
static void ColorA(PaintJob j, int idx, ref object v, float r) { var s = j.stream; s.colors[idx].a = Mathf.Lerp(s.colors[idx].a, (float)v, r); }
void PaintMesh(PaintJob j, Vector3 point, Lerper lerper, object 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; float pressure = Event.current.pressure > 0 ? Event.current.pressure : 1.0f; bool modPos = !(j.stream.positions == null || j.stream.positions.Length == 0); if (tab == Tab.Flow) { float strength = strokeDir.magnitude; Vector3 sd = strokeDir.normalized; Vector2 target = new Vector2(0.5f, 0.5f); for (int i = 0; i < j.verts.Length; ++i) { float d = Vector3.Distance(point, modPos ? j.stream.positions[i] : j.verts[i]); if (d < bz) { Vector3 n = j.normals[i]; Vector4 t = j.tangents[i]; if (j.stream.normals != null && j.stream.normals.Length == j.verts.Length) { n = j.stream.normals[i]; } if (j.stream.tangents != null && j.stream.tangents.Length == j.verts.Length) { t = j.stream.tangents[i]; } var mtx = j.meshFilter.transform.localToWorldMatrix; n = mtx.MultiplyVector(n); Vector3 tg = new Vector3(t.x, t.y, t.z); tg = mtx.MultiplyVector(tg); t.x = tg.x; t.y = tg.y; t.z = tg.z; target.x = 0.5f; target.y = 0.5f; if (flowBrushType == FlowBrushType.Direction) { Vector3 b = Vector3.Cross(n, new Vector3(t.x, t.y, t.z) * t.w); float dx = Vector3.Dot(t, sd); float dy = Vector3.Dot(b, sd); target = new Vector2(dx, dy); target.Normalize(); if (flowTarget == FlowTarget.ColorBA || flowTarget == FlowTarget.ColorRG || flowRemap01) { target.x = target.x * 0.5f + 0.5f; target.y = target.y * 0.5f + 0.5f; } } float str = 1.0f - d / bz; str *= strength; // take brush speed into account.. str = Mathf.Pow(str, brushFalloff); object obj = target; float finalStr = str * (float)deltaTime * brushFlow * pressure; if (finalStr > 0) { affected = true; lerper.Invoke(j, i, ref obj, finalStr); } } } } else if (tab == Tab.Deform) { for (int i = 0; i < j.verts.Length; ++i) { float d = Vector3.Distance(point, j.verts[i]); if (d < bz) { float str = 1.0f - d / bz; str = Mathf.Pow(str, brushFalloff); affected = true; PaintVertPosition(j, i, str * (float)deltaTime * brushFlow * pressure); } } } else { for (int i = 0; i < j.verts.Length; ++i) { float d = Vector3.Distance(point, j.verts[i]); if (d < bz) { float str = 1.0f - d / bz; str = Mathf.Pow(str, brushFalloff); float finalStr = str * (float)deltaTime * brushFlow * pressure; if (finalStr > 0) { affected = true; lerper.Invoke(j, i, ref value, finalStr); } } } } if (affected) { j.stream.Apply(); } }
void PaintVertPosition(PaintJob j, int i, float strength) { switch (vertexMode) { case VertexMode.Adjust: { switch (vertexContraint) { case VertexContraint.Normal: { Vector3 cur = j.stream.positions[i]; Vector3 dir = j.stream.normals[i].normalized; dir *= strength; cur += pull ? dir : -dir; j.stream.positions[i] = cur; break; } case VertexContraint.Camera: { Vector3 cur = j.stream.positions[i]; Vector3 dir = strokeDir; dir *= strength; cur += pull ? dir : -dir; j.stream.positions[i] = cur; break; } case VertexContraint.X: { Vector3 cur = j.stream.positions[i]; Vector3 dir = new Vector3(1, 0, 0); dir *= strength; cur += pull ? dir : -dir; j.stream.positions[i] = cur; break; } case VertexContraint.Y: { Vector3 cur = j.stream.positions[i]; Vector3 dir = new Vector3(0, 1, 0); dir *= strength; cur += pull ? dir : -dir; j.stream.positions[i] = cur; break; } case VertexContraint.Z: { Vector3 cur = j.stream.positions[i]; Vector3 dir = new Vector3(0, 0, 1); dir *= strength; cur += pull ? dir : -dir; j.stream.positions[i] = cur; break; } } break; } case VertexMode.Smooth: { Vector3 cur = j.stream.positions[i]; var con = j.vertexConnections[i]; for (int x = 0; x < con.Count; ++x) { cur += j.stream.positions[con[x]]; } cur /= (con.Count + 1); ConstrainAxis(ref cur, j.stream.positions[i]); j.stream.positions[i] = Vector3.Lerp(j.stream.positions[i], cur, Mathf.Clamp01(strength)); break; } case VertexMode.Smear: { Vector3 cur = j.stream.positions[i]; Vector3 dir = strokeDir; dir *= strength; cur += pull ? dir : -dir; j.stream.positions[i] = cur; break; } case VertexMode.HistoryEraser: { Vector3 cur = j.stream.positions[i]; Vector3 orig = j.verts[i]; ConstrainAxis(ref orig, cur); j.stream.positions[i] = Vector3.Lerp(cur, orig, Mathf.Clamp01(strength)); break; } } }
static void UV3_W(PaintJob j, int idx, ref object v, float r) { var s = j.stream; Vector4 vec = s.uv3[idx]; vec.w = Mathf.Lerp(vec.w, (float)v, r); s.uv3[idx] = vec; }
static void UV3_AsColor(PaintJob j, int idx, ref object v, float r) { var s = j.stream; Color c = (Color)v; Vector4 asVector = new Vector4(c.r, c.g, c.b, c.a); s.uv3[idx] = Vector4.Lerp(s.uv3[idx], asVector, r); }