internal override void OnBrushEnter(EditableObject target, BrushSettings settings) { base.OnBrushEnter(target, settings); if (!m_LikelyToSupportVertexSculpt) { return; } EditableObjectData data; if (!m_EditableObjectsData.TryGetValue(target, out data)) { data = new EditableObjectData(); m_EditableObjectsData.Add(target, data); } data.vertices = target.editMesh.vertices; data.neighborLookup = PolyMeshUtility.GetAdjacentVertices(target.editMesh); data.commonVertices = PolyMeshUtility.GetCommonVertices(target.editMesh); data.commonVertexCount = data.commonVertices.Length; }
static HierarchyChanged() { EditorApplication.hierarchyChanged += () => { foreach (var gameObject in Selection.gameObjects) { var mesh = Util.GetMesh(gameObject); var id = EditableObject.GetMeshId(mesh); // if the mesh is an instance managed by polybrush check that it's not a duplicate. if (id != -1) { if (id != gameObject.GetInstanceID() && EditorUtility.InstanceIDToObject(id) != null) { mesh = PolyMeshUtility.DeepCopy(mesh); mesh.name = EditableObject.k_MeshInstancePrefix + gameObject.GetInstanceID(); var mf = gameObject.GetComponent <MeshFilter>(); var sf = gameObject.GetComponent <SkinnedMeshRenderer>(); var polyMesh = gameObject.GetComponent <PolybrushMesh>(); if (polyMesh != null) { polyMesh.SetMesh(mesh); PrefabUtility.RecordPrefabInstancePropertyModifications(polyMesh); } else if (mf != null) { mf.sharedMesh = mesh; } else if (sf != null) { sf.sharedMesh = mesh; } } } } }; }
void RebuildCaches(EditableObjectData data) { PolyMesh mesh = data.CacheTarget.editMesh; data.VertexCount = mesh.vertexCount; data.TriangleLookup = PolyMeshUtility.GetAdjacentTriangles(mesh); if (meshAttributes == null) { // clear caches data.SplatCache = null; data.SplatCurrent = null; data.SplatTarget = null; data.SplatErase = null; return; } data.SplatCache = new SplatSet(mesh, meshAttributes); SetupBaseTextures(data.SplatCache); data.SplatCurrent = new SplatSet(data.SplatCache); data.SplatTarget = new SplatSet(data.VertexCount, meshAttributes); data.SplatErase = new SplatSet(data.VertexCount, meshAttributes); }
// Called when the mouse begins hovering an editable object. internal override void OnBrushEnter(EditableObject target, BrushSettings settings) { base.OnBrushEnter(target, settings); if (target.graphicsMesh == null) { return; } RebuildCaches(target, settings); m_TriangleLookup = PolyMeshUtility.GetAdjacentTriangles(target.editMesh); MeshRenderer mr = target.gameObjectAttached.GetComponent <MeshRenderer>(); if (mr != null && mr.sharedMaterials != null) { m_LikelySupportsVertexColors = mr.sharedMaterials.Any(x => x != null && x.shader != null && PolyShaderUtil.SupportsVertexColors(x.shader)); } else { m_LikelySupportsVertexColors = false; } }
/// <summary> /// Applies mesh changes back to the pb_Object (if necessary). Optionally does a /// mesh rebuild. /// </summary> /// <param name="rebuildMesh">Only applies to ProBuilder meshes.</param> /// <param name="optimize">Determines if the mesh collisions are rebuilt (if that option is enabled) or if /// the mehs is a probuilder object, the mesh is optimized (condensed to share verts, other /// otpimziations etc) </param> internal void Apply(bool rebuildMesh, bool optimize = false) { if (m_PolybrushMesh.mode == PolybrushMesh.Mode.AdditionalVertexStream) { if (PolyEditor.instance.tool == BrushTool.RaiseLower || PolyEditor.instance.tool == BrushTool.Smooth) { if (s_RebuildNormals.value && (modifiedChannels & MeshChannel.Position) > 0) { PolyMeshUtility.RecalculateNormals(editMesh); } if (optimize) { graphicsMesh.RecalculateBounds(); UpdateMeshCollider(); } } editMesh.ApplyAttributesToUnityMesh(graphicsMesh, modifiedChannels); graphicsMesh.UploadMeshData(false); EditorUtility.SetDirty(gameObjectAttached.GetComponent <Renderer>()); if (m_PolybrushMesh.componentsCache.MeshFilter) { Undo.RecordObject(m_PolybrushMesh.componentsCache.MeshFilter, "Assign Polymesh to MeshFilter"); } if (m_PolybrushMesh) { m_PolybrushMesh.SynchronizeWithMeshRenderer(); } } #if PROBUILDER_4_0_OR_NEWER // if it's a probuilder object rebuild the mesh without optimization if (isProBuilderObject) { ProBuilderBridge.SetPositions(gameObjectAttached, editMesh.vertices); ProBuilderBridge.SetTangents(gameObjectAttached, editMesh.tangents); if (editMesh.colors != null && editMesh.colors.Length == editMesh.vertexCount) { Color[] colors = System.Array.ConvertAll(editMesh.colors, x => (Color)x); ProBuilderBridge.SetColors(gameObjectAttached, colors); } if (rebuildMesh) { ProBuilderBridge.ToMesh(gameObjectAttached); ProBuilderBridge.Refresh(gameObjectAttached, optimize ? ProBuilderBridge.RefreshMask.All : (ProBuilderBridge.RefreshMask.Colors | ProBuilderBridge.RefreshMask.Normals | ProBuilderBridge.RefreshMask.Tangents)); } } #endif if (m_PolybrushMesh.mode == PolybrushMesh.Mode.AdditionalVertexStream) { modifiedChannels = MeshChannel.Null; return; } if (PolyEditor.instance.tool == BrushTool.RaiseLower || PolyEditor.instance.tool == BrushTool.Smooth) { if (s_RebuildNormals.value)// && (modifiedChannels & MeshChannel.Position) > 0) { PolyMeshUtility.RecalculateNormals(editMesh); } if (optimize) { UpdateMeshCollider(); graphicsMesh.RecalculateBounds(); } } editMesh.ApplyAttributesToUnityMesh(graphicsMesh, modifiedChannels); if (m_PolybrushMesh.componentsCache.MeshFilter) { Undo.RecordObject(m_PolybrushMesh.componentsCache.MeshFilter, "Assign Polymesh to MeshFilter"); } m_PolybrushMesh.SynchronizeWithMeshRenderer(); modifiedChannels = MeshChannel.Null; }
private void Initialize(GameObject go) { CheckBackwardCompatiblity(go); gameObjectAttached = go; isProBuilderObject = false; #if PROBUILDER_4_0_OR_NEWER if (ProBuilderBridge.ProBuilderExists()) { isProBuilderObject = ProBuilderBridge.IsValidProBuilderMesh(gameObjectAttached); } #endif Mesh mesh = null; MeshRenderer meshRenderer = gameObjectAttached.GetComponent <MeshRenderer>(); meshFilter = gameObjectAttached.GetComponent <MeshFilter>(); _skinMeshRenderer = gameObjectAttached.GetComponent <SkinnedMeshRenderer>(); originalMesh = go.GetMesh(); if (originalMesh == null && _skinMeshRenderer != null) { originalMesh = _skinMeshRenderer.sharedMesh; } m_PolybrushMesh = gameObjectAttached.GetComponent <PolybrushMesh>(); if (m_PolybrushMesh == null) { m_PolybrushMesh = Undo.AddComponent <PolybrushMesh>(gameObjectAttached); m_PolybrushMesh.Initialize(); m_PolybrushMesh.mode = (s_UseAdditionalVertexStreams) ? PolybrushMesh.Mode.AdditionalVertexStream : PolybrushMesh.Mode.Mesh; } //attach the skinmesh ref to the polybrushmesh //it will be used when making a prefab containing a skin mesh. The limitation here is that the skin mesh must comes from an asset (which is 99.9999% of the time) if (_skinMeshRenderer != null) { Mesh sharedMesh = _skinMeshRenderer.sharedMesh; if (AssetDatabase.Contains(sharedMesh)) { m_PolybrushMesh.skinMeshRef = sharedMesh; } } #if PROBUILDER_4_0_OR_NEWER // if it's a probuilder object rebuild the mesh without optimization if (isProBuilderObject) { if (ProBuilderBridge.IsValidProBuilderMesh(gameObjectAttached)) { ProBuilderBridge.ToMesh(gameObjectAttached); ProBuilderBridge.Refresh(gameObjectAttached); } } #endif if (meshRenderer != null || _skinMeshRenderer != null) { mesh = m_PolybrushMesh.storedMesh; if (mesh == null) { mesh = PolyMeshUtility.DeepCopy(originalMesh); hadVertexStreams = false; } else { //prevents leak if (!MeshInstanceMatchesGameObject(mesh, gameObjectAttached)) { mesh = PolyMeshUtility.DeepCopy(mesh); } } mesh.name = k_MeshInstancePrefix + gameObjectAttached.GetInstanceID(); } polybrushMesh.SetMesh(mesh); PrefabUtility.RecordPrefabInstancePropertyModifications(polybrushMesh); _graphicsMesh = m_PolybrushMesh.storedMesh; source = polybrushMesh.mode == PolybrushMesh.Mode.AdditionalVertexStream? ModelSource.AdditionalVertexStreams : PolyEditorUtility.GetMeshGUID(originalMesh); GenerateCompositeMesh(); }
/// <summary> /// Save any modifications to the EditableObject. If the mesh is a scene mesh or imported mesh, it /// will be saved to a new asset. If the mesh was originally an asset mesh, the asset is overwritten. /// </summary> /// <param name="mesh">mesh to save</param> /// <param name="meshFilter">will update the mesh filter with the new mesh if not null</param> /// <param name="skinnedMeshRenderer">will update the skinned mesh renderer with the new mesh if not null</param> /// <returns>return true if save was successful, false if user-canceled or otherwise failed.</returns> internal static bool SaveMeshAsset(Mesh mesh, MeshFilter meshFilter = null, SkinnedMeshRenderer skinnedMeshRenderer = null, int overridenDialogResult = -1, string overridenPath = "") { if (mesh == null) { return(false); } string save_path = !string.IsNullOrEmpty(overridenPath) ? overridenPath : DO_NOT_SAVE; ModelSource source = GetMeshGUID(mesh); switch (source) { case ModelSource.Asset: int saveChanges = overridenDialogResult != -1 ? overridenDialogResult : EditorUtility.DisplayDialogComplex( "Save Changes", "Save changes to edited mesh?", "Save", // DIALOG_OK "Cancel", // DIALOG_CANCEL "Save As"); // DIALOG_ALT if (saveChanges == DIALOG_OK) { save_path = AssetDatabase.GetAssetPath(mesh); } else if (saveChanges == DIALOG_ALT) { save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.name + ".asset", "asset", "Save edited mesh to"); } else { return(false); } break; case ModelSource.Imported: case ModelSource.Scene: default: save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.name + ".asset", "asset", "Save edited mesh to"); break; } if (!save_path.Equals(DO_NOT_SAVE) && !string.IsNullOrEmpty(save_path)) { Mesh existing = AssetDatabase.LoadAssetAtPath <Mesh>(save_path); bool overwrite = existing != null; Mesh dst = overwrite ? existing : new Mesh(); PolyMeshUtility.Copy(mesh, dst); if (!overwrite) { AssetDatabase.CreateAsset(dst, save_path); } AssetDatabase.Refresh(); return(true); } // Save was canceled return(false); }
/// <summary> /// Save any modifications to the EditableObject. If the mesh is a scene mesh or imported mesh, it /// will be saved to a new asset. If the mesh was originally an asset mesh, the asset is overwritten. /// </summary> /// <param name="mesh">mesh to save</param> /// <param name="meshFilter">will update the mesh filter with the new mesh if not null</param> /// <param name="skinnedMeshRenderer">will update the skinned mesh renderer with the new mesh if not null</param> /// <returns>return true if save was successful, false if user-canceled or otherwise failed.</returns> internal static bool SaveMeshAsset(Mesh mesh, MeshFilter meshFilter = null, SkinnedMeshRenderer skinnedMeshRenderer = null, int overridenDialogResult = -1, string overridenPath = "") { if (mesh == null) { return(false); } string save_path = !string.IsNullOrEmpty(overridenPath) ? overridenPath : DO_NOT_SAVE; ModelSource source = GetMeshGUID(mesh); switch (source) { case ModelSource.Asset: int saveChanges = overridenDialogResult != -1 ? overridenDialogResult : EditorUtility.DisplayDialogComplex( "Save Changes", "Save changes to edited mesh?", "Save", // DIALOG_OK "Cancel", // DIALOG_CANCEL "Save As"); // DIALOG_ALT if (saveChanges == DIALOG_OK) { save_path = AssetDatabase.GetAssetPath(mesh); } else if (saveChanges == DIALOG_ALT) { save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.name + ".asset", "asset", "Save edited mesh to"); } else { return(false); } break; case ModelSource.Imported: case ModelSource.Scene: default: save_path = EditorUtility.SaveFilePanelInProject("Save Mesh As", mesh.name + ".asset", "asset", "Save edited mesh to"); break; } if (!save_path.Equals(DO_NOT_SAVE) && !string.IsNullOrEmpty(save_path)) { Object existing = AssetDatabase.LoadMainAssetAtPath(save_path); if (existing != null && existing is Mesh) { //if the mesh that we want to create is the same than the mesh found at this path, do nothing if (existing.GetInstanceID() == mesh.GetInstanceID()) { //nothing to do here } // save over an existing mesh asset else { PolyMeshUtility.Copy((Mesh)existing, mesh); Object.DestroyImmediate(mesh, true); } } else { Mesh newMesh = new Mesh(); PolyMeshUtility.Copy(newMesh, mesh); AssetDatabase.CreateAsset(newMesh, save_path); } AssetDatabase.Refresh(); return(true); } // Save was canceled return(false); }
/// <summary> /// Calculates the per-vertex weight for each raycast hit and fills in brush target weights. /// </summary> /// <param name="target"></param> /// <param name="settings"></param> /// <param name="tool"></param> /// <param name="bMode"></param> internal static void CalculateWeightedVertices(BrushTarget target, BrushSettings settings, BrushTool tool = BrushTool.None, BrushMode bMode = null) { if (target == null || settings == null) { return; } if (target.editableObject == null) { return; } bool uniformScale = Math.VectorIsUniform(target.transform.lossyScale); float scale = uniformScale ? 1f / target.transform.lossyScale.x : 1f; PolyMesh mesh = target.editableObject.visualMesh; Transform transform = target.transform; int vertexCount = mesh.vertexCount; Vector3[] vertices = mesh.vertices; if (!uniformScale) { // As we only increase size only when it's needed, always make sure to // use the vertexCount variable in loop statements and not the buffer length. if (s_WorldBuffer.Length < vertexCount) { System.Array.Resize <Vector3>(ref s_WorldBuffer, vertexCount); } for (int i = 0; i < vertexCount; i++) { s_WorldBuffer[i] = transform.TransformPoint(vertices[i]); } vertices = s_WorldBuffer; } AnimationCurve curve = settings.falloffCurve; float radius = settings.radius * scale, falloff_mag = Mathf.Max((radius - radius * settings.falloff), 0.00001f); Vector3 hitPosition = Vector3.zero; PolyRaycastHit hit; if (tool == BrushTool.Texture && mesh.subMeshCount > 1) { var mode = bMode as BrushModeTexture; int[] submeshIndices = mesh.subMeshes[mode.m_CurrentMeshACIndex].indexes; for (int n = 0; n < target.raycastHits.Count; n++) { hit = target.raycastHits[n]; hit.SetVertexCount(vertexCount); for (int i = 0; i < vertexCount; i++) { hit.weights[i] = 0f; } hitPosition = uniformScale ? hit.position : transform.TransformPoint(hit.position); for (int i = 0; i < submeshIndices.Length; i++) { int currentIndex = submeshIndices[i]; float dist = (hitPosition - vertices[currentIndex]).magnitude; float delta = radius - dist; if (delta >= 0) { float weight = Mathf.Clamp(curve.Evaluate(1f - Mathf.Clamp(delta / falloff_mag, 0f, 1f)), 0f, 1f); hit.weights[currentIndex] = weight; } } } } else { int[][] common = PolyMeshUtility.GetCommonVertices(mesh); Vector3 buf = Vector3.zero; for (int n = 0; n < target.raycastHits.Count; n++) { hit = target.raycastHits[n]; hit.SetVertexCount(vertexCount); hitPosition = uniformScale ? hit.position : transform.TransformPoint(hit.position); for (int i = 0; i < common.Length; i++) { int[] commonItem = common[i]; int commonArrayCount = commonItem.Length; Math.Subtract(vertices[commonItem[0]], hitPosition, ref buf); float sqrDist = buf.sqrMagnitude; if (sqrDist > radius * radius) { for (int j = 0; j < commonArrayCount; j++) { hit.weights[commonItem[j]] = 0f; } } else { float weight = Mathf.Clamp(curve.Evaluate(1f - Mathf.Clamp((radius - Mathf.Sqrt(sqrDist)) / falloff_mag, 0f, 1f)), 0f, 1f); for (int j = 0; j < commonArrayCount; j++) { hit.weights[commonItem[j]] = weight; } } } } } target.GetAllWeights(true); }
/// <summary> /// Calculates the per-vertex weight for each raycast hit and fills in brush target weights. /// </summary> /// <param name="target"></param> /// <param name="settings"></param> /// <param name="tool"></param> /// <param name="bMode"></param> internal static void CalculateWeightedVertices(BrushTarget target, BrushSettings settings, BrushTool tool = BrushTool.None, BrushMode bMode = null) { //null checks if (target == null || settings == null) { return; } if (target.editableObject == null) { return; } bool uniformScale = PolyMath.VectorIsUniform(target.transform.lossyScale); float scale = uniformScale ? 1f / target.transform.lossyScale.x : 1f; PolyMesh mesh = target.editableObject.visualMesh; if (tool == BrushTool.Texture && mesh.subMeshCount > 1) { var mode = bMode as BrushModeTexture; int[] submeshIndices = mesh.subMeshes[mode.currentMeshACIndex].indexes; //List<List<int>> common = PolyMeshUtility.GetCommonVertices(mesh); Transform transform = target.transform; int vertexCount = mesh.vertexCount; Vector3[] vertices = mesh.vertices; if (!uniformScale) { Vector3[] world = new Vector3[vertexCount]; for (int i = 0; i < vertexCount; i++) { world[i] = transform.TransformPoint(vertices[i]); } vertices = world; } AnimationCurve curve = settings.falloffCurve; float radius = settings.radius * scale, falloff_mag = Mathf.Max((radius - radius * settings.falloff), 0.00001f); Vector3 hitPosition = Vector3.zero; PolyRaycastHit hit; for (int n = 0; n < target.raycastHits.Count; n++) { hit = target.raycastHits[n]; hit.SetVertexCount(vertexCount); for (int i = 0; i < vertexCount; i++) { hit.weights[i] = 0f; } hitPosition = uniformScale ? hit.position : transform.TransformPoint(hit.position); for (int i = 0; i < submeshIndices.Length; i++) { int currentIndex = submeshIndices[i]; float dist = (hitPosition - vertices[currentIndex]).magnitude; float delta = radius - dist; if (delta >= 0) { float weight = Mathf.Clamp(curve.Evaluate(1f - Mathf.Clamp(delta / falloff_mag, 0f, 1f)), 0f, 1f); hit.weights[currentIndex] = weight; } } } } else { List <List <int> > common = PolyMeshUtility.GetCommonVertices(mesh); Transform transform = target.transform; int vertexCount = mesh.vertexCount; Vector3[] vertices = mesh.vertices; if (!uniformScale) { Vector3[] world = new Vector3[vertexCount]; for (int i = 0; i < vertexCount; i++) { world[i] = transform.TransformPoint(vertices[i]); } vertices = world; } AnimationCurve curve = settings.falloffCurve; float radius = settings.radius * scale, falloff_mag = Mathf.Max((radius - radius * settings.falloff), 0.00001f); Vector3 hitPosition = Vector3.zero; PolyRaycastHit hit; for (int n = 0; n < target.raycastHits.Count; n++) { hit = target.raycastHits[n]; hit.SetVertexCount(vertexCount); hitPosition = uniformScale ? hit.position : transform.TransformPoint(hit.position); for (int i = 0; i < common.Count; i++) { int commonArrayCount = common[i].Count; float sqrDist = (hitPosition - vertices[common[i][0]]).sqrMagnitude; if (sqrDist > radius * radius) { for (int j = 0; j < commonArrayCount; j++) { hit.weights[common[i][j]] = 0f; } } else { float weight = Mathf.Clamp(curve.Evaluate(1f - Mathf.Clamp((radius - Mathf.Sqrt(sqrDist)) / falloff_mag, 0f, 1f)), 0f, 1f); for (int j = 0; j < commonArrayCount; j++) { hit.weights[common[i][j]] = weight; } } } } } target.GetAllWeights(true); }