[WPF] Muis oneindig lang door Viewport3D bewegen

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Hoi,

Ik heb een Viewport3D in een WPF applicatie (C#) waarin een kubus afgebeeld wordt. Met behulp van een aantal Mouse events kan ik nu de camera met de muis om deze kubus laten draaien. Dit krijg ik voor elkaar met behulp van deze stappen:
  • In het MouseDown event: sla de positie van de muis op (relatief, t.o.v. de viewport), en zet een flag dat de muisknop ingedrukt is
  • In het MouseMove event, als de muisknop is ingedrukt:
    1. Bereken verschil tussen huidige en vorige muis positie
    2. Voeg dit verschil toe aan een 'totalDx' en 'totalDy' waarden, die in principe de yaw en pitch van de rotatie voorstellen (op een factor na)
    3. Roteer de camera mbv deze yaw en pitch
    4. Sla de huidige muis positie op als de vorige muispositie (voor het volgende event)
  • In het MouseUp event: Reset de flag dat de muisknop is ingedrukt
Dit werkt allemaal prima, maar er is 1 probleem: zodra de muis uit het venster gaat stopt de rotatie uiteraard omdat de muis events niet meer doorkomen.

Wat ik eigenlijk wil is dat de muis onzichtbaar wordt zodra ik de rechtermuis inhoud (camera roteren kan alleen met rechtermuis ingedrukt) en dat de gebruiker de muis dan 'oneindig lang' kan blijven bewegen zonder dat deze ooit uit het venster raakt en de rotatie stopt.

Ik dacht dat ik dat wel even kon implementeren door deze logica toe te voegen:
  • In het MouseDown event: sla ook nog de absolute muis positie op (relatief t.o.v. het scherm)
  • In het MouseMove event:
    1. Lees de huidige absolute muis positie uit en sla lokaal op
    2. Voer de rotatie uit zoals gewoonlijk
    3. Zet de absolute muis positie gelijk aan de vorige absolute muis positie
    4. Sla de huidige absolute muis positie op als de vorige absolute muis positie (voor het volgende event)
Ik dacht dat dit wel zou werken, de muis zou simpelweg NA de beweging weer teruggezet worden naar zijn positie voor de beweging, en dat elke keer dat de MouseMove event aangeroepen wordt. De beweging zelf wordt dus wel nog gewoon berekend (met de code die ik al had) dus de rotatie van de camera zou niet moeten veranderen.

Op deze manier zou de muis dus oneindig lang kunnen blijven bewegen, alleen in plaats van bijvoorbeeld van pixels 1, 2, 3, 4, 5... gaat hij nu 1,2,1,2,1,2,1,2...
(Als de muis dus als een gek van links naar rechts gegooid wordt, zo snel dat de MouseMove event maar 1 keer aangeroepen wordt, zou dit nog steeds mis gaan aangezien de muis dan alsnog buiten het scherm kan komen, maar dat is wel erg overdreven...)


Maar goed, dit werkt dus niet helemaal lekker. Het principe werkt echter wel, maar het werkt heel schokkerig. Ik kan niet precies vinden waar het nu aan ligt, misschien dat de muis positie niet exact genoeg terug gezet wordt ofzo :?

Ik heb er maar even een video van gemaakt, dan is in ieder geval duidelijk wat er gebeurt:
Hier gaat het mis: YouTube: Mouse Fail
Hier gaat het goed: YouTube: Mouse Fail 2

In het eerste filmpje zet ik de muis dus wel steeds terug (ik heb de muis maar een zichtbaar gelaten om het effect te kunnen zien), in het tweede niet, daar loopt alles vloeiend.


De relevante code is als volgt:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
        private bool mouseDown;
        private Point previousRelativePosition;
        private Point previousAbsolutePosition;
        private double totalDx;
        private double totalDy;

        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (this.CanMoveCamera)
            {
                if (e.RightButton != MouseButtonState.Pressed) return;

                mouseDown = true;
                Point pos = Mouse.GetPosition(viewport);
                previousRelativePosition = new Point(pos.X - viewport.ActualWidth/2,
                                    viewport.ActualHeight / 2 - pos.Y);

                previousAbsolutePosition = MouseUtilities.GetPosition();

                //this.Cursor = Cursors.None;
            }
        }

        private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
        {
            mouseDown = false;
            this.Cursor = Cursors.Arrow;
        }

        private void Grid_MouseMove(object sender, MouseEventArgs e)
        {
            if (!mouseDown) return;

            // Get absolute mouse position
            Point absolutePos = MouseUtilities.GetPosition();

            // Get mouse position relative to viewport and transform it according to viewport size
            Point relativePos = Mouse.GetPosition(viewport);
            Point actualRelativePos = new Point(relativePos.X - viewport.ActualWidth / 2,
                                                viewport.ActualHeight / 2 - relativePos.Y);

            // Calculate difference between previous and current position
            double dx = actualRelativePos.X - previousRelativePosition.X;
            double dy = actualRelativePos.Y - previousRelativePosition.Y;

            totalDx += dx; 
            totalDy += dy;

            // Rotate
            this.Rotate();

            // Set absolute mouse position back to previous position
            MouseUtilities.SetPosition(previousAbsolutePosition);

            // Store current mouse position as previous mouse position for the next event
            previousRelativePosition = actualRelativePos;
            previousAbsolutePosition = absolutePos;
        }

De Rotate code is niet relevant (die werkt prima zoals je kan zien in het tweede filmpje).

De MouseUtilities klasse roept simpelweg de SetCursorPos en GetCursorPos API's aan:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    public static class MouseUtilities
    {
        public static Point GetPosition()
        {
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);
            return new Point(w32Mouse.X, w32Mouse.Y);
        }

        public static void SetPosition(Point pt)
        {
            SetPosition(pt.X, pt.Y);
        }

        public static void SetPosition(double x, double y)
        {
            SetCursorPos((int) x, (int) y);
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct Win32Point
        {
            public Int32 X;
            public Int32 Y;
        };

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetCursorPos(ref Win32Point pt);

        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        internal static extern bool SetCursorPos(int x, int y);  
    }



Wat doe ik fout? Is er iets fout met de logica die ik gebruik, of gaat dit gewoon mis omdat het mouse event niet snel genoeg gaat, of iets dergelijks..? Kan dit uberhaupt wel lukken op deze manier, of moet ik het in een heel andere hoek zoeken?

Bedankt!

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

Verwijderd

Zou je niet gewoon de muis zodra de rechtermuisknop in word gehouden op elk mousemove event terugzetten naar de mousedown positie en dan elke keer de delta x en y erbij optellen of is dit niet waar je heen wilt ?
of genereerd dat ook weer een mousemove event ..

[ Voor 10% gewijzigd door Verwijderd op 05-01-2011 21:52 ]


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Dat klinkt inderdaad nog logischer, maar ook dat werkt niet. Ik sla nu gewoon een 'mouseDownPosition' op in het MouseDown event (de absolute positie dus), en op het eind van de MouseMove zet ik de positie weer terug naar mouseDownPosition.

De muis blijft nu op dezelfde plaats (op een beetje knipperen na als ik snel genoeg beweeg), maar de camera lijkt ook meteen terug te draaien zodra de muis terug springt... Je ziet de camera dus af en toe wat springen maar hij vliegt meteen weer terug.

Ik dacht dat inderdaad opnieuw het MouseMove event aangeroepen werd, dus heb ik er een flag omheen gezet zodat er niets gebeurt in dat geval:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
        private bool dontRaiseMove = false;
        private void Grid_MouseMove(object sender, MouseEventArgs e)
        {
            // If this move event was caused by resetting the mouse position, then ignore it
            if (dontRaiseMove) return;

            if (!mouseDown) return;

            // Get mouse position relative to viewport and transform it according to viewport size
            Point relativePos = Mouse.GetPosition(viewport);
            Point actualRelativePos = new Point(relativePos.X - viewport.ActualWidth / 2,
                                                viewport.ActualHeight / 2 - relativePos.Y);

           
            // Calculate difference between previous and current position
            double dx = actualRelativePos.X - previousRelativePosition.X;
            double dy = actualRelativePos.Y - previousRelativePosition.Y;

            totalDx += dx;
            totalDy += dy;
            // Rotate
            this.Rotate();
           

            // Set absolute mouse position back to previous position
            dontRaiseMove = true;  // make sure the move event is not handled during this move
            MouseUtilities.SetPosition(mouseDownPosition);
            dontRaiseMove = false; // and make sure it is handled again after this move
            

            // Store current mouse position as previous mouse position for the next event
            previousRelativePosition = actualRelativePos;
        }

Maar ook dat werkt niet. Zelfde verhaal, kleine verspringingen die meteen weer terugspringen.

Ik heb zelfs nog geprobeerd om wel nog de nieuwe muispositie (relatief) op te vragen en op te slaan, maar daar verder niets mee te doen (geen Rotate aanroepen) als dontRaiseMove true is, maar ook dat werkt niet...

Het lijkt er toch echt op alsof de camera weer op de beginpositie reageert, terwijl dat toch niet zo hoort te zijn in m'n code?

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • SaphuA
  • Registratie: September 2005
  • Laatst online: 10-09 22:00
.

[ Voor 102% gewijzigd door SaphuA op 01-02-2022 16:28 ]


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
SaphuA schreef op donderdag 06 januari 2011 @ 00:45:
Ik weet niet wat je doet in die MouseUtitlities, maar je weet zeker dat de muis ook werkelijk verplaatst is zodra je de flag uit zet?
Je bedoelt dat de muis pas verplaatst wordt een tijdje nadat ik de SetPosition aanroep doe? Dus dat ik de flag alweer uit zet (dontRaiseMove = false) nog voordat de muis daadwerkelijk bewogen heeft? Dat zou ik wel vreemd vinden, maar dat is misschien wel een mogelijkheid ja... Ik vind dit soort dingen altijd lastig te testen, ik kan wel een breakpoint zetten op de MouseMove event, die wordt 1 keer aangeroepen, en na het uitvoeren van SetPosition wordt hij niet opnieuw geraakt, maar dat is natuurlijk logisch omdat ik in de debugger zit en het venster dus niet eens zichtbaar is (dus kunnen er nooit MouseMove events optreden, toch?)...

Stel dat de muis inderdaad pas iets later bewogen wordt, wat kan ik dan doen om te zorgen dat de camera in dat geval niet meebeweegt? Ik zou het overigens wel erg vreemd vinden, dat zou betekenen dat die call asynchroon is ofzo (niet blocking), lijkt me niet heel logisch, maar ik heb dan ook vrij weinig verstand van het aanroepen van API's (behalve gewoon opzoeken van voorbeeldje en uitvoeren) dus ik weet er niet veel van.

Overigens staat de code in MouseUtilities gewoon in m'n post dus die kun je gewoon zien.
SaphuA schreef op donderdag 06 januari 2011 @ 00:45:
Het kan zijn dat je er niets aan hebt, maar is het niet gewoon een idee om na het klikken en verplaatsen van de muis het 3d object te blijven roteren? Ook als de muis stil staan. Dus afhankelijk van de positie van de muis tov de startpositie de richting en de snelheid bepalen.
Je bedoelt dat de camera met constante snelheid blijft roteren als ik de muis een centimeter verplaats en dan stil houdt? Dat is misschien een mogelijkheid, maar dat lijkt me niet heel handig. Je moet juist nauwkeurig rond het object kunnen kijken, en dan is het veel logischer om dat handmatig met de muis te bepalen in plaats van dat de camera 'vanzelf' beweegt en je maar moet zien dat je de muisknop op het juiste moment loslaat. Dat wil ik dus echt als laatste mogelijkheid houden.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • roy-t
  • Registratie: Oktober 2004
  • Laatst online: 08-09 11:33
Vanuit games gedacht werkt het meestal zo.

Zodra de rechtermuis ingedrukt wordt word de cursor verborgen en de muis in het midden gezet. Elke frame (+- 60x per seconde) wordt de muispositie bekeken, de veranderingen worden gebruikt en de muis wordt weer in het midden gezet.

Wordt de rechtermuis los gelaten, dan wordt deze teruggezet op de positie voordat de rechtermuis ingedrukt wordt.

Op deze manier kun je nooit van het scherm aflopen. Wel is het even klooien om in vanilla WPF dit voor elkaar te krijgen, maar desnoods neem je een timer die 60x per seconden tikt (houd wel rekening met het feit dat timers nooit precies zijn. Bereken daarom altijd de bewegingen als deltaPositie*deltaTijd).

~ Mijn prog blog!


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Of ik nou de muis naar het midden van het scherm zet of naar een willkeurige plaats (de plaats waar de klik plaatsvond) zou niet uit moeten maken, het gaat om de relatieve beweging (verschil tussen huidige en vorige positie).

Ik heb je idee om de muis naar het midden te zetten echter toch eens geprobeerd, en nu denk ik dat ik weet waar het probleem ligt, al weet ik nog steeds niet hoe ik het op zou moeten lossen.


Als ik nu zonder de muis te bewegen op de rechtermuisknop druk, dan verspringt de muis naar het midden van de viewport, en de kubus beweegt mee!! Dat moet natuurlijk niet. Het lijkt er dus op dat de MouseMove event wel degelijk uitgevoerd wordt, zelfs als ik alleen klik en niet beweeg.

Of dit iets met WPF te maken heeft of niet weet ik niet zeker, maar ik weet niet hoe ik het op zou moeten lossen. Ik zet de dontRaiseMove flag gewoon weer op true, verzet dan de muis, en daarna weer op false, en daarna pas wordt de MouseMove aangeroepen.
Ik heb net iets gelezen over routed eventargs, en dat het kan zijn dat het event eigenlijk van een ander object afkomt, dus check ik nu ook of de OriginalSource 'canvas' is. 'canvas' is een Canvas wat ik over de viewport heen leg (Background=Transparent) en de mouse events zijn de mouse events van dit canvas (en dus niet van de viewport zelf, want dan runnen ze alleen als de muis op een 3d object is).

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (this.CanMoveCamera)
            {
                if (e.RightButton != MouseButtonState.Pressed) return;

                mouseDown = true;
                //...
.
                Point centerOfViewport = viewport.PointToScreen(new Point(viewport.ActualWidth / 2, viewport.ActualHeight / 2));

                // Zet muis naar het midden, en gebruik dontRaiseMove flag om te zorgen dat de code
                // in MouseMove niet draait (anders gaat de camera mee bewegen bij deze beweging)
                dontRaiseMove = true;
                mouseDownPosition = centerOfViewport;
                MouseUtilities.SetPosition(centerOfViewport);
                dontRaiseMove = false;
            }
        }

        private bool dontRaiseMove = false;
        private void Grid_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.OriginalSource != canvas) return; 

            // If this move event was caused by resetting the mouse position, then ignore it
            if (dontRaiseMove) return;

            if (!mouseDown) return;

            // Doe rotatie...

In mijn hoofd zou dit gewoon moeten kloppen... Druk op rechtermuis:
  • dontRaiseMove = true;
  • Verplaats muis
  • Zou dit voor een aanroep van MouseMove zorgen dan gebeurt er niets want dontRaiseMove = true
  • dontRaiseMove = false;
Maar toch werkt het blijkbaar niet zo... Het lijkt erop dat de MouseMove aangeroepen wordt nog nadat ik dontRaiseMove op false terugzet... En ik moet die natuurlijk terugzetten, anders worden vanaf de muisklik nooit meer move events gehandled en dan werkt helemaal niets meer :+

Hoe kan ik ervoor zorgen dat de MouseMove alleen runt als ik zelf de muis beweeg, en niet doordat ik klik of doordat ik programmatisch de muis beweeg?

[ Voor 48% gewijzigd door NickThissen op 06-01-2011 10:23 ]

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Gimmeabrake
  • Registratie: December 2008
  • Laatst online: 23-08 10:45
Hoeveel MouseEvents firen er als jij handmatig de cursor verplaatst? Mocht het er maar 1 zijn, kun je dan niet gewoon de positie opslaan waar je naartoe gaat moven, en in de MouseMove event checken of de positie uit de event daarmee overeenkomt? Niet de meest nette methode, maar de kans dat de user precies die ene pixel weet te raken is minimaal. En zelfs als de user wel langs de pixel komt, valt het niet op aangezien de pixel ervoor en erna wel firen...

Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Nou, ik ben eruit... Er zat toch een logica fout in, al snap ik nog steeds niet helemaal waar :p

Ik heb het nu ietsje anders aangepakt. De muis wordt nu inderdaad steeds naar het midden van de viewport terug gezet (ook zodra er geklikt wordt). In de MouseMove event wordt de positie van de muis opgehaald, en uitgerekend hoever deze positie van het midden is (x - width, height - y). Dat is nu ook meteen de dx en dy, aangezien de vorige positie toch (0,0) was :)
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (this.CanMoveCamera)
            {
                if (e.RightButton != MouseButtonState.Pressed) return;

                // Indicate that the right-mouse button is down
                mouseDown = true;

                // Calculate the center of the viewport in screen coordinates
                centerOfViewport = viewport.PointToScreen(new Point(viewport.ActualWidth / 2, viewport.ActualHeight / 2));

                // Set the mouse cursor to that position
                MouseUtilities.SetPosition(centerOfViewport);
                
                // Hide the cursor
                this.Cursor = Cursors.None;
            }
        }

        private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
        {
            // Indicate that the mouse is no longer pressed and make the cursor visible again
            mouseDown = false;
            this.Cursor = Cursors.Arrow;
        }

        private void Grid_MouseMove(object sender, MouseEventArgs e)
        {
            // If the right-mouse is not pressed, don't do anything
            if (!mouseDown) return;
            
            // Get mouse position relative to viewport and transform it to the center
            // Literally, actualRelativePos contains the X and Y amounts that the mouse is away from the center of the viewport
            Point relativePos = Mouse.GetPosition(viewport);
            Point actualRelativePos = new Point(relativePos.X - viewport.ActualWidth / 2,
                                                viewport.ActualHeight / 2 - relativePos.Y);

            // dx and dy are the amounts  by which the mouse moved this move event. Since we keep resetting the mouse to the
            // center, this is just the new position of the mouse, relative to the center: actualRelativePos.
            double dx = actualRelativePos.X;
            double dy = actualRelativePos.Y;

            yaw += dx;
            pitch += dy;

            // Rotate
            this.Rotate();           

            // Set mouse position back to the center of the viewport in screen coordinates
            MouseUtilities.SetPosition(centerOfViewport);  
        }

Dit werkt prima :)

Toch bedankt voor de hulp :D

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • beany
  • Registratie: Juni 2001
  • Laatst online: 16:42

beany

Meeheheheheh

Volgens mij had je beter de mouse kunnen capturen. Veel simpeler, effectiever en 'zoals het hoort'.

http://msdn.microsoft.com....input.mouse.capture.aspx

Dagelijkse stats bronnen: https://x.com/GeneralStaffUA en https://www.facebook.com/GeneralStaff.ua


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
beany schreef op donderdag 06 januari 2011 @ 10:58:
Volgens mij had je beter de mouse kunnen capturen. Veel simpeler, effectiever en 'zoals het hoort'.

http://msdn.microsoft.com....input.mouse.capture.aspx
Ah, dat klinkt ook handig :)
Maar dan zou de rotatie nog steeds stoppen als je tegen de rand van je scherm aankomt, toch? Dan is het toch beter op deze manier, zo kun je kunnen blijven draaien tot je duizelig bent (of geen ruimte meer hebt op je bureau) :+

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • beany
  • Registratie: Juni 2001
  • Laatst online: 16:42

beany

Meeheheheheh

Hmm, goed punt... Misschien is die SetPosition dan nog zo gek nog niet. Verberg dan wel de cursor, staat anders zo raar ;)

Dagelijkse stats bronnen: https://x.com/GeneralStaffUA en https://www.facebook.com/GeneralStaff.ua


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
NickThissen schreef op donderdag 06 januari 2011 @ 10:51:
C#:
1
2
3
 
                // Hide the cursor
                this.Cursor = Cursors.None;
;)

Mijn iRacing profiel

Pagina: 1