using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.AddressableAssets; using petabytes.LTCLight; public class ConstructMirror : MonoBehaviour { static GameObject S; static int nO; //public enum PolygonPreset { Rectangle, Disc, Triangle, Custom } public static void AddMirror(int NO) { //print("CONTRUCT MIRRORS__________________________________________________________" + nO); nO = NO; string[] C = _G.OBJs[nO]; C[1] =C[1] .Replace("MIRD","MIRR"); // if (C[1] == "MIRR105") // { // LoadObject3D("MIRD105"); // } //else //{ float w = DOIT.ConvertStringToNumber(C[6]); float h = DOIT.ConvertStringToNumber(C[7]); float d = DOIT.ConvertStringToNumber(C[8]); float e = DOIT.ConvertStringToNumber(C[11]); float px = DOIT.ConvertStringToNumber(C[15]); float py = DOIT.ConvertStringToNumber(C[16]); float pz = DOIT.ConvertStringToNumber(C[17]); float rx = DOIT.ConvertStringToNumber(C[18]); float ry = DOIT.ConvertStringToNumber(C[19]); float rz = DOIT.ConvertStringToNumber(C[20]); //string c = C[61]; float BackLightIntensity = 0; if(C[62].Split("?")[0]=="BackLight"){ BackLightIntensity = float.Parse(C[62].Split("?")[1]);} float LineLightIntensity = 0; if(C[63].Split("?")[0]=="LineLight"){LineLightIntensity = float.Parse(C[63].Split("?")[1]);} //create BASE GameObject BASE = GameObject.CreatePrimitive(PrimitiveType.Cube);//new GameObject(); BASE.transform.localScale = new Vector3(w + 0.5f, h + 0.5f, d + 1f); BASE.name = _G.OBJs[nO][0]; BASE.transform.SetParent(GameObject.Find("SCENE").transform); GameObject Mirror = new("Mirror"); List AllPoints = new(); float BorderWidth = e; float BorderDepth = d; Material BorderMat = UIT_MATERIAL.GetMaterial(C[60]); GameObject M = new(C[1]); List inner = new(); float CornerRadius = 6; if (C[1] == "MIRR101") { BuildRectanglePoints(AllPoints, w, h);}//Rectangle if (C[1] == "MIRR102"){BuildOvalPoints(AllPoints, w, h, segments: 64);}//Round if (C[1] == "MIRR103") { BuildOvalPoints(AllPoints, w, h, segments: 64);}//Ovalw if (C[1] == "MIRR104"){ BuildTopRoundedOnlyPoints(AllPoints, w, h, arcSegments: 20);}//Top Round if (C[1] == "MIRR105"){BuildCapsuleVerticalPoints(AllPoints, w, h, 20);}//Capsule Vertical if (C[1] == "MIRR106"){BuildCapsuleHorizontalPoints(AllPoints, w, h, arcSegments: 20); }//Capsule Horizontal if (C[1] == "MIRR107") {BuildRoundedRectPoints(AllPoints, w, h, CornerRadius, arcSegments: 10); }//Round corners if (C[1] == "MIRR108") {BuildThreeSideRightArcPoints(AllPoints, w, h, arcSegments: 20); }//Arc Right if (C[1] == "MIRR109"){BuildThreeSideLeftArcPoints(AllPoints, w, h, arcSegments: 20);}//Arc Left inner = InsetContour(AllPoints, 0.5f); StaticCoroutine.Start(CreateMcxFlat(M, "MirrorGlass", AllPoints,inner, _G.MIRROR, zOffset: -0.001f, BackLightIntensity, BorderWidth)); CreateLineOnMirror(M, "LineGlass", AllPoints, BorderWidth,_G.GLOW, 1f, LineLightIntensity, -0.1f); if(e>0)CreateMcxBorderBackward(M, "MirrorFrame", inner, BorderWidth, BorderDepth, BorderMat); M.transform.SetParent(Mirror.transform); //RigidBody BASE.AddComponent(); BASE.GetComponent().useGravity = false; BASE.GetComponent().isKinematic = true; BASE.GetComponent().detectCollisions = true; BASE.GetComponent().isTrigger = true; Mirror.transform.SetParent(BASE.transform); //Stet in scene BASE.transform.position = new Vector3(px, py, pz); BASE.transform.localRotation = Quaternion.Euler(rx, ry, rz); BASE.AddComponent(typeof(MoveObject)); BASE.GetComponent().sharedMaterial = _G.INV; //} } public static void LoadObject3D(string Objectname) { print("----NEW MIRRIR-----"); _G.Load = false; GameObject.Find("HIDER").transform.Find("LoadingCircle").gameObject.SetActive(true); //nO=NO; Addressables.InstantiateAsync(Objectname).Completed += face_Completed; } public static void AddMirrorPaint() { string[] C = _G.OBJs[nO]; float w = DOIT.ConvertStringToNumber(C[6]); float h = DOIT.ConvertStringToNumber(C[7]); float d = DOIT.ConvertStringToNumber(C[8]); float px = DOIT.ConvertStringToNumber(C[15]); float py = DOIT.ConvertStringToNumber(C[16]); float pz = DOIT.ConvertStringToNumber(C[17]); float rx = DOIT.ConvertStringToNumber(C[18]); float ry = DOIT.ConvertStringToNumber(C[19]); float rz = DOIT.ConvertStringToNumber(C[20]); Material MatA=UIT_MATERIAL.GetMaterial(C[60]); //create BASE GameObject BASE = GameObject.CreatePrimitive(PrimitiveType.Cube);//new GameObject(); BASE.transform.localScale = new Vector3(w + +0.5f, h + +0.5f, d + 0.5f); BASE.name = C[0]; BASE.transform.parent = GameObject.Find("SCENE").transform; Destroy(GameObject.Find("New Game Object")); //Model S.transform.localRotation = Quaternion.Euler(0, 0, 0); float sw = DOIT.ConvertStringToNumber(_OL.GetValue(C[1],"w")); float sh = DOIT.ConvertStringToNumber(_OL.GetValue(C[1],"h")); float sd = DOIT.ConvertStringToNumber(_OL.GetValue(C[1],"d")); S.transform.localScale = new Vector3(w / sw, h / sh, d / sd); S.name = "S1"; S.transform.parent = BASE.transform; GameObject c1 = BASE.transform.Find("S1").transform.Find("C1").gameObject; GameObject c2 = BASE.transform.Find("S1").transform.Find("C2").gameObject; for (int i = 0; i < c1.transform.childCount; i++) { c1.transform.GetChild(i).GetComponent().sharedMaterial= _G.MIRROR; c1.transform.GetChild(i).GetComponent().probeAnchor = GameObject.Find("Reflection Stainless").transform; } for (int i = 0; i < c2.transform.childCount; i++) { c2.transform.GetChild(i).GetComponent().material = MatA; //c3.transform.GetChild(i).GetComponent().probeAnchor = GameObject.Find("Reflection Stainless").transform; } //Stet in scene BASE.transform.position = new Vector3(px, py, pz); BASE.transform.localRotation = Quaternion.Euler(rx, ry, rz); BASE.AddComponent(typeof(MoveObject)); BASE.GetComponent().sharedMaterial= _G.INV;//Resources.Load("MATERIALS/INVISIBLE") as Material; //BM.INV; } private static void face_Completed(AsyncOperationHandle handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { S = handle.Result; AddMirrorPaint(); GameObject.Find("HIDER").transform.Find("LoadingCircle").gameObject.SetActive(false); _G.Load = true; } } public static void CreateMcx(GameObject parent, string name, float width, float height, List points, float thickness, Material customMaterial) { if (points == null || points.Count < 3) { Debug.LogError("Il faut au moins 3 points pour créer un mesh!"); return; } GameObject meshObject = new GameObject(name); meshObject.transform.SetParent(parent.transform); MeshFilter meshFilter = meshObject.AddComponent(); MeshRenderer meshRenderer = meshObject.AddComponent(); // Créer le mesh avec épaisseur Mesh mesh = CreateExtrudedMesh(points, thickness); meshFilter.mesh = mesh; // Assigner le matériau URP if (customMaterial != null) { meshRenderer.material = customMaterial; } else { // Utiliser le shader URP Lit par défaut Material urpMaterial = new(Shader.Find("Universal Render Pipeline/Lit")); urpMaterial.color = Color.white; urpMaterial.SetFloat("_Metallic", 0.8f); // Pour un effet miroir urpMaterial.SetFloat("_Smoothness", 0.9f); // Surface très lisse meshRenderer.material = urpMaterial; } //Debug.Log($"Mesh URP '{name}' créé avec {points.Count} points et épaisseur de {thickness}"); } private static Mesh CreateExtrudedMesh(List points, float thickness) { Mesh mesh = new Mesh(); mesh.name = "ExtrudedMesh"; int pointCount = points.Count; float halfThickness = thickness * 0.5f; // Vertices Vector3[] vertices = new Vector3[pointCount * 2]; for (int i = 0; i < pointCount; i++) { vertices[i] = points[i] + new Vector3(0, 0, halfThickness); // Face avant (+) vertices[pointCount + i] = points[i] + new Vector3(0, 0, -halfThickness); // Face arrière (-) } // UVs Vector2[] uvs = new Vector2[pointCount * 2]; for (int i = 0; i < pointCount; i++) { float t = (float)i / (pointCount - 1); uvs[i] = new Vector2(t, 0); uvs[pointCount + i] = new Vector2(t, 1); } // Triangles List triangles = new List(); // Face avant (en éventail depuis le premier point) for (int i = 1; i < pointCount - 1; i++) { triangles.Add(0); triangles.Add(i + 1); triangles.Add(i); } // Face arrière for (int i = 1; i < pointCount - 1; i++) { triangles.Add(pointCount); triangles.Add(pointCount + i); triangles.Add(pointCount + i + 1); } // Côtés (ordre inversé pour normales vers l'extérieur) for (int i = 0; i < pointCount; i++) { int current = i; int next = (i + 1) % pointCount; triangles.Add(current); triangles.Add(next); triangles.Add(pointCount + current); triangles.Add(next); triangles.Add(pointCount + next); triangles.Add(pointCount + current); } mesh.vertices = vertices; mesh.triangles = triangles.ToArray(); mesh.uv = uvs; mesh.RecalculateNormals(); mesh.RecalculateBounds(); mesh.RecalculateTangents(); return mesh; } //Calculate public static float CalculateRadius(Vector3 point1, Vector3 point2, Vector3 point3) { // Ensure the points lie on the same plane (XY-plane is assumed, Z is ignored) Vector2 p1 = new Vector2(point1.x, point1.y); Vector2 p2 = new Vector2(point2.x, point2.y); Vector2 p3 = new Vector2(point3.x, point3.y); // Calculate the midpoints of two chords Vector2 mid1 = (p1 + p2) / 2; Vector2 mid2 = (p2 + p3) / 2; // Calculate perpendicular slopes of the chords Vector2 dir1 = (p2 - p1).normalized; Vector2 dir2 = (p3 - p2).normalized; Vector2 perp1 = new Vector2(-dir1.y, dir1.x); // Perpendicular to dir1 Vector2 perp2 = new Vector2(-dir2.y, dir2.x); // Perpendicular to dir2 // Calculate intersection of the perpendicular bisectors // Solve for center point (Cx, Cy) if (!LineIntersection(mid1, perp1, mid2, perp2, out Vector2 center)) { Debug.LogError("Points are collinear or invalid for a circle."); return -1f; } // Calculate the radius as the distance from the center to one of the points float radius = Vector2.Distance(center, p1); return radius; } private static bool LineIntersection(Vector2 pointA, Vector2 dirA, Vector2 pointB, Vector2 dirB, out Vector2 intersection) { float determinant = dirA.x * dirB.y - dirA.y * dirB.x; if (Mathf.Abs(determinant) < 1e-6) { intersection = Vector2.zero; return false; // Lines are parallel } float t = ((pointB.x - pointA.x) * dirB.y - (pointB.y - pointA.y) * dirB.x) / determinant; intersection = pointA + t * dirA; return true; } public static float CalculateAngle(Vector3 point1, Vector3 point3, Vector2 center) { Vector2 p1 = new Vector2(point1.x, point1.y); Vector2 p3 = new Vector2(point3.x, point3.y); // Vectors from the center to the points Vector2 v1 = p1 - center; Vector2 v2 = p3 - center; // Calculate the cosine of the angle using the dot product formula float dotProduct = Vector2.Dot(v1.normalized, v2.normalized); float angleRadians = Mathf.Acos(dotProduct); // Result is in radians // Convert to degrees float angleDegrees = angleRadians * Mathf.Rad2Deg; return angleDegrees; } //------------------Create Morror-------------------------------- public static IEnumerator CreateMcxFlat(GameObject parent, string name, List points, List innerpoints, Material mat, float zOffset , float BackLightIntensity, float Borderwaide) { //string renderingLayerName="Outline"; if (points == null || points.Count < 3) { Debug.LogError("Il faut au moins 3 points pour créer un mesh plat!"); yield return null; } // Si dernier point == premier, enlève-le if (Vector3.Distance(points[0], points[^1]) < 0.0001f) points.RemoveAt(points.Count - 1); GameObject meshObject = new(name); meshObject.transform.SetParent(parent.transform, false); meshObject.transform.localPosition = new Vector3(0, 0, zOffset); MeshFilter mf = meshObject.AddComponent(); MeshRenderer mr = meshObject.AddComponent(); mf.sharedMesh = CreateFlatMeshConvex(points); mr.sharedMaterial = mat; mr.probeAnchor = GameObject.Find("Reflection Probe").transform; //if (BackLightIntensity > 0) { if (!meshObject.TryGetComponent(out var light)) light = meshObject.AddComponent(); // Attendre l'init du manager (sinon ton setter log "not initialized") while (!LTCLightManager.IsInitialized) yield return null; // IMPORTANT: min 3 points if (points == null || points.Count < 3) { Debug.LogError("Need at least 3 points for LTC polygon."); yield break; } // Ça configure la forme custom en build (WebGPU) light.PolygonPoints = innerpoints; light.Intensity = new(1f,BackLightIntensity); //if(Borderwaide<=0.5f){light.Intensity = new(1,0.5f);} light.Range = 50f; //} } private static Mesh CreateFlatMeshConvex(List points) { Mesh mesh = new Mesh(); mesh.name = "FlatMesh"; int n = points.Count; if (n < 3) return mesh; // vertices (z=0) Vector3[] vertices = new Vector3[n]; Vector2[] points2D = new Vector2[n]; for (int i = 0; i < n; i++) { vertices[i] = new Vector3(points[i].x, points[i].y, 0f); points2D[i] = new Vector2(points[i].x, points[i].y); } // Utiliser Triangulator pour gérer les formes concaves Triangulator triangulator = new(points2D); int[] triangles = triangulator.Triangulate(); mesh.vertices = vertices; mesh.triangles = triangles; mesh.RecalculateNormals(); mesh.RecalculateBounds(); mesh.RecalculateTangents(); return mesh; } //----------------------BIULD SHAPES----------------------------- static void BuildRectanglePoints(List pts, float w, float h) { pts.Clear(); float hw = w * 0.5f; float hh = h * 0.5f; // Ordre horaire (bas-gauche -> haut-gauche -> haut-droit -> bas-droit) pts.Add(new Vector3(-hw, -hh, 0f)); pts.Add(new Vector3(-hw, hh, 0f)); pts.Add(new Vector3( hw, hh, 0f)); pts.Add(new Vector3( hw, -hh, 0f)); // Optionnel : fermer explicitement // pts.Add(pts[0]); } static void BuildOvalPoints(List pts, float w, float h, int segments = 48) { pts.Clear(); float rx = w * 0.5f; // rayon X float ry = h * 0.5f; // rayon Y if (rx <= 0.0001f || ry <= 0.0001f) return; if (segments < 12) segments = 12; // Sens horaire (comme tes autres formes) for (int i = 0; i < segments; i++) { float t = (float)i / segments; // 0..1 float a = -t * Mathf.PI * 2f; // horaire float x = Mathf.Cos(a) * rx; float y = Mathf.Sin(a) * ry; pts.Add(new Vector3(x, y, 0f)); } // Optionnel: fermer explicitement // pts.Add(pts[0]); } static void BuildTopRoundedOnlyPoints(List pts, float w, float h, int arcSegments = 20) { pts.Clear(); if (arcSegments < 4) arcSegments = 4; float hw = w * 0.5f; float hh = h * 0.5f; // Rayon du top arrondi : // - par défaut = hw (demi-cercle basé sur la largeur) // - MAIS il ne doit pas dépasser la hauteur disponible, sinon ça "déforme" float r = Mathf.Min(hw, h); // sécurité // Encore mieux: r ne doit pas dépasser h (sinon base de l'arc passe sous le bas) r = Mathf.Min(r, h); // ok // Base de l’arc (ligne où commence le demi-cercle) float yBase = hh - r; // Si h est très petit, on clamp pour garder une forme valide if (yBase < -hh) yBase = -hh; Vector3 center = new Vector3(0f, yBase, 0f); // 1) Bas-gauche pts.Add(new Vector3(-hw, -hh, 0f)); // 2) Monter côté gauche jusqu'à la base de l’arc pts.Add(new Vector3(-hw, yBase, 0f)); // 3) Arc du haut: de gauche -> droite en passant par le haut (180° -> 0°) // IMPORTANT: on ne veut PAS ré-ajouter le premier point de l’arc (déjà -hw,yBase) for (int i = 1; i <= arcSegments; i++) { float t = (float)i / arcSegments; float a = Mathf.Lerp(180f, 0f, t) * Mathf.Deg2Rad; float x = center.x + Mathf.Cos(a) * r; float y = center.y + Mathf.Sin(a) * r; pts.Add(new Vector3(x, y, 0f)); } // 4) Descendre côté droit jusqu’en bas-droit pts.Add(new Vector3(hw, -hh, 0f)); // Optionnel: fermer explicitement si ton code le veut // pts.Add(pts[0]); } static void BuildCapsuleVerticalPoints(List pts, float w, float h, int arcSegments = 20) { pts.Clear(); if (arcSegments < 6) arcSegments = 6; // Capsule verticale nécessite h >= w if (h < w) { float tmp = w; w = h; h = tmp; } float hw = w * 0.5f; float hh = h * 0.5f; float r = hw; // rayon = w/2 float yTop = hh - r; // centre demi-cercle du haut float yBot = -hh + r; // centre demi-cercle du bas // Si h == w => cercle complet if (Mathf.Abs(yTop - yBot) < 0.0001f) { int seg = arcSegments * 2; for (int i = 0; i < seg; i++) { float t = (float)i / seg; float a = -t * Mathf.PI * 2f; pts.Add(new Vector3(Mathf.Cos(a) * r, Mathf.Sin(a) * r, 0f)); } return; } Vector3 centerTop = new Vector3(0f, yTop, 0f); Vector3 centerBot = new Vector3(0f, yBot, 0f); // 1) Départ: côté gauche du demi-cercle BAS pts.Add(new Vector3(-hw, yBot, 0f)); // 2) Monter côté GAUCHE jusqu'à la base de l'arc supérieur pts.Add(new Vector3(-hw, yTop, 0f)); // 3) Arc du HAUT: de gauche -> droite (180° -> 0°) // On saute i=0 car (-hw, yTop) est déjà ajouté for (int i = 1; i <= arcSegments; i++) { float t = (float)i / arcSegments; float a = Mathf.Lerp(180f, 0f, t) * Mathf.Deg2Rad; float x = centerTop.x + Mathf.Cos(a) * r; float y = centerTop.y + Mathf.Sin(a) * r; pts.Add(new Vector3(x, y, 0f)); } // 4) Descendre côté DROIT jusqu'à la base de l'arc inférieur pts.Add(new Vector3(hw, yBot, 0f)); // 5) Arc du BAS: de droite -> gauche (0° -> 180°) VERS LE BAS // On saute i=0 car (hw, yBot) est déjà ajouté for (int i = 1; i < arcSegments; i++) // ATTENTION: i < arcSegments (pas <=) { float t = (float)i / arcSegments; float a = Mathf.Lerp(0f, 180f, t) * Mathf.Deg2Rad; float x = centerBot.x + Mathf.Cos(a) * r; float y = centerBot.y - Mathf.Sin(a) * r; // MOINS pour aller vers le bas pts.Add(new Vector3(x, y, 0f)); } // Le contour se ferme automatiquement vers le point de départ (-hw, yBot) // Ne PAS ajouter de point supplémentaire! } static void BuildCapsuleHorizontalPoints(List pts, float w, float h, int arcSegments = 20) { pts.Clear(); if (arcSegments < 6) arcSegments = 6; // Capsule horizontale nécessite w >= h if (w < h) { float tmp = w; w = h; h = tmp; } float hw = w * 0.5f; float hh = h * 0.5f; float r = hh; // rayon = h/2 float xRight = hw - r; // centre demi-cercle de droite float xLeft = -hw + r; // centre demi-cercle de gauche // Si w == h => cercle complet if (Mathf.Abs(xRight - xLeft) < 0.0001f) { int seg = arcSegments * 2; for (int i = 0; i < seg; i++) { float t = (float)i / seg; float a = -t * Mathf.PI * 2f; pts.Add(new Vector3(Mathf.Cos(a) * r, Mathf.Sin(a) * r, 0f)); } return; } Vector3 centerRight = new Vector3(xRight, 0f, 0f); Vector3 centerLeft = new Vector3(xLeft, 0f, 0f); // 1) Départ: en haut du demi-cercle GAUCHE pts.Add(new Vector3(xLeft, hh, 0f)); // 2) Ligne horizontale HAUTE: de gauche vers droite pts.Add(new Vector3(xRight, hh, 0f)); // 3) Demi-cercle de DROITE (haut -> bas, 180° -> 0°) // Équivalent vertical: le haut allait de gauche à droite // Ici: la droite va du haut vers le bas for (int i = 1; i <= arcSegments; i++) { float t = (float)i / arcSegments; float a = Mathf.Lerp(180f, 0f, t) * Mathf.Deg2Rad; // Rotation de 90°: échange Sin/Cos et ajuste les signes float x = centerRight.x + Mathf.Sin(a) * r; float y = centerRight.y - Mathf.Cos(a) * r; pts.Add(new Vector3(x, y, 0f)); } // 4) Ligne horizontale BASSE: de droite vers gauche pts.Add(new Vector3(xLeft, -hh, 0f)); // 5) Demi-cercle de GAUCHE (bas -> haut, 0° -> 180°) for (int i = 1; i < arcSegments; i++) { float t = (float)i / arcSegments; float a = Mathf.Lerp(0f, 180f, t) * Mathf.Deg2Rad; float x = centerLeft.x - Mathf.Sin(a) * r; float y = centerLeft.y - Mathf.Cos(a) * r; pts.Add(new Vector3(x, y, 0f)); } } static void BuildRoundedRectPoints(List pts, float w, float h, float cornerRadius, int arcSegments = 8) { pts.Clear(); float hw = w * 0.5f; float hh = h * 0.5f; float r = Mathf.Clamp(cornerRadius, 0f, Mathf.Min(hw, hh)); if (r <= 0.0001f) { // Rectangle simple (horaire) pts.Add(new Vector3(-hw, -hh, 0)); pts.Add(new Vector3(-hw, hh, 0)); pts.Add(new Vector3( hw, hh, 0)); pts.Add(new Vector3( hw, -hh, 0)); return; } // Centres des arcs Vector3 cBL = new Vector3(-hw + r, -hh + r, 0); // bottom-left Vector3 cTL = new Vector3(-hw + r, hh - r, 0); // top-left Vector3 cTR = new Vector3( hw - r, hh - r, 0); // top-right Vector3 cBR = new Vector3( hw - r, -hh + r, 0); // bottom-right // Ordre HORAIRE — départ en bas à gauche (après le coin) pts.Add(new Vector3(-hw + r, -hh, 0)); // bas, juste après le coin gauche // Coin bas-gauche (270° -> 180°) - INVERSÉ pour horaire AddArc(pts, cBL, r, 270f, 180f, arcSegments); // Côté gauche (bas -> haut) pts.Add(new Vector3(-hw, hh - r, 0)); // Coin haut-gauche (180° -> 90°) - INVERSÉ pour horaire AddArc(pts, cTL, r, 180f, 90f, arcSegments); // Haut (gauche -> droite) pts.Add(new Vector3(hw - r, hh, 0)); // Coin haut-droit (90° -> 0°) - INVERSÉ pour horaire AddArc(pts, cTR, r, 90f, 0f, arcSegments); // Côté droit (haut -> bas) pts.Add(new Vector3(hw, -hh + r, 0)); // Coin bas-droit (0° -> -90°/270°) - INVERSÉ pour horaire AddArc(pts, cBR, r, 0f, -90f, arcSegments); } static void AddArc(List pts, Vector3 center, float radius, float startAngle, float endAngle, int segments) { // Ne pas ajouter le premier point (déjà ajouté par la ligne précédente) for (int i = 1; i <= segments; i++) { float t = (float)i / segments; float a = Mathf.Lerp(startAngle, endAngle, t) * Mathf.Deg2Rad; float x = center.x + Mathf.Cos(a) * radius; float y = center.y + Mathf.Sin(a) * radius; pts.Add(new Vector3(x, y, 0f)); } } static void BuildThreeSideRightArcPoints(List pts, float w, float h, int arcSegments = 16) { pts.Clear(); if (arcSegments < 2) arcSegments = 2; float hw = w * 0.5f; float hh = h * 0.5f; // Centre de l'arc au coin bas-gauche Vector3 center = new Vector3(-hw, -hh, 0f); // Rayons de l'arc float rx = w; // rayon horizontal float ry = h; // rayon vertical // Ordre HORAIRE // 1) Départ: bas-gauche (centre de l'arc) pts.Add(new Vector3(-hw, -hh, 0f)); // 2) Côté gauche vertical (bas -> haut) pts.Add(new Vector3(-hw, hh, 0f)); // 3) Arc en quart de cercle: du haut vers la droite (90° -> 0°) // On saute i=0 car le point (-hw, hh) est déjà ajouté for (int i = 1; i <= arcSegments; i++) { float t = (float)i / arcSegments; float a = Mathf.Lerp(90f, 0f, t) * Mathf.Deg2Rad; float x = center.x + Mathf.Cos(a) * rx; float y = center.y + Mathf.Sin(a) * ry; pts.Add(new Vector3(x, y, 0f)); print("Vector===="+x+", "+ y); } // Le contour se ferme automatiquement vers le point de départ } static void BuildThreeSideLeftArcPoints(List pts, float w, float h, int arcSegments = 16) { pts.Clear(); if (arcSegments < 2) arcSegments = 2; float hw = w * 0.5f; float hh = h * 0.5f; // Centre de l'arc au coin BAS-DROIT Vector3 center = new Vector3(hw, -hh, 0f); // Rayons de l'arc float rx = w; // rayon horizontal float ry = h; // rayon vertical // Ordre HORAIRE (pour normales vers l'avant) // 1) Départ: bas-droit (centre de l'arc) pts.Add(new Vector3(hw, -hh, 0f)); // 2) Ligne du bas (droite -> gauche) pts.Add(new Vector3(-hw, -hh, 0f)); // 3) Arc bombant vers l'extérieur GAUCHE (180° -> 90°) // MAIS on saute le dernier point pour éviter un doublon avec la ligne verticale for (int i = 1; i < arcSegments; i++) // CHANGEMENT: i < arcSegments (pas <=) { float t = (float)i / arcSegments; float a = Mathf.Lerp(180f, 90f, t) * Mathf.Deg2Rad; float x = center.x + Mathf.Cos(a) * rx; float y = center.y + Mathf.Sin(a) * ry; pts.Add(new Vector3(x, y, 0f)); } // 4) Point du haut explicitement pts.Add(new Vector3(hw, hh, 0f)); // Le contour se ferme automatiquement vers le point de départ } public static List OutsetContour(List points, float outsetDistance) { int n = points.Count; if (n < 3) return null; List result = new List(n); for (int i = 0; i < n; i++) { Vector3 prev = points[(i - 1 + n) % n]; Vector3 curr = points[i]; Vector3 next = points[(i + 1) % n]; // Vecteurs des arêtes Vector2 edge1 = new Vector2(curr.x - prev.x, curr.y - prev.y); Vector2 edge2 = new Vector2(next.x - curr.x, next.y - curr.y); float len1 = edge1.magnitude; float len2 = edge2.magnitude; if (len1 < 0.0001f || len2 < 0.0001f) { result.Add(curr); continue; } edge1 /= len1; edge2 /= len2; // Normales perpendiculaires (vers l'EXTÉRIEUR - inversé par rapport à InsetContour) Vector2 normal1 = new Vector2(edge1.y, -edge1.x); // INVERSÉ Vector2 normal2 = new Vector2(edge2.y, -edge2.x); // INVERSÉ // Bisectrice Vector2 bisector = (normal1 + normal2); float bisectorLen = bisector.magnitude; if (bisectorLen < 0.0001f) { // Angle de 180° - utiliser normal1 result.Add(new Vector3( curr.x + normal1.x * outsetDistance, curr.y + normal1.y * outsetDistance, curr.z )); continue; } bisector /= bisectorLen; // Calcul de l'angle entre les deux arêtes float dot = Vector2.Dot(edge1, edge2); // Détecter si c'est une courbe lisse (dot proche de 1) if (dot > 0.98f) // angle < ~11 degrés { // Sur une courbe: offset constant perpendiculaire result.Add(new Vector3( curr.x + bisector.x * outsetDistance, curr.y + bisector.y * outsetDistance, curr.z )); } else { // Sur un coin: calculer l'offset avec correction d'angle float angle = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f)); float sinHalfAngle = Mathf.Sin(angle * 0.5f); // Protection contre angles très aigus if (sinHalfAngle < 0.1f) { sinHalfAngle = 0.1f; } float offsetDistance = outsetDistance / sinHalfAngle; // Limiter l'offset offsetDistance = Mathf.Min(offsetDistance, outsetDistance * 3f); result.Add(new Vector3( curr.x + bisector.x * offsetDistance, curr.y + bisector.y * offsetDistance, curr.z )); } } return result; } /// ---------------------------------Contour----------------------------------------------- /// ---------------------------------Contour----------------------------------------------- /// ---------------------------------Contour----------------------------------------------- /// /// static List RemoveClosingDuplicate(List pts) { if (pts.Count > 1 && Vector3.Distance(pts[0], pts[^1]) < 0.0001f) pts.RemoveAt(pts.Count - 1); return pts; } static Mesh CreateExtrudedRingMesh(IReadOnlyList outer, IReadOnlyList inner, float thickness) { int n = outer.Count; if (n < 3 || inner.Count != n) return null; Mesh mesh = new Mesh(); mesh.name = "ExtrudedRingMesh"; float half = thickness * 0.5f; // 4 anneaux de vertices: // 0..n-1 = outer front // n..2n-1 = outer back // 2n..3n-1 = inner front // 3n..4n-1 = inner back Vector3[] v = new Vector3[n * 4]; for (int i = 0; i < n; i++) { v[i] = outer[i] + new Vector3(0, 0, half); v[i + n] = outer[i] + new Vector3(0, 0, -half); v[i + 2*n] = inner[i] + new Vector3(0, 0, half); v[i + 3*n] = inner[i] + new Vector3(0, 0, -half); } var tris = new List(n * 24); for (int i = 0; i < n; i++) { int j = (i + 1) % n; int of_i = i; int of_j = j; int ob_i = i + n; int ob_j = j + n; int inf_i = i + 2*n; int inf_j = j + 2*n; int inb_i = i + 3*n; int inb_j = j + 3*n; // --- Face avant (z +): quad (outer i->j) avec (inner i->j) // (outer i, outer j, inner j) + (outer i, inner j, inner i) tris.Add(of_i); tris.Add(of_j); tris.Add(inf_j); tris.Add(of_i); tris.Add(inf_j); tris.Add(inf_i); // --- Face arrière (z -): inverse winding // (outer i, inner j, outer j) + (outer i, inner i, inner j) tris.Add(ob_i); tris.Add(inb_j); tris.Add(ob_j); tris.Add(ob_i); tris.Add(inb_i); tris.Add(inb_j); // --- Paroi extérieure (outer side) // (of_i, ob_j, ob_i) + (of_i, of_j, ob_j) tris.Add(of_i); tris.Add(ob_j); tris.Add(ob_i); tris.Add(of_i); tris.Add(of_j); tris.Add(ob_j); // --- Paroi intérieure (inner side) -> winding opposé pour normales vers l’intérieur du trou // (inf_i, inb_i, inb_j) + (inf_i, inb_j, inf_j) tris.Add(inf_i); tris.Add(inb_i); tris.Add(inb_j); tris.Add(inf_i); tris.Add(inb_j); tris.Add(inf_j); } mesh.vertices = v; mesh.triangles = tris.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateBounds(); mesh.RecalculateTangents(); return mesh; } public static void CreateMcxBorder( GameObject parent,string name,List outerPoints,float borderWidth,float borderDepth,Material borderMaterial) { if (outerPoints == null || outerPoints.Count < 3) return; outerPoints = RemoveClosingDuplicate(outerPoints); // Sécurité: clamp borderWidth pour éviter d'inverser la forme // (garde au plus 45% de la plus petite dimension approximative) float safe = borderWidth; if (safe < 0.0001f) safe = 0.0001f; List inner = InsetContour(outerPoints, safe); if (inner == null || inner.Count != outerPoints.Count) return; Mesh ring = CreateExtrudedRingMesh(outerPoints, inner, borderDepth); if (ring == null) return; GameObject meshObject = new GameObject(name); meshObject.transform.SetParent(parent.transform, false); MeshFilter mf = meshObject.AddComponent(); MeshRenderer mr = meshObject.AddComponent(); mf.sharedMesh = ring; mr.sharedMaterial = borderMaterial; } public static void CreateMcxBorderBackward(GameObject parent,string name,List innerPoints, float frameWidth, float frameDepth,Material frameMat){ if (innerPoints == null || innerPoints.Count < 3) return; // retire point de fermeture si présent if (Vector3.Distance(innerPoints[0], innerPoints[^1]) < 0.0001f) innerPoints.RemoveAt(innerPoints.Count - 1); // Calculer l'EXTÉRIEUR en utilisant OutsetContour (expansion vers l'extérieur) List outer = OutsetContour(innerPoints, frameWidth); if (outer == null || outer.Count != innerPoints.Count) return; // Créer le mesh avec outer à l'extérieur et innerPoints à l'intérieur Mesh ring = CreateExtrudedRingMeshBackward(outer, innerPoints, frameDepth); if (ring == null) return; GameObject go = new GameObject(name); go.transform.SetParent(parent.transform, false); var mf = go.AddComponent(); var mr = go.AddComponent(); mf.sharedMesh = ring; mr.sharedMaterial = frameMat; } public class Triangulator { private List m_points = new List(); public Triangulator(Vector2[] points) { m_points = new List(points); } public int[] Triangulate() { List indices = new List(); int n = m_points.Count; if (n < 3) return indices.ToArray(); int[] V = new int[n]; if (Area() > 0) { for (int v = 0; v < n; v++) V[v] = v; } else { for (int v = 0; v < n; v++) V[v] = (n - 1) - v; } int nv = n; int count = 2 * nv; for (int v = nv - 1; nv > 2;) { if ((count--) <= 0) return indices.ToArray(); int u = v; if (nv <= u) u = 0; v = u + 1; if (nv <= v) v = 0; int w = v + 1; if (nv <= w) w = 0; if (Snip(u, v, w, nv, V)) { int a, b, c, s, t; a = V[u]; b = V[v]; c = V[w]; indices.Add(a); indices.Add(b); indices.Add(c); for (s = v, t = v + 1; t < nv; s++, t++) V[s] = V[t]; nv--; count = 2 * nv; } } indices.Reverse(); return indices.ToArray(); } private float Area() { int n = m_points.Count; float A = 0.0f; for (int p = n - 1, q = 0; q < n; p = q++) { Vector2 pval = m_points[p]; Vector2 qval = m_points[q]; A += pval.x * qval.y - qval.x * pval.y; } return (A * 0.5f); } private bool Snip(int u, int v, int w, int n, int[] V) { int p; Vector2 A = m_points[V[u]]; Vector2 B = m_points[V[v]]; Vector2 C = m_points[V[w]]; if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x)))) return false; for (p = 0; p < n; p++) { if ((p == u) || (p == v) || (p == w)) continue; Vector2 P = m_points[V[p]]; if (InsideTriangle(A, B, C, P)) return false; } return true; } private bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) { float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; float cCROSSap, bCROSScp, aCROSSbp; ax = C.x - B.x; ay = C.y - B.y; bx = A.x - C.x; by = A.y - C.y; cx = B.x - A.x; cy = B.y - A.y; apx = P.x - A.x; apy = P.y - A.y; bpx = P.x - B.x; bpy = P.y - B.y; cpx = P.x - C.x; cpy = P.y - C.y; aCROSSbp = ax * bpy - ay * bpx; cCROSSap = cx * apy - cy * apx; bCROSScp = bx * cpy - by * cpx; return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); } } static Mesh CreateExtrudedRingMeshForward(IReadOnlyList outer, IReadOnlyList inner, float depth) { int n = outer.Count; if (n < 3 || inner.Count != n) return null; Mesh mesh = new Mesh(); mesh.name = "ExtrudedRingForward"; float zBack = 0f; // fond du cadre float zFront = depth; // avant du cadre // 4 anneaux: // 0..n-1 = outer back (zBack) // n..2n-1 = outer front (zFront) // 2n..3n-1 = inner back (zBack) // 3n..4n-1 = inner front (zFront) Vector3[] v = new Vector3[n * 4]; for (int i = 0; i < n; i++) { v[i] = new Vector3(outer[i].x, outer[i].y, zBack); v[i + n] = new Vector3(outer[i].x, outer[i].y, zFront); v[i + 2*n] = new Vector3(inner[i].x, inner[i].y, zBack); v[i + 3*n] = new Vector3(inner[i].x, inner[i].y, zFront); } var tris = new List(n * 24); for (int i = 0; i < n; i++) { int j = (i + 1) % n; int ob_i = i; // outer back int ob_j = j; int of_i = i + n; // outer front int of_j = j + n; int ib_i = i + 2*n; // inner back int ib_j = j + 2*n; int inf_i = i + 3*n; // inner front int inf_j = j + 3*n; // --- Face AVANT (zFront): ring (outer front + inner front) tris.Add(of_i); tris.Add(of_j); tris.Add(inf_j); tris.Add(of_i); tris.Add(inf_j); tris.Add(inf_i); // --- Face ARRIÈRE (zBack): ring (outer back + inner back) (winding inversé) tris.Add(ob_i); tris.Add(ib_j); tris.Add(ob_j); tris.Add(ob_i); tris.Add(ib_i); tris.Add(ib_j); // --- Paroi extérieure (outer side) tris.Add(ob_i); tris.Add(of_j); tris.Add(of_i); tris.Add(ob_i); tris.Add(ob_j); tris.Add(of_j); // --- Paroi intérieure (inner side) (winding opposé) tris.Add(ib_i); tris.Add(inf_i); tris.Add(inf_j); tris.Add(ib_i); tris.Add(inf_j); tris.Add(ib_j); } mesh.vertices = v; mesh.triangles = tris.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateBounds(); mesh.RecalculateTangents(); return mesh; } public static void CreateMcxBorderForward( GameObject parent,string name,List outerPoints,float frameWidth, float frameDepth, Material frameMat){ if (outerPoints == null || outerPoints.Count < 3) return; // enlever point de fermeture si présent if (Vector3.Distance(outerPoints[0], outerPoints[^1]) < 0.0001f) outerPoints.RemoveAt(outerPoints.Count - 1); List inner = InsetContour(outerPoints, frameWidth); if (inner == null || inner.Count != outerPoints.Count) return; Mesh ring = CreateExtrudedRingMeshForward(outerPoints, inner, frameDepth); if (ring == null) return; GameObject go = new GameObject(name); go.transform.SetParent(parent.transform, false); var mf = go.AddComponent(); var mr = go.AddComponent(); mf.sharedMesh = ring; mr.sharedMaterial = frameMat; } private static Mesh CreateExtrudedRingMeshBackward(List outer, List inner, float depth) { Mesh mesh = new Mesh(); mesh.name = "ExtrudedRing"; int n = outer.Count; if (n != inner.Count || n < 3) return null; // On va dupliquer les vertices pour avoir des hard edges List vertices = new List(); List triangles = new List(); // ===== FRONT FACE (anneau) ===== int frontStart = vertices.Count; for (int i = 0; i < n; i++) { vertices.Add(new Vector3(outer[i].x, outer[i].y, 0f)); vertices.Add(new Vector3(inner[i].x, inner[i].y, 0f)); } for (int i = 0; i < n; i++) { int next = (i + 1) % n; int outerCurr = frontStart + i * 2; int innerCurr = frontStart + i * 2 + 1; int outerNext = frontStart + next * 2; int innerNext = frontStart + next * 2 + 1; triangles.Add(outerCurr); triangles.Add(outerNext); triangles.Add(innerCurr); triangles.Add(innerCurr); triangles.Add(outerNext); triangles.Add(innerNext); } // ===== BACK FACE (anneau) ===== int backStart = vertices.Count; for (int i = 0; i < n; i++) { vertices.Add(new Vector3(outer[i].x, outer[i].y, -depth)); vertices.Add(new Vector3(inner[i].x, inner[i].y, -depth)); } for (int i = 0; i < n; i++) { int next = (i + 1) % n; int outerCurr = backStart + i * 2; int innerCurr = backStart + i * 2 + 1; int outerNext = backStart + next * 2; int innerNext = backStart + next * 2 + 1; triangles.Add(outerCurr); triangles.Add(innerCurr); triangles.Add(outerNext); triangles.Add(innerCurr); triangles.Add(innerNext); triangles.Add(outerNext); } // ===== OUTER SIDE WALL ===== int outerWallStart = vertices.Count; for (int i = 0; i < n; i++) { vertices.Add(new Vector3(outer[i].x, outer[i].y, 0f)); // front vertices.Add(new Vector3(outer[i].x, outer[i].y, -depth)); // back } for (int i = 0; i < n; i++) { int next = (i + 1) % n; int frontCurr = outerWallStart + i * 2; int backCurr = outerWallStart + i * 2 + 1; int frontNext = outerWallStart + next * 2; int backNext = outerWallStart + next * 2 + 1; triangles.Add(frontCurr); triangles.Add(backCurr); triangles.Add(frontNext); triangles.Add(frontNext); triangles.Add(backCurr); triangles.Add(backNext); } // ===== INNER SIDE WALL ===== int innerWallStart = vertices.Count; for (int i = 0; i < n; i++) { vertices.Add(new Vector3(inner[i].x, inner[i].y, 0f)); // front vertices.Add(new Vector3(inner[i].x, inner[i].y, -depth)); // back } for (int i = 0; i < n; i++) { int next = (i + 1) % n; int frontCurr = innerWallStart + i * 2; int backCurr = innerWallStart + i * 2 + 1; int frontNext = innerWallStart + next * 2; int backNext = innerWallStart + next * 2 + 1; triangles.Add(frontCurr); triangles.Add(frontNext); triangles.Add(backCurr); triangles.Add(frontNext); triangles.Add(backNext); triangles.Add(backCurr); } mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateBounds(); mesh.RecalculateTangents(); return mesh; } public static List InsetContour(List points, float insetDistance) { int n = points.Count; if (n < 3) return null; List result = new List(n); for (int i = 0; i < n; i++) { Vector3 prev = points[(i - 1 + n) % n]; Vector3 curr = points[i]; Vector3 next = points[(i + 1) % n]; // Vecteurs des arêtes Vector2 edge1 = new Vector2(curr.x - prev.x, curr.y - prev.y); Vector2 edge2 = new Vector2(next.x - curr.x, next.y - curr.y); float len1 = edge1.magnitude; float len2 = edge2.magnitude; if (len1 < 0.0001f || len2 < 0.0001f) { result.Add(curr); continue; } edge1 /= len1; edge2 /= len2; // Normales perpendiculaires (vers l'intérieur) Vector2 normal1 = new Vector2(-edge1.y, edge1.x); Vector2 normal2 = new Vector2(-edge2.y, edge2.x); // Bisectrice Vector2 bisector = (normal1 + normal2); float bisectorLen = bisector.magnitude; if (bisectorLen < 0.0001f) { // Angle de 180° - utiliser normal1 result.Add(new Vector3( curr.x + normal1.x * insetDistance, curr.y + normal1.y * insetDistance, curr.z )); continue; } bisector /= bisectorLen; // Calcul de l'angle entre les deux arêtes float dot = Vector2.Dot(edge1, edge2); // Détecter si c'est une courbe lisse (dot proche de 1) if (dot > 0.98f) // angle < ~11 degrés { // Sur une courbe: offset constant perpendiculaire result.Add(new Vector3( curr.x + bisector.x * insetDistance, curr.y + bisector.y * insetDistance, curr.z )); } else { // Sur un coin: calculer l'offset avec correction d'angle float angle = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f)); float sinHalfAngle = Mathf.Sin(angle * 0.5f); // Protection contre angles très aigus if (sinHalfAngle < 0.1f) { sinHalfAngle = 0.1f; } float offsetDistance = insetDistance / sinHalfAngle; // Limiter l'offset offsetDistance = Mathf.Min(offsetDistance, insetDistance * 3f); result.Add(new Vector3( curr.x + bisector.x * offsetDistance, curr.y + bisector.y * offsetDistance, curr.z )); } } return result; } static bool IsClockwise2D(List pts) { double sum = 0; for (int i = 0; i < pts.Count; i++) { Vector3 a = pts[i]; Vector3 b = pts[(i + 1) % pts.Count]; sum += (b.x - a.x) * (b.y + a.y); } return sum > 0; // true = clockwise } public static List InsetContourNew(List points, float insetDistance) { int n = points.Count; if (n < 3) return null; bool cw = IsClockwise2D(points); List result = new List(n); for (int i = 0; i < n; i++) { Vector3 prev = points[(i - 1 + n) % n]; Vector3 curr = points[i]; Vector3 next = points[(i + 1) % n]; Vector2 edge1 = new Vector2(curr.x - prev.x, curr.y - prev.y); Vector2 edge2 = new Vector2(next.x - curr.x, next.y - curr.y); float len1 = edge1.magnitude; float len2 = edge2.magnitude; if (len1 < 0.0001f || len2 < 0.0001f) { result.Add(curr); continue; } edge1 /= len1; edge2 /= len2; // ✅ Normales vers l'intérieur selon winding // CCW -> intérieur = tourner à gauche : (-y, x) // CW -> intérieur = tourner à droite : (y, -x) Vector2 normal1 = cw ? new Vector2(edge1.y, -edge1.x) : new Vector2(-edge1.y, edge1.x); Vector2 normal2 = cw ? new Vector2(edge2.y, -edge2.x) : new Vector2(-edge2.y, edge2.x); Vector2 bisector = (normal1 + normal2); float bisectorLen = bisector.magnitude; if (bisectorLen < 0.0001f) { result.Add(new Vector3( curr.x + normal1.x * insetDistance, curr.y + normal1.y * insetDistance, curr.z )); continue; } bisector /= bisectorLen; float dot = Vector2.Dot(edge1, edge2); if (dot > 0.98f) { result.Add(new Vector3( curr.x + bisector.x * insetDistance, curr.y + bisector.y * insetDistance, curr.z )); } else { float angle = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f)); float sinHalfAngle = Mathf.Sin(angle * 0.5f); if (sinHalfAngle < 0.1f) sinHalfAngle = 0.1f; float offsetDistance = insetDistance / sinHalfAngle; offsetDistance = Mathf.Min(offsetDistance, insetDistance * 3f); result.Add(new Vector3( curr.x + bisector.x * offsetDistance, curr.y + bisector.y * offsetDistance, curr.z )); } } return result; } //-------------------------Line Glass------------------------------ public static void CreateLineOnMirror( GameObject parent, string name, List mirrorOuterPoints, // contour EXTERIEUR (même taille que le cadre) float frameWidth, Material glowMaterial, float insetDistance, float lineWidth , float zOffset) { if (mirrorOuterPoints == null || mirrorOuterPoints.Count < 3) { Debug.LogError("Il faut au moins 3 points pour créer une ligne!"); return; } // copie + retire point de fermeture si présent List workingPoints = new(mirrorOuterPoints); if (Vector3.Distance(workingPoints[0], workingPoints[^1]) < 0.0001f) workingPoints.RemoveAt(workingPoints.Count - 1); // ✅ Inset total pour être DANS le cadre float totalInset = frameWidth + insetDistance + lineWidth * 0.5f; List linePoints = InsetContourNew(workingPoints, totalInset); if (linePoints == null || linePoints.Count < 3) { Debug.LogError("Impossible de calculer les points de la ligne!"); return; } GameObject lineObject = new(name); lineObject.transform.SetParent(parent.transform, false); lineObject.transform.localPosition = Vector3.zero; LineRenderer lr = lineObject.AddComponent(); lr.useWorldSpace = false; lr.loop = true; lr.positionCount = linePoints.Count; for (int i = 0; i < linePoints.Count; i++) lr.SetPosition(i, new Vector3(linePoints[i].x, linePoints[i].y, zOffset)); lr.material = glowMaterial; lr.startWidth = lineWidth; lr.endWidth = lineWidth; lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; lr.receiveShadows = false; // ✅ Pour une ligne “collée” au miroir (pas face caméra) lr.alignment = LineAlignment.TransformZ; lr.numCornerVertices = 8; lr.numCapVertices = 8; } }