Laden...

Teilkreis über Vollkreis zeichnen?

Erstellt von 7.e.Q vor 12 Jahren Letzter Beitrag vor 4 Jahren 9.647 Views
7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 12 Jahren
Teilkreis über Vollkreis zeichnen?

Hi Leute,

ich möchte gern über eine vorhandene Ellipse (Shape) eine weitere zeichnen, die die gleichen Eigenschaften bezüglich Geometrie besitzt, außer dass sie nur einen bestimmtes Bogenmaß abdeckt. Es soll also sowas wie ein ArcSegment werden.

Allerdings ist mir ArcSegment viel zu kompliziert umgesetzt, als dass ich damit in diesem speziellen Fall irgendwas produktives in brauchbarer Zeit umgesetzt bekäme.

Die (ich nenn's mal) "Teilellipse" sollte möglichst rein in XAML umgesetz sein, mit Binding an ein Objekt das einfach Anfangs und Endwinkel vorgibt.

Hat jemand eine Idee, wie man das realisieren könnte? "Einfach nur" eine Teilellipse zeichnen?

Danke

Grüße,
Hendrik

5.657 Beiträge seit 2006
vor 12 Jahren

Es gibt dafür meiner Meinung nach keine fertigen Lösungen, aber wen du selbst soetwas umsetzen willst, und nicht weißt, wie es geht, kann ich dir diesen Artikel von Charles Petzold empfehlen: Mathematics of ArcSegment

Weeks of programming can save you hours of planning

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 12 Jahren

Ja, den hab ich schon überflogen, werde da aber nicht so recht schlau draus.

Ich brech mir hier echt einen ab. Es kann doch nicht so schwer sein, nur einen Teil einer Ellipse unter Angabe eines Mittelpunktes (X, Y), zweier Radien (X, Y) und zweier Winkel (Anfang, Ende) zu zeichnen. Das ist doch kein besonderes Anliegen, oder?

5.657 Beiträge seit 2006
vor 12 Jahren

Offenbar schon, sonst hätte sich Charles Petzold nicht die Mühe gemacht, so einen Artikel zu schreiben 😃

Wahrscheinlich geht es wohl am einfachsten, wenn man die Bogensegmente als Bezier-Kurven darstellen kann. Man kann ja z.B. auch einen Kreis oder eine Ellipse sehr genau damit zeichnen.

Oder du verwendest einen GraphicPath, dann kannst du Ellipsen zeichnen und mit anderen Objekten kombinieren, bis dein Bogensegment bzw. Ellipsensegment übrig bleibt.

Vielleicht findet man auch eine Lösung, wenn du mal sagst, was du eigentlich darstellen möchtest. Wird es ein Tortendiagramm?

Weeks of programming can save you hours of planning

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 12 Jahren

Also der Sinn des ganzen ist folgender:

ich hab einem Freund zugesagt, ihm ein Programm zu schreiben, mit dem er schematisch die Muskulatur an Gliedmaßen von Insekten darstellen kann. Er hätte es gerne so, dass zwei oder mehr Elemente beispielsweise eines Wespenbeins in Vorderansicht als Kreise dargestellt werden. Als ob man direkt von vorn auf das ausgestreckte Bein schaut (grob, sehr schematisch halt). Der größere Kreis ist das aus der Perspektive hintere Glied des Beines, der jeweils kleinere Kreis das jeweils eine Ebene weiter vorne liegende Glied. Das sind die Vollkreise.

Die Teilkreise sollen nun Kontaktpunkte darstellen, an denen die Muskeln am Bein angewachsen sind. Es gehören also immer zwei Teilkreise zusammen. Einer auf einem weiter außen liegenden Vollkreis und einer auf einem weiter innen liegenden Vollkreis.

Und diese (schulligung, VERFLUCHTEN) Teilkreise zu zeichnen, bereitet mir gerade arge Kopfschmerzen.

Der Freund hat einfach keine Lust, für zig Proben immer und immer wieder die selben Bilder malen zu müssen. Da soll 'ne einfache Eingabemaske her, die ihm die Darstellung aus vorhandenen Daten erzeugt, bzw. wo er einfach klicken kann.

Und da ich das ganze nun so haben möchte, dass alle Elemente der Darstellung auswähl- und änderbare Objekte sind, versuche ich das so weit es eben geht in WPF und XAML zu programmieren. Grafische Darstellung und Daten sollen eben so weit es geht von einander getrennt bleiben.

5.657 Beiträge seit 2006
vor 12 Jahren

Aha, also ein Insekten-Simulator...

Die Teilkreise sollen nun Kontaktpunkte darstellen, an denen die Muskeln am Bein angewachsen sind. Es gehören also immer zwei Teilkreise zusammen. Einer auf einem weiter außen liegenden Vollkreis und einer auf einem weiter innen liegenden Vollkreis.

Puh, naja, oder anders gefragt: Was genau muß an den Teilkreisen alles parametrisch einstellbar sein?

Es gibt im Prinzip zwei Herangehensweisen:

  • Entweder zeichnest du dir einen oder mehrere Teilkreise in einem Zeichenprogramm. Die Kurven kannst du dann in XAML exportieren und beliebig transformieren, um die Insektenbeine zu zeichnen.
  • Oder du verwendest die CombinedGeometry-Klasse, um beliebige Formen aus Ellipsen und Dreiecken zu erstellen.

Weeks of programming can save you hours of planning

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 12 Jahren

Ein Insektensimulator. 😄 Fast.

Einstellbar sein sollen "nur" Anfangs- und Endwinkel in einem polaren Koordinatensystem nach mathematischem Standard (pos. X als Cosinus des Winkels, pos. Y als Sinus des Winkels), dessen Ursprung im Mittelpunkt der Vollkreise liegt. Eventuell noch so Sachen wie Farbe und Breite des Strichs.

//edit 1
achso: der Radius des Kreises (bzw. beide Radien) ergibt sich aus dem zugeordneten Glied. Das ist also auch quasi eine einstellbare Größe. Ich zieh das als hierarchische Struktur auf: Körperteil -> Teilglied -> Kontaktpunkt, wobei jedes Körperteil mehrere Teilglieder und jedes Teilglied mehrere Kontaktpunkte haben kann.
//edit 1 ende

Die Winkel sollen veränderbar sein (möglichst genau). Am besten per Drag&Drop mit Thumbs an beiden Enden, wenn der jeweilige Kontaktpunkt ausgewählt ist. Auch einer der Gründe, wieso ich das alles mit den vorhandenen Komponenten aufzuziehen versuche und es nicht selber per OnRender zeichne. Die bieten schon einige dafür notwendige Funktionen.

CombinedGeometry... kann ich die auch so verwenden, dass das ein offener Pfad wird? Ich hab's schon versucht, die Teilkreise als Exclude zu erzeugen, indem ich ein "Rechteck" ausschneide, dem eine den Anfangs- und Endwinkeln entsprechende Ecke fehlt. Schwer zu erklären... jedenfalls hätte ich damit zwar einen passenden Kreisbogen bekommen; jedoch wäre dieser Teil eines geschlossenen Tortenstücks gewesen. Ich brauch aber wirklich NUR den Kreisbogen als offenen Pfad.

Is datt kompliziert...

5.657 Beiträge seit 2006
vor 12 Jahren

Mit CreateGeometry kannst du keine Pfade erstellen, soweit ich weiß, sondern nur geschlossene Formen.

Aber was spricht dagegen, einen Kreisbogen aus einem großen und einem bißchen kleineren Kreis zu erstellen (indem du den kleineren vom größeren abziehst) und dann mit einem Dreieck zu verschneiden (mit Intersection)?

Weeks of programming can save you hours of planning

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 12 Jahren

Das könnte in der Tat funktionieren. Jedoch muss ich mir noch Gedanken machen, wie ich am besten die Verschneidung mit dem Dreieck realisiere, da es auch Kreisbögen geben kann, die mehr als 90° abdecken. Manche möglicherweise mehr als 180°. Da wird das dann mit dem Dreieck nix. Da müssten mehrere her... huah! Das wird ein Spaß... Da werde ich mit XAML alleine wohl nicht weit kommen, was?!

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 12 Jahren

So. Das Zeichnen des Teilkreises funktioniert jetzt:

Ich habe mir eine Custom Shape erzeugt, die über den Winkelbereich in 1° Schritten BezierSegments anlegt. Performance ist da erstmal Nebensache.

Nächstes Problem: ((gelöst)

Die Shape verhält sich seltsam beim Verkleinern des Fensters. Statt dass sie stretched und mit dem Fenster zusammen kleiner wird, wird sie größer. Ich habe den Eindruck, das hängt damit zusammen, dass der Strich, der aus der PathGeometry gezeichnet wird, aufgrund seiner Dicke über den Rand des Renderbereichs hinaus steht.

Vielleicht mag sich ja mal "fix" jemand meine Custom Shape anschauen und mir sagen, was daran Bockmist ist. Ich vermute 'ne Menge. Das Ding ist jetzt mal quick'n'dirty zusammen geschustert.

//update: Problem gelöst. Nun folgende Variante tut ihren Dienst exakt so, wie ich es gerne hätte. Verbesserungsvorschläge werden natürlich trotzdem gerne angenommen.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows;
using System.Windows.Data;
using System.Windows.Controls.Primitives;

namespace InsectJoint
{
    public class Arc : Shape
    {
        public double StartAngle
        {
            get { return (double)GetValue(StartAngleProperty); }
            set { SetValue(StartAngleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StartAngle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StartAngleProperty =
            DependencyProperty.Register("StartAngle", typeof(double), typeof(Arc), new UIPropertyMetadata(0.0D));



        public double EndAngle
        {
            get { return (double)GetValue(EndAngleProperty); }
            set { SetValue(EndAngleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for EndAngle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EndAngleProperty =
            DependencyProperty.Register("EndAngle", typeof(double), typeof(Arc), new UIPropertyMetadata(0.0D));

        public bool AngleInDegrees
        {
            get { return (bool)GetValue(AngleInDegreesProperty); }
            set { SetValue(AngleInDegreesProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AngleInDegrees.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AngleInDegreesProperty =
            DependencyProperty.Register("AngleInDegrees", typeof(bool), typeof(Arc), new UIPropertyMetadata(true));


        PathGeometry _geo = new PathGeometry();
        PathFigure _fig = new PathFigure();

        public Arc()
        {
            _fig.IsClosed = false;
            _geo.Figures.Add(_fig);
        }

        protected override Geometry DefiningGeometry
        {
            get
            {
                _fig.Segments.Clear();

                double start = StartAngle, end = EndAngle;
                if (AngleInDegrees)
                {
                    start *= (Math.PI / 180.0D);
                    end *= (Math.PI / 180.0D);
                }

                double step = 2.0D * (Math.PI / 180);
                
                _fig.StartPoint = calcPoint(start);

                for (double i = start + step; i <= end; i += (step * 3))
                {
                    Point p1 = calcPoint(i - step * 2);
                    Point p2 = calcPoint(i - step);
                    Point p3 = calcPoint(i);

                    BezierSegment seg = new BezierSegment();
                    seg.Point1 = p1;
                    seg.Point2 = p2;
                    seg.Point3 = p3;

                    _fig.Segments.Add(seg);
                }

                return _geo;
            }
        }

        private Point calcPoint(double angle)
        {
            Rect r = LayoutInformation.GetLayoutSlot(this);
            double offX = (r.Width / 2.0D);
            double offY = (r.Height / 2.0D);
            double radX = (r.Width / 2.0D);
            double radY = (r.Height / 2.0D);

            Point p = new Point();
            p.X = radX * Math.Cos(angle) + offX;
            p.Y = radY * -Math.Sin(angle) + offY;
            return p;
        }
    }
}


B
1 Beiträge seit 2020
vor 4 Jahren

Dieser Beitrag ist zwar schon sehr alt, aber ich möchte trotzdem gerne jedem helfen, der wie ich heute danach sucht und evtl. umständlich nach Lösungen sucht, die mit dem ArcSegment wunderbar zu handeln sind.
Meine Lösung im XAML wäre:

            <Path Stroke="GreenYellow" StrokeThickness="4">
                <Path.Data>
                    <PathGeometry>
                        <PathGeometry.Figures>
                            <PathFigureCollection>
                                <PathFigure StartPoint="100,100">
                                    <PathFigure.Segments>
                                        <PathSegmentCollection>
                                            <LineSegment Point="{Binding ArcStartPoint}"/>
                                            <ArcSegment Size="50, 50" IsLargeArc="{Binding BogenUeber180Grad, Mode=OneWay}" SweepDirection="Clockwise" Point="{Binding ArcEndPoint}"/>
                                            <LineSegment Point="100,100"/>
                                        </PathSegmentCollection>
                                    </PathFigure.Segments>
                                </PathFigure>
                            </PathFigureCollection>
                        </PathGeometry.Figures>
                    </PathGeometry>
                </Path.Data>
            </Path>

Dabei habe ich für meine Bedürfnisse zusätzlich noch einen quasi Zeiger vom Mittelpunkt zum Startpunkt und zum Endpunkt eingebaut.

Es gibt bei dem ArcSegment hauptsächlich folgendes zu verstehen:
* Will man einen kompletten Kreis/Ellipse, darf der StartPoint nicht genau auf dem Point liegen.
* Beim Kreis in der Bildmitte, sollte man Size halb so groß setzen, wie den Durchmesser des gewünschten Kreises.
* IsLargeArc sollte wie hier mit dem Binding auf BogenUeber180Grad dann true sein, wenn der Winkel dazwischen>180° ist und false, wenn dieser kleiner 180° ist.

Die beiden Punkte ArcStartPoint und ArcEndPoint kann man sich mit den üblichen trigonometrischen Berechnungen, wie Cosinussatz und Bogenmass berechnen.
Für eine komplexe Simulation eines Insekts kann ich mir vorstellen, die Orientierung leicht zu verlieren, aber viel einfacher wird es wohl nicht werden.
Bei Ellipsen wird es zugegebenerweise etwas aufwendiger, aber die braucht man schematisch vielleicht auch nicht unbedingt.

Immer erst mal heftig mit dem Kopf nachdenken 🙂