Jump to content
Unity Insider Forum

Mesh deformieren mittels Mausklick


minuschki

Recommended Posts

Hallo zusammen

Zur Situation: Ich habe einen Berg als Mesh in die Szene importiert, dessen Form ich durch gezielte Mausklicks verändern möchte. Der Berg besitzt einen MeshCollider, so dass ein hit.point beim Anklicken des Objekts ausgelesen werden kann. Mit Debug.DrawLine habe ich das kontrolliert und die Linie endet genau am gewünschten Punkt. Nun sollte bei allen Meshpunkten, die sich innerhalb eines definierten Radius befinden, der Wert der Höhe leicht angehoben werden. 

Das Problem: Offenbar passen die Koordinaten der Meshpunkte und des hit.point nicht zueinander! Das Array "verticies" hat eine Länge von 40401. Da der Berg quadratisch ist, ergibt das eine Seitenlänge von 201 x 201 Meshpunkten. Damit er gross genug in der Szene erscheint, steht im Inspektor bei Scale: x: 100, y: 100, z:100 und seine Position ist etwas seitlich verschoben und zurückgesetzt: x: 60, y: 0, z : 680. Wenn ich auf den Berg klicke erhalte ich für den hit.point beispielweise x-Werte zwischen -350 und +350, je nachdem, ob man ganz link bzw. ganz recht auf den Berg klickt. Die Höhenverschiebung funktioniert aber gar nicht oder höchsten an einer falschen Stelle. Wenn ich die Bedingung "if (distance.sqrMagnitude < radius)" durch "if (Random.Range(0, 2) == 1)" ersetze, erscheint der ganze Berg sehr zackig, was immerhin bedeutet, dass die Meshpunkte manipuliert werden können.

Was muss man im Script ändern, damit die Werte des hit.point und die Meshpunkte mit einander korrekt korrelieren?

Besten Dank für eure Hilfe!

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeshModeler : MonoBehaviour
{

	[Range(1.5f, 2f)]
	public float radius = 2f;

	[Range(0.5f, 5f)]
	public float deformPower = 2f;

	Mesh mesh;
	Vector3[] verticies;
	Vector3[] modifiedVerts;


	void Start ()
	{
		mesh = GetComponent<MeshFilter>().mesh;
		verticies = mesh.vertices;
		modifiedVerts = mesh.vertices;
	}


	void Update ()
	{
		if (Input.GetMouseButton (0))
		{
			RaycastHit hit;
			Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);

			if (Physics.Raycast (ray, out hit, 3000))
			{

				for(int v = 0; v < modifiedVerts.Length; v++)
				{
					Vector3 distance = modifiedVerts [v] - hit.point;

					float smoothingFactor = 20f;
					float force = deformPower / (1f + hit.point.sqrMagnitude);

					if (distance.sqrMagnitude < radius)
					{
						modifiedVerts [v] = modifiedVerts [v] + (Vector3.up * force) / smoothingFactor;
					}
				}

				RecalculateMesh ();
			}
		}
	}


	void RecalculateMesh()
	{
		mesh.vertices = modifiedVerts;
		GetComponent<MeshCollider> ().sharedMesh = mesh;
		mesh.RecalculateNormals ();
	}
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin!

Erstmal: Sehr guter Post, da machst du einem das Helfen schön einfach mit :)

Zitat

"if (distance.sqrMagnitude < radius)"

Hier könnte ein Fehler liegen, je nachdem, ob dir das bewusst ist, aber: sqrMagnitude ist die Länge des Vektors zum Quadrat. Wenn du deinen Radius nicht auch mit sich selber multiplizierst, dann stimmt dieser Abgleich nicht.

Ansonsten würde ich sagen, hier ist das Problem local space vs. world space. Dein hit.point ist in world space. Das heißt: Wenn der Berg auf Position (0, 0, 1000) wäre, dann wäre dein hit.point auch irgendwo da in der Nähe. Die Vertex-Koordinaten sind aber in local space - sie ändern sich nicht, wenn du den Berg verschiebst. Wenn das Vertex an der Spitze des Berges z.B. bei (0, 10, 0) liegt, dann liegt es da immer, auch wenn dein Berg auf (0, 0, 1000) oder (0, 0, -1000) liegt.

Du musst also entweder die Vertex-Positionen in world space umrechnen oder hit.point in den local space des Berges. Ersteres müsstest du einmal pro Vertex machen, letzteres nur ein einzelnes Mal. Von daher:

if (Physics.Raycast (ray, out hit, 3000))
{
  var localHitPoint = transform.InverseTransformPoint(hit.point);

und dann in den folgenden Zeilen mit localHitPoint statt mit hit.point arbeiten.

Jetzt vergleichst du den lokalen Punkt am Berg mit den lokalen Punkten des Berg-Meshes.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hallo Sascha

Ich frage mich, ob man die unten gezeigte for-Schleife nicht erheblich optimieren könnte. Immerhin werden da jedesmal alle 40401 Meshpunkte kontrolliert, obwohl schlussendlich nur etwa 300 innerhalb des Radius liegen und manipuliert werden müssen. Im Moment ruckelt es recht stark. Ich denke aber, die Berechnung könnte doch viel flüssiger ablaufen, wenn man nur das quadratische Feld der möglichen Kandidaten (localHitPoint +- Radius in x und z Richtung) kontrollieren würde.

Stehe ich da völlig im Schilf oder gäbe es diese Möglichkeit? Eventuell mit einer Doppelschleife, die weiss nicht wie aussehen würde?

 

for (int v = 0; v < modVerts.Length; v++)
{
	Vector3 distance = modVerts [v] - localHitPoint;
	float force = deformPower / (1f + localHitPoint.sqrMagnitude);

	if (distance.sqrMagnitude < radius * radius)
	{
		modVerts [v] = modVerts [v] + (Vector3.up * force) / smoothingFactor;
	}
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Naja, mehrere Schleifen haben zusammen genauso viele Durchläufe wie eine einzelne.

Du hast mehrere Möglichkeiten:

  1. Die Punkte so filtern, dass du nur Punkte in einem Bereich um den Klick überhaupt in Betracht ziehst.
    Um Punkte vorzufiltern, brauchst du eine so genannte Beschleunigungsstruktur, z.B. eine Spatial Hashmap. Damit teilst du Punkte im Raum vorzeitig in Sektoren ein und betrachtest nur die Sektoren, die um deinen Klick herum liegen und dann nur die Punkte in diesen Sektoren. Alle anderen brauchst du dann nichtmal ansehen.
  2. Ist dein Mesh von oben mehr oder weniger ein Grid? Oder sind die Vertices überall irgendwie angeordnet? Wenn du von oben ein mehr oder weniger gleichmäßiges Netz hast, kannst du natürlich einfacher Punkte vorsortieren.
  3. Die Anzahl an Vertices reduzieren. 40k ist schon heftig für ein Echtzeit-deformiertes Mesh.
  4. Multithreading ist auch eine Option.

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...