diff options
author | Joe Crayne <joe@jerkface.net> | 2019-05-17 17:55:23 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2019-05-17 17:55:23 -0400 |
commit | 19b7e91dd12f9d22e4a2991341c1622ec83968cb (patch) | |
tree | 227537f18dc6b25b2bad1ca2023c39442cbda87a | |
parent | 330649949aea845e0472d0d99fbd30fb00bd6183 (diff) |
Enable higher-precision rotation.
-rw-r--r-- | MeshSketch.hs | 141 |
1 files changed, 81 insertions, 60 deletions
diff --git a/MeshSketch.hs b/MeshSketch.hs index 8f38863..273c3dc 100644 --- a/MeshSketch.hs +++ b/MeshSketch.hs | |||
@@ -1,7 +1,8 @@ | |||
1 | {-# LANGUAGE OverloadedLabels #-} | 1 | {-# LANGUAGE CPP #-} |
2 | {-# LANGUAGE LambdaCase #-} | ||
3 | {-# LANGUAGE OverloadedLabels #-} | ||
2 | {-# LANGUAGE OverloadedStrings #-} | 4 | {-# LANGUAGE OverloadedStrings #-} |
3 | {-# LANGUAGE LambdaCase #-} | 5 | {-# LANGUAGE RecordWildCards #-} |
4 | {-# LANGUAGE RecordWildCards #-} | ||
5 | module MeshSketch where | 6 | module MeshSketch where |
6 | 7 | ||
7 | import Codec.Picture as Juicy | 8 | import Codec.Picture as Juicy |
@@ -16,6 +17,7 @@ import Data.Text (Text) | |||
16 | import Data.Map.Strict (Map) | 17 | import Data.Map.Strict (Map) |
17 | import qualified Data.Map.Strict as Map | 18 | import qualified Data.Map.Strict as Map |
18 | import qualified Data.Vector as V | 19 | import qualified Data.Vector as V |
20 | import qualified Data.Vector.Generic as G (init) | ||
19 | import Foreign.Marshal.Array | 21 | import Foreign.Marshal.Array |
20 | import Foreign.Storable | 22 | import Foreign.Storable |
21 | import GI.Gdk | 23 | import GI.Gdk |
@@ -24,7 +26,7 @@ import GI.GLib.Constants | |||
24 | import GI.Gtk hiding (IsWindow,windowFullscreen,windowUnfullscreen) | 26 | import GI.Gtk hiding (IsWindow,windowFullscreen,windowUnfullscreen) |
25 | import LambdaCube.GL as LC | 27 | import LambdaCube.GL as LC |
26 | import LambdaCube.GL.Mesh as LC | 28 | import LambdaCube.GL.Mesh as LC |
27 | import Numeric.LinearAlgebra hiding ((<>)) | 29 | import Numeric.LinearAlgebra as Math hiding ((<>)) |
28 | import System.Environment | 30 | import System.Environment |
29 | import System.IO | 31 | import System.IO |
30 | import System.IO.Error | 32 | import System.IO.Error |
@@ -86,16 +88,17 @@ data State = State | |||
86 | 88 | ||
87 | data Camera = Camera | 89 | data Camera = Camera |
88 | { camHeightAngle :: Float | 90 | { camHeightAngle :: Float |
89 | , camTarget :: Vector Float | 91 | , camTarget :: Vector Float -- 3-vector |
90 | , camDirection :: Vector Float | 92 | , camDirection :: Vector Float -- 3-vector |
91 | , camDistance :: Float | 93 | , camDistance :: Float |
92 | , camWidth :: Float | 94 | , camWidth :: Float |
93 | , camHeight :: Float | 95 | , camHeight :: Float |
94 | , camUp :: Vector Float | 96 | , camUp :: Vector Float -- 3-vector |
95 | , camWorldToScreen :: Maybe (Matrix Float) | 97 | , camWorldToScreen :: Maybe (Matrix Float) -- 4×4 |
96 | , camScreenToWorld :: Maybe (Matrix Float) | 98 | , camScreenToWorld :: Maybe (Matrix Float) -- 4×4 |
97 | } | 99 | } |
98 | 100 | ||
101 | camPos :: Camera -> Vector Float | ||
99 | camPos c = camTarget c - scale (camDistance c) (camDirection c) | 102 | camPos c = camTarget c - scale (camDistance c) (camDirection c) |
100 | 103 | ||
101 | initCamera :: Camera | 104 | initCamera :: Camera |
@@ -122,6 +125,13 @@ viewProjection c | |||
122 | pos = camTarget c - scale (camDistance c) (camDirection c) | 125 | pos = camTarget c - scale (camDistance c) (camDirection c) |
123 | proj = perspective 0.1 100 (camHeightAngle c) (camWidth c / camHeight c) | 126 | proj = perspective 0.1 100 (camHeightAngle c) (camWidth c / camHeight c) |
124 | 127 | ||
128 | realToFracVector :: ( Real a | ||
129 | , Fractional b | ||
130 | , Storable a | ||
131 | , Storable b | ||
132 | ) => Vector a -> Vector b | ||
133 | realToFracVector v = Math.fromList $ map realToFrac $ Math.toList v | ||
134 | |||
125 | realToFracMatrix :: (Real a, Fractional t, Element t, Element a) => Matrix a -> Matrix t | 135 | realToFracMatrix :: (Real a, Fractional t, Element t, Element a) => Matrix a -> Matrix t |
126 | realToFracMatrix m = fromLists $ map realToFrac <$> toLists m | 136 | realToFracMatrix m = fromLists $ map realToFrac <$> toLists m |
127 | 137 | ||
@@ -337,12 +347,12 @@ onRender w realized gl = do | |||
337 | 347 | ||
338 | onResize :: GLArea -> Realized -> Int32 -> Int32 -> IO () | 348 | onResize :: GLArea -> Realized -> Int32 -> Int32 -> IO () |
339 | onResize glarea realized w h = do | 349 | onResize glarea realized w h = do |
340 | let storage = stStorage realized | ||
341 | -- Plenty of options here. I went with the last one. | 350 | -- Plenty of options here. I went with the last one. |
342 | -- 1. gLContextGetWindow :: HasCallStack => GLContext -> IO (Maybe Window) | 351 | -- 1. gLContextGetWindow :: HasCallStack => GLContext -> IO (Maybe Window) |
343 | -- 2. getGLContextWindow :: GLContext -> IO (Maybe Window) | 352 | -- 2. getGLContextWindow :: GLContext -> IO (Maybe Window) |
344 | -- 3. widgetGetWindow :: HasCallStack => GLArea -> IO (Maybe Window) | 353 | -- 3. widgetGetWindow :: HasCallStack => GLArea -> IO (Maybe Window) |
345 | widgetGetWindow glarea >>= mapM_ (\win -> do | 354 | mwin <- widgetGetWindow glarea |
355 | forM_ mwin $ \win -> do | ||
346 | (wd,ht) <- do wd <- windowGetWidth win | 356 | (wd,ht) <- do wd <- windowGetWidth win |
347 | ht <- windowGetHeight win | 357 | ht <- windowGetHeight win |
348 | return (fromIntegral wd,fromIntegral ht) | 358 | return (fromIntegral wd,fromIntegral ht) |
@@ -352,11 +362,19 @@ onResize glarea realized w h = do | |||
352 | , camWorldToScreen = Nothing | 362 | , camWorldToScreen = Nothing |
353 | , camScreenToWorld = Nothing | 363 | , camScreenToWorld = Nothing |
354 | } | 364 | } |
355 | LC.setScreenSize (stStorage realized) wd ht) | 365 | LC.setScreenSize (stStorage realized) wd ht |
356 | 366 | ||
357 | -- This computes a point in world coordinates on the view screen if | 367 | -- This computes a point in world coordinates on the view screen if |
358 | -- we assume the camera is located at the origin. | 368 | -- we assume the camera is located at the origin. |
359 | computeDirection :: Camera -> Double -> Double -> Vector Float | 369 | computeDirection :: Camera -> Double -> Double -> Vector Float |
370 | computeDirection cam h k | Just pv <- camScreenToWorld cam = | ||
371 | let d0 = fromList [ 2 * realToFrac h/camWidth cam - 1 | ||
372 | , 1 - 2 * realToFrac k/camHeight cam | ||
373 | , 1 | ||
374 | , 1 | ||
375 | ] :: Vector Float | ||
376 | d1 = pv #> d0 | ||
377 | in scale (1 /(d1!3) ) $ G.init d1 | ||
360 | computeDirection cam h k = | 378 | computeDirection cam h k = |
361 | let d̂ = camDirection cam -- forward | 379 | let d̂ = camDirection cam -- forward |
362 | û = camUp cam -- upward | 380 | û = camUp cam -- upward |
@@ -366,7 +384,11 @@ computeDirection cam h k = | |||
366 | xd = (camHeight cam / 2) / tan (camHeightAngle cam / 2) | 384 | xd = (camHeight cam / 2) / tan (camHeightAngle cam / 2) |
367 | in scale xr r̂ + scale xu û + scale xd d̂ | 385 | in scale xr r̂ + scale xu û + scale xd d̂ |
368 | 386 | ||
369 | rotate :: Float -> Vector Float -> Matrix Float | 387 | rotate :: ( Floating a |
388 | , Math.Container Vector a | ||
389 | , Indexable (Vector a) a | ||
390 | , Normed (Vector a) | ||
391 | ) => a -> Vector a -> Matrix a | ||
370 | rotate cosθ u = (3><3) | 392 | rotate cosθ u = (3><3) |
371 | [ cosθ + ux² mcosθ , (uy.uy)mcosθ - uz sinθ , (ux.uz)mcosθ + uy sinθ | 393 | [ cosθ + ux² mcosθ , (uy.uy)mcosθ - uz sinθ , (ux.uz)mcosθ + uy sinθ |
372 | , (uy.ux)mcosθ + uz sinθ , cosθ + uy² mcosθ , (uy.uz)mcosθ - ux sinθ | 394 | , (uy.ux)mcosθ + uz sinθ , cosθ + uy² mcosθ , (uy.uz)mcosθ - ux sinθ |
@@ -383,19 +405,27 @@ rotate cosθ u = (3><3) | |||
383 | uy² = uy . uy | 405 | uy² = uy . uy |
384 | uz² = uz . uz | 406 | uz² = uz . uz |
385 | 407 | ||
408 | updateCameraRotation :: IsWidget a => a -> State -> Double -> Double -> IO () | ||
386 | updateCameraRotation w st h k = do | 409 | updateCameraRotation w st h k = do |
387 | m <- readIORef (stDragFrom st) | 410 | m <- readIORef (stDragFrom st) |
388 | forM_ m $ \(df,cam) -> do | 411 | forM_ m $ \(df0,cam) -> do |
389 | let d̂ = camDirection cam -- forward | 412 | let d̂ = camDirection cam -- forward |
390 | û = camUp cam -- upward | 413 | û = camUp cam -- upward |
391 | r̂ = d̂ `cross` û -- rightward | 414 | -- r̂ = d̂ `cross` û -- rightward |
392 | -- fr = df `dot` r̂ | 415 | #if 0 |
393 | -- fu = df `dot` û | 416 | -- This turned out to be pointless. |
394 | -- fd = df `dot` d̂ | 417 | promote :: Vector Float -> Vector Double |
395 | dt = computeDirection cam h k | 418 | promote = realToFracVector |
396 | -- tr = dt `dot` r̂ | 419 | demote :: Vector Double -> Vector Float |
397 | -- tu = dt `dot` û | 420 | demote = realToFracVector |
398 | -- td = dt `dot` d̂ | 421 | #else |
422 | promote = id | ||
423 | demote = id | ||
424 | {-# INLINE promote #-} | ||
425 | {-# INLINE demote #-} | ||
426 | #endif | ||
427 | df = promote df0 | ||
428 | dt = promote $ computeDirection cam h k | ||
399 | cosθ = dot df dt / realToFrac (norm_2 df) / realToFrac (norm_2 dt) | 429 | cosθ = dot df dt / realToFrac (norm_2 df) / realToFrac (norm_2 dt) |
400 | axis0 = df `cross` dt | 430 | axis0 = df `cross` dt |
401 | small x = abs x < 0.00001 | 431 | small x = abs x < 0.00001 |
@@ -404,8 +434,8 @@ updateCameraRotation w st h k = do | |||
404 | then fromList [0,1,0] | 434 | then fromList [0,1,0] |
405 | else axis0 | 435 | else axis0 |
406 | cam' = cam | 436 | cam' = cam |
407 | { camDirection = d̂ <# rotate cosθ axis | 437 | { camDirection = demote $ promote d̂ <# rotate cosθ axis |
408 | , camUp = û <# rotate cosθ axis | 438 | , camUp = demote $ promote û <# rotate cosθ axis |
409 | , camWorldToScreen = Nothing | 439 | , camWorldToScreen = Nothing |
410 | , camScreenToWorld = Nothing | 440 | , camScreenToWorld = Nothing |
411 | } | 441 | } |
@@ -414,6 +444,7 @@ updateCameraRotation w st h k = do | |||
414 | forM_ mwin $ \win -> | 444 | forM_ mwin $ \win -> |
415 | windowInvalidateRect win Nothing False | 445 | windowInvalidateRect win Nothing False |
416 | 446 | ||
447 | sanitizeCamera :: State -> IO () | ||
417 | sanitizeCamera st = do | 448 | sanitizeCamera st = do |
418 | modifyIORef (stCamera st) $ \cam -> | 449 | modifyIORef (stCamera st) $ \cam -> |
419 | let d = camDirection cam | 450 | let d = camDirection cam |
@@ -435,10 +466,29 @@ sanitizeCamera st = do | |||
435 | , camScreenToWorld = Nothing | 466 | , camScreenToWorld = Nothing |
436 | } | 467 | } |
437 | 468 | ||
469 | worldCoordinates :: State -> Double -> Double -> IO (Vector Float) | ||
470 | worldCoordinates st h k = do | ||
471 | pv <- atomicModifyIORef' (stCamera st) projectionView | ||
472 | cam <- readIORef (stCamera st) | ||
473 | let d0 = fromList [ 2 * realToFrac h/camWidth cam - 1 | ||
474 | , 1 - 2 * realToFrac k/camHeight cam | ||
475 | , 1 | ||
476 | , 1 | ||
477 | ] :: Vector Float | ||
478 | d1 = pv #> d0 | ||
479 | return $ scale (1 /(d1!3) ) d1 | ||
480 | |||
481 | pushRing :: IsWidget w => w -> State -> Double -> Double -> IO (Vector Float) | ||
482 | pushRing w st h k = do | ||
483 | d <- worldCoordinates st h k | ||
484 | Just win <- getWidgetWindow w | ||
485 | pushBack (stRingBuffer st) (d!0) (d!1) (d!2) | ||
486 | windowInvalidateRect win Nothing False | ||
487 | return d | ||
488 | |||
438 | 489 | ||
439 | onEvent :: IsWidget w => w -> Realized -> Event -> IO Bool | 490 | onEvent :: IsWidget w => w -> Realized -> Event -> IO Bool |
440 | onEvent w realized ev = do | 491 | onEvent w realized ev = do |
441 | let st = stState realized | ||
442 | msrc <- eventGetSourceDevice ev | 492 | msrc <- eventGetSourceDevice ev |
443 | inputSource <- forM msrc $ \src -> do | 493 | inputSource <- forM msrc $ \src -> do |
444 | src <- get src #inputSource | 494 | src <- get src #inputSource |
@@ -446,7 +496,7 @@ onEvent w realized ev = do | |||
446 | etype <- get ev #type | 496 | etype <- get ev #type |
447 | -- putStrLn $ "onEvent! " ++ show (etype,inputSource) | 497 | -- putStrLn $ "onEvent! " ++ show (etype,inputSource) |
448 | let put x = putStrLn (show inputSource ++ " " ++ show x) | 498 | let put x = putStrLn (show inputSource ++ " " ++ show x) |
449 | let st = stState realized | 499 | st = stState realized |
450 | case etype of | 500 | case etype of |
451 | 501 | ||
452 | EventTypeMotionNotify -> do | 502 | EventTypeMotionNotify -> do |
@@ -457,19 +507,7 @@ onEvent w realized ev = do | |||
457 | mev <- get ev #motion | 507 | mev <- get ev #motion |
458 | h <- get mev #x | 508 | h <- get mev #x |
459 | k <- get mev #y | 509 | k <- get mev #y |
460 | -- let d = camPos cam + computeDirection cam h k | 510 | d <- pushRing w st h k |
461 | pv <- atomicModifyIORef' (stCamera st) projectionView | ||
462 | cam <- readIORef (stCamera st) | ||
463 | let d0 = fromList [ 2 * realToFrac h/camWidth cam - 1 | ||
464 | , 1 - 2 * realToFrac k/camHeight cam | ||
465 | , 1 | ||
466 | , 1 | ||
467 | ] :: Vector Float | ||
468 | d1 = pv #> d0 | ||
469 | d = scale (1 /(d1!3) ) d1 | ||
470 | pushBack (stRingBuffer st) (d!0) (d!1) (d!2) | ||
471 | Just win <- getWidgetWindow w | ||
472 | windowInvalidateRect win Nothing False | ||
473 | put (etype,(h,k),d) | 511 | put (etype,(h,k),d) |
474 | _ -> do | 512 | _ -> do |
475 | mev <- get ev #motion | 513 | mev <- get ev #motion |
@@ -486,16 +524,7 @@ onEvent w realized ev = do | |||
486 | bev <- get ev #button | 524 | bev <- get ev #button |
487 | h <- get bev #x | 525 | h <- get bev #x |
488 | k <- get bev #y | 526 | k <- get bev #y |
489 | pv <- atomicModifyIORef' (stCamera st) projectionView | 527 | d <- pushRing w st h k |
490 | cam <- readIORef (stCamera st) | ||
491 | let d0 = fromList [ 2 * realToFrac h/camWidth cam - 1 | ||
492 | , 1 - 2 * realToFrac k/camHeight cam | ||
493 | , 1 | ||
494 | , 1 | ||
495 | ] :: Vector Float | ||
496 | d1 = pv #> d0 | ||
497 | d = scale (1 /(d1!3) ) d1 | ||
498 | pushBack (stRingBuffer st) (d!0) (d!1) (d!2) | ||
499 | Just win <- getWidgetWindow w | 528 | Just win <- getWidgetWindow w |
500 | windowInvalidateRect win Nothing False | 529 | windowInvalidateRect win Nothing False |
501 | put (etype,(h,k),d) | 530 | put (etype,(h,k),d) |
@@ -503,8 +532,9 @@ onEvent w realized ev = do | |||
503 | bev <- get ev #button | 532 | bev <- get ev #button |
504 | h <- get bev #x | 533 | h <- get bev #x |
505 | k <- get bev #y | 534 | k <- get bev #y |
535 | d <- G.init <$> worldCoordinates st h k | ||
506 | cam <- readIORef (stCamera st) | 536 | cam <- readIORef (stCamera st) |
507 | let d = computeDirection cam h k | 537 | -- let d = computeDirection cam h k |
508 | writeIORef (stDragFrom st) $ Just (d,cam) | 538 | writeIORef (stDragFrom st) $ Just (d,cam) |
509 | put (etype,(h,k),d) | 539 | put (etype,(h,k),d) |
510 | return () | 540 | return () |
@@ -516,16 +546,7 @@ onEvent w realized ev = do | |||
516 | bev <- get ev #button | 546 | bev <- get ev #button |
517 | h <- get bev #x | 547 | h <- get bev #x |
518 | k <- get bev #y | 548 | k <- get bev #y |
519 | pv <- atomicModifyIORef' (stCamera st) projectionView | 549 | d <- pushRing w st h k |
520 | cam <- readIORef (stCamera st) | ||
521 | let d0 = fromList [ 2 * realToFrac h/camWidth cam - 1 | ||
522 | , 1 - 2 * realToFrac k/camHeight cam | ||
523 | , 1 | ||
524 | , 1 | ||
525 | ] :: Vector Float | ||
526 | d1 = pv #> d0 | ||
527 | d = scale (1 /(d1!3) ) d1 | ||
528 | pushBack (stRingBuffer st) (d!0) (d!1) (d!2) | ||
529 | Just win <- getWidgetWindow w | 550 | Just win <- getWidgetWindow w |
530 | windowInvalidateRect win Nothing False | 551 | windowInvalidateRect win Nothing False |
531 | _ -> do | 552 | _ -> do |