public static void init() { foreach (Transform t in Selection.transforms) { if (t.GetComponent <MeshFilter>()) { pb_Object pb = ProBuilderize(t); if (pb.GetComponent <MeshCollider>()) { DestroyImmediate(pb.GetComponent <MeshCollider>()); } } } }
/** * Do the thing. Return a pb_ActionResult indicating the success/failure of action. */ public override pb_ActionResult DoAction() { #if !UNITY_4_6 && !UNITY_4_7 ShadowCastingMode shadowMode = (ShadowCastingMode)EditorPrefs.GetInt("pb_CreateShadowObject_shadowMode", (int)ShadowCastingMode.ShadowsOnly); #endif float extrudeDistance = EditorPrefs.GetFloat("pb_CreateShadowObject_volumeSize", .08f); ExtrudeMethod extrudeMethod = (ExtrudeMethod)EditorPrefs.GetInt("pb_CreateShadowObject_extrudeMethod", (int)ExtrudeMethod.FaceNormal); foreach (pb_Object pb in selection) { pb_Object shadow = GetShadowObject(pb); if (shadow == null) { continue; } foreach (pb_Face f in shadow.faces) { f.ReverseIndices(); f.manualUV = true; } shadow.Extrude(shadow.faces, extrudeMethod, extrudeDistance); shadow.ToMesh(); shadow.Refresh(); shadow.Optimize(); #if !UNITY_4_6 && !UNITY_4_7 MeshRenderer mr = shadow.gameObject.GetComponent <MeshRenderer>(); mr.shadowCastingMode = shadowMode; if (shadowMode == ShadowCastingMode.ShadowsOnly) { mr.receiveShadows = false; } #endif Collider collider = shadow.GetComponent <Collider>(); while (collider != null) { GameObject.DestroyImmediate(collider); collider = shadow.GetComponent <Collider>(); } } // This is necessary! Otherwise the pb_Editor will be working with caches from // outdated meshes and throw errors. pb_Editor.Refresh(); return(new pb_ActionResult(Status.Success, "Create Shadow Object")); }
public static void ProBuilderizeObjects(bool preserveFaces) { foreach (Transform t in Selection.transforms) { if (t.GetComponent <MeshFilter>()) { pb_Object pb = ProBuilderize(t, preserveFaces); if (pb.GetComponent <MeshCollider>()) { DestroyImmediate(pb.GetComponent <MeshCollider>()); } } } }
bool FaceRaycast(Vector2 mouse, out pb_Object pb, out pb_Face face) { var ray = Camera.main.ScreenPointToRay(mouse); RaycastHit rayHit; if (Physics.Raycast(ray.origin, ray.direction, out rayHit)) { pb = rayHit.transform.gameObject.GetComponent <pb_Object>(); if (pb == null) { face = null; return(false); } Mesh m = pb.GetComponent <MeshFilter>().sharedMesh; int[] tri = new int[3] { m.triangles[rayHit.triangleIndex * 3 + 0], m.triangles[rayHit.triangleIndex * 3 + 1], m.triangles[rayHit.triangleIndex * 3 + 2] }; return(pb.FaceWithTriangle(tri, out face)); } pb = null; face = null; return(false); }
/** * \brief This is how we figure out what face is clicked. */ public bool FaceCheck(Vector3 pos) { Ray ray = Camera.main.ScreenPointToRay(pos); RaycastHit hit; if (Physics.Raycast(ray.origin, ray.direction, out hit)) { pb_Object hitpb = hit.transform.gameObject.GetComponent <pb_Object>(); if (hitpb == null) { return(false); } Mesh m = hitpb.GetComponent <MeshFilter>().sharedMesh; int[] tri = new int[3] { m.triangles[hit.triangleIndex * 3 + 0], m.triangles[hit.triangleIndex * 3 + 1], m.triangles[hit.triangleIndex * 3 + 2] }; currentSelection.pb = hitpb; return(hitpb.FaceWithTriangle(tri, out currentSelection.face)); } return(false); }
/// <summary> /// <b>호출</b> : Util.MakeWalls(), Util.MakeOutlineWalls()<br></br> /// <b>참조</b> : GetVertices(), FindFace(), GetIntersect(), Util.Index(), pb_Object.CreateInstanceWithVerticesFaces() <br></br> /// 벽 생성 함수. /// 문/창문이 있을 경우, 그 부분에 구멍이 뚫린 벽 모델을 얻기 위해 /// Probuilder로 기존 벽과 오브젝트의 transfrom 정보를 참조하여 모든 점과 교점을 수집한 뒤, /// 부분적으로 사각형 단위로 submesh를 생성하고 통합하여 mesh를 생성한다.<br></br> /// 현재 월드좌표에서 y축을 높이 축으로 사용 중이며, /// 따라서 벽 생성이 xy 평면 또는 zy평면에서 생성되므로 /// 각 벽의 방향에 따라 주 좌표 축(메인 인덱스, index)로 삼고, y축을 sub 좌표 축(index2)로 사용한다. /// 또 mesh 생성시 normal(앞/뒷면)구분을 위해 벽의 회전 각도에 따라 주 좌표 축의 방향(dir)을 음수나 양수로 지정하였다. /// </summary> /// <param name="wall">생성시 참조할 기본벽</param> /// <param name="elements">문/창문 리스트</param> /// <param name="material">생성된 벽에 적용할 재질</param> /// <param name="area">생성된 벽의 면적</param> /// <param name="isDebug">디버그 옵션</param> /// <returns> /// probuilder로 새로 생성한 벽 mesh 오브젝트 /// </returns> public static GameObject MakeWalls(Transform wall, List <Transform> elements, Material material, out float area, bool isDebug = false) { debugMode = isDebug; List <Vector3> vertices = new List <Vector3>(); List <Transform> elementsInThis = new List <Transform>(); area = 0; // 벽의 회전 각도로 메인 축 인덱스 찾기 : x = 0, z = 2 int index = Util.Index(wall); // 벽의 회전 각도로 메인 축의 방향 찾기 : 정방향 = 1, 역방향 = -1 float dir = (wall.eulerAngles.y == 90 || wall.eulerAngles.y == 180) ? -1 : 1; // 기본 벽을 기준으로 모서리 점 추가 vertices.AddRange(GetVertices(wall, wall, index)); // 벽 안에 문/창문이 있을 경우 점 추가 foreach (var element in elements) { if (GetIntersect(wall, element, index, dir)) { Vector3[] verts = GetVertices(element, wall, index); vertices.AddRange(verts); elementsInThis.Add(element); } } // 초기 dot 생성 확인 //if (debugMode) GameObject.DontDestroyOnLoad(Util.Visualize(vertices, "dots")); // 면 만들기 pb_Object pb = pb_Object.CreateInstanceWithVerticesFaces(vertices.ToArray(), FindFace(vertices, elementsInThis, index, 1, dir, out area)); pb.GetComponent <MeshRenderer>().material = material; pb.GetComponent <MeshRenderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided; //pb.name = "Wall"+index; return(pb.gameObject); }
/// <summary> /// <b>호출</b> : DecoratePresenter.BindItem()<br></br> /// <b>참조</b> : Util.GetGridValue(), pb_Object.CreateInstanceWithVerticesFaces() <br></br> /// 문 없음 객체에서 문 모델 오브젝트 대신 빈 벽을 땜빵할 mesh를 새롭게 생성하는 함수. /// 벽지/바닥재 적용 시 uv 스케일링과 월드좌표 반영을 위해서 참조 quad를 probuilder로 다시 생성한다. /// </summary> /// <param name="quad">다시 생성할 참조 quad</param> /// <param name="material">적용할 기본 재질</param> /// <param name="area">생성된 mesh의 면적 - 견적에서 사용됨.</param> /// <returns>probuilder로 생성된 mesh 오브젝트</returns> public static GameObject MakeQuad(GameObject quad, Material material, out float area) { // quad의 모서리 정보(x,y)를 월드 좌표로 수집. Vector3[] vertices = new Vector3[4]; vertices[0] = quad.transform.TransformPoint(Vector3.left * 0.5f + Vector3.down * 0.5f); vertices[1] = quad.transform.TransformPoint(Vector3.right * 0.5f + Vector3.down * 0.5f); vertices[2] = quad.transform.TransformPoint(Vector3.left * 0.5f + Vector3.up * 0.5f); vertices[3] = quad.transform.TransformPoint(Vector3.right * 0.5f + Vector3.up * 0.5f); pb_Object pb = pb_Object.CreateInstanceWithVerticesFaces(vertices, MakeBox(new int[] { 0, 1, 2, 3 })); pb.GetComponent <MeshRenderer>().material = material; pb.name = quad.name; GameObject.Destroy(quad); area = Util.GetGridValue(quad.transform.localScale.x * quad.transform.localScale.y); return(pb.gameObject); }
/** * \brief Creates a new #pb_Object using passed vertices to construct geometry. * Typically you would not call this directly, as the #ProBuilder class contains * a wrapper for this purpose. In fact, I'm not sure why this is public... * @param vertices A vertex array (Vector3[]) containing the points to be used in * the construction of the #pb_Object. Vertices must be wound in counter-clockise * order. Triangles will be wound in vertex groups of 4, with the winding order * 0,1,2 1,3,2. Ex: * \code{.cs} * // Creates a pb_Object plane * pb_Object.CreateInstanceWithPoints(new Vector3[4]{ * new Vector3(-.5f, -.5f, 0f), * new Vector3(.5f, -.5f, 0f), * new Vector3(-.5f, .5f, 0f), * new Vector3(.5f, .5f, 0f) * }); * * \endcode * \returns The resulting #pb_Object. */ public static pb_Object CreateInstanceWithPoints(Vector3[] vertices) { if (vertices.Length % 4 != 0) { Debug.LogWarning("Invalid Geometry. Make sure vertices in are pairs of 4 (faces)."); return(null); } GameObject _gameObject = new GameObject(); pb_Object pb_obj = _gameObject.AddComponent <pb_Object>(); pb_obj.SetName("Object"); pb_obj.GeometryWithPoints(vertices); pb_obj.GetComponent <pb_Entity>().SetEntity(EntityType.Detail); return(pb_obj); }
/** * \brief Duplicates and mirrors the passed pb_Object. * @param pb The donor pb_Object. * @param axe The axis to mirror the object on. * \returns The newly duplicated pb_Object. * \sa ProBuilder.Axis */ public static pb_Object Mirror(pb_Object pb, Vector3 scale) { pb_Object p = pb_Object.InitWithObject(pb); p.MakeUnique(); p.transform.parent = pb.transform.parent; p.transform.localRotation = pb.transform.localRotation; Vector3 lScale = p.gameObject.transform.localScale; p.transform.localScale = new Vector3(lScale.x * scale.x, lScale.y * scale.y, lScale.z * scale.z); // if flipping on an odd number of axes, flip winding order if( (scale.x * scale.y * scale.z) < 0) p.ReverseWindingOrder(p.faces); p.FreezeScaleTransform(); p.transform.localScale = pb.transform.localScale; Collider col = pb.GetComponent<Collider>(); ColliderType colType = ColliderType.None; if(col != null) { if(col is MeshCollider) colType = ColliderType.MeshCollider; else colType = ColliderType.BoxCollider; } pb_Editor_Utility.InitObjectFlags(p, colType, pb.GetComponent<pb_Entity>().entityType); p.ToMesh(); p.Refresh(); // InitObjectFlags runs ScreenCenter() p.transform.position = pb.transform.position; Undo.RegisterCreatedObjectUndo(p.gameObject, "Mirror Object"); return p; }
public static void GenerateUV2(this pb_Object pb, bool forceUpdate) { if (pb_Preferences_Internal.GetBool(pb_Constant.pbDisableAutoUV2Generation) && !forceUpdate) { return; } // SetUVParams(8f, 15f, 15f, 20f); UnwrapParam param; UnwrapParam.SetDefaults(out param); param.angleError = Mathf.Clamp(pb.angleError, 1f, 75f) * .01f; param.areaError = Mathf.Clamp(pb.areaError, 1f, 75f) * .01f; param.hardAngle = Mathf.Clamp(pb.hardAngle, 0f, 180f); param.packMargin = Mathf.Clamp(pb.packMargin, 1f, 64) * .001f; Unwrapping.GenerateSecondaryUVSet(pb.GetComponent <MeshFilter>().sharedMesh, param); EditorUtility.SetDirty(pb); }
private static bool GetMeshForComponent(ModelExporter exporter, pb_Object component, FbxNode fbxNode) { Mesh mesh = new Mesh(); Material[] materials = null; pb_MeshCompiler.Compile(component, ref mesh, out materials, m_FbxOptions.quads ? MeshTopology.Quads : MeshTopology.Triangles); exporter.ExportMesh(mesh, fbxNode, materials); UnityEngine.Object.DestroyImmediate(mesh); // since probuilder can't handle mesh assets that may be externally reloaded, just strip pb // stuff for now. pb_Entity entity = component.GetComponent <pb_Entity>(); component.dontDestroyMeshOnDelete = true; UnityEngine.Object.DestroyImmediate(component); if (entity != null) { UnityEngine.Object.DestroyImmediate(entity); } return(true); }
/** * Creates a new pb_Object instance with the provided vertices, faces, and sharedIndex information. */ public static pb_Object CreateInstanceWithElements(Vector3[] v, Vector2[] u, Color[] c, pb_Face[] f, pb_IntArray[] si, pb_IntArray[] si_uv) { GameObject _gameObject = new GameObject(); pb_Object pb = _gameObject.AddComponent <pb_Object>(); pb.SetVertices(v); pb.SetUV(u); pb.SetColors(c); pb.SetSharedIndices(si ?? pb_IntArrayUtility.ExtractSharedIndices(v)); pb.SetSharedIndicesUV(si_uv ?? new pb_IntArray[0] { }); pb.SetFaces(f); pb.ToMesh(); pb.Refresh(); pb.GetComponent <pb_Entity>().SetEntity(EntityType.Detail); return(pb); }
private static void OnFaceChanged(pb_Object pb) { StaticEditorFlags flags = GameObjectUtility.GetStaticEditorFlags(pb.gameObject); // if nodraw found if (pb.containsNodraw) { if ((flags & StaticEditorFlags.BatchingStatic) == StaticEditorFlags.BatchingStatic) { flags ^= StaticEditorFlags.BatchingStatic; GameObjectUtility.SetStaticEditorFlags(pb.gameObject, flags); } } else { // if nodraw not found, and entity type should be batching static if (pb.GetComponent <pb_Entity>().entityType != EntityType.Mover) { flags = flags | StaticEditorFlags.BatchingStatic; GameObjectUtility.SetStaticEditorFlags(pb.gameObject, flags); } } }
public static void GenerateUV2(this pb_Object pb, bool show_NoDraw) { if (pb.onlyNodraw) { Vector2[] u = new Vector2[pb.msh.vertices.Length]; for (int n = 0; n < u.Length; n++) { u[n] = Vector2.zero; } pb.SetUV2(u); return; } Vector2[] uvs = pb.msh.uv; // nodraw uvs are nuked in this process, so save 'em pb.ToMesh(true); // re-draw meshes without nodraw faces // SetUVParams(8f, 15f, 15f, 20f); UnwrapParam param; UnwrapParam.SetDefaults(out param); param.angleError = Mathf.Clamp(pb.angleError, 1f, 75f) * .01f; param.areaError = Mathf.Clamp(pb.areaError, 1f, 75f) * .01f; param.hardAngle = Mathf.Clamp(pb.hardAngle, 0f, 180f); param.packMargin = Mathf.Clamp(pb.packMargin, 1f, 64) * .001f; Unwrapping.GenerateSecondaryUVSet(pb.GetComponent <MeshFilter>().sharedMesh, param); if (show_NoDraw) { pb.ToMesh(false); } pb.msh.uv = uvs; EditorUtility.SetDirty(pb); }
private static void OnFaceChanged( pb_Object pb ) { StaticEditorFlags flags = GameObjectUtility.GetStaticEditorFlags( pb.gameObject ); // if nodraw found if(pb.containsNodraw) { if( (flags & StaticEditorFlags.BatchingStatic) == StaticEditorFlags.BatchingStatic ) { flags ^= StaticEditorFlags.BatchingStatic; GameObjectUtility.SetStaticEditorFlags(pb.gameObject, flags); } } else { // if nodraw not found, and entity type should be batching static if(pb.GetComponent<pb_Entity>().entityType != EntityType.Mover) { flags = flags | StaticEditorFlags.BatchingStatic; GameObjectUtility.SetStaticEditorFlags(pb.gameObject, flags); } } }
/** * \brief Given an array of "donors", this method returns a merged #pb_Object. */ public static bool CombineObjects(pb_Object[] pbs, out pb_Object combined) { combined = null; if(pbs.Length < 1) return false; List<Vector3> v = new List<Vector3>(); List<Vector2> u = new List<Vector2>(); List<Color> c = new List<Color>(); List<pb_Face> f = new List<pb_Face>(); List<pb_IntArray> s = new List<pb_IntArray>(); List<pb_IntArray> suv = new List<pb_IntArray>(); foreach(pb_Object pb in pbs) { int vertexCount = v.Count; // Vertices v.AddRange(pb.VerticesInWorldSpace()); // UVs u.AddRange(pb.uv); // Colors c.AddRange(pb.colors); // Faces pb_Face[] faces = new pb_Face[pb.faces.Length]; for(int i = 0; i < faces.Length; i++) { faces[i] = new pb_Face(pb.faces[i]); faces[i].manualUV = true; faces[i].ShiftIndices(vertexCount); faces[i].RebuildCaches(); } f.AddRange(faces); // Shared Indices pb_IntArray[] si = pb.GetSharedIndices(); for(int i = 0; i < si.Length; i++) { for(int n = 0; n < si[i].Length; n++) si[i][n] += vertexCount; } s.AddRange(si); // Shared Indices UV { pb_IntArray[] si_uv = pb.GetSharedIndicesUV(); for(int i = 0; i < si_uv.Length; i++) { for(int n = 0; n < si_uv[i].Length; n++) si_uv[i][n] += vertexCount; } suv.AddRange(si_uv); } } GameObject go = (GameObject)GameObject.Instantiate(pbs[0].gameObject); go.transform.position = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one; // Destroy the children foreach(Transform t in go.transform) GameObject.DestroyImmediate(t.gameObject); if(go.GetComponent<pb_Object>()) GameObject.DestroyImmediate(go.GetComponent<pb_Object>()); if(go.GetComponent<pb_Entity>()) GameObject.DestroyImmediate(go.GetComponent<pb_Entity>()); combined = go.AddComponent<pb_Object>(); combined.SetVertices(v.ToArray()); combined.SetUV(u.ToArray()); combined.SetColors(c.ToArray()); combined.SetFaces(f.ToArray()); combined.SetSharedIndices( s.ToArray() ?? pb_IntArrayUtility.ExtractSharedIndices(v.ToArray()) ); combined.SetSharedIndicesUV( suv.ToArray() ?? new pb_IntArray[0] {}); combined.ToMesh(); combined.GetComponent<pb_Entity>().SetEntity( pbs[0].GetComponent<pb_Entity>().entityType ); combined.CenterPivot( pbs[0].transform.position ); combined.Refresh(); // refresh donors since deleting the children of the instantiated object could cause them to lose references foreach(pb_Object pb in pbs) pb.Verify(); return true; }
/** * \brief Given an array of "donors", this method returns a merged #pb_Object. */ public static bool CombineObjects(pb_Object[] pbs, out pb_Object combined) { combined = null; if (pbs.Length < 1) { return(false); } List <Vector3> v = new List <Vector3>(); List <Vector2> u = new List <Vector2>(); List <Color> c = new List <Color>(); List <pb_Face> f = new List <pb_Face>(); List <pb_IntArray> s = new List <pb_IntArray>(); List <pb_IntArray> suv = new List <pb_IntArray>(); foreach (pb_Object pb in pbs) { int vertexCount = v.Count; // Vertices v.AddRange(pb.VerticesInWorldSpace()); // UVs u.AddRange(pb.uv); // Colors c.AddRange(pb.colors); // Faces pb_Face[] faces = new pb_Face[pb.faces.Length]; for (int i = 0; i < faces.Length; i++) { faces[i] = new pb_Face(pb.faces[i]); faces[i].manualUV = true; faces[i].ShiftIndices(vertexCount); faces[i].RebuildCaches(); } f.AddRange(faces); // Shared Indices pb_IntArray[] si = pb.GetSharedIndices(); for (int i = 0; i < si.Length; i++) { for (int n = 0; n < si[i].Length; n++) { si[i][n] += vertexCount; } } s.AddRange(si); // Shared Indices UV { pb_IntArray[] si_uv = pb.GetSharedIndicesUV(); for (int i = 0; i < si_uv.Length; i++) { for (int n = 0; n < si_uv[i].Length; n++) { si_uv[i][n] += vertexCount; } } suv.AddRange(si_uv); } } GameObject go = (GameObject)GameObject.Instantiate(pbs[0].gameObject); go.transform.position = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one; // Destroy the children foreach (Transform t in go.transform) { GameObject.DestroyImmediate(t.gameObject); } if (go.GetComponent <pb_Object>()) { GameObject.DestroyImmediate(go.GetComponent <pb_Object>()); } if (go.GetComponent <pb_Entity>()) { GameObject.DestroyImmediate(go.GetComponent <pb_Entity>()); } combined = go.AddComponent <pb_Object>(); combined.SetVertices(v.ToArray()); combined.SetUV(u.ToArray()); combined.SetColors(c.ToArray()); combined.SetFaces(f.ToArray()); combined.SetSharedIndices(s.ToArray() ?? pb_IntArrayUtility.ExtractSharedIndices(v.ToArray())); combined.SetSharedIndicesUV(suv.ToArray() ?? new pb_IntArray[0] { }); combined.ToMesh(); combined.GetComponent <pb_Entity>().SetEntity(pbs[0].GetComponent <pb_Entity>().entityType); combined.CenterPivot(pbs[0].transform.position); combined.Refresh(); // refresh donors since deleting the children of the instantiated object could cause them to lose references foreach (pb_Object pb in pbs) { pb.Verify(); } return(true); }
/** * Creates the icosphere, and loads all the cache information. */ void Start() { audioSource = GetComponent <AudioSource>(); if (audioSource.clip == null) { missingClipWarning.SetActive(true); } // Create a new icosphere. ico = pb_ShapeGenerator.IcosahedronGenerator(icoRadius, icoSubdivisions); // Shell is all the faces on the new icosphere. pb_Face[] shell = ico.faces; // Materials are set per-face on pb_Object meshes. pb_Objects will automatically // condense the mesh to the smallest set of subMeshes possible based on materials. #if !PROTOTYPE foreach (pb_Face f in shell) { f.material = material; } #else ico.gameObject.GetComponent <MeshRenderer>().sharedMaterial = material; #endif // Extrude all faces on the icosphere by a small amount. The third boolean parameter // specifies that extrusion should treat each face as an individual, not try to group // all faces together. ico.Extrude(shell, ExtrudeMethod.IndividualFaces, startingExtrusion); // ToMesh builds the mesh positions, submesh, and triangle arrays. Call after adding // or deleting vertices, or changing face properties. ico.ToMesh(); // Refresh builds the normals, tangents, and UVs. ico.Refresh(); outsides = new FaceRef[shell.Length]; Dictionary <int, int> lookup = ico.sharedIndices.ToDictionary(); // Populate the outsides[] cache. This is a reference to the tops of each extruded column, including // copies of the sharedIndices. for (int i = 0; i < shell.Length; ++i) { outsides[i] = new FaceRef(shell[i], pb_Math.Normal(ico, shell[i]), ico.sharedIndices.AllIndicesWithValues(lookup, shell[i].distinctIndices).ToArray() ); } // Store copy of positions array un-modified original_vertices = new Vector3[ico.vertices.Length]; System.Array.Copy(ico.vertices, original_vertices, ico.vertices.Length); // displaced_vertices should mirror icosphere mesh vertices. displaced_vertices = ico.vertices; icoMesh = ico.GetComponent <MeshFilter>().sharedMesh; icoTransform = ico.transform; faces_length = (float)outsides.Length; // Build the waveform ring. icoPosition = icoTransform.position; #if UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_4 waveform.SetVertexCount(WAVEFORM_SAMPLES); #elif UNITY_5_5 waveform.numPositions = WAVEFORM_SAMPLES; #else waveform.positionCount = WAVEFORM_SAMPLES; #endif if (bounceWaveform) { waveform.transform.parent = icoTransform; } audioSource.Play(); }