Inputs — souris & Trackpad
Dans cette partie tu vas mettre en place tout un système pour contrôler ta souris, faire le pont Objective-C → C++ et contrôler ta caméra.
Le bridge
Il n'est pas essentiel de connaitre en détail cette partie, ce n'est pas encore du shading pur.
Complètes ta structure partagée
/// C++ — InputState.h — update:18/05/26
bool keyD = false; // tu avais déjà
bool leftMouseDown = false;
bool rightMouseDown = false;
simd::float2 mousePosition = { 0, 0 }; // position absolue pixel
// État continu — Mouvement (valeurs normalisées -1 à 1)
simd::float3 moveDirection = { 0, 0, 0 }; // donnée non-partagée avec le GPU, float3 suffisant
simd::float2 mouseDelta = { 0, 0 }; // drag x,y accumulés depuis le dernier poll
float scrollDelta = 0.f;
float speedMultiplier = 1.0f; // tu avais déjà
};
Le déplacement souris
NSTrackingArea — c'est quoi ?
La plupart des entrées clavier et souris sont reçues par la classe NSWindow dans RenderWindow.
Le déplacement de la souris provient de la classe NSTrackingArea dans ApplicationController.
/// Objective-C++ — Exemple — avait été créé dans createView() :
NSTrackingArea* trackingArea = [[NSTrackingArea alloc]
initWithRect:_mtkView.bounds // taille maximale d'une fenêtre
options:(NSTrackingMouseMoved | NSTrackingActiveInKeyWindow |
NSTrackingInVisibleRect | NSTrackingActiveAlways)
owner:self userInfo:nil];
[_mtkView addTrackingArea:trackingArea];
// Sans ça, mouseMoved n'est appelé que pendant un drag.
Tu stockes ce que la fonction mouseMoved() fournie renvoie via la variable delta.
/// Objective-C++ — AppViewController.mm — update:18/05/26 — ApplicationController
@implementation ApplicationController
{
RenderWindow* _window;
std::unique_ptr<Renderer> _renderer;
simd::float2 _mousePosition; // compatible event.delta renvoyé par NSTrackingArea
}
Tu calculs les deltas :
\( scaleX = \frac{largeurFenêtre}{largeurLimite} \qquad scaleY = \frac{hauteurFenêtre}{hauteurLimite} \)
\( mouseX = positionCurseurX * scaleX \)
\( mouseY = (hauteurLimite - positionCurseurY) * scaleY \)
/// Objective-C++ — AppViewController.mm — update:18/05/26 — ApplicationController
// cursor libre — nécessite NSTrackingArea
- (void)mouseMoved:(NSEvent *)event // déjà déclarée mais vide pour éviter une erreur
{
NSPoint p = [_mtkView convertPoint:[event locationInWindow] fromView:nil];
CGSize d = _mtkView.drawableSize, b = _mtkView.bounds.size;
_mousePosition = { static_cast<float>(p.x * (d.width / b.width)),
static_cast<float>((b.height - p.y) * (d.height / b.height)) };
} // Conversion points → pixels (Retina : backingScaleFactor ≥ 2) + Y-flip
Tu ajoutes la donnée event.deltaX et la donnée event.deltaY à la structure avant l'envoie vers ton code C++.
/// Objective-C++ — AppViewController.mm — update:18/05/26 — ApplicationController
- (void)drawInMTKView:(nonnull MTKView *)view
{
static CFTimeInterval lastTime = CACurrentMediaTime();
CFTimeInterval now = CACurrentMediaTime();
float delta = (float)(now - lastTime);
lastTime = now;
delta = fminf(delta, 0.1f);
InputState input = [_window pollInputState];
input.mousePosition = _mousePosition; // ← injecté ici
_renderer->m_input = input;
_renderer->draw((__bridge MTK::View *)view, now, delta);
}
Le reste des entrées souris
Retina — points vs pixels
// Un "point" AppKit ≠ un pixel sur Retina
// backingScaleFactor = 2.0 sur Retina → 1 point = 4 pixels
CGFloat scale = screen.backingScaleFactor ?: 1.0; // 1.0 ou 2.0
CGSize sizePts = _mtkView.bounds.size;
_mtkView.drawableSize = CGSizeMake(sizePts.width * scale, sizePts.height * scale);
// → le drawable Metal est toujours en pixels réels
// → les coordonnées NSEvent sont en points — toujours convertir
/// Objective-C++ — AppViewController.mm — update:18/05/26
@implementation RenderWindow
{
NSMutableSet<NSNumber*>* _pressedKeys;
simd::float2 _mouseDelta; // accumulé entre deux frames
float _scrollDelta;
BOOL _leftMouseDown;
BOOL _rightMouseDown;
}
/// Objective-C++ — AppViewController.mm — update:18/05/26 — avant: pollInputState
// mouseDragged : bouton gauche enfoncé — accumuler le delta pour la caméra
- (void)mouseDragged:(NSEvent *)event
{
constexpr float sensitivity = 0.003f;
_mouseDelta.x += event.deltaX * sensitivity;
_mouseDelta.y += event.deltaY * sensitivity;
}
/// Objective-C++ — AppViewController.mm — update:18/05/26 — avant: pollInputState
- (void)mouseDown:(NSEvent *)event { _leftMouseDown = YES; }
- (void)mouseUp:(NSEvent *)event { _leftMouseDown = NO; }
- (void)rightMouseDown:(NSEvent *)event { _rightMouseDown = YES; }
- (void)rightMouseUp:(NSEvent *)event { _rightMouseDown = NO; }
/// Objective-C++ — AppViewController.mm — update:18/05/26 — avant: pollInputState
// zoom ou défilement UI
- (void)scrollWheel:(NSEvent *)event
{
float delta = event.scrollingDeltaY;
if (event.hasPreciseScrollingDeltas) // Trackpad multi-touch → précision fine
delta *= 0.1f;
_scrollDelta += delta / 4.f;
}
Notre structure avec toutes les entrées claviers, à chaque image. Prête à être envoyée à notre C++ pour être lue.
/// Objective-C++ — AppViewController.mm — update:18/05/26
InputState state;
if ([_pressedKeys containsObject:@(0x7B)]) { state.keyLeft = true; }
if ([_pressedKeys containsObject:@(0x7C)]) { state.keyRight = true; }
if ([_pressedKeys containsObject:@(0x31)]) { state.keySpace = true; }
if ([_pressedKeys containsObject:@(0x80)]) { state.speedMultiplier = 1.89f; }
if ([_pressedKeys containsObject:@(0x81)]) { /* Do something */ }
if ([_pressedKeys containsObject:@(0x35)]) { [[NSApplication sharedApplication] terminate:nil]; }
state.mouseDelta = _mouseDelta; _mouseDelta = {0, 0};
state.scrollDelta = _scrollDelta; _scrollDelta = 0.f;
state.leftMouseDown = _leftMouseDown;
state.rightMouseDown = _rightMouseDown;
Capturer le curseur (mode FPS)
// Dans keyDown ou un bouton :
// Masquer le curseur + le confiner à la fenêtre
[NSCursor hide];
CGAssociateMouseAndMouseCursorPosition(NO); // découple curseur OS et position
// Pour le libérer :
[NSCursor unhide];
CGAssociateMouseAndMouseCursorPosition(YES);
mouseY = (boundsSize.height - cursorPos.y) * scaleY. L'oubli de ce flip est une source classique de décalage de picking souris.