aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Sunet <jeannekamikaze@gmail.com>2012-07-31 12:33:29 +0200
committerMarc Sunet <jeannekamikaze@gmail.com>2012-07-31 12:33:29 +0200
commitf9ea673e0623aa7bef0e625467708d837ae3ad2f (patch)
treed8b0392bf9a23adbda3df49c7875af65e373fd90
initial commit
-rw-r--r--LICENSE7
-rw-r--r--Setup.hs2
-rw-r--r--Spear.cabal120
-rw-r--r--Spear.lkshs18
-rw-r--r--Spear.lkshw10
-rw-r--r--Spear/App.hs10
-rw-r--r--Spear/App/Application.hs122
-rw-r--r--Spear/App/Input.hs220
-rw-r--r--Spear/Assets/Image.hsc144
-rw-r--r--Spear/Assets/Image/BMP/BMP_load.c257
-rw-r--r--Spear/Assets/Image/BMP/BMP_load.h23
-rw-r--r--Spear/Assets/Image/Image.c8
-rw-r--r--Spear/Assets/Image/Image.h32
-rw-r--r--Spear/Assets/Image/Image_error_code.h15
-rw-r--r--Spear/Assets/Image/sys_types.h16
-rw-r--r--Spear/Assets/Model.hsc334
-rw-r--r--Spear/Assets/Model/MD2/MD2_load.c483
-rw-r--r--Spear/Assets/Model/MD2/MD2_load.h23
-rw-r--r--Spear/Assets/Model/Model.c73
-rw-r--r--Spear/Assets/Model/Model.h79
-rw-r--r--Spear/Assets/Model/Model_error_code.h16
-rw-r--r--Spear/Assets/Model/OBJ/Makefile10
-rw-r--r--Spear/Assets/Model/OBJ/OBJ_load.cc273
-rw-r--r--Spear/Assets/Model/OBJ/OBJ_load.h25
-rw-r--r--Spear/Assets/Model/OBJ/test.cc47
-rw-r--r--Spear/Assets/Model/sys_types.h16
-rw-r--r--Spear/Collision.hs19
-rw-r--r--Spear/Collision/AABB.hs32
-rw-r--r--Spear/Collision/Collision.hs119
-rw-r--r--Spear/Collision/Collisioner.hs80
-rw-r--r--Spear/Collision/Sphere.hs36
-rw-r--r--Spear/Collision/Triangle.hs40
-rw-r--r--Spear/Collision/Types.hs6
-rw-r--r--Spear/GLSL.hs20
-rw-r--r--Spear/GLSL/Buffer.hs111
-rw-r--r--Spear/GLSL/Error.hs45
-rw-r--r--Spear/GLSL/Management.hs297
-rw-r--r--Spear/GLSL/Texture.hs110
-rw-r--r--Spear/GLSL/Uniform.hs67
-rw-r--r--Spear/GLSL/VAO.hs88
-rw-r--r--Spear/Game.hs42
-rw-r--r--Spear/Math/Camera.hs69
-rw-r--r--Spear/Math/Entity.hs31
-rw-r--r--Spear/Math/Matrix3.hs295
-rw-r--r--Spear/Math/Matrix4.hs453
-rw-r--r--Spear/Math/MatrixUtils.hs18
-rw-r--r--Spear/Math/Octree.hs282
-rw-r--r--Spear/Math/Plane.hs33
-rw-r--r--Spear/Math/Quaternion.hs0
-rw-r--r--Spear/Math/Spatial.hs84
-rw-r--r--Spear/Math/Vector3.hs217
-rw-r--r--Spear/Math/Vector4.hs200
-rw-r--r--Spear/Render/AnimatedModel.hs183
-rw-r--r--Spear/Render/Box.hs193
-rw-r--r--Spear/Render/Light.hs25
-rw-r--r--Spear/Render/Material.hs16
-rw-r--r--Spear/Render/Model.hsc61
-rw-r--r--Spear/Render/Program.hs119
-rw-r--r--Spear/Render/RenderModel.c232
-rw-r--r--Spear/Render/RenderModel.h49
-rw-r--r--Spear/Render/Renderable.hs8
-rw-r--r--Spear/Render/Sphere.hs45
-rw-r--r--Spear/Render/StaticModel.hs123
-rw-r--r--Spear/Render/Texture.hs34
-rw-r--r--Spear/Render/Triangle.hs10
-rw-r--r--Spear/Scene/Graph.hs143
-rw-r--r--Spear/Scene/Light.hs82
-rw-r--r--Spear/Scene/Loader.hs414
-rw-r--r--Spear/Scene/Scene.hs152
-rw-r--r--Spear/Scene/SceneResources.hs72
-rw-r--r--Spear/Setup.hs52
-rw-r--r--Spear/Sys/Timer.hs194
-rw-r--r--Spear/Sys/Timer.hsc175
-rw-r--r--Spear/Sys/Timer/Timer.h73
-rw-r--r--Spear/Sys/Timer/ctimer.c172
-rw-r--r--Spear/Sys/Timer/main.hs22
-rw-r--r--Spear/Updatable.hs9
77 files changed, 7835 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..914c31a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
1Copyright (c) 2012 Marc Sunet
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
index 0000000..9a994af
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
1import Distribution.Simple
2main = defaultMain
diff --git a/Spear.cabal b/Spear.cabal
new file mode 100644
index 0000000..ab8f6b9
--- /dev/null
+++ b/Spear.cabal
@@ -0,0 +1,120 @@
1name: Spear
2version: 0.1
3cabal-version: >=1.2
4build-type: Simple
5license: BSD3
6license-file: LICENSE
7maintainer: jeannekamikaze@gmail.com
8homepage: http://spear.shellblade.net
9synopsis: A 3D game framework.
10description:
11category: Game
12author: Marc Sunet
13data-dir: ""
14
15library
16 build-depends: GLFW -any, OpenGL -any, OpenGLRaw -any,
17 StateVar -any, base -any, bytestring -any, directory -any,
18 mtl -any, transformers -any, resource-simple -any, parsec >= 3.1.3, containers,
19 ansi-terminal, vector
20
21 exposed-modules:
22 Spear.App
23 Spear.App.Application
24 Spear.App.Input
25
26 Spear.Assets.Image
27 Spear.Assets.Model
28
29 Spear.Collision
30 Spear.Collision.AABB
31 Spear.Collision.Collision
32 Spear.Collision.Collisioner
33 Spear.Collision.Sphere
34 Spear.Collision.Triangle
35 Spear.Collision.Types
36
37 Spear.Game
38
39 Spear.GLSL
40 Spear.GLSL.Buffer
41 Spear.GLSL.Error
42 Spear.GLSL.Management
43 Spear.GLSL.Texture
44 Spear.GLSL.Uniform
45 Spear.GLSL.VAO
46
47 Spear.Math.Camera
48 Spear.Math.Entity
49 Spear.Math.Matrix3
50 Spear.Math.Matrix4
51 Spear.Math.MatrixUtils
52 Spear.Math.Octree
53 Spear.Math.Plane
54 Spear.Math.Spatial
55 Spear.Math.Vector3
56 Spear.Math.Vector4
57
58 Spear.Render.AnimatedModel
59 Spear.Render.Material
60 Spear.Render.Model
61 Spear.Render.Program
62 Spear.Render.Renderable
63 Spear.Render.StaticModel
64 Spear.Render.Texture
65
66 Spear.Scene.Graph
67 Spear.Scene.Light
68 Spear.Scene.Loader
69 Spear.Scene.Scene
70 Spear.Scene.SceneResources
71
72 Spear.Setup
73
74 Spear.Sys.Timer
75
76 Spear.Updatable
77 exposed: True
78
79 buildable: True
80
81 build-tools: hsc2hs -any
82
83 c-sources:
84 Spear/Assets/Image/Image.c
85 Spear/Assets/Image/BMP/BMP_load.c
86 Spear/Assets/Model/Model.c
87 Spear/Assets/Model/MD2/MD2_load.c
88 Spear/Assets/Model/OBJ/OBJ_load.cc
89 Spear/Render/RenderModel.c
90 Spear/Sys/Timer/ctimer.c
91
92 extensions: TypeFamilies
93
94 includes:
95 Spear/Assets/Image/BMP/BMP_load.h
96 Spear/Assets/Image/Image.h
97 Spear/Assets/Image/Image_error_code.h
98 Spear/Assets/Image/sys_types.h
99 Spear/Assets/Model/MD2/MD2_load.h
100 Spear/Assets/Model/OBJ/OBJ_load.h
101 Spear/Assets/Model/Model.h
102 Spear/Assets/Model/Model_error_code.h
103 Spear/Assets/Model/sys_types.h
104 Spear/Render/RenderModel.h
105 Timer/Timer.h
106
107 include-dirs:
108 Spear/Assets/Image
109 Spear/Assets/Model
110 Spear/Render
111 Spear/Sys
112
113 hs-source-dirs: .
114
115 ghc-options: -O2 -rtsopts
116
117 cc-options: -O2 -g -Wno-unused-result
118
119 extra-libraries: stdc++
120
diff --git a/Spear.lkshs b/Spear.lkshs
new file mode 100644
index 0000000..a5674ae
--- /dev/null
+++ b/Spear.lkshs
@@ -0,0 +1,18 @@
1Version of session file format:
2 1
3Time of storage:
4 "Tue Jul 31 01:00:21 CEST 2012"
5Layout: VerticalP (TerminalP {paneGroups = fromList [], paneTabs = Just TopP, currentPage = 0, detachedId = Nothing, detachedSize = Nothing}) (HorizontalP (TerminalP {paneGroups = fromList [("Browser",HorizontalP (TerminalP {paneGroups = fromList [], paneTabs = Nothing, currentPage = 0, detachedId = Nothing, detachedSize = Nothing}) (HorizontalP (TerminalP {paneGroups = fromList [], paneTabs = Nothing, currentPage = 0, detachedId = Nothing, detachedSize = Nothing}) (TerminalP {paneGroups = fromList [], paneTabs = Nothing, currentPage = 0, detachedId = Nothing, detachedSize = Nothing}) 290) 199)], paneTabs = Just BottomP, currentPage = 0, detachedId = Nothing, detachedSize = Nothing}) (TerminalP {paneGroups = fromList [], paneTabs = Nothing, currentPage = 0, detachedId = Nothing, detachedSize = Nothing}) 701) 953
6Population: [(Just (ErrorsSt ErrorsState),[SplitP RightP,SplitP TopP]),(Just (FilesSt FilesState),[SplitP RightP,SplitP TopP]),(Just (BufferSt (BufferState "/home/jeanne/programming/haskell/Spear/demos/simple-scene/GameMessage.hs" 295)),[SplitP LeftP]),(Just (BufferSt (BufferState "/home/jeanne/programming/haskell/Spear/demos/simple-scene/GameObject.hs" 5019)),[SplitP LeftP]),(Just (BufferSt (BufferState "/home/jeanne/programming/haskell/Spear/demos/simple-scene/GameState.hs" 265)),[SplitP LeftP]),(Just (InfoSt (InfoState Nothing)),[SplitP RightP,SplitP TopP,GroupP "Browser",SplitP BottomP,SplitP BottomP]),(Just (LogSt LogState),[SplitP RightP,SplitP BottomP]),(Just (BufferSt (BufferState "/home/jeanne/programming/haskell/Spear/Spear/Math/Matrix4.hs" 8726)),[SplitP LeftP]),(Just (ModulesSt (ModulesState 286 (PackageScope False,False) (Just (ModuleName ["Spear","Math","Camera"]),Nothing) (ExpanderState {packageExp = ([],[]), packageExpNoBlack = ([[0,4],[0]],[]), packageDExp = ([],[]), packageDExpNoBlack = ([],[]), workspaceExp = ([],[]), workspaceExpNoBlack = ([],[]), workspaceDExp = ([],[]), workspaceDExpNoBlack = ([],[]), systemExp = ([],[]), systemExpNoBlack = ([],[])}))),[SplitP RightP,SplitP TopP,GroupP "Browser",SplitP BottomP,SplitP TopP]),(Just (BufferSt (BufferState "/home/jeanne/programming/haskell/Spear/Spear/Math/Vector3.hs" 3534)),[SplitP LeftP]),(Just (WorkspaceSt WorkspaceState),[SplitP RightP,SplitP TopP,GroupP "Browser",SplitP TopP]),(Just (BufferSt (BufferState "/home/jeanne/programming/haskell/Spear/demos/simple-scene/main.hs" 3944)),[SplitP LeftP])]
7Window size: (1796,979)
8Completion size:
9 (750,400)
10Workspace: Just "/home/jeanne/programming/haskell/Spear/Spear.lkshw"
11Active pane: Just "GameMessage.hs"
12Toolbar visible:
13 True
14FindbarState: (False,FindState {entryStr = "", entryHist = ["asd","MouseButton"], replaceStr = "MouseProperty", replaceHist = [], caseSensitive = False, entireWord = False, wrapAround = False, regex = False, lineNr = 1})
15Recently opened files:
16 ["/home/jeanne/programming/haskell/Spear/Spear/Collision/Collision.hs","/home/jeanne/programming/haskell/Spear/Spear/Math/Camera.hs","/home/jeanne/programming/haskell/Spear/Spear/Math/Entity.hs","/home/jeanne/programming/haskell/Spear/Spear/Scene/Scene.hs","/home/jeanne/programming/haskell/Spear/Spear/Math/Spatial.hs","/home/jeanne/programming/haskell/Spear/Spear/Assets/Image/BMP/BMP_load.c","/home/jeanne/programming/haskell/Spear/Spear/Assets/Model/OBJ/OBJ_load.cc","/home/jeanne/programming/haskell/Spear/Spear/Assets/Model.hsc","/home/jeanne/programming/haskell/Spear/Spear/Assets/Model/MD2/MD2_load.c","/home/jeanne/programming/haskell/Spear/Spear/Assets/Model/Model.h","/home/jeanne/programming/haskell/Spear/demos/simple-scene/OgroAnimation.hs","/home/jeanne/programming/haskell/Spear/Spear/App/Input.hs"]
17Recently opened workspaces:
18 ["/home/jeanne/programming/haskell/Spear/Spear.lkshw","/home/jeanne/leksah.lkshw"] \ No newline at end of file
diff --git a/Spear.lkshw b/Spear.lkshw
new file mode 100644
index 0000000..47ee51d
--- /dev/null
+++ b/Spear.lkshw
@@ -0,0 +1,10 @@
1Version of workspace file format:
2 1
3Time of storage:
4 "Tue Jul 31 00:59:07 CEST 2012"
5Name of the workspace:
6 "Spear"
7File paths of contained packages:
8 ["demos/simple-scene/simple-scene.cabal","Spear.cabal"]
9Maybe file path of an active package:
10 Just "Spear.cabal" \ No newline at end of file
diff --git a/Spear/App.hs b/Spear/App.hs
new file mode 100644
index 0000000..a962414
--- /dev/null
+++ b/Spear/App.hs
@@ -0,0 +1,10 @@
1module Spear.App
2(
3 module Spear.App.Application
4, module Spear.App.Input
5)
6where
7
8
9import Spear.App.Application
10import Spear.App.Input
diff --git a/Spear/App/Application.hs b/Spear/App/Application.hs
new file mode 100644
index 0000000..49fbbc7
--- /dev/null
+++ b/Spear/App/Application.hs
@@ -0,0 +1,122 @@
1module Spear.App.Application
2(
3 -- * Data types
4 Dimensions
5, Context
6, SpearWindow
7, Update
8 -- * Setup
9, setup
10, quit
11, releaseWindow
12 -- * Main loop
13, run
14, runCapped
15)
16where
17
18
19import Spear.Game
20import Spear.Setup
21import Spear.Sys.Timer as Timer
22
23import Control.Applicative
24import Control.Monad (forever, when)
25import Control.Monad.Trans.Error
26import Control.Monad.Trans.Class (lift)
27import Graphics.UI.GLFW as GLFW
28import Graphics.Rendering.OpenGL as GL
29import System.Exit
30import Unsafe.Coerce
31
32
33-- | Window dimensions.
34type Dimensions = (Int, Int)
35
36-- | A tuple specifying the desired OpenGL context, of the form (Major, Minor).
37type Context = (Int, Int)
38
39
40-- | Represents a window.
41newtype SpearWindow = SpearWindow { rkey :: Resource }
42
43
44-- | Set up an application 'SpearWindow'.
45setup :: Dimensions -> [DisplayBits] -> WindowMode -> Context -> Setup SpearWindow
46setup (w, h) displayBits windowMode (major, minor) = do
47 glfwInit
48
49 setupIO $ do
50 openWindowHint OpenGLVersionMajor major
51 openWindowHint OpenGLVersionMinor minor
52 disableSpecial AutoPollEvent
53
54 let dimensions = GL.Size (unsafeCoerce w) (unsafeCoerce h)
55 result <- openWindow dimensions displayBits windowMode
56 windowTitle $= "Spear Game Framework"
57 GL.viewport $= (Position 0 0, Size (fromIntegral w) (fromIntegral h))
58
59 initialiseTimingSubsystem
60
61 rkey <- register quit
62 return $ SpearWindow rkey
63
64
65-- | Release the given 'SpearWindow'.
66releaseWindow :: SpearWindow -> Setup ()
67releaseWindow = release . rkey
68
69
70glfwInit :: Setup ()
71glfwInit = do
72 result <- setupIO GLFW.initialize
73 case result of
74 False -> setupError "GLFW.initialize failed"
75 True -> return ()
76
77
78-- | Close the application's window.
79quit :: IO ()
80quit = GLFW.terminate
81
82
83-- | Return true if the application should continue running, false otherwise.
84type Update s = Float -> Game s (Bool)
85
86
87-- | Run the application's main loop.
88run :: Update s -> Game s ()
89run update = do
90 timer <- gameIO $ start newTimer
91 run' timer update
92
93
94run' :: Timer -> Update s -> Game s ()
95run' timer update = do
96 timer' <- gameIO $ tick timer
97 continue <- update $ getDelta timer'
98 case continue of
99 False -> return ()
100 True -> run' timer' update
101
102
103-- | Run the application's main loop, with a limit on the frame rate.
104runCapped :: Int -> Update s -> Game s ()
105runCapped maxFPS update = do
106 let ddt = 1.0 / (fromIntegral maxFPS)
107 timer <- gameIO $ start newTimer
108 runCapped' ddt timer update
109
110
111runCapped' :: Float -> Timer -> Update s -> Game s ()
112runCapped' ddt timer update = do
113 timer' <- gameIO $ tick timer
114 continue <- update $ getDelta timer'
115 case continue of
116 False -> return ()
117 True -> do
118 t'' <- gameIO $ tick timer'
119 let dt = getDelta t''
120 when (dt < ddt) $ gameIO $ Timer.sleep (ddt - dt)
121 runCapped' ddt timer' update
122
diff --git a/Spear/App/Input.hs b/Spear/App/Input.hs
new file mode 100644
index 0000000..74ee1eb
--- /dev/null
+++ b/Spear/App/Input.hs
@@ -0,0 +1,220 @@
1module Spear.App.Input
2(
3 -- * Data types
4 Key(..)
5, MouseButton(..)
6, MouseProp(..)
7, Keyboard
8, Mouse(..)
9, Input(..)
10, DelayedMouseState
11 -- * Input state querying
12, getKeyboard
13, newMouse
14, getMouse
15, getInput
16, pollInput
17 -- * Toggled input
18, toggledMouse
19, toggledKeyboard
20 -- * Delayed input
21, delayedMouse
22)
23where
24
25
26import Data.Char (ord)
27import qualified Data.Vector.Unboxed as V
28import qualified Graphics.UI.GLFW as GLFW
29import Graphics.Rendering.OpenGL.GL.CoordTrans
30import Graphics.Rendering.OpenGL.GL.StateVar
31
32
33data Key
34 = KEY_A | KEY_B | KEY_C | KEY_D | KEY_E | KEY_F | KEY_G | KEY_H
35 | KEY_I | KEY_J | KEY_K | KEY_L | KEY_M | KEY_N | KEY_O | KEY_P
36 | KEY_Q | KEY_R | KEY_S | KEY_T | KEY_U | KEY_V | KEY_W | KEY_X
37 | KEY_Y | KEY_Z | KEY_0 | KEY_1 | KEY_2 | KEY_3 | KEY_4 | KEY_5
38 | KEY_6 | KEY_7 | KEY_8 | KEY_9 | KEY_F1 | KEY_F2 | KEY_F3
39 | KEY_F4 | KEY_F5 | KEY_F6 | KEY_F7 | KEY_F8 | KEY_F9 | KEY_F10
40 | KEY_F11 | KEY_F12 | KEY_ESC
41 deriving (Enum, Bounded)
42
43
44type Keyboard = Key -> Bool
45
46
47data MouseButton = LMB | RMB | MMB
48 deriving (Enum, Bounded)
49
50
51data MouseProp = MouseX | MouseY | MouseDX | MouseDY
52
53
54data Mouse = Mouse
55 { button :: MouseButton -> Bool
56 , property :: MouseProp -> Float
57 }
58
59
60data Input = Input
61 { keyboard :: Keyboard
62 , mouse :: Mouse
63 }
64
65
66-- | Get the keyboard.
67getKeyboard :: IO Keyboard
68getKeyboard =
69 let keyboard' :: V.Vector Bool -> Keyboard
70 keyboard' keystate key = keystate V.! fromEnum key
71 keys = fmap toEnum [0..fromEnum (maxBound :: Key)]
72 in
73 (fmap (V.fromList . fmap ((==) GLFW.Press)) . mapM GLFW.getKey . fmap toGLFWkey $ keys)
74 >>= return . keyboard'
75
76
77-- | Return a dummy mouse.
78--
79-- This function should be called to get an initial mouse.
80--
81-- The returned mouse has all keys unpressed, position set to (0,0) and 0 deta values.
82--
83-- For further mouse updates, see 'getMouse'.
84newMouse :: Mouse
85newMouse = Mouse (const False) (const 0)
86
87
88-- | Get the mouse.
89--
90-- The previous mouse state is required to compute position deltas.
91getMouse :: Mouse -> IO Mouse
92getMouse oldMouse =
93 let getButton :: V.Vector Bool -> MouseButton -> Bool
94 getButton mousestate button = mousestate V.! fromEnum button
95
96 prop' :: Float -> Float -> MouseProp -> Float
97 prop' xpos _ MouseX = xpos
98 prop' _ ypos MouseY = ypos
99 prop' xpos _ MouseDX = xpos - property oldMouse MouseX
100 prop' _ ypos MouseDY = ypos - property oldMouse MouseY
101
102 buttons = fmap toEnum [0..fromEnum (maxBound :: MouseButton)]
103 getKeystate = (fmap (V.fromList . fmap ((==) GLFW.Press)) .
104 mapM GLFW.getMouseButton .
105 fmap toGLFWbutton $ buttons)
106 in do
107 Position xpos ypos <- get GLFW.mousePos
108 keystate <- getKeystate
109 return $ Mouse (getButton keystate) (prop' (fromIntegral xpos) (fromIntegral ypos))
110
111
112-- | Get input devices.
113getInput :: Mouse -> IO Input
114getInput oldMouse = do
115 keyboard <- getKeyboard
116 mouse <- getMouse oldMouse
117 return $ Input keyboard mouse
118
119
120-- | Poll input devices.
121pollInput :: IO ()
122pollInput = GLFW.pollEvents
123
124
125-- | Return a mouse that reacts to button toggles.
126toggledMouse :: Mouse -- ^ Previous mouse state.
127 -> Mouse -- ^ Current mouse state.
128 -> Mouse -- ^ Toggled mouse.
129
130toggledMouse prev cur = cur { button = \bt -> button cur bt && not (button prev bt) }
131
132
133-- | Return a keyboard that reacts to key toggles.
134toggledKeyboard :: Keyboard -- ^ Previous keyboard state.
135 -> Keyboard -- ^ Current keyboard state.
136 -> Keyboard -- ^ Toggled keyboard.
137
138toggledKeyboard prev cur key = cur key && not (prev key)
139
140
141
142
143-- | Accumulated delays for each mouse button.
144type DelayedMouseState = MouseButton -> Float
145
146
147delayedMouse :: (MouseButton -> Float) -- ^ Delay configuration for each button.
148 -> Mouse -- ^ Current mouse state.
149 -> Float -- ^ Time elapsed since last udpate.
150 -> DelayedMouseState
151 -> (Mouse, DelayedMouseState)
152
153delayedMouse delay mouse dt dms =
154 let
155 accum x = dms x + dt
156 active x = accum x >= delay x
157 button' x = active x && button mouse x
158 accum' x = if button' x then 0 else accum x
159 in
160 (mouse { button = button' }, accum')
161
162
163
164
165toGLFWkey :: Key -> Int
166toGLFWkey KEY_A = ord 'A'
167toGLFWkey KEY_B = ord 'B'
168toGLFWkey KEY_C = ord 'C'
169toGLFWkey KEY_D = ord 'D'
170toGLFWkey KEY_E = ord 'E'
171toGLFWkey KEY_F = ord 'F'
172toGLFWkey KEY_G = ord 'G'
173toGLFWkey KEY_H = ord 'H'
174toGLFWkey KEY_I = ord 'I'
175toGLFWkey KEY_J = ord 'J'
176toGLFWkey KEY_K = ord 'K'
177toGLFWkey KEY_L = ord 'L'
178toGLFWkey KEY_M = ord 'M'
179toGLFWkey KEY_N = ord 'N'
180toGLFWkey KEY_O = ord 'O'
181toGLFWkey KEY_P = ord 'P'
182toGLFWkey KEY_Q = ord 'Q'
183toGLFWkey KEY_R = ord 'R'
184toGLFWkey KEY_S = ord 'S'
185toGLFWkey KEY_T = ord 'T'
186toGLFWkey KEY_U = ord 'U'
187toGLFWkey KEY_V = ord 'V'
188toGLFWkey KEY_W = ord 'W'
189toGLFWkey KEY_X = ord 'X'
190toGLFWkey KEY_Y = ord 'Y'
191toGLFWkey KEY_Z = ord 'Z'
192toGLFWkey KEY_0 = ord '0'
193toGLFWkey KEY_1 = ord '1'
194toGLFWkey KEY_2 = ord '2'
195toGLFWkey KEY_3 = ord '3'
196toGLFWkey KEY_4 = ord '4'
197toGLFWkey KEY_5 = ord '5'
198toGLFWkey KEY_6 = ord '6'
199toGLFWkey KEY_7 = ord '7'
200toGLFWkey KEY_8 = ord '8'
201toGLFWkey KEY_9 = ord '9'
202toGLFWkey KEY_F1 = fromEnum GLFW.F1
203toGLFWkey KEY_F2 = fromEnum GLFW.F2
204toGLFWkey KEY_F3 = fromEnum GLFW.F3
205toGLFWkey KEY_F4 = fromEnum GLFW.F4
206toGLFWkey KEY_F5 = fromEnum GLFW.F5
207toGLFWkey KEY_F6 = fromEnum GLFW.F6
208toGLFWkey KEY_F7 = fromEnum GLFW.F7
209toGLFWkey KEY_F8 = fromEnum GLFW.F8
210toGLFWkey KEY_F9 = fromEnum GLFW.F9
211toGLFWkey KEY_F10 = fromEnum GLFW.F10
212toGLFWkey KEY_F11 = fromEnum GLFW.F11
213toGLFWkey KEY_F12 = fromEnum GLFW.F12
214toGLFWkey KEY_ESC = fromEnum GLFW.ESC
215
216
217toGLFWbutton :: MouseButton -> GLFW.MouseButton
218toGLFWbutton LMB = GLFW.ButtonLeft
219toGLFWbutton RMB = GLFW.ButtonRight
220toGLFWbutton MMB = GLFW.ButtonMiddle
diff --git a/Spear/Assets/Image.hsc b/Spear/Assets/Image.hsc
new file mode 100644
index 0000000..2b5c482
--- /dev/null
+++ b/Spear/Assets/Image.hsc
@@ -0,0 +1,144 @@
1{-# LANGUAGE CPP, ForeignFunctionInterface #-}
2
3module Spear.Assets.Image
4(
5 -- * Data types
6 Image
7 -- * Loading and unloading
8, loadImage
9, releaseImage
10 -- * Accessors
11, width
12, height
13, bpp
14, pixels
15)
16where
17
18
19import Spear.Setup
20import Foreign.Ptr
21import Foreign.Storable
22import Foreign.C.Types
23import Foreign.C.String
24import Foreign.Marshal.Utils as Foreign (with)
25import Foreign.Marshal.Alloc (alloca)
26import Data.List (splitAt, elemIndex)
27import Data.Char (toLower)
28
29
30#include "Image.h"
31#include "BMP/BMP_load.h"
32
33
34data ImageErrorCode
35 = ImageSuccess
36 | ImageReadError
37 | ImageMemoryAllocationError
38 | ImageFileNotFound
39 | ImageInvalidFormat
40 | ImageNoSuitableLoader
41 deriving (Eq, Enum, Show)
42
43
44data CImage = CImage
45 { cwidth :: CInt
46 , cheight :: CInt
47 , cbpp :: CInt
48 , cpixels :: Ptr CUChar
49 }
50
51
52instance Storable CImage where
53 sizeOf _ = #{size Image}
54 alignment _ = alignment (undefined :: CInt)
55
56 peek ptr = do
57 width <- #{peek Image, width} ptr
58 height <- #{peek Image, height} ptr
59 bpp <- #{peek Image, bpp} ptr
60 pixels <- #{peek Image, pixels} ptr
61 return $ CImage width height bpp pixels
62
63 poke ptr (CImage width height bpp pixels) = do
64 #{poke Image, width} ptr width
65 #{poke Image, height} ptr height
66 #{poke Image, bpp} ptr bpp
67 #{poke Image, pixels} ptr pixels
68
69
70-- | Represents an image 'Resource'.
71data Image = Image
72 { imageData :: CImage
73 , rkey :: Resource
74 }
75
76
77foreign import ccall "Image.h image_free"
78 image_free :: Ptr CImage -> IO ()
79
80
81foreign import ccall "BMP_load.h BMP_load"
82 bmp_load' :: Ptr CChar -> Ptr CImage -> IO Int
83
84
85bmp_load :: Ptr CChar -> Ptr CImage -> IO ImageErrorCode
86bmp_load file image = bmp_load' file image >>= \code -> return . toEnum $ code
87
88
89-- | Load the image specified by the given file.
90loadImage :: FilePath -> Setup Image
91loadImage file = do
92 dotPos <- case elemIndex '.' file of
93 Nothing -> setupError $ "file name has no extension: " ++ file
94 Just p -> return p
95
96 let ext = map toLower . tail . snd $ splitAt dotPos file
97
98 result <- setupIO . alloca $ \ptr -> do
99 status <- withCString file $ \fileCstr -> do
100 case ext of
101 "bmp" -> bmp_load fileCstr ptr
102 _ -> return ImageNoSuitableLoader
103
104 case status of
105 ImageSuccess -> peek ptr >>= return . Right
106 ImageReadError -> return . Left $ "read error"
107 ImageMemoryAllocationError -> return . Left $ "memory allocation error"
108 ImageFileNotFound -> return . Left $ "file not found"
109 ImageInvalidFormat -> return . Left $ "invalid format"
110 ImageNoSuitableLoader -> return . Left $ "no suitable loader for extension " ++ ext
111
112 case result of
113 Right image -> register (freeImage image) >>= return . Image image
114 Left err -> setupError $ "loadImage: " ++ err
115
116
117-- | Release the given 'Image'.
118releaseImage :: Image -> Setup ()
119releaseImage = release . rkey
120
121
122-- | Free the given 'CImage'.
123freeImage :: CImage -> IO ()
124freeImage image = Foreign.with image image_free
125
126
127-- | Return the given image's width.
128width :: Image -> Int
129width = fromIntegral . cwidth . imageData
130
131
132-- | Return the given image's height.
133height :: Image -> Int
134height = fromIntegral . cheight . imageData
135
136
137-- | Return the given image's bits per pixel.
138bpp :: Image -> Int
139bpp = fromIntegral . cbpp . imageData
140
141
142-- | Return the given image's pixels.
143pixels :: Image -> Ptr CUChar
144pixels = cpixels . imageData
diff --git a/Spear/Assets/Image/BMP/BMP_load.c b/Spear/Assets/Image/BMP/BMP_load.c
new file mode 100644
index 0000000..5c1b195
--- /dev/null
+++ b/Spear/Assets/Image/BMP/BMP_load.c
@@ -0,0 +1,257 @@
1#include "BMP_load.h"
2#include <stdio.h>
3#include <string.h>
4#include <stdlib.h>
5#include <math.h>
6
7#define BITMAP_ID 0x4D42
8
9
10/// Bitmap file header structure.
11typedef struct
12{
13 U16 type; // Specifies the image type; must be BM (0x4D42).
14 U32 size; // Specifies the size in bytes of the bitmap file.
15 U32 reserved; // Reserved; must be zero.
16 U32 offBits; // Specifies the offset, in bytes, from the BitmapFileHeader structure to the bitmap bits.
17}
18BitmapFileHeader;
19
20
21/// Bitmap info header structure.
22typedef struct
23{
24 U32 size; // Specifies the number of bytes required by the structure.
25 U32 width; // Specifies the width of the bitmap, in pixels.
26 U32 height; // Specifies the height of the bitmap, in pixels.
27 U16 planes; // Specifies the number of color planes; must be 1.
28 U16 bitCount; // Specifies the number of bits per pixel; must be 1, 4, 16, 24, or 32.
29 U32 compression; // Specifies the type of compression.
30 U32 imageSize; // Specifies the size of the image in bytes.
31 U32 xPelsPerMeter; // Specifies the number of pixels per meter on the x axis.
32 U32 yPelsPerMeter; // Specifies the number of pixels per meter on the y axis.
33 U32 clrUsed; // Specifies the number of colours used by the bitmap.
34 U32 clrImportant; // Specifies the number of colours that are important.
35}
36BitmapInfoHeader;
37
38
39static void safe_free (void* ptr)
40{
41 if (ptr) free (ptr);
42}
43
44
45static Image_error_code read_raw_data(
46 FILE* filePtr, const BitmapFileHeader* bitmapFileHeader,
47 const BitmapInfoHeader* bitmapInfoHeader, U8** data)
48{
49 U8* bitmapImage;
50 U8* auxrow;
51 size_t row_size = bitmapInfoHeader->width * 3;
52 size_t numBytes = bitmapInfoHeader->height * row_size;
53 size_t bytes_read;
54
55 // Allocate memory for the bitmap data and the auxiliary row.
56 bitmapImage = (U8*) malloc (numBytes);
57 auxrow = (U8*) malloc (row_size);
58 if (!bitmapImage || !auxrow)
59 {
60 safe_free (bitmapImage);
61 safe_free (auxrow);
62 return Image_Memory_Allocation_Error;
63 }
64
65 // Move the file pointer to the beginning of bitmap data and read the data.
66 fseek(filePtr, bitmapFileHeader->offBits, SEEK_SET);
67 bytes_read = fread(bitmapImage, 1, numBytes, filePtr);
68 if (bytes_read != numBytes)
69 {
70 free(bitmapImage);
71 return Image_Read_Error;
72 }
73
74 size_t i;
75
76 // Reverse rows.
77 /*size_t h = bitmapInfoHeader->height / 2;
78 for (i = 0; i < h; ++i)
79 {
80 U8* row1 = bitmapImage + i * row_size;
81 U8* row2 = bitmapImage + (bitmapInfoHeader->height - i - 1) * row_size;
82
83 memcpy (auxrow, row1, row_size);
84 memcpy (row1, row2, row_size);
85 memcpy (row2, auxrow, row_size);
86 }*/
87
88 // Swap B and R channels. BGR -> RGB.
89 for (i = 0; i < bytes_read; i+= 3)
90 {
91 U8 tmp = bitmapImage[i];
92 bitmapImage[i] = bitmapImage[i+2];
93 bitmapImage[i+2] = tmp;
94 }
95
96 *data = bitmapImage;
97
98 return Image_Success;
99}
100
101
102static Image_error_code read_paletised_data8
103 (FILE* filePtr, const BitmapInfoHeader *bitmapInfoHeader, U8** data)
104{
105 U8* bitmapImage;
106 U8* palette;
107 U8* colourIndices;
108 size_t bytes_read;
109
110 size_t paletteSize = pow(2, bitmapInfoHeader->bitCount) * 4;
111 size_t colourIndicesSize = bitmapInfoHeader->width * bitmapInfoHeader->height;
112 int bitmapImageSize = colourIndicesSize * 3;
113
114 // Save memory for the palette, colour indices and bitmap image.
115 palette = (U8*) malloc (paletteSize);
116 colourIndices = (U8*) malloc(colourIndicesSize);
117 bitmapImage = (U8*) malloc(bitmapImageSize);
118 if (!palette | !colourIndices || !bitmapImage)
119 {
120 safe_free (palette);
121 safe_free (colourIndices);
122 safe_free (bitmapImage);
123 return Image_Memory_Allocation_Error;
124 }
125
126 // Read the colour palette.
127 bytes_read = fread(palette, 1, paletteSize, filePtr);
128 if (bytes_read != paletteSize) return Image_Read_Error;
129
130 // Read the colour indices.
131 bytes_read = fread(colourIndices, 1, colourIndicesSize, filePtr);
132 if (bytes_read != colourIndicesSize) return Image_Read_Error;
133
134 // Decode the image data.
135 U8* imgptr = &bitmapImage[bitmapImageSize - (bitmapInfoHeader->width * 4)];
136
137 size_t i;
138 for (i = 0; i < colourIndicesSize; i++)
139 {
140 int index = colourIndices[i];
141
142 memcpy(imgptr, (const void*) &palette[index * 4], 3);
143 imgptr += 3;
144
145 if (!((i+1) % bitmapInfoHeader->width))
146 {
147 imgptr -= (bitmapInfoHeader->width * 4 * 2);
148 }
149 }
150
151 free(palette);
152 free(colourIndices);
153
154 *data = bitmapImage;
155
156 return Image_Success;
157}
158
159
160Image_error_code BMP_load (const char* filename, Image* image)
161{
162 FILE* filePtr;
163 BitmapFileHeader bitmapFileHeader;
164 BitmapInfoHeader bitmapInfoHeader;
165 U8 buf[40];
166 U8* data;
167
168 // Open the file in binary read-only mode.
169 filePtr = fopen(filename, "rb");
170 if (!filePtr) return Image_File_Not_Found;
171
172 if ((fread(buf, 14, 1, filePtr)) != 1)
173 {
174 fclose(filePtr);
175 return Image_Read_Error;
176 }
177
178 bitmapFileHeader.type = *((U16*)buf);
179 bitmapFileHeader.size = *((U32*)(buf+2));
180 bitmapFileHeader.reserved = *((U32*)(buf+6));
181 bitmapFileHeader.offBits = *((U32*)(buf+10));
182
183 // Check that this is in fact a BMP file.
184 if (bitmapFileHeader.type != BITMAP_ID)
185 {
186 fprintf(stderr, "Not a valid BMP file\n");
187 fclose(filePtr);
188 return Image_Invalid_Format;
189 }
190
191 if ((fread(&buf, 40, 1, filePtr)) != 1)
192 {
193 fclose(filePtr);
194 return Image_Read_Error;
195 }
196
197 bitmapInfoHeader.size = *((U32*)(buf));
198 bitmapInfoHeader.width = *((U32*)(buf+4));
199 bitmapInfoHeader.height = *((U32*)(buf+8));
200 bitmapInfoHeader.planes = *((U16*)(buf+12));
201 bitmapInfoHeader.bitCount = *((U16*)(buf+14));
202 bitmapInfoHeader.compression = *((U32*)(buf+16));
203 bitmapInfoHeader.imageSize = *((U32*)(buf+20));
204 bitmapInfoHeader.xPelsPerMeter = *((U32*)(buf+24));
205 bitmapInfoHeader.yPelsPerMeter = *((U32*)(buf+28));
206 bitmapInfoHeader.clrUsed = *((U32*)(buf+32));
207 bitmapInfoHeader.clrImportant = *((U32*)(buf+36));
208
209 // Check that no compression is used.
210 // Compression is not supported at the moment.
211 if (bitmapInfoHeader.compression != 0)
212 {
213 fprintf(stderr, "Compression not supported\n");
214 fclose(filePtr);
215 return Image_Invalid_Format;
216 }
217
218 // Check that this is a Windows BMP file.
219 // Other formats are not supported.
220 if (bitmapInfoHeader.size != 40)
221 {
222 fprintf(stderr, "Only Windows BMP files supported\n");
223 fclose(filePtr);
224 return Image_Invalid_Format;
225 }
226
227 Image_error_code status;
228
229 if (bitmapInfoHeader.bitCount == 8)
230 {
231 // The BMP file uses a colour palette.
232 // We are already positioned at the colour palette.
233 status = read_paletised_data8 (filePtr, &bitmapInfoHeader, &data);
234 }
235 else if (bitmapInfoHeader.bitCount >= 16)
236 {
237 // The BMP file uses no colour palette.
238 status = read_raw_data (filePtr, &bitmapFileHeader, &bitmapInfoHeader, &data);
239 }
240 else
241 {
242 fprintf(stderr, "Only 24-bit and 16-bit palette images supported\n");
243 fclose(filePtr);
244 return Image_Invalid_Format;
245 }
246
247 fclose(filePtr);
248
249 if (data == 0) return status;
250
251 image->width = bitmapInfoHeader.width;
252 image->height = bitmapInfoHeader.height;
253 image->bpp = 3;
254 image->pixels = data;
255
256 return Image_Success;
257}
diff --git a/Spear/Assets/Image/BMP/BMP_load.h b/Spear/Assets/Image/BMP/BMP_load.h
new file mode 100644
index 0000000..f4ad32f
--- /dev/null
+++ b/Spear/Assets/Image/BMP/BMP_load.h
@@ -0,0 +1,23 @@
1#ifndef _BMP_LOAD_H
2#define _BMP_LOAD_H
3
4
5#include "../Image.h"
6#include "../Image_error_code.h"
7
8
9#ifdef __cplusplus
10extern "C" {
11#endif
12
13
14/// Loads the BMP file specified by the given string.
15/// (0,0) corresponds to the top left corner of the image.
16Image_error_code BMP_load (const char* filename, Image* image);
17
18
19#ifdef __cplusplus
20}
21#endif
22
23#endif // _BMP_LOAD_H
diff --git a/Spear/Assets/Image/Image.c b/Spear/Assets/Image/Image.c
new file mode 100644
index 0000000..9abebe2
--- /dev/null
+++ b/Spear/Assets/Image/Image.c
@@ -0,0 +1,8 @@
1#include "Image.h"
2#include <stdlib.h>
3
4
5void image_free (Image* image)
6{
7 free (image->pixels);
8}
diff --git a/Spear/Assets/Image/Image.h b/Spear/Assets/Image/Image.h
new file mode 100644
index 0000000..bffdd97
--- /dev/null
+++ b/Spear/Assets/Image/Image.h
@@ -0,0 +1,32 @@
1#ifndef _SPEAR_IMAGE_H
2#define _SPEAR_IMAGE_H
3
4#include "sys_types.h"
5
6
7typedef struct
8{
9 int width;
10 int height;
11 int bpp; // Bits per pixel.
12 // If bpp = 3 then format = RGB.
13 // If bpp = 4 then format = RGBA.
14 U8* pixels;
15}
16Image;
17
18
19#ifdef __cplusplus
20extern "C" {
21#endif
22
23/// Frees the given Image from memory.
24/// The 'image' pointer itself is not freed.
25void image_free (Image* image);
26
27#ifdef __cplusplus
28}
29#endif
30
31
32#endif // _SPEAR_IMAGE_H
diff --git a/Spear/Assets/Image/Image_error_code.h b/Spear/Assets/Image/Image_error_code.h
new file mode 100644
index 0000000..9e78aeb
--- /dev/null
+++ b/Spear/Assets/Image/Image_error_code.h
@@ -0,0 +1,15 @@
1#ifndef _SPEAR_IMAGE_ERROR_CODE_H
2#define _SPEAR_IMAGE_ERROR_CODE_H
3
4typedef enum
5{
6 Image_Success,
7 Image_Read_Error,
8 Image_Memory_Allocation_Error,
9 Image_File_Not_Found,
10 Image_Invalid_Format,
11 Image_No_Suitable_Loader,
12}
13Image_error_code;
14
15#endif // _SPEAR_IMAGE_ERROR_CODE_H
diff --git a/Spear/Assets/Image/sys_types.h b/Spear/Assets/Image/sys_types.h
new file mode 100644
index 0000000..e4eb251
--- /dev/null
+++ b/Spear/Assets/Image/sys_types.h
@@ -0,0 +1,16 @@
1#ifndef _SPEAR_SYS_TYPES_H
2#define _SPEAR_SYS_TYPES_H
3
4#include <stdint.h>
5
6typedef int8_t I8;
7typedef int16_t I16;
8typedef int32_t I32;
9typedef int64_t I64;
10typedef uint8_t U8;
11typedef uint16_t U16;
12typedef uint32_t U32;
13typedef uint64_t U64;
14
15#endif // _SPEAR_SYS_TYPES_H
16
diff --git a/Spear/Assets/Model.hsc b/Spear/Assets/Model.hsc
new file mode 100644
index 0000000..e8eff0f
--- /dev/null
+++ b/Spear/Assets/Model.hsc
@@ -0,0 +1,334 @@
1{-# LANGUAGE CPP, ForeignFunctionInterface #-}
2
3module Spear.Assets.Model
4(
5 -- * Data types
6 ModelErrorCode
7, Vec3
8, TexCoord
9, CModel(..)
10, Animation(..)
11, Model
12 -- * Loading and unloading
13, loadModel
14, releaseModel
15 -- * Accessors
16, animated
17, vertices
18, normals
19, texCoords
20, triangles
21, skins
22, numFrames
23, numVertices
24, numTriangles
25, numTexCoords
26, numSkins
27, cmodel
28, animation
29, animationByName
30, numAnimations
31 -- * Manipulation
32, transform
33)
34where
35
36
37import Spear.Setup
38import qualified Spear.Math.Matrix4 as M4
39import qualified Spear.Math.Matrix3 as M3
40import Spear.Math.MatrixUtils
41
42import qualified Data.ByteString.Char8 as B
43import Data.Char (toLower)
44import Data.List (splitAt, elemIndex)
45import qualified Data.Vector as V
46import Foreign.Ptr
47import Foreign.Storable
48import Foreign.C.Types
49import Foreign.C.String
50import Foreign.Marshal.Utils as Foreign (with)
51import Foreign.Marshal.Alloc (alloca, allocaBytes)
52import Foreign.Marshal.Array (copyArray, peekArray)
53import Unsafe.Coerce (unsafeCoerce)
54
55
56#include "Model.h"
57#include "MD2/MD2_load.h"
58#include "OBJ/OBJ_load.h"
59
60
61data ModelErrorCode
62 = ModelSuccess
63 | ModelReadError
64 | ModelMemoryAllocationError
65 | ModelFileNotFound
66 | ModelFileMismatch
67 | ModelNoSuitableLoader
68 deriving (Eq, Enum, Show)
69
70
71data Vec3 = Vec3 !CFloat !CFloat !CFloat
72
73data TexCoord = TexCoord !CFloat !CFloat
74
75data Triangle = Triangle !CUShort !CUShort !CUShort !CUShort !CUShort !CUShort
76
77data Skin = Skin !(Ptr Char)
78
79data CAnimation = CAnimation !B.ByteString !CUInt !CUInt
80
81
82-- | The model's underlying representation.
83data CModel = CModel
84 { cVerts :: Ptr Vec3 -- ^ Pointer to an array of 'cnFrames' * 'cnVerts' vertices.
85 , cNormals :: Ptr Vec3 -- ^ Pointer to an array of 'cnFrames' * cnVerts normals.
86 , cTexCoords :: Ptr TexCoord -- ^ Pointer to an array of 'cnTris' texture coordinates.
87 , cTris :: Ptr Triangle -- ^ Pointer to an array of 'cnTris' triangles.
88 , cSkins :: Ptr Skin -- ^ Pointer to an array of 'cnSkins' skins.
89 , cAnimations :: Ptr CAnimation -- ^ Pointer to an array of 'cnAnimations' animations.
90 , cnFrames :: CUInt -- ^ Number of frames.
91 , cnVerts :: CUInt -- ^ Number of vertices per frame.
92 , cnTris :: CUInt -- ^ Number of triangles in one frame.
93 , cnTexCoords :: CUInt -- ^ Number of texture coordinates in one frame.
94 , cnSkins :: CUInt -- ^ Number of skins.
95 , cnAnimations :: CUInt -- ^ Number of animations.
96 }
97
98
99instance Storable CModel where
100 sizeOf _ = #{size Model}
101 alignment _ = alignment (undefined :: CUInt)
102
103 peek ptr = do
104 vertices <- #{peek Model, vertices} ptr
105 normals <- #{peek Model, normals} ptr
106 texCoords <- #{peek Model, texCoords} ptr
107 triangles <- #{peek Model, triangles} ptr
108 skins <- #{peek Model, skins} ptr
109 animations <- #{peek Model, animations} ptr
110 numFrames <- #{peek Model, numFrames} ptr
111 numVertices <- #{peek Model, numVertices} ptr
112 numTriangles <- #{peek Model, numTriangles} ptr
113 numTexCoords <- #{peek Model, numTexCoords} ptr
114 numSkins <- #{peek Model, numSkins} ptr
115 numAnimations <- #{peek Model, numAnimations} ptr
116 return $
117 CModel vertices normals texCoords triangles skins animations
118 numFrames numVertices numTriangles numTexCoords numSkins numAnimations
119
120 poke ptr
121 (CModel verts normals texCoords tris skins animations
122 numFrames numVerts numTris numTex numSkins numAnimations) = do
123 #{poke Model, vertices} ptr verts
124 #{poke Model, normals} ptr normals
125 #{poke Model, texCoords} ptr texCoords
126 #{poke Model, triangles} ptr tris
127 #{poke Model, skins} ptr skins
128 #{poke Model, animations} ptr animations
129 #{poke Model, numFrames} ptr numFrames
130 #{poke Model, numVertices} ptr numVerts
131 #{poke Model, numTriangles} ptr numTris
132 #{poke Model, numTexCoords} ptr numTex
133 #{poke Model, numSkins} ptr numSkins
134 #{poke Model, numAnimations} ptr numAnimations
135
136
137-- data CAnimation = CAnimation !(Ptr CChar) !CUInt !CUInt
138instance Storable CAnimation where
139 sizeOf _ = #{size animation}
140 alignment _ = alignment (undefined :: CUInt)
141
142 peek ptr = do
143 name <- B.packCString (unsafeCoerce ptr)
144 start <- #{peek animation, start} ptr
145 end <- #{peek animation, end} ptr
146 return $ CAnimation name start end
147
148 poke ptr (CAnimation name start end) = do
149 B.useAsCStringLen name $ \(sptr, len) -> copyArray (unsafeCoerce ptr) sptr len
150 #{poke animation, start} ptr start
151 #{poke animation, end} ptr end
152
153
154data Animation = Animation
155 { name :: String
156 , start :: Int
157 , end :: Int
158 }
159
160
161-- | A model 'Resource'.
162data Model = Model
163 { modelData :: CModel
164 , mAnimations :: V.Vector Animation
165 , rkey :: Resource
166 }
167
168
169foreign import ccall "Model.h model_free"
170 model_free :: Ptr CModel -> IO ()
171
172
173foreign import ccall "MD2_load.h MD2_load"
174 md2_load' :: Ptr CChar -> CChar -> CChar -> Ptr CModel -> IO Int
175
176
177foreign import ccall "OBJ_load.h OBJ_load"
178 obj_load' :: Ptr CChar -> CChar -> CChar -> Ptr CModel -> IO Int
179
180
181md2_load :: Ptr CChar -> CChar -> CChar -> Ptr CModel -> IO ModelErrorCode
182md2_load file clockwise leftHanded model =
183 md2_load' file clockwise leftHanded model >>= \code -> return . toEnum $ code
184
185
186obj_load :: Ptr CChar -> CChar -> CChar -> Ptr CModel -> IO ModelErrorCode
187obj_load file clockwise leftHanded model =
188 obj_load' file clockwise leftHanded model >>= \code -> return . toEnum $ code
189
190
191-- | Load the model specified by the given 'FilePath'.
192loadModel :: FilePath -> Setup Model
193loadModel file = do
194 dotPos <- case elemIndex '.' file of
195 Nothing -> setupError $ "file name has no extension: " ++ file
196 Just p -> return p
197
198 let ext = map toLower . tail . snd $ splitAt dotPos file
199
200 result <- setupIO . alloca $ \ptr -> do
201 status <- withCString file $ \fileCstr -> do
202 case ext of
203 "md2" -> md2_load fileCstr 0 0 ptr
204 "obj" -> obj_load fileCstr 0 0 ptr
205 _ -> return ModelNoSuitableLoader
206
207 case status of
208 ModelSuccess -> peek ptr >>= return . Right
209 ModelReadError -> return . Left $ "read error"
210 ModelMemoryAllocationError -> return . Left $ "memory allocation error"
211 ModelFileNotFound -> return . Left $ "file not found"
212 ModelFileMismatch -> return . Left $ "file mismatch"
213 ModelNoSuitableLoader -> return . Left $ "no suitable loader for extension " ++ ext
214
215 case result of
216 Right model ->
217 let numAnimations = fromIntegral $ cnAnimations model
218 in register (freeModel model) >>=
219 case numAnimations of
220 0 -> return . Model model V.empty
221 _ -> \key -> setupIO $ do
222 canims <- peekArray numAnimations $ cAnimations model
223 let animations = V.fromList $ fmap fromCAnimation canims
224 return $ Model model animations key
225
226 Left err -> setupError $ "loadModel: " ++ err
227
228
229fromCAnimation :: CAnimation -> Animation
230fromCAnimation (CAnimation cname start end) =
231 Animation (B.unpack cname) (fromIntegral start) (fromIntegral end)
232
233
234-- | Release the given 'Model'.
235releaseModel :: Model -> Setup ()
236releaseModel = release . rkey
237
238
239-- | Free the given 'CModel'.
240freeModel :: CModel -> IO ()
241freeModel model = Foreign.with model model_free
242
243
244-- | Return 'True' if the given 'Model' is animated, 'False' otherwise.
245animated :: Model -> Bool
246animated = (>1) . numFrames
247
248
249-- | Return the given 'Model's vertices.
250vertices :: Model -> Ptr Vec3
251vertices = cVerts . modelData
252
253
254-- | Return the given 'Model's normals.
255normals :: Model -> Ptr Vec3
256normals = cNormals . modelData
257
258
259-- | Return the given 'Model's texCoords.
260texCoords :: Model -> Ptr TexCoord
261texCoords = cTexCoords . modelData
262
263
264-- | Return the given 'Model's triangles.
265triangles :: Model -> Ptr Triangle
266triangles = cTris . modelData
267
268
269-- | Return the given 'Model's skins.
270skins :: Model -> Ptr Skin
271skins = cSkins . modelData
272
273
274-- | Return the given 'Model's number of frames.
275numFrames :: Model -> Int
276numFrames = fromIntegral . cnFrames . modelData
277
278
279-- | Return the given 'Model's number of vertices.
280numVertices :: Model -> Int
281numVertices = fromIntegral . cnVerts . modelData
282
283
284-- | Return the given 'Model's number of triangles.
285numTriangles :: Model -> Int
286numTriangles = fromIntegral . cnTris . modelData
287
288
289-- | Return the given 'Model's number of texture coordinates.
290numTexCoords :: Model -> Int
291numTexCoords = fromIntegral . cnTexCoords . modelData
292
293
294-- | Return the given 'Model's number of skins.
295numSkins :: Model -> Int
296numSkins = fromIntegral . cnSkins . modelData
297
298
299-- | Return the underlying 'CModel'.
300cmodel :: Model -> CModel
301cmodel = modelData
302
303
304-- | Return the model's ith animation.
305animation :: Model -> Int -> Animation
306animation model i = mAnimations model V.! i
307
308
309-- | Return the animation specified by the given string.
310animationByName :: Model -> String -> Maybe Animation
311animationByName model anim = V.find ((==) anim . name) $ mAnimations model
312
313
314-- | Return the number of animations in the given 'Model'.
315numAnimations :: Model -> Int
316numAnimations = V.length . mAnimations
317
318
319-- | Transform the given 'Model's vertices with the given matrix.
320transform :: M4.Matrix4 -> Model -> IO ()
321transform mat (Model model _ _) =
322 allocaBytes (16*sizeFloat) $ \matPtr ->
323 allocaBytes (9*sizeFloat) $ \normalPtr ->
324 with model $ \modelPtr -> do
325 poke matPtr mat
326 poke normalPtr $ fastNormalMatrix mat
327 model_transform modelPtr matPtr normalPtr
328
329
330foreign import ccall "Model.h model_transform"
331 model_transform :: Ptr CModel -> Ptr M4.Matrix4 -> Ptr M3.Matrix3 -> IO ()
332
333
334sizeFloat = #{size float}
diff --git a/Spear/Assets/Model/MD2/MD2_load.c b/Spear/Assets/Model/MD2/MD2_load.c
new file mode 100644
index 0000000..238bc9a
--- /dev/null
+++ b/Spear/Assets/Model/MD2/MD2_load.c
@@ -0,0 +1,483 @@
1#include "MD2_load.h"
2#include <stdio.h>
3#include <string.h>
4#include <stdlib.h> // malloc
5#include <math.h> // sqrt
6
7//! The MD2 magic number used to identify MD2 files.
8#define MD2_ID 0x32504449
9
10//! Limit values for the MD2 file format.
11#define MD2_MAX_TRIANGLES 4096
12#define MD2_MAX_VERTICES 2048
13#define MD2_MAX_TEXCOORDS 2048
14#define MD2_MAX_FRAMES 512
15#define MD2_MAX_SKINS 32
16
17
18/// MD2 file header.
19typedef struct
20{
21 I32 magic; /// The magic number "IDP2"; 844121161 in decimal; 0x32504449
22 I32 version; /// Version number, always 8.
23 I32 skinWidth; /// Width of the skin(s) in pixels.
24 I32 skinHeight; /// Height of the skin(s) in pixels.
25 I32 frameSize; /// Size of a single frame in bytes.
26 I32 numSkins; /// Number of skins.
27 I32 numVertices; /// Number of vertices in a single frame.
28 I32 numTexCoords; /// Number of texture coordinates.
29 I32 numTriangles; /// Number of triangles.
30 I32 numGlCommands; /// Number of dwords in the Gl command list.
31 I32 numFrames; /// Number of frames.
32 I32 offsetSkins; /// Offset from the start of the file to the array of skins.
33 I32 offsetTexCoords; /// Offset from the start of the file to the array of texture coordinates.
34 I32 offsetTriangles; /// Offset from the start of the file to the array of triangles.
35 I32 offsetFrames; /// Offset from the start of the file to the array of frames.
36 I32 offsetGlCommands; /// Offset from the start of the file to the array of Gl commands.
37 I32 offsetEnd; /// Offset from the start of the file to the end of the file (the file size).
38}
39md2Header_t;
40
41
42/// Represents a texture coordinate index.
43typedef struct
44{
45 I16 s;
46 I16 t;
47}
48texCoord_t;
49
50
51/// Represents a frame point.
52typedef struct
53{
54 U8 x, y, z;
55 U8 lightNormalIndex;
56}
57vertex_t;
58
59
60/// Represents a single frame.
61typedef struct
62{
63 vec3 scale;
64 vec3 translate;
65 I8 name[16];
66 vertex_t vertices[1];
67}
68frame_t;
69
70
71static void normalise (vec3* v)
72{
73 float x = v->x;
74 float y = v->y;
75 float z = v->z;
76 float mag = sqrt (x*x + y*y + z*z);
77 mag = mag == 0 ? 1 : mag;
78 v->x = x / mag;
79 v->y = y / mag;
80 v->z = z / mag;
81}
82
83
84// Left handed cross product.
85// a x b = c.
86// (0,1,0) x (1,0,0) = (0,0,-1).
87static void cross (const vec3* a, const vec3* b, vec3* c)
88{
89 c->x = a->y * b->z - a->z * b->y;
90 c->y = a->z * b->x - a->x * b->z;
91 c->z = a->x * b->y - a->y * b->x;
92}
93
94
95static void vec3_sub (const vec3* a, const vec3* b, vec3* out)
96{
97 out->x = a->x - b->x;
98 out->y = a->y - b->y;
99 out->z = a->z - b->z;
100}
101
102
103static void normal (char clockwise, const vec3* p1, const vec3* p2, const vec3* p3, vec3* n)
104{
105 vec3 v1, v2;
106 if (clockwise)
107 {
108 vec3_sub (p3, p2, &v1);
109 vec3_sub (p1, p2, &v2);
110 }
111 else
112 {
113 vec3_sub (p1, p2, &v1);
114 vec3_sub (p3, p2, &v2);
115 }
116 cross (&v1, &v2, n);
117 normalise (n);
118}
119
120
121typedef struct
122{
123 vec3* normals;
124 vec3* base;
125 unsigned int N;
126}
127normal_map;
128
129
130static void normal_map_initialise (normal_map* m, unsigned int N)
131{
132 m->N = N;
133}
134
135
136static void normal_map_clear (normal_map* m, vec3* normals, vec3* base)
137{
138 memset (normals, 0, m->N * sizeof(vec3));
139 m->normals = normals;
140 m->base = base;
141}
142
143
144static void normal_map_insert (normal_map* m, vec3* vec, vec3 normal)
145{
146 unsigned int i = vec - m->base;
147 vec3* n = m->normals + i;
148 n->x += normal.x;
149 n->y += normal.y;
150 n->z += normal.z;
151}
152
153
154static void compute_normals (normal_map* m, char left_handed)
155{
156 vec3* n = m->normals;
157 unsigned int i;
158 for (i = 0; i < m->N; ++i)
159 {
160 if (!left_handed)
161 {
162 n->x = -n->x;
163 n->y = -n->y;
164 n->z = -n->z;
165 }
166 normalise (n);
167 n++;
168 }
169}
170
171
172static void safe_free (void* ptr)
173{
174 if (ptr) free (ptr);
175}
176
177
178static char frame_equal (const char* name1, const char* name2)
179{
180 char equal = 1;
181 int i;
182
183 if (((name1 == 0) && (name2 != 0)) || ((name1 != 0) && (name2 == 0)))
184 {
185 return 0;
186 }
187
188 for (i = 0; i < 16; ++i)
189 {
190 char c1 = *name1;
191 char c2 = *name2;
192 if ((c1 >= '0' && c1 <= '9') || (c2 >= '0' && c2 <= '9')) break;
193 if (c1 != c2)
194 {
195 equal = 0;
196 break;
197 }
198 if (c1 == '_' || c2 == '_') break;
199 name1++;
200 name2++;
201 }
202 return equal;
203}
204
205
206static void animation_remove_numbers (char* name)
207{
208 int i;
209 for (i = 0; i < 16; ++i)
210 {
211 char c = *name;
212 if (c == 0) break;
213 if (c >= '0' && c <= '9') *name = 0;
214 name++;
215 }
216}
217
218
219Model_error_code MD2_load (const char* filename, char clockwise, char left_handed, Model* model)
220{
221 FILE* filePtr;
222 vec3* vertices;
223 vec3* normals;
224 texCoord* texCoords;
225 triangle* triangles;
226 skin* skins;
227 animation* animations;
228 int i;
229
230 // Open the file for reading.
231 filePtr = fopen(filename, "rb");
232 if (!filePtr) return Model_File_Not_Found;
233
234 // Make sure it is an MD2 file.
235 int magic;
236 if ((fread(&magic, 4, 1, filePtr)) != 1)
237 {
238 fclose(filePtr);
239 return Model_Read_Error;
240 }
241
242 if (magic != MD2_ID) return Model_File_Mismatch;
243
244 // Find out the file size.
245 long int fileSize;
246 fseek(filePtr, 0, SEEK_END);
247 fileSize = ftell(filePtr);
248 fseek(filePtr, 0, SEEK_SET);
249
250 // Allocate a chunk of data to store the file in.
251 char *buffer = (char*) malloc(fileSize);
252 if (!buffer)
253 {
254 fclose(filePtr);
255 return Model_Memory_Allocation_Error;
256 }
257
258 // Read the entire file into memory.
259 if ((fread(buffer, 1, fileSize, filePtr)) != (unsigned int)fileSize)
260 {
261 fclose(filePtr);
262 free(buffer);
263 return Model_Read_Error;
264 }
265
266 // File stream is no longer needed.
267 fclose(filePtr);
268
269 // Set a pointer to the header for parsing.
270 md2Header_t* header = (md2Header_t*) buffer;
271
272 // Compute the number of animations.
273 unsigned numAnimations = 1;
274 int currentFrame;
275 const char* name = 0;
276 for (currentFrame = 0; currentFrame < header->numFrames; ++currentFrame)
277 {
278 frame_t* frame = (frame_t*) &buffer[header->offsetFrames + currentFrame * header->frameSize];
279 if (name == 0)
280 {
281 name = frame->name;
282 }
283 else if (!frame_equal(name, frame->name))
284 {
285 numAnimations++;
286 name = frame->name;
287 }
288 }
289
290 // Allocate memory for arrays.
291 vertices = (vec3*) malloc(sizeof(vec3) * header->numVertices * header->numFrames);
292 normals = (vec3*) malloc(sizeof(vec3) * header->numVertices * header->numFrames);
293 texCoords = (texCoord*) malloc(sizeof(texCoord) * header->numTexCoords);
294 triangles = (triangle*) malloc(sizeof(triangle) * header->numTriangles);
295 skins = (skin*) malloc(sizeof(skin) * header->numSkins);
296 animations = (animation*) malloc (numAnimations * sizeof(animation));
297
298 if (!vertices || !normals || !texCoords || !triangles || !skins || !animations)
299 {
300 safe_free (animations);
301 safe_free (skins);
302 safe_free (triangles);
303 safe_free (texCoords);
304 safe_free (normals);
305 safe_free (vertices);
306 free (buffer);
307 return Model_Memory_Allocation_Error;
308 }
309
310 // Load the model's vertices.
311 // Loop through each frame, grab the vertices that make it up, transform them back
312 // to their real coordinates and store them in the model's vertex array.
313 for (currentFrame = 0; currentFrame < header->numFrames; ++currentFrame)
314 {
315 // Set a frame pointer to the current frame.
316 frame_t* frame = (frame_t*) &buffer[header->offsetFrames + currentFrame * header->frameSize];
317
318 // Set a vertex pointer to the model's vertex array, at the appropiate position.
319 vec3* vert = &vertices[currentFrame * header->numVertices];
320
321 // Now parse those vertices and transform them back.
322 int currentVertex;
323 for (currentVertex = 0; currentVertex != header->numVertices; ++currentVertex)
324 {
325 vert[currentVertex].x = frame->vertices[currentVertex].x * frame->scale.x + frame->translate.x;
326 vert[currentVertex].y = frame->vertices[currentVertex].y * frame->scale.y + frame->translate.y;
327 vert[currentVertex].z = frame->vertices[currentVertex].z * frame->scale.z + frame->translate.z;
328 }
329 }
330
331 // Load the model's triangles.
332
333 // Set a pointer to the triangles array in the buffer.
334 triangle* t = (triangle*) &buffer[header->offsetTriangles];
335
336 if (clockwise)
337 {
338 for (i = 0; i < header->numTriangles; ++i)
339 {
340 triangles[i].vertexIndices[0] = t[i].vertexIndices[0];
341 triangles[i].vertexIndices[1] = t[i].vertexIndices[1];
342 triangles[i].vertexIndices[2] = t[i].vertexIndices[2];
343
344 triangles[i].textureIndices[0] = t[i].textureIndices[0];
345 triangles[i].textureIndices[1] = t[i].textureIndices[1];
346 triangles[i].textureIndices[2] = t[i].textureIndices[2];
347 }
348 }
349 else
350 {
351 for (i = 0; i < header->numTriangles; ++i)
352 {
353 triangles[i].vertexIndices[0] = t[i].vertexIndices[0];
354 triangles[i].vertexIndices[1] = t[i].vertexIndices[2];
355 triangles[i].vertexIndices[2] = t[i].vertexIndices[1];
356
357 triangles[i].textureIndices[0] = t[i].textureIndices[0];
358 triangles[i].textureIndices[1] = t[i].textureIndices[2];
359 triangles[i].textureIndices[2] = t[i].textureIndices[1];
360 }
361 }
362
363 // Load the texture coordinates.
364 float sw = (float) header->skinWidth;
365 float sh = (float) header->skinHeight;
366 texCoord_t* texc = (texCoord_t*) &buffer[header->offsetTexCoords];
367 for (i = 0; i < header->numTexCoords; ++i)
368 {
369 texCoords[i].s = (float)texc->s / sw;
370 texCoords[i].t = 1.0f - (float)texc->t / sh;
371 texc++;
372 }
373
374 // Iterate over every frame and compute normals for every triangle.
375 vec3 n;
376
377 normal_map map;
378 normal_map_initialise (&map, header->numVertices);
379
380 for (currentFrame = 0; currentFrame < header->numFrames; ++currentFrame)
381 {
382 // Set a pointer to the triangle array.
383 triangle* t = triangles;
384
385 // Set a pointer to the vertex array at the appropiate position.
386 vec3* vertex_array = vertices + header->numVertices * currentFrame;
387
388 // Set a pointer to the normals array at the appropiate position.
389 vec3* normals_ptr = normals + header->numVertices * currentFrame;
390
391 normal_map_clear (&map, normals_ptr, vertex_array);
392
393 for (i = 0; i < header->numTriangles; ++i)
394 {
395 // Compute face normal.
396 vec3* v0 = &vertex_array[t->vertexIndices[0]];
397 vec3* v1 = &vertex_array[t->vertexIndices[1]];
398 vec3* v2 = &vertex_array[t->vertexIndices[2]];
399 normal (clockwise, v0, v1, v2, &n);
400
401 // Add face normal to each of the face's vertices.
402 normal_map_insert (&map, v0, n);
403 normal_map_insert (&map, v1, n);
404 normal_map_insert (&map, v2, n);
405
406 t++;
407 }
408
409 compute_normals (&map, left_handed);
410 }
411
412 // Load the model's skins.
413 const skin* s = (const skin*) &buffer[header->offsetSkins];
414 for (i = 0; i < header->numSkins; ++i)
415 {
416 memcpy (skins[i].name, s->name, 64);
417 s++;
418 }
419
420 // Load the model's animations.
421 unsigned start = 0;
422 name = 0;
423 animation* currentAnimation = animations;
424 for (currentFrame = 0; currentFrame < header->numFrames; ++currentFrame)
425 {
426 frame_t* frame = (frame_t*) &buffer[header->offsetFrames + currentFrame * header->frameSize];
427 if (name == 0)
428 {
429 name = frame->name;
430 }
431 else if (!frame_equal(name, frame->name))
432 {
433 memcpy (currentAnimation->name, name, 16);
434 animation_remove_numbers (currentAnimation->name);
435 currentAnimation->start = start;
436 currentAnimation->end = currentFrame-1;
437 if (currentAnimation != animations)
438 {
439 animation* prev = currentAnimation;
440 prev--;
441 prev->end = start-1;
442 }
443 name = frame->name;
444 currentAnimation++;
445 start = currentFrame;
446 }
447 }
448 currentAnimation = animations + numAnimations - 1;
449 memcpy (currentAnimation->name, name, 16);
450 animation_remove_numbers (currentAnimation->name);
451 currentAnimation->start = start;
452 currentAnimation->end = header->numFrames-1;
453
454 printf ("finished loading model %s\n", filename);
455 printf ("numAnimations: %u\n", numAnimations);
456 printf ("animations: %p\n", animations);
457
458 currentAnimation = animations;
459 for (i = 0; i < numAnimations; ++i)
460 {
461 printf ("Animation %d, name: %s, start: %d, end %d\n",
462 i, currentAnimation->name, currentAnimation->start, currentAnimation->end);
463 currentAnimation++;
464 }
465
466 model->vertices = vertices;
467 model->normals = normals;
468 model->texCoords = texCoords;
469 model->triangles = triangles;
470 model->skins = skins;
471 model->animations = animations;
472
473 model->numFrames = header->numFrames;
474 model->numVertices = header->numVertices;
475 model->numTriangles = header->numTriangles;
476 model->numTexCoords = header->numTexCoords;
477 model->numSkins = header->numSkins;
478 model->numAnimations = numAnimations;
479
480 free(buffer);
481
482 return Model_Success;
483}
diff --git a/Spear/Assets/Model/MD2/MD2_load.h b/Spear/Assets/Model/MD2/MD2_load.h
new file mode 100644
index 0000000..75e1b26
--- /dev/null
+++ b/Spear/Assets/Model/MD2/MD2_load.h
@@ -0,0 +1,23 @@
1#ifndef _MD2_LOAD_H
2#define _MD2_LOAD_H
3
4#include "../Model.h"
5#include "../Model_error_code.h"
6
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12/// Loads the MD2 file specified by the given string.
13/// 'clockwise' should be 1 if you plan to render the model in a clockwise environment, 0 otherwise.
14/// 'smooth_normals' should be 1 if you want the loader to compute smooth normals, 0 otherwise.
15Model_error_code MD2_load (const char* filename, char clockwise, char left_handed, Model* model);
16
17#ifdef __cplusplus
18}
19#endif
20
21
22#endif // _MD2_LOAD_H
23
diff --git a/Spear/Assets/Model/Model.c b/Spear/Assets/Model/Model.c
new file mode 100644
index 0000000..94959e9
--- /dev/null
+++ b/Spear/Assets/Model/Model.c
@@ -0,0 +1,73 @@
1#include "Model.h"
2#include <stdlib.h> // free
3#include <math.h>
4
5
6#define TO_RAD M_PI / 180.0
7
8
9static void safe_free (void* ptr)
10{
11 if (ptr)
12 {
13 free (ptr);
14 ptr = 0;
15 }
16}
17
18
19void model_free (Model* model)
20{
21 safe_free (model->vertices);
22 safe_free (model->normals);
23 safe_free (model->texCoords);
24 safe_free (model->triangles);
25 safe_free (model->skins);
26}
27
28
29static void mul (float m[16], vec3* v)
30{
31 float x = v->x;
32 float y = v->y;
33 float z = v->z;
34 v->x = x*m[0] + y*m[4] + z*m[8] + m[12];
35 v->y = x*m[1] + y*m[5] + z*m[9] + m[13];
36 v->z = x*m[2] + y*m[6] + z*m[10] + m[14];
37}
38
39
40static void mul_normal (float m[9], vec3* n)
41{
42 float x = n->x;
43 float y = n->y;
44 float z = n->z;
45 n->x = x*m[0] + y*m[3] + z*m[6];
46 n->y = x*m[1] + y*m[4] + z*m[7];
47 n->z = x*m[2] + y*m[5] + z*m[8];
48 x = n->x;
49 y = n->y;
50 z = n->z;
51 float mag = sqrt(x*x + y*y + z*z);
52 mag = mag == 0.0 ? 1.0 : mag;
53 n->x /= mag;
54 n->y /= mag;
55 n->z /= mag;
56}
57
58
59void model_transform (Model* model, float mat[16], float normal[9])
60{
61 unsigned i = 0;
62 unsigned j = model->numVertices * model->numFrames;
63 vec3* v = model->vertices;
64 vec3* n = model->normals;
65
66 for (; i < j; ++i)
67 {
68 mul (mat, v);
69 mul_normal (normal, n);
70 v++;
71 n++;
72 }
73}
diff --git a/Spear/Assets/Model/Model.h b/Spear/Assets/Model/Model.h
new file mode 100644
index 0000000..f23377a
--- /dev/null
+++ b/Spear/Assets/Model/Model.h
@@ -0,0 +1,79 @@
1#ifndef _SPEAR_MODEL_H
2#define _SPEAR_MODEL_H
3
4#include "sys_types.h"
5
6
7typedef struct
8{
9 char name[64];
10}
11skin;
12
13
14typedef struct
15{
16 float x, y, z;
17}
18vec3;
19
20
21typedef struct
22{
23 float s, t;
24}
25texCoord;
26
27
28typedef struct
29{
30 U16 vertexIndices[3];
31 U16 textureIndices[3];
32}
33triangle;
34
35
36typedef struct
37{
38 char name[16];
39 unsigned int start;
40 unsigned int end;
41}
42animation;
43
44
45typedef struct
46{
47 vec3* vertices; // One array per frame.
48 vec3* normals; // One array per frame. One normal per vertex per frame.
49 texCoord* texCoords; // One array for all frames.
50 triangle* triangles; // One array for all frames.
51 skin* skins; // Holds the model's texture files.
52 animation* animations; // Holds the model's animations.
53
54 unsigned int numFrames;
55 unsigned int numVertices; // Number of vertices per frame.
56 unsigned int numTriangles; // Number of triangles in one frame.
57 unsigned int numTexCoords; // Number of texture coordinates in one frame.
58 unsigned int numSkins;
59 unsigned int numAnimations;
60}
61Model;
62
63
64#ifdef __cplusplus
65extern "C" {
66#endif
67
68/// Frees the given Model from memory.
69/// The 'model' pointer itself is not freed.
70void model_free (Model* model);
71
72/// Transform the given Model's vertices by the given matrix.
73void model_transform (Model* model, float mat[16], float normal[9]);
74
75#ifdef __cplusplus
76}
77#endif
78
79#endif // _SPEAR_MODEL_H
diff --git a/Spear/Assets/Model/Model_error_code.h b/Spear/Assets/Model/Model_error_code.h
new file mode 100644
index 0000000..a94a31b
--- /dev/null
+++ b/Spear/Assets/Model/Model_error_code.h
@@ -0,0 +1,16 @@
1#ifndef _SPEAR_MODEL_ERROR_CODE_H
2#define _SPEAR_MODEL_ERROR_CODE_H
3
4typedef enum
5{
6 Model_Success,
7 Model_Read_Error,
8 Model_Memory_Allocation_Error,
9 Model_File_Not_Found,
10 Model_File_Mismatch,
11 Model_No_Suitable_Loader,
12}
13Model_error_code;
14
15#endif // _SPEAR_MODEL_ERROR_CODE_H
16
diff --git a/Spear/Assets/Model/OBJ/Makefile b/Spear/Assets/Model/OBJ/Makefile
new file mode 100644
index 0000000..6f9556f
--- /dev/null
+++ b/Spear/Assets/Model/OBJ/Makefile
@@ -0,0 +1,10 @@
1all: OBJ_load.h OBJ_load.cc test.cc ../Model.c
2 g++ -g -c OBJ_load.cc
3 g++ -g -c test.cc
4 g++ -g -c ../Model.c -o Model.o
5 g++ -o test *.o
6
7clean:
8 @rm -f test
9 @rm -f *.o
10
diff --git a/Spear/Assets/Model/OBJ/OBJ_load.cc b/Spear/Assets/Model/OBJ/OBJ_load.cc
new file mode 100644
index 0000000..bf409b1
--- /dev/null
+++ b/Spear/Assets/Model/OBJ/OBJ_load.cc
@@ -0,0 +1,273 @@
1#include "OBJ_load.h"
2#include <cstdio>
3#include <cstdlib> // free
4#include <cstring> // memcpy
5#include <cmath> // sqrt
6#include <vector>
7
8
9char lastError [128];
10
11
12static void safe_free (void* ptr)
13{
14 if (ptr)
15 {
16 free (ptr);
17 ptr = 0;
18 }
19}
20
21
22// Cross product.
23// (0,1,0) x (1,0,0) = (0,0,-1).
24static void cross (const vec3& a, const vec3& b, vec3& c)
25{
26 c.x = a.y * b.z - a.z * b.y;
27 c.y = a.z * b.x - a.x * b.z;
28 c.z = a.x * b.y - a.y * b.x;
29}
30
31
32static void vec3_sub (const vec3& a, const vec3& b, vec3& out)
33{
34 out.x = a.x - b.x;
35 out.y = a.y - b.y;
36 out.z = a.z - b.z;
37}
38
39
40static void compute_normal (char clockwise, const vec3& p1, const vec3& p2, const vec3& p3, vec3& n)
41{
42 vec3 v1, v2;
43 if (clockwise)
44 {
45 vec3_sub (p3, p2, v1);
46 vec3_sub (p1, p2, v2);
47 }
48 else
49 {
50 vec3_sub (p1, p2, v1);
51 vec3_sub (p3, p2, v2);
52 }
53 cross (v1, v2, n);
54}
55
56
57static void normalise (vec3& v)
58{
59 float x = v.x;
60 float y = v.y;
61 float z = v.z;
62 float mag = sqrt (x*x + y*y + z*z);
63 mag = mag == 0.0f ? 1.0f : mag;
64 v.x /= mag;
65 v.y /= mag;
66 v.z /= mag;
67}
68
69
70static void vec3_add (const vec3& a, vec3& b)
71{
72 b.x += a.x;
73 b.y += a.y;
74 b.z += a.z;
75}
76
77
78static void read_vertex (FILE* file, vec3& vert)
79{
80 fscanf (file, "%f %f", &vert.x, &vert.y);
81 if (fscanf(file, "%f", &vert.z) == 0) vert.z = 0.0f;
82}
83
84
85static void read_normal (FILE* file, vec3& normal)
86{
87 fscanf (file, "%f %f %f", &normal.x, &normal.y, &normal.z);
88}
89
90
91static void read_tex_coord (FILE* file, texCoord& texc)
92{
93 fscanf (file, "%f %f", &texc.s, &texc.t);
94}
95
96
97static void read_face (FILE* file, char clockwise,
98 const std::vector<vec3>& vertices,
99 std::vector<vec3>& normals,
100 std::vector<triangle>& triangles)
101{
102 std::vector<unsigned int> idxs;
103 std::vector<unsigned int> texCoords;
104
105 unsigned int index;
106 unsigned int normal;
107 unsigned int texc;
108
109 fscanf (file, "f");
110
111 while (!feof(file) && fscanf(file, "%d", &index) > 0)
112 {
113 idxs.push_back(index);
114
115 if (fgetc (file) == '/')
116 {
117 fscanf (file, "%d", &texc);
118 texCoords.push_back(texc);
119 }
120 else fseek (file, -1, SEEK_CUR);
121
122 if (fgetc (file) == '/')
123 {
124 fscanf (file, "%d", &normal);
125 }
126 else fseek (file, -1, SEEK_CUR);
127 }
128
129 // Triangulate the face and add its triangles to the triangles vector.
130 triangle tri;
131 tri.vertexIndices[0] = idxs[0] - 1;
132 tri.textureIndices[0] = texCoords[0] - 1;
133
134 for (int i = 1; i < idxs.size()-1; i++)
135 {
136 tri.vertexIndices[1] = idxs[i] - 1;
137 tri.textureIndices[1] = texCoords[i] - 1;
138 tri.vertexIndices[2] = idxs[i+1] - 1;
139 tri.textureIndices[2] = texCoords[i+1] - 1;
140 triangles.push_back(tri);
141 }
142
143 // Compute face normal and add contribution to each of the face's vertices.
144 unsigned int i0 = tri.vertexIndices[0];
145 unsigned int i1 = tri.vertexIndices[1];
146 unsigned int i2 = tri.vertexIndices[2];
147
148 vec3 n;
149 compute_normal (clockwise, vertices[i0], vertices[i1], vertices[i2], n);
150
151 for (int i = 0; i < idxs.size(); i++)
152 {
153 vec3_add (n, normals[idxs[i]-1]);
154 }
155}
156
157
158Model_error_code OBJ_load (const char* filename, char clockwise, char left_handed, Model* model)
159{
160 vec3* norms = 0;
161 vec3* verts = 0;
162 texCoord* texcs = 0;
163 triangle* tris = 0;
164 FILE* file = 0;
165
166 try
167 {
168 file = fopen (filename, "r");
169
170 vec3 vert;
171 vec3 normal;
172 texCoord texc;
173
174 std::vector<vec3> vertices;
175 std::vector<vec3> normals;
176 std::vector<texCoord> texCoords;
177 std::vector<triangle> triangles;
178
179 while (!feof(file))
180 {
181 switch (fgetc(file))
182 {
183 case 'v':
184 switch (fgetc(file))
185 {
186 case 't':
187 read_tex_coord (file, texc);
188 texCoords.push_back(texc);
189 break;
190
191 case 'n':
192 read_normal (file, normal);
193 break;
194
195 default:
196 read_vertex (file, vert);
197 vertices.push_back(vert);
198 break;
199 }
200 break;
201
202 case 'f':
203 // If the normals vector has no size, initialise it.
204 if (normals.size() == 0)
205 {
206 vec3 zero;
207 zero.x = 0.0f; zero.y = 0.0f; zero.z = 0.0f;
208 normals = std::vector<vec3>(vertices.size(), zero);
209 }
210 read_face (file, clockwise, vertices, normals, triangles);
211 break;
212
213 case '#':
214 {
215 int x = 17;
216 while (x != '\n' && x != EOF) x = fgetc(file);
217 break;
218 }
219
220 default: break;
221 }
222 }
223
224 fclose (file);
225
226 unsigned int numVertices = vertices.size();
227 unsigned int numTexCoords = texCoords.size();
228 unsigned int numTriangles = triangles.size();
229
230 verts = new vec3 [numVertices];
231 norms = new vec3 [numVertices];
232 texcs = new texCoord [numTexCoords];
233 tris = new triangle [numTriangles];
234
235 memcpy (verts, &vertices[0], numVertices * sizeof(vec3));
236 memcpy (norms, &normals[0], numVertices * sizeof(vec3));
237 memcpy (texcs, &texCoords[0], numTexCoords * sizeof(texCoord));
238 memcpy (tris, &triangles[0], numTriangles * sizeof(triangle));
239
240 // Copy normals if the model file specified them.
241
242
243
244 // Otherwise normalise the normals that have been previously computed.
245
246 for (size_t i = 0; i < numVertices; ++i)
247 {
248 normalise(norms[i]);
249 }
250
251 model->vertices = verts;
252 model->normals = norms;
253 model->texCoords = texcs;
254 model->triangles = tris;
255 model->skins = 0;
256 model->animations = 0;
257 model->numFrames = 1;
258 model->numVertices = numVertices;
259 model->numTriangles = numTriangles;
260 model->numTexCoords = numTexCoords;
261 model->numSkins = 0;
262 model->numAnimations = 0;
263
264 return Model_Success;
265 }
266 catch (std::bad_alloc)
267 {
268 safe_free (verts);
269 safe_free (texcs);
270 safe_free (tris);
271 return Model_Memory_Allocation_Error;
272 }
273}
diff --git a/Spear/Assets/Model/OBJ/OBJ_load.h b/Spear/Assets/Model/OBJ/OBJ_load.h
new file mode 100644
index 0000000..f1de6c7
--- /dev/null
+++ b/Spear/Assets/Model/OBJ/OBJ_load.h
@@ -0,0 +1,25 @@
1#ifndef _OBJ_LOAD_H
2#define _OBJ_LOAD_H
3
4#include "../Model.h"
5#include "../Model_error_code.h"
6
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12/// Loads the OBJ file specified by the given string.
13/// 'clockwise' should be 1 if you plan to render the model in a clockwise environment, 0 otherwise.
14/// 'smooth_normals' should be 1 if you want the loader to compute smooth normals, 0 otherwise.
15Model_error_code OBJ_load (const char* filename, char clockwise, char left_handed, Model* model);
16
17/// Gets the last error generated by the OBJ loader.
18char* get_last_error ();
19
20#ifdef __cplusplus
21}
22#endif
23
24
25#endif // _OBJ_LOAD_H
diff --git a/Spear/Assets/Model/OBJ/test.cc b/Spear/Assets/Model/OBJ/test.cc
new file mode 100644
index 0000000..31e0e39
--- /dev/null
+++ b/Spear/Assets/Model/OBJ/test.cc
@@ -0,0 +1,47 @@
1#include "OBJ_load.h"
2#include <cstdio>
3
4
5int main (void)
6{
7 Model model;
8 OBJ_load ("/home/jeanne/assets/box.obj", 1, 1, &model);
9
10 printf("Vertices:\n");
11
12 for (size_t i = 0; i < model.numVertices; ++i)
13 {
14 vec3 v = model.vertices[i];
15 printf ("%f, %f, %f\n", v.x, v.y, v.z);
16 }
17
18 printf("\nNormals:\n");
19
20 for (size_t i = 0; i < model.numVertices; ++i)
21 {
22 vec3 n = model.normals[i];
23 printf ("%f, %f, %f\n", n.x, n.y, n.z);
24 }
25
26 printf("\nTex coords:\n");
27
28 for (size_t i = 0; i < model.numTexCoords; ++i)
29 {
30 texCoord tex = model.texCoords[i];
31 printf("%f, %f\n", tex.s, tex.t);
32 }
33
34 printf("\nTriangles:\n");
35
36 for (size_t i = 0; i < model.numTriangles; ++i)
37 {
38 triangle t = model.triangles[i];
39 printf ("%d, %d, %d - %d, %d, %d\n",
40 t.vertexIndices[0]+1, t.vertexIndices[1]+1, t.vertexIndices[2]+1,
41 t.textureIndices[0]+1, t.textureIndices[1]+1, t.textureIndices[2]+1);
42 }
43
44 model_free (&model);
45
46 return 0;
47}
diff --git a/Spear/Assets/Model/sys_types.h b/Spear/Assets/Model/sys_types.h
new file mode 100644
index 0000000..e4eb251
--- /dev/null
+++ b/Spear/Assets/Model/sys_types.h
@@ -0,0 +1,16 @@
1#ifndef _SPEAR_SYS_TYPES_H
2#define _SPEAR_SYS_TYPES_H
3
4#include <stdint.h>
5
6typedef int8_t I8;
7typedef int16_t I16;
8typedef int32_t I32;
9typedef int64_t I64;
10typedef uint8_t U8;
11typedef uint16_t U16;
12typedef uint32_t U32;
13typedef uint64_t U64;
14
15#endif // _SPEAR_SYS_TYPES_H
16
diff --git a/Spear/Collision.hs b/Spear/Collision.hs
new file mode 100644
index 0000000..d2de02d
--- /dev/null
+++ b/Spear/Collision.hs
@@ -0,0 +1,19 @@
1module Spear.Collision
2(
3 module Spear.Collision.AABB
4, module Spear.Collision.Collision
5, module Spear.Collision.Sphere
6, module Spear.Collision.Triangle
7, module Spear.Collision.Types
8)
9where
10
11
12import Spear.Collision.AABB hiding (contains)
13import Spear.Collision.Collision
14import Spear.Collision.Sphere hiding (contains)
15import Spear.Collision.Triangle
16import Spear.Collision.Types
17
18import qualified Spear.Collision.AABB as AABB (contains)
19import qualified Spear.Collision.Sphere as Sphere (contains)
diff --git a/Spear/Collision/AABB.hs b/Spear/Collision/AABB.hs
new file mode 100644
index 0000000..2676af0
--- /dev/null
+++ b/Spear/Collision/AABB.hs
@@ -0,0 +1,32 @@
1module Spear.Collision.AABB
2(
3 AABB(..)
4, aabb
5, contains
6)
7where
8
9
10import Spear.Math.Vector3 as Vector
11
12
13-- | An axis-aligned bounding box.
14data AABB = AABB
15 { min :: !Vector3
16 , max :: !Vector3
17 }
18 deriving Eq
19
20
21-- | Create a 'AABB' from the given points.
22aabb :: [Vector3] -> AABB
23
24aabb [] = error "Attempting to build a BoundingVolume from an empty list!"
25
26aabb (x:xs) = foldr update (AABB x x) xs
27 where update p (AABB min max) = AABB (Vector.min p min) (Vector.max p max)
28
29
30-- | Return 'True' if the given 'AABB' contains the given point, 'False' otherwise.
31contains :: AABB -> Vector3 -> Bool
32(AABB min max) `contains` v = v >= min && v <= max
diff --git a/Spear/Collision/Collision.hs b/Spear/Collision/Collision.hs
new file mode 100644
index 0000000..50be0d7
--- /dev/null
+++ b/Spear/Collision/Collision.hs
@@ -0,0 +1,119 @@
1module Spear.Collision.Collision
2(
3 Collisionable(..)
4, collidePlane
5, aabbFromSphere
6)
7where
8
9
10import Spear.Collision.AABB as AABB
11import Spear.Collision.Sphere as Sphere
12import Spear.Collision.Types
13import Spear.Math.Plane
14import Spear.Math.Vector3
15
16
17class Collisionable a where
18 collideBox :: AABB -> a -> CollisionType
19 collideSphere :: Sphere -> a -> CollisionType
20 collidePlane :: Plane -> a -> CollisionType
21
22
23instance Collisionable AABB where
24
25 collideBox box1@(AABB min1 max1) box2@(AABB min2 max2)
26 | box1 == box2 = Equal
27 | min1 > max2 = NoCollision
28 | max1 < min2 = NoCollision
29 | box1 `AABB.contains` min2 && box1 `AABB.contains` max2 = FullyContains
30 | box2 `AABB.contains` min1 && box2 `AABB.contains` max1 = FullyContainedBy
31 | (x max1) < (x min2) = NoCollision
32 | (x min1) > (x max2) = NoCollision
33 | (y max1) < (y min2) = NoCollision
34 | (y min1) > (y max2) = NoCollision
35 | (z max1) < (z min2) = NoCollision
36 | (z min1) > (z max2) = NoCollision
37 | otherwise = Collision
38
39 collideSphere sphere@(Sphere c r) aabb@(AABB min max)
40 | test == FullyContains || test == FullyContainedBy = test
41 | normSq (c - boxC) > (l + r)^2 = NoCollision
42 | otherwise = Collision
43 where
44 test = aabb `collideBox` aabbFromSphere sphere
45 boxC = min + (max-min)/2
46 l = norm $ min + (vec3 (x boxC) (y min) (z min)) - min
47
48 collidePlane pl aabb@(AABB {})
49 | sameSide tests = NoCollision
50 | otherwise = Collision
51 where
52 tests = fmap (classify pl) $ aabbPoints aabb
53 sameSide (x:xs) = all (==x) xs
54
55
56instance Collisionable Sphere where
57
58 collideBox box sphere = case collideSphere sphere box of
59 FullyContains -> FullyContainedBy
60 FullyContainedBy -> FullyContains
61 x -> x
62
63 collideSphere s1@(Sphere c1 r1) s2@(Sphere c2 r2)
64 | s1 == s2 = Equal
65 | distance_centers <= sub_radii = if (r1 > r2) then FullyContains else FullyContainedBy
66 | distance_centers <= sum_radii = Collision
67 | otherwise = NoCollision
68 where
69 distance_centers = normSq $ c1 - c2
70 sum_radii = (r1 + r2)^2
71 sub_radii = (r1 - r2)^2
72
73 collidePlane pl s = NoCollision
74
75
76aabbPoints :: AABB -> [Vector3]
77aabbPoints (AABB min max) = [p1,p2,p3,p4,p5,p6,p7,p8]
78 where
79 p1 = vec3 (x min) (y min) (z min)
80 p2 = vec3 (x min) (y min) (z max)
81 p3 = vec3 (x min) (y max) (z min)
82 p4 = vec3 (x min) (y max) (z max)
83 p5 = vec3 (x max) (y min) (z min)
84 p6 = vec3 (x max) (y min) (z max)
85 p7 = vec3 (x max) (y max) (z min)
86 p8 = vec3 (x max) (y max) (z max)
87
88
89-- | Create the minimal AABB fully containing the specified Sphere.
90aabbFromSphere :: Sphere -> AABB
91aabbFromSphere (Sphere c r) = AABB bot top
92 where
93 bot = c - (vec3 r r r)
94 top = c + (vec3 r r r)
95
96
97-- | Create the minimal AABB fully containing the specified 'BoundingVolume's.
98{-aabb :: [BoundingVolume] -> BoundingVolume
99aabb = Spear.Collision.BoundingVolume.fromList BoundingBox . foldr generate []
100 where
101 generate (AABB min max) acc = p1:p2:p3:p4:p5:p6:p7:p8:acc
102 where
103 p1 = vec3 (x min) (y min) (z min)
104 p2 = vec3 (x min) (y min) (z max)
105 p3 = vec3 (x min) (y max) (z min)
106 p4 = vec3 (x min) (y max) (z max)
107 p5 = vec3 (x max) (y min) (z min)
108 p6 = vec3 (x max) (y min) (z max)
109 p7 = vec3 (x max) (y max) (z min)
110 p8 = vec3 (x max) (y max) (z max)
111
112 generate (Sphere c r) acc = p1:p2:p3:p4:p5:p6:acc
113 where
114 p1 = c + unitX * (vec3 r r r)
115 p2 = c - unitX * (vec3 r r r)
116 p3 = c + unitY * (vec3 r r r)
117 p4 = c - unitY * (vec3 r r r)
118 p5 = c + unitZ * (vec3 r r r)
119 p6 = c - unitZ * (vec3 r r r)-}
diff --git a/Spear/Collision/Collisioner.hs b/Spear/Collision/Collisioner.hs
new file mode 100644
index 0000000..c0194bd
--- /dev/null
+++ b/Spear/Collision/Collisioner.hs
@@ -0,0 +1,80 @@
1module Spear.Collision.Collisioner
2(
3 Collisioner
4, CollisionType(..)
5, aabbCollisioner
6, sphereCollisioner
7, buildAABB
8, collide
9)
10where
11
12
13import Spear.Math.Vector3 as Vector
14import qualified Spear.Collision.AABB as Box
15import qualified Spear.Collision.Sphere as Sphere
16import Spear.Collision.Collision as C
17import Spear.Collision.Types
18
19
20-- | A collisioner component.
21-- Wraps collision primitives so that one can collide them without being aware of
22-- the underlying type.
23data Collisioner
24 -- | An axis-aligned bounding box.
25 = AABB {getBox :: !(Box.AABB)}
26 -- | A bounding sphere.
27 | Sphere {getSphere :: !(Sphere.Sphere)
28 }
29
30
31-- | Create a 'Collisioner' from the specified 'AABB'.
32aabbCollisioner :: Box.AABB -> Collisioner
33aabbCollisioner = AABB
34
35
36-- | Create a 'Collisioner' from the specified 'BSphere'.
37sphereCollisioner :: Sphere.Sphere -> Collisioner
38sphereCollisioner = Sphere
39
40
41-- | Create the minimal 'AABB' fully containing the specified collisioners.
42buildAABB :: [Collisioner] -> Box.AABB
43buildAABB cols = Box.aabb $ Spear.Collision.Collisioner.generatePoints cols
44
45
46-- | Create the minimal 'AABB' collisioner fully containing the specified 'BSphere'.
47boxFromSphere :: Sphere.Sphere -> Collisioner
48boxFromSphere = AABB . aabbFromSphere
49
50
51generatePoints :: [Collisioner] -> [Vector3]
52generatePoints = foldr generate []
53 where
54 generate (AABB (Box.AABB min max)) acc = p1:p2:p3:p4:p5:p6:p7:p8:acc
55 where
56 p1 = vec3 (x min) (y min) (z min)
57 p2 = vec3 (x min) (y min) (z max)
58 p3 = vec3 (x min) (y max) (z min)
59 p4 = vec3 (x min) (y max) (z max)
60 p5 = vec3 (x max) (y min) (z min)
61 p6 = vec3 (x max) (y min) (z max)
62 p7 = vec3 (x max) (y max) (z min)
63 p8 = vec3 (x max) (y max) (z max)
64
65 generate (Sphere (Sphere.Sphere c r)) acc = p1:p2:p3:p4:p5:p6:acc
66 where
67 p1 = c + unitX * (vec3 r r r)
68 p2 = c - unitX * (vec3 r r r)
69 p3 = c + unitY * (vec3 r r r)
70 p4 = c - unitY * (vec3 r r r)
71 p5 = c + unitZ * (vec3 r r r)
72 p6 = c - unitZ * (vec3 r r r)
73
74
75-- | Collide the given collisioners.
76collide :: Collisioner -> Collisioner -> CollisionType
77collide (AABB box1) (AABB box2) = collideBox box1 box2
78collide (Sphere s1) (Sphere s2) = collideSphere s1 s2
79collide (AABB box) (Sphere sphere) = collideBox box sphere
80collide (Sphere sphere) (AABB box) = collideSphere sphere box
diff --git a/Spear/Collision/Sphere.hs b/Spear/Collision/Sphere.hs
new file mode 100644
index 0000000..de670bc
--- /dev/null
+++ b/Spear/Collision/Sphere.hs
@@ -0,0 +1,36 @@
1module Spear.Collision.Sphere
2(
3 Sphere(..)
4, sphere
5, contains
6)
7where
8
9
10import Spear.Math.Vector3 as Vector
11
12
13-- | A bounding volume.
14data Sphere = Sphere
15 { center :: !Vector3
16 , radius :: !Float
17 }
18 deriving Eq
19
20
21-- | Create a 'Sphere' from the given points.
22sphere :: [Vector3] -> Sphere
23
24sphere [] = error "Attempting to build a BoundingVolume from an empty list!"
25
26sphere (x:xs) = Sphere c r
27 where
28 c = min + (max-min)/2
29 r = norm $ max - c
30 (min,max) = foldr update (x,x) xs
31 update p (min,max) = (Vector.min p min, Vector.max p max)
32
33
34-- | Return 'True' if the given 'Sphere' contains the given point, 'False' otherwise.
35contains :: Sphere -> Vector3 -> Bool
36(Sphere center radius) `contains` p = radius*radius >= normSq (p - center)
diff --git a/Spear/Collision/Triangle.hs b/Spear/Collision/Triangle.hs
new file mode 100644
index 0000000..2391e9f
--- /dev/null
+++ b/Spear/Collision/Triangle.hs
@@ -0,0 +1,40 @@
1module Spear.Collision.Triangle
2(
3 Triangle(..)
4)
5where
6
7
8import Spear.Math.Vector3
9
10import Foreign.C.Types
11import Foreign.Storable
12
13
14data Triangle = Triangle
15 { p0 :: Vector3
16 , p1 :: Vector3
17 , p2 :: Vector3
18 }
19
20
21sizeVector3 = 3 * sizeOf (undefined :: CFloat)
22
23
24instance Storable Triangle where
25
26 sizeOf _ = 3 * sizeVector3
27 alignment _ = alignment (undefined :: CFloat)
28
29 peek ptr = do
30 p0 <- peekByteOff ptr 0
31 p1 <- peekByteOff ptr $ 1 * sizeVector3
32 p2 <- peekByteOff ptr $ 2 * sizeVector3
33
34 return $ Triangle p0 p1 p2
35
36
37 poke ptr (Triangle p0 p1 p2) = do
38 pokeByteOff ptr 0 p0
39 pokeByteOff ptr (1*sizeVector3) p1
40 pokeByteOff ptr (2*sizeVector3) p2
diff --git a/Spear/Collision/Types.hs b/Spear/Collision/Types.hs
new file mode 100644
index 0000000..efbf7f9
--- /dev/null
+++ b/Spear/Collision/Types.hs
@@ -0,0 +1,6 @@
1module Spear.Collision.Types
2where
3
4-- | Encodes several collision situations.
5data CollisionType = NoCollision | Collision | FullyContains | FullyContainedBy | Equal
6 deriving (Eq, Ord, Show)
diff --git a/Spear/GLSL.hs b/Spear/GLSL.hs
new file mode 100644
index 0000000..4d81a73
--- /dev/null
+++ b/Spear/GLSL.hs
@@ -0,0 +1,20 @@
1module Spear.GLSL
2(
3 module Spear.GLSL.Buffer
4, module Spear.GLSL.Error
5, module Spear.GLSL.Management
6, module Spear.GLSL.Texture
7, module Spear.GLSL.Uniform
8, module Spear.GLSL.VAO
9, module Graphics.Rendering.OpenGL.Raw.Core31
10)
11where
12
13
14import Spear.GLSL.Buffer
15import Spear.GLSL.Error
16import Spear.GLSL.Management
17import Spear.GLSL.Texture
18import Spear.GLSL.Uniform
19import Spear.GLSL.VAO
20import Graphics.Rendering.OpenGL.Raw.Core31
diff --git a/Spear/GLSL/Buffer.hs b/Spear/GLSL/Buffer.hs
new file mode 100644
index 0000000..0f43d66
--- /dev/null
+++ b/Spear/GLSL/Buffer.hs
@@ -0,0 +1,111 @@
1module Spear.GLSL.Buffer
2(
3 GLBuffer
4, TargetBuffer(..)
5, BufferUsage(..)
6, newBuffer
7, releaseBuffer
8, bindBuffer
9, bufferData
10, withGLBuffer
11)
12where
13
14
15import Spear.Setup
16import Spear.GLSL.Management
17
18import Graphics.Rendering.OpenGL.Raw.Core31
19import Control.Monad.Trans.Class (lift)
20import Data.StateVar
21import Foreign.Ptr
22import Foreign.Marshal.Utils as Foreign (with)
23import Foreign.Marshal.Alloc (alloca)
24import Foreign.Storable (peek)
25import Unsafe.Coerce
26
27
28-- | Represents an OpenGL buffer.
29data GLBuffer = GLBuffer
30 { getBuffer :: GLuint
31 , rkey :: Resource
32 }
33
34
35-- | Represents a target buffer.
36data TargetBuffer
37 = ArrayBuffer
38 | ElementArrayBuffer
39 | PixelPackBuffer
40 | PixelUnpackBuffer
41 deriving (Eq, Show)
42
43
44fromTarget :: TargetBuffer -> GLenum
45fromTarget ArrayBuffer = gl_ARRAY_BUFFER
46fromTarget ElementArrayBuffer = gl_ELEMENT_ARRAY_BUFFER
47fromTarget PixelPackBuffer = gl_PIXEL_PACK_BUFFER
48fromTarget PixelUnpackBuffer = gl_PIXEL_UNPACK_BUFFER
49
50
51-- | Represents a type of buffer usage.
52data BufferUsage
53 = StreamDraw
54 | StreamRead
55 | StreamCopy
56 | StaticDraw
57 | StaticRead
58 | StaticCopy
59 | DynamicDraw
60 | DynamicRead
61 | DynamicCopy
62 deriving (Eq, Show)
63
64
65fromUsage :: BufferUsage -> GLenum
66fromUsage StreamDraw = gl_STREAM_DRAW
67fromUsage StreamRead = gl_STREAM_READ
68fromUsage StreamCopy = gl_STREAM_COPY
69fromUsage StaticDraw = gl_STATIC_DRAW
70fromUsage StaticRead = gl_STATIC_READ
71fromUsage StaticCopy = gl_STATIC_COPY
72fromUsage DynamicDraw = gl_DYNAMIC_DRAW
73fromUsage DynamicRead = gl_DYNAMIC_READ
74fromUsage DynamicCopy = gl_DYNAMIC_COPY
75
76
77-- | Create a 'GLBuffer'.
78newBuffer :: Setup GLBuffer
79newBuffer = do
80 h <- setupIO . alloca $ \ptr -> do
81 glGenBuffers 1 ptr
82 peek ptr
83
84 rkey <- register $ deleteBuffer h
85 return $ GLBuffer h rkey
86
87
88-- | Release the given 'GLBuffer'.
89releaseBuffer :: GLBuffer -> Setup ()
90releaseBuffer = release . rkey
91
92
93-- | Delete the given 'GLBuffer'.
94deleteBuffer :: GLuint -> IO ()
95deleteBuffer buf = Foreign.with buf $ glDeleteBuffers 1
96
97
98-- | Bind the given 'GLBuffer'.
99bindBuffer :: GLBuffer -> TargetBuffer -> IO ()
100bindBuffer buf target = glBindBuffer (fromTarget target) $ getBuffer buf
101
102
103-- | Set buffer data.
104bufferData :: TargetBuffer -> Int -> Ptr a -> BufferUsage -> IO ()
105bufferData target n bufData usage = glBufferData (fromTarget target) (unsafeCoerce n) bufData (fromUsage usage)
106
107
108-- | Apply the given function the 'GLBuffer''s id.
109withGLBuffer :: GLBuffer -> (GLuint -> a) -> a
110withGLBuffer buf f = f $ getBuffer buf
111
diff --git a/Spear/GLSL/Error.hs b/Spear/GLSL/Error.hs
new file mode 100644
index 0000000..7865996
--- /dev/null
+++ b/Spear/GLSL/Error.hs
@@ -0,0 +1,45 @@
1module Spear.GLSL.Error
2(
3 getGLError
4, printGLError
5, assertGL
6)
7where
8
9
10import Spear.Setup
11
12import Graphics.Rendering.OpenGL.Raw.Core31
13import System.IO (hPutStrLn, stderr)
14
15
16-- | Get the last OpenGL error.
17getGLError :: IO (Maybe String)
18getGLError = fmap translate glGetError
19 where
20 translate err
21 | err == gl_NO_ERROR = Nothing
22 | err == gl_INVALID_ENUM = Just "Invalid enum"
23 | err == gl_INVALID_VALUE = Just "Invalid value"
24 | err == gl_INVALID_OPERATION = Just "Invalid operation"
25 | err == gl_OUT_OF_MEMORY = Just "Out of memory"
26 | otherwise = Just "Unknown error"
27
28
29-- | Print the last OpenGL error.
30printGLError :: IO ()
31printGLError = getGLError >>= \err -> case err of
32 Nothing -> return ()
33 Just str -> hPutStrLn stderr str
34
35
36-- | Run the given 'Setup' action and check for OpenGL errors.
37-- If an OpenGL error is produced, an exception is thrown
38-- containing the given string and the OpenGL error.
39assertGL :: Setup a -> String -> Setup a
40assertGL action err = do
41 result <- action
42 status <- setupIO getGLError
43 case status of
44 Just str -> setupError $ "OpenGL error raised: " ++ err ++ "; " ++ str
45 Nothing -> return result
diff --git a/Spear/GLSL/Management.hs b/Spear/GLSL/Management.hs
new file mode 100644
index 0000000..81cf45f
--- /dev/null
+++ b/Spear/GLSL/Management.hs
@@ -0,0 +1,297 @@
1module Spear.GLSL.Management
2(
3 -- * Data types
4 GLSLShader
5, GLSLProgram
6, ShaderType(..)
7 -- * Program manipulation
8, newProgram
9, releaseProgram
10, linkProgram
11, useProgram
12, withGLSLProgram
13 -- * Shader manipulation
14, attachShader
15, detachShader
16, loadShader
17, newShader
18, releaseShader
19 -- ** Source loading
20, loadSource
21, shaderSource
22, readSource
23, compile
24 -- * Location
25, attribLocation
26, fragLocation
27, uniformLocation
28 -- * Helper functions
29, ($=)
30, Data.StateVar.get
31)
32where
33
34
35import Spear.Setup
36
37import Control.Monad ((<=<), forM)
38import Control.Monad.Trans.State as State
39import Control.Monad.Trans.Error
40import Control.Monad.Trans.Class
41import Control.Monad (mapM_, when)
42import qualified Data.ByteString.Char8 as B
43import Data.StateVar
44import Foreign.Ptr
45import Foreign.Storable
46import Foreign.C.String
47import Foreign.Marshal.Alloc (alloca)
48import Foreign.Marshal.Array (withArray)
49import Graphics.Rendering.OpenGL.Raw.Core31
50import System.Directory (doesFileExist, getCurrentDirectory, setCurrentDirectory)
51import Unsafe.Coerce
52
53
54-- | Represents a GLSL shader handle.
55data GLSLShader = GLSLShader
56 { getShader :: GLuint
57 , getShaderKey :: Resource
58 }
59
60
61-- | Represents a GLSL program handle.
62data GLSLProgram = GLSLProgram
63 { getProgram :: GLuint
64 , getProgramKey :: Resource
65 }
66
67
68-- | Encodes several shader types.
69data ShaderType = VertexShader | FragmentShader deriving (Eq, Show)
70
71
72toGLShader :: ShaderType -> GLenum
73toGLShader VertexShader = gl_VERTEX_SHADER
74toGLShader FragmentShader = gl_FRAGMENT_SHADER
75
76
77-- | Apply the given function to the GLSLProgram's id.
78withGLSLProgram :: GLSLProgram -> (GLuint -> a) -> a
79withGLSLProgram prog f = f $ getProgram prog
80
81
82-- | Get the location of the given uniform variable within the given program.
83uniformLocation :: GLSLProgram -> String -> GettableStateVar GLint
84uniformLocation prog var = makeGettableStateVar get
85 where
86 get = withCString var $ \str -> glGetUniformLocation (getProgram prog) (unsafeCoerce str)
87
88
89-- | Get or set the location of the given variable to a fragment shader colour number.
90fragLocation :: GLSLProgram -> String -> StateVar GLint
91fragLocation prog var = makeStateVar get set
92 where
93 get = withCString var $ \str -> glGetFragDataLocation (getProgram prog) (unsafeCoerce str)
94 set idx = withCString var $ \str ->
95 glBindFragDataLocation (getProgram prog) (unsafeCoerce idx) (unsafeCoerce str)
96
97
98-- | Get or set the location of the given attribute within the given program.
99attribLocation :: GLSLProgram -> String -> StateVar GLint
100attribLocation prog var = makeStateVar get set
101 where
102 get = withCString var $ \str -> glGetAttribLocation (getProgram prog) (unsafeCoerce str)
103 set idx = withCString var $ \str ->
104 glBindAttribLocation (getProgram prog) (unsafeCoerce idx) (unsafeCoerce str)
105
106
107-- | Create a 'GLSLProgram'.
108newProgram :: [GLSLShader] -> Setup GLSLProgram
109newProgram shaders = do
110 h <- setupIO glCreateProgram
111 when (h == 0) $ setupError "glCreateProgram failed"
112 rkey <- register $ deleteProgram h
113 let program = GLSLProgram h rkey
114
115 mapM_ (setupIO . attachShader program) shaders
116 linkProgram program
117
118 return program
119
120
121-- | Release the given 'GLSLProgram'.
122releaseProgram :: GLSLProgram -> Setup ()
123releaseProgram = release . getProgramKey
124
125
126-- | Delete the given 'GLSLProgram'.
127deleteProgram :: GLuint -> IO ()
128--deleteProgram = glDeleteProgram
129deleteProgram prog = do
130 putStrLn $ "Deleting shader program " ++ show prog
131 glDeleteProgram prog
132
133
134-- | Link the given GLSL program.
135linkProgram :: GLSLProgram -> Setup ()
136linkProgram prog = do
137 let h = getProgram prog
138 err <- setupIO $ do
139 glLinkProgram h
140 alloca $ \statptr -> do
141 glGetProgramiv h gl_LINK_STATUS statptr
142 status <- peek statptr
143 case status of
144 0 -> getStatus glGetProgramiv glGetProgramInfoLog h
145 _ -> return ""
146
147 case length err of
148 0 -> return ()
149 _ -> setupError err
150
151
152-- | Use the given GLSL program.
153useProgram :: GLSLProgram -> IO ()
154useProgram prog = glUseProgram $ getProgram prog
155
156
157-- | Attach the given GLSL shader to the given GLSL program.
158attachShader :: GLSLProgram -> GLSLShader -> IO ()
159attachShader prog shader = glAttachShader (getProgram prog) (getShader shader)
160
161
162-- | Detach the given GLSL shader from the given GLSL program.
163detachShader :: GLSLProgram -> GLSLShader -> IO ()
164detachShader prog shader = glDetachShader (getProgram prog) (getShader shader)
165
166
167-- | Load a shader from the file specified by the given string.
168--
169-- This function creates a new shader. To load source code into an existing shader,
170-- see 'loadSource', 'shaderSource' and 'readSource'.
171loadShader :: FilePath -> ShaderType -> Setup GLSLShader
172loadShader file shaderType = do
173 shader <- newShader shaderType
174 loadSource file shader
175 compile file shader
176 return shader
177
178
179-- | Create a new shader.
180newShader :: ShaderType -> Setup GLSLShader
181newShader shaderType = do
182 h <- setupIO $ glCreateShader (toGLShader shaderType)
183 case h of
184 0 -> setupError "glCreateShader failed"
185 _ -> do
186 rkey <- register $ deleteShader h
187 return $ GLSLShader h rkey
188
189
190-- | Release the given 'GLSLShader'.
191releaseShader :: GLSLShader -> Setup ()
192releaseShader = release . getShaderKey
193
194
195-- | Free the given shader.
196deleteShader :: GLuint -> IO ()
197--deleteShader = glDeleteShader
198deleteShader shader = do
199 putStrLn $ "Deleting shader " ++ show shader
200 glDeleteShader shader
201
202
203-- | Load a shader source from the file specified by the given string into the given shader.
204loadSource :: FilePath -> GLSLShader -> Setup ()
205loadSource file h = do
206 exists <- setupIO $ doesFileExist file
207 case exists of
208 False -> setupError "the specified shader file does not exist"
209 True -> setupIO $ do
210 code <- readSource file
211 withCString code $ shaderSource h
212
213
214-- | Load the given shader source into the given shader.
215shaderSource :: GLSLShader -> CString -> IO ()
216shaderSource shader str =
217 let ptr = unsafeCoerce str
218 in withArray [ptr] $ flip (glShaderSource (getShader shader) 1) nullPtr
219
220
221-- | Compile the given shader.
222compile :: FilePath -> GLSLShader -> Setup ()
223compile file shader = do
224 let h = getShader shader
225
226 -- Compile
227 setupIO $ glCompileShader h
228
229 -- Verify status
230 err <- setupIO $ alloca $ \statusPtr -> do
231 glGetShaderiv h gl_COMPILE_STATUS statusPtr
232 result <- peek statusPtr
233 case result of
234 0 -> getStatus glGetShaderiv glGetShaderInfoLog h
235 _ -> return ""
236
237 case length err of
238 0 -> return ()
239 _ -> setupError $ "Unable to compile shader " ++ file ++ ":\n" ++ err
240
241
242type StatusCall = GLuint -> GLenum -> Ptr GLint -> IO ()
243type LogCall = GLuint -> GLsizei -> Ptr GLsizei -> Ptr GLchar -> IO ()
244
245
246getStatus :: StatusCall -> LogCall -> GLuint -> IO String
247getStatus getStatus getLog h = do
248 alloca $ \lenPtr -> do
249 getStatus h gl_INFO_LOG_LENGTH lenPtr
250 len <- peek lenPtr
251 case len of
252 0 -> return ""
253 _ -> withCString (replicate (unsafeCoerce len) '\0') $ getErrorString getLog h (unsafeCoerce len)
254
255
256getErrorString :: LogCall -> GLuint -> GLsizei -> CString -> IO String
257getErrorString getLog h len str = do
258 let ptr = unsafeCoerce str
259 getLog h len nullPtr ptr
260 peekCString str
261
262
263-- | Load the shader source specified by the given file.
264--
265-- This function implements an #include mechanism, so the given file can
266-- refer to other files.
267readSource :: FilePath -> IO String
268readSource = fmap B.unpack . readSource'
269
270
271readSource' :: FilePath -> IO B.ByteString
272readSource' file = do
273 let includeB = B.pack "#include"
274 newLineB = B.pack "\n"
275 isInclude = ((==) includeB) . B.take 8
276 clean = B.dropWhile (\c -> c == ' ')
277 cleanInclude = B.filter (\c -> c /= '"') . B.dropWhile (\c -> c /= ' ')
278 toLines = B.splitWith (\c -> c == '\n' || c == '\r')
279 addNewLine s = if (B.last s /= '\n') then B.append s newLineB else s
280 parse = fmap B.concat . (fmap . fmap $ flip B.append newLineB) . sequence .
281 fmap (processLine . clean) . toLines
282 processLine l =
283 if isInclude l
284 then readSource' $ B.unpack . clean . cleanInclude $ l
285 else return l
286
287 contents <- B.readFile file
288
289 dir <- getCurrentDirectory
290 let dir' = dir ++ "/" ++ (reverse . dropWhile (\c -> c /= '/') . reverse) file
291
292 setCurrentDirectory dir'
293 code <- parse contents
294 setCurrentDirectory dir
295
296 return code
297
diff --git a/Spear/GLSL/Texture.hs b/Spear/GLSL/Texture.hs
new file mode 100644
index 0000000..8d361a1
--- /dev/null
+++ b/Spear/GLSL/Texture.hs
@@ -0,0 +1,110 @@
1module Spear.GLSL.Texture
2(
3 Texture
4, SettableStateVar
5, GLenum
6, ($)
7 -- * Creation and destruction
8, newTexture
9, releaseTexture
10 -- * Manipulation
11, bindTexture
12, loadTextureData
13, texParami
14, texParamf
15, activeTexture
16)
17where
18
19
20import Spear.Setup
21
22import Data.StateVar
23import Foreign.Marshal.Alloc (alloca)
24import Foreign.Marshal.Utils (with)
25import Foreign.Ptr
26import Foreign.Storable (peek)
27import Graphics.Rendering.OpenGL.Raw.Core31
28import Unsafe.Coerce (unsafeCoerce)
29
30
31-- | Represents a texture resource.
32data Texture = Texture
33 { getTex :: GLuint
34 , rkey :: Resource
35 }
36
37
38instance Eq Texture where
39 t1 == t2 = getTex t1 == getTex t2
40
41
42instance Ord Texture where
43 t1 < t2 = getTex t1 < getTex t2
44
45
46-- | Create a new 'Texture'.
47newTexture :: Setup Texture
48newTexture = do
49 tex <- setupIO . alloca $ \ptr -> do
50 glGenTextures 1 ptr
51 peek ptr
52
53 rkey <- register $ deleteTexture tex
54 return $ Texture tex rkey
55
56
57-- | Release the given 'Texture'.
58releaseTexture :: Texture -> Setup ()
59releaseTexture = release . rkey
60
61
62-- | Delete the given 'Texture'.
63deleteTexture :: GLuint -> IO ()
64--deleteTexture tex = with tex $ glDeleteTextures 1
65deleteTexture tex = do
66 putStrLn $ "Releasing texture " ++ show tex
67 with tex $ glDeleteTextures 1
68
69
70-- | Bind the given 'Texture'.
71bindTexture :: Texture -> IO ()
72bindTexture = glBindTexture gl_TEXTURE_2D . getTex
73
74
75-- | Load data onto the bound 'Texture'.
76loadTextureData :: GLenum
77 -> Int -- ^ Target
78 -> Int -- ^ Level
79 -> Int -- ^ Internal format
80 -> Int -- ^ Width
81 -> Int -- ^ Height
82 -> GLenum -- ^ Border
83 -> GLenum -- ^ Texture type
84 -> Ptr a -- ^ Texture data
85 -> IO ()
86loadTextureData target level internalFormat width height border format texType texData = do
87 glTexImage2D target
88 (fromIntegral level)
89 (fromIntegral internalFormat)
90 (fromIntegral width)
91 (fromIntegral height)
92 (fromIntegral border)
93 (fromIntegral format)
94 texType
95 texData
96
97
98-- | Set the bound texture's given parameter to the given value.
99texParami :: GLenum -> GLenum -> SettableStateVar GLenum
100texParami target param = makeSettableStateVar $ \val -> glTexParameteri target param $ fromIntegral . fromEnum $ val
101
102
103-- | Set the bound texture's given parameter to the given value.
104texParamf :: GLenum -> GLenum -> SettableStateVar Float
105texParamf target param = makeSettableStateVar $ \val -> glTexParameterf target param (unsafeCoerce val)
106
107
108-- | Set the active texture unit.
109activeTexture :: SettableStateVar GLenum
110activeTexture = makeSettableStateVar glActiveTexture
diff --git a/Spear/GLSL/Uniform.hs b/Spear/GLSL/Uniform.hs
new file mode 100644
index 0000000..f186333
--- /dev/null
+++ b/Spear/GLSL/Uniform.hs
@@ -0,0 +1,67 @@
1module Spear.GLSL.Uniform
2(
3 uniformVec3
4, uniformVec4
5, uniformMat3
6, uniformMat4
7, uniformfl
8, uniformil
9)
10where
11
12
13import Spear.GLSL.Management
14import Spear.Math.Matrix3 (Matrix3)
15import Spear.Math.Matrix4 (Matrix4)
16import Spear.Math.Vector3 as V3
17import Spear.Math.Vector4 as V4
18
19import Foreign.Marshal.Array (withArray)
20import Foreign.Marshal.Utils
21import Graphics.Rendering.OpenGL.Raw.Core31
22import Unsafe.Coerce
23
24
25uniformVec3 :: GLint -> Vector3 -> IO ()
26uniformVec3 loc v = glUniform3f loc x' y' z'
27 where x' = unsafeCoerce $ V3.x v
28 y' = unsafeCoerce $ V3.y v
29 z' = unsafeCoerce $ V3.z v
30
31
32uniformVec4 :: GLint -> Vector4 -> IO ()
33uniformVec4 loc v = glUniform4f loc x' y' z' w'
34 where x' = unsafeCoerce $ V4.x v
35 y' = unsafeCoerce $ V4.y v
36 z' = unsafeCoerce $ V4.z v
37 w' = unsafeCoerce $ V4.w v
38
39
40uniformMat3 :: GLint -> Matrix3 -> IO ()
41uniformMat3 loc mat =
42 with mat $ \ptrMat ->
43 glUniformMatrix3fv loc 1 (toEnum 0) (unsafeCoerce ptrMat)
44
45
46uniformMat4 :: GLint -> Matrix4 -> IO ()
47uniformMat4 loc mat =
48 with mat $ \ptrMat ->
49 glUniformMatrix4fv loc 1 (toEnum 0) (unsafeCoerce ptrMat)
50
51
52uniformfl :: GLint -> [GLfloat] -> IO ()
53uniformfl loc vals = withArray vals $ \ptr ->
54 case length vals of
55 1 -> glUniform1fv loc 1 ptr
56 2 -> glUniform2fv loc 1 ptr
57 3 -> glUniform3fv loc 1 ptr
58 4 -> glUniform4fv loc 1 ptr
59
60
61uniformil :: GLint -> [GLint] -> IO ()
62uniformil loc vals = withArray vals $ \ptr ->
63 case length vals of
64 1 -> glUniform1iv loc 1 ptr
65 2 -> glUniform2iv loc 1 ptr
66 3 -> glUniform3iv loc 1 ptr
67 4 -> glUniform4iv loc 1 ptr
diff --git a/Spear/GLSL/VAO.hs b/Spear/GLSL/VAO.hs
new file mode 100644
index 0000000..f121636
--- /dev/null
+++ b/Spear/GLSL/VAO.hs
@@ -0,0 +1,88 @@
1module Spear.GLSL.VAO
2(
3 VAO
4 -- * Creation and destruction
5, newVAO
6, releaseVAO
7 -- * Manipulation
8, bindVAO
9, enableVAOAttrib
10, attribVAOPointer
11 -- * Rendering
12, drawArrays
13, drawElements
14)
15where
16
17
18import Spear.Setup
19import Control.Monad.Trans.Class (lift)
20import Foreign.Marshal.Utils as Foreign (with)
21import Foreign.Marshal.Alloc (alloca)
22import Foreign.Storable (peek)
23import Foreign.Ptr
24import Unsafe.Coerce
25import Graphics.Rendering.OpenGL.Raw.Core31
26
27
28-- | Represents a vertex array object.
29data VAO = VAO
30 { getVAO :: GLuint
31 , rkey :: Resource
32 }
33
34
35instance Eq VAO where
36 vao1 == vao2 = getVAO vao1 == getVAO vao2
37
38
39instance Ord VAO where
40 vao1 < vao2 = getVAO vao1 < getVAO vao2
41
42
43-- | Create a new 'VAO'.
44newVAO :: Setup VAO
45newVAO = do
46 h <- setupIO . alloca $ \ptr -> do
47 glGenVertexArrays 1 ptr
48 peek ptr
49
50 rkey <- register $ deleteVAO h
51 return $ VAO h rkey
52
53
54-- | Release the given 'VAO'.
55releaseVAO :: VAO -> Setup ()
56releaseVAO = release . rkey
57
58
59-- | Delete the given 'VAO'.
60deleteVAO :: GLuint -> IO ()
61deleteVAO vao = Foreign.with vao $ glDeleteVertexArrays 1
62
63
64-- | Bind the given 'VAO'.
65bindVAO :: VAO -> IO ()
66bindVAO = glBindVertexArray . getVAO
67
68
69-- | Enable the given vertex attribute of the bound 'VAO'.
70enableVAOAttrib :: GLuint -> IO ()
71enableVAOAttrib = glEnableVertexAttribArray
72
73
74-- | Bind the bound buffer to the given point.
75attribVAOPointer :: GLuint -> GLint -> GLenum -> Bool -> GLsizei -> Int -> IO ()
76attribVAOPointer idx ncomp dattype normalise stride off =
77 glVertexAttribPointer idx ncomp dattype (unsafeCoerce normalise) stride (unsafeCoerce off)
78
79
80-- | Draw the bound 'VAO'.
81drawArrays :: GLenum -> Int -> Int -> IO ()
82drawArrays mode first count = glDrawArrays mode (unsafeCoerce first) (unsafeCoerce count)
83
84
85-- | Draw the bound 'VAO', indexed mode.
86drawElements :: GLenum -> Int -> GLenum -> Ptr a -> IO ()
87drawElements mode count t idxs = glDrawElements mode (unsafeCoerce count) t idxs
88
diff --git a/Spear/Game.hs b/Spear/Game.hs
new file mode 100644
index 0000000..08fc460
--- /dev/null
+++ b/Spear/Game.hs
@@ -0,0 +1,42 @@
1module Spear.Game
2(
3 Game
4, gameIO
5, getGameState
6, saveGameState
7, modifyGameState
8, runGame
9)
10where
11
12
13import Control.Monad.Trans.Class (lift)
14import Control.Monad.State.Strict
15
16
17type Game s = StateT s IO
18
19
20-- | Perform the given IO action in the 'Game' monad.
21gameIO :: IO a -> Game s a
22gameIO = lift
23
24
25-- | Retrieve the game state.
26getGameState :: Game s s
27getGameState = get
28
29
30-- | Save the game state.
31saveGameState :: s -> Game s ()
32saveGameState = put
33
34
35-- | Modify the game state.
36modifyGameState :: (s -> s) -> Game s ()
37modifyGameState = modify
38
39
40-- | Run the given game.
41runGame :: Game s a -> s -> IO ()
42runGame game state = runStateT game state >> return ()
diff --git a/Spear/Math/Camera.hs b/Spear/Math/Camera.hs
new file mode 100644
index 0000000..118997a
--- /dev/null
+++ b/Spear/Math/Camera.hs
@@ -0,0 +1,69 @@
1module Spear.Math.Camera
2where
3
4
5import qualified Spear.Math.Matrix4 as M
6import qualified Spear.Math.Spatial as S
7import Spear.Math.Vector3
8
9
10data Camera = Camera
11 { projection :: M.Matrix4
12 , transform :: M.Matrix4
13 }
14
15
16-- | Build a perspective camera.
17perspective :: Float -- ^ Fovy - Vertical field of view angle in degrees.
18 -> Float -- ^ Aspect ratio.
19 -> Float -- ^ Near clip.
20 -> Float -- ^ Far clip.
21 -> Vector3 -- ^ Right vector.
22 -> Vector3 -- ^ Up vector.
23 -> Vector3 -- ^ Forward vector.
24 -> Vector3 -- ^ Position vector.
25 -> Camera
26
27perspective fovy r n f right up fwd pos =
28 Camera
29 { projection = M.perspective fovy r n f
30 , transform = M.transform right up fwd pos
31 }
32
33
34-- | Build an orthogonal camera.
35ortho :: Float -- ^ Left.
36 -> Float -- ^ Right.
37 -> Float -- ^ Bottom.
38 -> Float -- ^ Top.
39 -> Float -- ^ Near clip.
40 -> Float -- ^ Far clip.
41 -> Vector3 -- ^ Right vector.
42 -> Vector3 -- ^ Up vector.
43 -> Vector3 -- ^ Forward vector.
44 -> Vector3 -- ^ Position vector.
45 -> Camera
46
47ortho l r b t n f right up fwd pos =
48 Camera
49 { projection = M.ortho l r b t n f
50 , transform = M.transform right up fwd pos
51 }
52
53
54instance S.Spatial Camera where
55 move v cam = cam { transform = M.translv v * transform cam }
56 moveFwd f cam = cam { transform = M.translv (scale f $ S.fwd cam) * transform cam }
57 moveBack f cam = cam { transform = M.translv (scale (-f) $ S.fwd cam) * transform cam }
58 strafeLeft f cam = cam { transform = M.translv (scale (-f) $ S.right cam) * transform cam }
59 strafeRight f cam = cam { transform = M.translv (scale f $ S.right cam) * transform cam }
60 pitch a cam = cam { transform = transform cam * M.axisAngle (S.right cam) a }
61 yaw a cam = cam { transform = transform cam * M.axisAngle (S.up cam) a }
62 roll a cam = cam { transform = transform cam * M.axisAngle (S.fwd cam) a }
63 pos = M.position . transform
64 fwd = M.forward . transform
65 up = M.up . transform
66 right = M.right . transform
67 transform (Camera _ t) = t
68 setTransform t (Camera proj _) = Camera proj t
69
diff --git a/Spear/Math/Entity.hs b/Spear/Math/Entity.hs
new file mode 100644
index 0000000..298b611
--- /dev/null
+++ b/Spear/Math/Entity.hs
@@ -0,0 +1,31 @@
1module Spear.Math.Entity
2(
3 Entity(..)
4)
5where
6
7
8import qualified Spear.Math.Matrix4 as M
9import qualified Spear.Math.Spatial as S
10import qualified Spear.Math.Vector3 as V
11
12
13-- | An entity in 3D space.
14newtype Entity = Entity { transform :: M.Matrix4 }
15
16
17instance S.Spatial Entity where
18 move v ent = ent { transform = M.translv v * transform ent }
19 moveFwd f ent = ent { transform = M.translv (V.scale f $ S.fwd ent) * transform ent }
20 moveBack f ent = ent { transform = M.translv (V.scale (-f) $ S.fwd ent) * transform ent }
21 strafeLeft f ent = ent { transform = M.translv (V.scale (-f) $ S.right ent) * transform ent }
22 strafeRight f ent = ent { transform = M.translv (V.scale f $ S.right ent) * transform ent }
23 pitch a ent = ent { transform = transform ent * M.axisAngle (S.right ent) a }
24 yaw a ent = ent { transform = transform ent * M.axisAngle (S.up ent) a }
25 roll a ent = ent { transform = transform ent * M.axisAngle (S.fwd ent) a }
26 pos = M.position . transform
27 fwd = M.forward . transform
28 up = M.up . transform
29 right = M.right . transform
30 transform (Entity t) = t
31 setTransform t (Entity _) = Entity t
diff --git a/Spear/Math/Matrix3.hs b/Spear/Math/Matrix3.hs
new file mode 100644
index 0000000..bc8f149
--- /dev/null
+++ b/Spear/Math/Matrix3.hs
@@ -0,0 +1,295 @@
1module Spear.Math.Matrix3
2(
3 Matrix3
4 -- * Accessors
5, m00, m01, m02
6, m10, m11, m12
7, m20, m21, m22
8, col0, col1, col2
9, row0, row1, row2
10 -- * Construction
11, mat3
12, mat3fromVec
13, Spear.Math.Matrix3.id
14 -- * Transformations
15 -- ** Rotation
16, rotX
17, rotY
18, rotZ
19, axisAngle
20 -- ** Scale
21, Spear.Math.Matrix3.scale
22, scalev
23 -- ** Reflection
24, reflectX
25, reflectY
26, reflectZ
27 -- * Operations
28, transpose
29, Spear.Math.Matrix3.zipWith
30, Spear.Math.Matrix3.map
31, mul
32--, inverse
33)
34where
35
36
37import Spear.Math.Vector3 as Vector3
38import Spear.Math.Vector4 as Vector4
39
40import Foreign.Storable
41
42
43-- | Represents a 3x3 column major matrix.
44data Matrix3 = Matrix3
45 { m00 :: !Float, m10 :: !Float, m20 :: !Float
46 , m01 :: !Float, m11 :: !Float, m21 :: !Float
47 , m02 :: !Float, m12 :: !Float, m22 :: !Float
48 }
49
50
51instance Show Matrix3 where
52
53 show (Matrix3 m00 m10 m20 m01 m11 m21 m02 m12 m22) =
54 show' m00 ++ ", " ++ show' m10 ++ ", " ++ show' m20 ++ "\n" ++
55 show' m01 ++ ", " ++ show' m11 ++ ", " ++ show' m21 ++ "\n" ++
56 show' m02 ++ ", " ++ show' m12 ++ ", " ++ show' m22 ++ "\n"
57 where
58 show' f = if abs f < 0.0000001 then "0" else show f
59
60
61instance Num Matrix3 where
62 (Matrix3 a00 a01 a02 a03 a04 a05 a06 a07 a08)
63 + (Matrix3 b00 b01 b02 b03 b04 b05 b06 b07 b08)
64 = Matrix3 (a00 + b00) (a01 + b01) (a02 + b02)
65 (a03 + b03) (a04 + b04) (a05 + b05)
66 (a06 + b06) (a07 + b07) (a08 + b08)
67
68 (Matrix3 a00 a01 a02 a03 a04 a05 a06 a07 a08)
69 - (Matrix3 b00 b01 b02 b03 b04 b05 b06 b07 b08)
70 = Matrix3 (a00 - b00) (a01 - b01) (a02 - b02)
71 (a03 - b03) (a04 - b04) (a05 - b05)
72 (a06 - b06) (a07 - b07) (a08 - b08)
73
74 (Matrix3 a00 a10 a20 a01 a11 a21 a02 a12 a22)
75 * (Matrix3 b00 b10 b20 b01 b11 b21 b02 b12 b22)
76 = Matrix3 (a00 * b00 + a10 * b01 + a20 * b02)
77 (a00 * b10 + a10 * b11 + a20 * b12)
78 (a00 * b20 + a10 * b21 + a20 * b22)
79
80 (a01 * b00 + a11 * b01 + a21 * b02)
81 (a01 * b10 + a11 * b11 + a21 * b12)
82 (a01 * b20 + a11 * b21 + a21 * b22)
83
84 (a02 * b00 + a12 * b01 + a22 * b02)
85 (a02 * b10 + a12 * b11 + a22 * b12)
86 (a02 * b20 + a12 * b21 + a22 * b22)
87
88 abs = Spear.Math.Matrix3.map abs
89
90 signum = Spear.Math.Matrix3.map signum
91
92 fromInteger i = mat3 i' i' i' i' i' i' i' i' i' where i' = fromInteger i
93
94
95instance Storable Matrix3 where
96 sizeOf _ = 36
97 alignment _ = 4
98
99 peek ptr = do
100 a00 <- peekByteOff ptr 0; a01 <- peekByteOff ptr 4; a02 <- peekByteOff ptr 8;
101 a10 <- peekByteOff ptr 12; a11 <- peekByteOff ptr 16; a12 <- peekByteOff ptr 20;
102 a20 <- peekByteOff ptr 24; a21 <- peekByteOff ptr 28; a22 <- peekByteOff ptr 32;
103
104 return $ Matrix3 a00 a10 a20
105 a01 a11 a21
106 a02 a12 a22
107
108 poke ptr (Matrix3 a00 a01 a02
109 a10 a11 a12
110 a20 a21 a22) = do
111 pokeByteOff ptr 0 a00; pokeByteOff ptr 4 a01; pokeByteOff ptr 8 a02;
112 pokeByteOff ptr 12 a10; pokeByteOff ptr 16 a11; pokeByteOff ptr 20 a12;
113 pokeByteOff ptr 24 a20; pokeByteOff ptr 28 a21; pokeByteOff ptr 32 a22;
114
115
116col0 (Matrix3 a00 _ _ a10 _ _ a20 _ _ ) = vec3 a00 a10 a20
117col1 (Matrix3 _ a01 _ _ a11 _ _ a21 _ ) = vec3 a01 a11 a21
118col2 (Matrix3 _ _ a02 _ _ a12 _ _ a22) = vec3 a02 a12 a22
119
120
121row0 (Matrix3 a00 a01 a02 _ _ _ _ _ _ ) = vec3 a00 a01 a02
122row1 (Matrix3 _ _ _ a10 a11 a12 _ _ _ ) = vec3 a10 a11 a12
123row2 (Matrix3 _ _ _ _ _ _ a20 a21 a22) = vec3 a20 a21 a22
124
125
126-- | Build a 'Matrix3' from the specified values.
127mat3 :: Float -> Float -> Float ->
128 Float -> Float -> Float ->
129 Float -> Float -> Float -> Matrix3
130mat3 m00 m01 m02 m10 m11 m12 m20 m21 m22 = Matrix3
131 m00 m10 m20
132 m01 m11 m21
133 m02 m12 m22
134
135
136-- | Build a 'Matrix3' from three vectors in 3D.
137mat3fromVec :: Vector3 -> Vector3 -> Vector3 -> Matrix3
138mat3fromVec v0 v1 v2 = Matrix3
139 (Vector3.x v0) (Vector3.x v1) (Vector3.x v2)
140 (Vector3.y v0) (Vector3.y v1) (Vector3.y v2)
141 (Vector3.z v0) (Vector3.z v1) (Vector3.z v2)
142
143
144-- | Zip two 'Matrix3' together with the specified function.
145zipWith :: (Float -> Float -> Float) -> Matrix3 -> Matrix3 -> Matrix3
146zipWith f a b = Matrix3
147 (f (m00 a) (m00 b)) (f (m10 a) (m10 b)) (f (m20 a) (m20 b))
148 (f (m01 a) (m01 b)) (f (m11 a) (m11 b)) (f (m21 a) (m21 b))
149 (f (m02 a) (m02 b)) (f (m12 a) (m12 b)) (f (m22 a) (m22 b))
150
151
152-- | Map the specified function to the specified 'Matrix3'.
153map :: (Float -> Float) -> Matrix3 -> Matrix3
154map f m = Matrix3
155 (f . m00 $ m) (f . m10 $ m) (f . m20 $ m)
156 (f . m01 $ m) (f . m11 $ m) (f . m21 $ m)
157 (f . m02 $ m) (f . m12 $ m) (f . m22 $ m)
158
159
160-- | Return the identity matrix.
161id :: Matrix3
162id = mat3
163 1 0 0
164 0 1 0
165 0 0 1
166
167
168-- | Create a rotation matrix rotating about the X axis.
169-- The given angle must be in degrees.
170rotX :: Float -> Matrix3
171rotX angle = mat3
172 1 0 0
173 0 c (-s)
174 0 s c
175 where
176 s = sin . fromDeg $ angle
177 c = cos . fromDeg $ angle
178
179
180-- | Create a rotation matrix rotating about the Y axis.
181-- The given angle must be in degrees.
182rotY :: Float -> Matrix3
183rotY angle = mat3
184 c 0 s
185 0 1 0
186 (-s) 0 c
187 where
188 s = sin . fromDeg $ angle
189 c = cos . fromDeg $ angle
190
191
192-- | Create a rotation matrix rotating about the Z axis.
193-- The given angle must be in degrees.
194rotZ :: Float -> Matrix3
195rotZ angle = mat3
196 c (-s) 0
197 s c 0
198 0 0 1
199 where
200 s = sin . fromDeg $ angle
201 c = cos . fromDeg $ angle
202
203
204-- | Create a rotation matrix rotating about the specified axis.
205-- The given angle must be in degrees.
206axisAngle :: Vector3 -> Float -> Matrix3
207axisAngle v angle = mat3
208 (c + omc*x^2) (omc*xy-sz) (omc*xz+sy)
209 (omc*xy+sz) (c+omc*y^2) (omc*yz-sx)
210 (omc*xz-sy) (omc*yz+sx) (c+omc*z^2)
211 where
212 x = Vector3.x v
213 y = Vector3.y v
214 z = Vector3.z v
215 s = sin . fromDeg $ angle
216 c = cos . fromDeg $ angle
217 xy = x*y
218 xz = x*z
219 yz = y*z
220 sx = s*x
221 sy = s*y
222 sz = s*z
223 omc = 1 - c
224
225
226-- | Create a scale matrix.
227scale :: Float -> Float -> Float -> Matrix3
228scale sx sy sz = mat3
229 sx 0 0
230 0 sy 0
231 0 0 sz
232
233
234-- | Create a scale matrix.
235scalev :: Vector3 -> Matrix3
236scalev v = mat3
237 sx 0 0
238 0 sy 0
239 0 0 sz
240 where
241 sx = Vector3.x v
242 sy = Vector3.y v
243 sz = Vector3.z v
244
245
246-- | Create an X reflection matrix.
247reflectX :: Matrix3
248reflectX = mat3
249 (-1) 0 0
250 0 1 0
251 0 0 1
252
253
254-- | Create a Y reflection matrix.
255reflectY :: Matrix3
256reflectY = mat3
257 1 0 0
258 0 (-1) 0
259 0 0 1
260
261
262-- | Create a Z reflection matrix.
263reflectZ :: Matrix3
264reflectZ = mat3
265 1 0 0
266 0 1 0
267 0 0 (-1)
268
269
270-- | Transpose the specified matrix.
271transpose :: Matrix3 -> Matrix3
272transpose m = mat3
273 (m00 m) (m01 m) (m02 m)
274 (m10 m) (m11 m) (m12 m)
275 (m20 m) (m21 m) (m22 m)
276
277
278-- | Transform the given vector in 3D space with the given matrix.
279mul :: Matrix3 -> Vector3 -> Vector3
280mul m v = vec3 x' y' z'
281 where
282 v' = vec3 (Vector3.x v) (Vector3.y v) (Vector3.z v)
283 x' = row0 m `Vector3.dot` v'
284 y' = row1 m `Vector3.dot` v'
285 z' = row2 m `Vector3.dot` v'
286
287
288-- | Invert the given 'Matrix3'.
289{-inverse :: Matrix3 -> Matrix3
290inverse mat = -}
291
292
293fromDeg :: (Floating a) => a -> a
294fromDeg = (*pi) . (/180)
295
diff --git a/Spear/Math/Matrix4.hs b/Spear/Math/Matrix4.hs
new file mode 100644
index 0000000..a86dc84
--- /dev/null
+++ b/Spear/Math/Matrix4.hs
@@ -0,0 +1,453 @@
1module Spear.Math.Matrix4
2(
3 Matrix4
4 -- * Accessors
5, m00, m01, m02, m03
6, m10, m11, m12, m13
7, m20, m21, m22, m23
8, m30, m31, m32, m33
9, col0, col1, col2, col3
10, row0, row1, row2, row3
11, right, up, forward, position
12 -- * Construction
13, mat4
14, mat4fromVec
15, transform
16, lookAt
17, Spear.Math.Matrix4.id
18 -- * Transformations
19 -- ** Translation
20, transl
21, translv
22 -- ** Rotation
23, rotX
24, rotY
25, rotZ
26, axisAngle
27 -- ** Scale
28, Spear.Math.Matrix4.scale
29, scalev
30 -- ** Reflection
31, reflectX
32, reflectY
33, reflectZ
34 -- ** Projection
35, ortho
36, perspective
37 -- * Operations
38, Spear.Math.Matrix4.zipWith
39, Spear.Math.Matrix4.map
40, transpose
41, inverseTransform
42, mul
43, mulp
44, muld
45)
46where
47
48
49import Spear.Math.Vector3 as Vector3
50import Spear.Math.Vector4 as Vector4
51
52import Foreign.Storable
53
54
55-- | Represents a 4x4 column major matrix.
56data Matrix4 = Matrix4
57 { m00 :: !Float, m10 :: !Float, m20 :: !Float, m30 :: !Float
58 , m01 :: !Float, m11 :: !Float, m21 :: !Float, m31 :: !Float
59 , m02 :: !Float, m12 :: !Float, m22 :: !Float, m32 :: !Float
60 , m03 :: !Float, m13 :: !Float, m23 :: !Float, m33 :: !Float
61 }
62
63
64instance Show Matrix4 where
65
66 show (Matrix4 m00 m10 m20 m30 m01 m11 m21 m31 m02 m12 m22 m32 m03 m13 m23 m33) =
67 show' m00 ++ ", " ++ show' m10 ++ ", " ++ show' m20 ++ ", " ++ show' m30 ++ "\n" ++
68 show' m01 ++ ", " ++ show' m11 ++ ", " ++ show' m21 ++ ", " ++ show' m31 ++ "\n" ++
69 show' m02 ++ ", " ++ show' m12 ++ ", " ++ show' m22 ++ ", " ++ show' m32 ++ "\n" ++
70 show' m03 ++ ", " ++ show' m13 ++ ", " ++ show' m23 ++ ", " ++ show' m33 ++ "\n"
71 where
72 show' f = if abs f < 0.0000001 then "0" else show f
73
74
75instance Num Matrix4 where
76 (Matrix4 a00 a01 a02 a03 a04 a05 a06 a07 a08 a09 a10 a11 a12 a13 a14 a15)
77 + (Matrix4 b00 b01 b02 b03 b04 b05 b06 b07 b08 b09 b10 b11 b12 b13 b14 b15)
78 = Matrix4 (a00 + b00) (a01 + b01) (a02 + b02) (a03 + b03)
79 (a04 + b04) (a05 + b05) (a06 + b06) (a07 + b07)
80 (a08 + b08) (a09 + b09) (a10 + b10) (a11 + b11)
81 (a12 + b12) (a13 + b13) (a14 + b14) (a15 + b15)
82
83 (Matrix4 a00 a01 a02 a03 a04 a05 a06 a07 a08 a09 a10 a11 a12 a13 a14 a15)
84 - (Matrix4 b00 b01 b02 b03 b04 b05 b06 b07 b08 b09 b10 b11 b12 b13 b14 b15)
85 = Matrix4 (a00 - b00) (a01 - b01) (a02 - b02) (a03 - b03)
86 (a04 - b04) (a05 - b05) (a06 - b06) (a07 - b07)
87 (a08 - b08) (a09 - b09) (a10 - b10) (a11 - b11)
88 (a12 - b12) (a13 - b13) (a14 - b14) (a15 - b15)
89
90 (Matrix4 a00 a10 a20 a30 a01 a11 a21 a31 a02 a12 a22 a32 a03 a13 a23 a33)
91 * (Matrix4 b00 b10 b20 b30 b01 b11 b21 b31 b02 b12 b22 b32 b03 b13 b23 b33)
92 = Matrix4 (a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03)
93 (a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13)
94 (a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23)
95 (a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33)
96
97 (a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03)
98 (a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13)
99 (a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23)
100 (a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33)
101
102 (a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03)
103 (a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13)
104 (a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23)
105 (a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33)
106
107 (a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03)
108 (a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13)
109 (a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23)
110 (a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33)
111
112 abs = Spear.Math.Matrix4.map abs
113
114 signum = Spear.Math.Matrix4.map signum
115
116 fromInteger i = mat4 i' i' i' i' i' i' i' i' i' i' i' i' i' i' i' i' where i' = fromInteger i
117
118
119instance Storable Matrix4 where
120 sizeOf _ = 64
121 alignment _ = 4
122
123 peek ptr = do
124 a00 <- peekByteOff ptr 0; a01 <- peekByteOff ptr 4; a02 <- peekByteOff ptr 8; a03 <- peekByteOff ptr 12;
125 a10 <- peekByteOff ptr 16; a11 <- peekByteOff ptr 20; a12 <- peekByteOff ptr 24; a13 <- peekByteOff ptr 28;
126 a20 <- peekByteOff ptr 32; a21 <- peekByteOff ptr 36; a22 <- peekByteOff ptr 40; a23 <- peekByteOff ptr 44;
127 a30 <- peekByteOff ptr 48; a31 <- peekByteOff ptr 52; a32 <- peekByteOff ptr 56; a33 <- peekByteOff ptr 60;
128
129 return $ Matrix4 a00 a10 a20 a30
130 a01 a11 a21 a31
131 a02 a12 a22 a32
132 a03 a13 a23 a33
133
134 poke ptr (Matrix4 a00 a10 a20 a30
135 a01 a11 a21 a31
136 a02 a12 a22 a32
137 a03 a13 a23 a33) = do
138 pokeByteOff ptr 0 a00; pokeByteOff ptr 4 a01; pokeByteOff ptr 8 a02; pokeByteOff ptr 12 a03;
139 pokeByteOff ptr 16 a10; pokeByteOff ptr 20 a11; pokeByteOff ptr 24 a12; pokeByteOff ptr 28 a13;
140 pokeByteOff ptr 32 a20; pokeByteOff ptr 36 a21; pokeByteOff ptr 40 a22; pokeByteOff ptr 44 a23;
141 pokeByteOff ptr 48 a30; pokeByteOff ptr 52 a31; pokeByteOff ptr 56 a32; pokeByteOff ptr 60 a33;
142
143
144col0 (Matrix4 a00 _ _ _ a01 _ _ _ a02 _ _ _ a03 _ _ _ ) = vec4 a00 a01 a02 a03
145col1 (Matrix4 _ a10 _ _ _ a11 _ _ _ a12 _ _ _ a13 _ _ ) = vec4 a10 a11 a12 a13
146col2 (Matrix4 _ _ a20 _ _ _ a21 _ _ _ a22 _ _ _ a23 _ ) = vec4 a20 a21 a22 a23
147col3 (Matrix4 _ _ _ a30 _ _ _ a31 _ _ _ a32 _ _ _ a33) = vec4 a30 a31 a32 a33
148
149
150row0 (Matrix4 a00 a01 a02 a03 _ _ _ _ _ _ _ _ _ _ _ _ ) = vec4 a00 a01 a02 a03
151row1 (Matrix4 _ _ _ _ a10 a11 a12 a13 _ _ _ _ _ _ _ _ ) = vec4 a10 a11 a12 a13
152row2 (Matrix4 _ _ _ _ _ _ _ _ a20 a21 a22 a23 _ _ _ _ ) = vec4 a20 a21 a22 a23
153row3 (Matrix4 _ _ _ _ _ _ _ _ _ _ _ _ a30 a31 a32 a33) = vec4 a30 a31 a32 a33
154
155
156right (Matrix4 a00 _ _ _ a01 _ _ _ a02 _ _ _ _ _ _ _) = vec3 a00 a01 a02
157up (Matrix4 _ a10 _ _ _ a11 _ _ _ a12 _ _ _ _ _ _) = vec3 a10 a11 a12
158forward (Matrix4 _ _ a20 _ _ _ a21 _ _ _ a22 _ _ _ _ _) = vec3 a20 a21 a22
159position (Matrix4 _ _ _ a30 _ _ _ a31 _ _ _ a32 _ _ _ _) = vec3 a30 a31 a32
160
161
162-- | Build a matrix from the specified values.
163mat4 :: Float -> Float -> Float -> Float ->
164 Float -> Float -> Float -> Float ->
165 Float -> Float -> Float -> Float ->
166 Float -> Float -> Float -> Float -> Matrix4
167mat4 m00 m10 m20 m30 m01 m11 m21 m31 m02 m12 m22 m32 m03 m13 m23 m33 = Matrix4
168 m00 m10 m20 m30
169 m01 m11 m21 m31
170 m02 m12 m22 m32
171 m03 m13 m23 m33
172
173
174-- | Build a matrix from four vectors in 4D.
175mat4fromVec :: Vector4 -> Vector4 -> Vector4 -> Vector4 -> Matrix4
176mat4fromVec v0 v1 v2 v3 = Matrix4
177 (Vector4.x v0) (Vector4.x v1) (Vector4.x v2) (Vector4.x v3)
178 (Vector4.y v0) (Vector4.y v1) (Vector4.y v2) (Vector4.y v3)
179 (Vector4.z v0) (Vector4.z v1) (Vector4.z v2) (Vector4.z v3)
180 (Vector4.w v0) (Vector4.w v1) (Vector4.w v2) (Vector4.w v3)
181
182
183-- | Build a transformation 'Matrix4' from the given vectors.
184transform :: Vector3 -- ^ Right vector.
185 -> Vector3 -- ^ Up vector.
186 -> Vector3 -- ^ Forward vector.
187 -> Vector3 -- ^ Position.
188 -> Matrix4
189
190transform right up fwd pos = mat4
191 (Vector3.x right) (Vector3.x up) (Vector3.x fwd) (Vector3.x pos)
192 (Vector3.y right) (Vector3.y up) (Vector3.y fwd) (Vector3.y pos)
193 (Vector3.z right) (Vector3.z up) (Vector3.z fwd) (Vector3.z pos)
194 0 0 0 1
195
196
197-- | Build a transformation 'Matrix4' defined by the given position and target.
198--
199-- This function is essentially like gluLookAt.
200lookAt :: Vector3 -- ^ Eye position.
201 -> Vector3 -- ^ Target point.
202 -> Vector3 -- ^ Up vector.
203 -> Matrix4
204
205lookAt pos target up =
206 let fwd = Vector3.normalise $ target - pos
207 r = fwd `cross` up
208 in
209 transform r up (-fwd) pos
210
211
212-- | Zip two matrices together with the specified function.
213zipWith :: (Float -> Float -> Float) -> Matrix4 -> Matrix4 -> Matrix4
214zipWith f a b = Matrix4
215 (f (m00 a) (m00 b)) (f (m10 a) (m10 b)) (f (m20 a) (m20 b)) (f (m30 a) (m30 b))
216 (f (m01 a) (m01 b)) (f (m11 a) (m11 b)) (f (m21 a) (m21 b)) (f (m31 a) (m31 b))
217 (f (m02 a) (m02 b)) (f (m12 a) (m12 b)) (f (m22 a) (m22 b)) (f (m32 a) (m32 b))
218 (f (m03 a) (m03 b)) (f (m13 a) (m13 b)) (f (m23 a) (m23 b)) (f (m33 a) (m33 b))
219
220
221-- | Map the specified function to the specified matrix.
222map :: (Float -> Float) -> Matrix4 -> Matrix4
223map f m = Matrix4
224 (f . m00 $ m) (f . m10 $ m) (f . m20 $ m) (f . m30 $ m)
225 (f . m01 $ m) (f . m11 $ m) (f . m21 $ m) (f . m31 $ m)
226 (f . m02 $ m) (f . m12 $ m) (f . m22 $ m) (f . m32 $ m)
227 (f . m03 $ m) (f . m13 $ m) (f . m23 $ m) (f . m33 $ m)
228
229
230-- | Return the identity matrix.
231id :: Matrix4
232id = mat4
233 1 0 0 0
234 0 1 0 0
235 0 0 1 0
236 0 0 0 1
237
238
239-- | Create a translation matrix.
240transl :: Float -> Float -> Float -> Matrix4
241transl x y z = mat4
242 1 0 0 x
243 0 1 0 y
244 0 0 1 z
245 0 0 0 1
246
247
248-- | Create a translation matrix.
249translv :: Vector3 -> Matrix4
250translv v = mat4
251 1 0 0 (Vector3.x v)
252 0 1 0 (Vector3.y v)
253 0 0 1 (Vector3.z v)
254 0 0 0 1
255
256
257-- | Create a rotation matrix rotating about the X axis.
258-- The given angle must be in degrees.
259rotX :: Float -> Matrix4
260rotX angle = mat4
261 1 0 0 0
262 0 c (-s) 0
263 0 s c 0
264 0 0 0 1
265 where
266 s = sin . toRAD $ angle
267 c = cos . toRAD $ angle
268
269
270-- | Create a rotation matrix rotating about the Y axis.
271-- The given angle must be in degrees.
272rotY :: Float -> Matrix4
273rotY angle = mat4
274 c 0 s 0
275 0 1 0 0
276 (-s) 0 c 0
277 0 0 0 1
278 where
279 s = sin . toRAD $ angle
280 c = cos . toRAD $ angle
281
282
283-- | Create a rotation matrix rotating about the Z axis.
284-- The given angle must be in degrees.
285rotZ :: Float -> Matrix4
286rotZ angle = mat4
287 c (-s) 0 0
288 s c 0 0
289 0 0 1 0
290 0 0 0 1
291 where
292 s = sin . toRAD $ angle
293 c = cos . toRAD $ angle
294
295
296-- | Create a rotation matrix rotating about the specified axis.
297-- The given angle must be in degrees.
298axisAngle :: Vector3 -> Float -> Matrix4
299axisAngle v angle = mat4
300 (c + omc*x^2) (omc*xy-sz) (omc*xz+sy) 0
301 (omc*xy+sz) (c+omc*y^2) (omc*yz-sx) 0
302 (omc*xz-sy) (omc*yz+sx) (c+omc*z^2) 0
303 0 0 0 1
304 where
305 x = Vector3.x v
306 y = Vector3.y v
307 z = Vector3.z v
308 s = sin . toRAD $ angle
309 c = cos . toRAD $ angle
310 xy = x*y
311 xz = x*z
312 yz = y*z
313 sx = s*x
314 sy = s*y
315 sz = s*z
316 omc = 1 - c
317
318
319-- | Create a scale matrix.
320scale :: Float -> Float -> Float -> Matrix4
321scale sx sy sz = mat4
322 sx 0 0 0
323 0 sy 0 0
324 0 0 sz 0
325 0 0 0 1
326
327
328-- | Create a scale matrix.
329scalev :: Vector3 -> Matrix4
330scalev v = mat4
331 sx 0 0 0
332 0 sy 0 0
333 0 0 sz 0
334 0 0 0 1
335 where
336 sx = Vector3.x v
337 sy = Vector3.y v
338 sz = Vector3.z v
339
340
341-- | Create an X reflection matrix.
342reflectX :: Matrix4
343reflectX = mat4
344 (-1) 0 0 0
345 0 1 0 0
346 0 0 1 0
347 0 0 0 1
348
349
350-- | Create a Y reflection matrix.
351reflectY :: Matrix4
352reflectY = mat4
353 1 0 0 0
354 0 (-1) 0 0
355 0 0 1 0
356 0 0 0 1
357
358
359-- | Create a Z reflection matrix.
360reflectZ :: Matrix4
361reflectZ = mat4
362 1 0 0 0
363 0 1 0 0
364 0 0 (-1) 0
365 0 0 0 1
366
367
368-- | Create an orthogonal projection matrix.
369ortho :: Float -- ^ Left.
370 -> Float -- ^ Right.
371 -> Float -- ^ Bottom.
372 -> Float -- ^ Top.
373 -> Float -- ^ Near clip.
374 -> Float -- ^ Far clip.
375 -> Matrix4
376
377ortho l r b t n f =
378 let tx = (-(r+l)/(r-l))
379 ty = (-(t+b)/(t-b))
380 tz = (-(f+n)/(f-n))
381 in mat4
382 (2/(r-l)) 0 0 tx
383 0 (2/(t-b)) 0 ty
384 0 0 ((-2)/(f-n)) tz
385 0 0 0 1
386
387
388-- | Create a perspective projection matrix.
389perspective :: Float -- ^ Fovy - Vertical field of view angle in degrees.
390 -> Float -- ^ Aspect ratio.
391 -> Float -- ^ Near clip distance.
392 -> Float -- ^ Far clip distance
393 -> Matrix4
394perspective fovy r near far =
395 let f = 1 / tan (toRAD fovy / 2)
396 a = near - far
397 in mat4
398 (f/r) 0 0 0
399 0 f 0 0
400 0 0 ((near+far)/a) (2*near*far/a)
401 0 0 (-1) 0
402
403
404-- | Transpose the specified matrix.
405transpose :: Matrix4 -> Matrix4
406transpose m = mat4
407 (m00 m) (m01 m) (m02 m) (m03 m)
408 (m10 m) (m11 m) (m12 m) (m13 m)
409 (m20 m) (m21 m) (m22 m) (m23 m)
410 (m30 m) (m31 m) (m32 m) (m33 m)
411
412
413-- | Invert the given transformation matrix.
414inverseTransform :: Matrix4 -> Matrix4
415inverseTransform mat = mat4fromVec u v w p where
416 u = vec4 (Vector4.x $ col0 mat) (Vector4.y $ col0 mat) (Vector4.z $ col0 mat) 0
417 v = vec4 (Vector4.x $ col1 mat) (Vector4.y $ col1 mat) (Vector4.z $ col1 mat) 0
418 w = vec4 (Vector4.x $ col2 mat) (Vector4.y $ col2 mat) (Vector4.z $ col2 mat) 0
419 p = vec4 tdotu tdotv tdotw 1
420 t = -(col3 mat)
421 tdotu = t `Vector4.dot` u
422 tdotv = t `Vector4.dot` v
423 tdotw = t `Vector4.dot` w
424
425
426-- | Invert the given matrix.
427{-inverse :: Matrix4 -> Matrix4
428inverse mat = mat4 i0 i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12 i13 i14 i15
429 where
430 i0 = -}
431
432
433-- | Transform the given vector in 3D space with the given matrix.
434mul :: Float -> Matrix4 -> Vector3 -> Vector3
435mul w m v = vec3 x' y' z'
436 where
437 v' = vec4 (Vector3.x v) (Vector3.y v) (Vector3.z v) w
438 x' = row0 m `Vector4.dot` v'
439 y' = row1 m `Vector4.dot` v'
440 z' = row2 m `Vector4.dot` v'
441
442
443-- | Transform the given point vector in 3D space with the given matrix.
444mulp :: Matrix4 -> Vector3 -> Vector3
445mulp = mul 1
446
447
448-- | Transform the given directional vector in 3D space with the given matrix.
449muld :: Matrix4 -> Vector3 -> Vector3
450muld = mul 0
451
452
453toRAD = (*pi) . (/180)
diff --git a/Spear/Math/MatrixUtils.hs b/Spear/Math/MatrixUtils.hs
new file mode 100644
index 0000000..88ad3b1
--- /dev/null
+++ b/Spear/Math/MatrixUtils.hs
@@ -0,0 +1,18 @@
1module Spear.Math.MatrixUtils
2(
3 fastNormalMatrix
4)
5where
6
7
8import Spear.Math.Matrix3 as M3
9import Spear.Math.Matrix4 as M4
10
11
12fastNormalMatrix :: Matrix4 -> Matrix3
13fastNormalMatrix m =
14 let m' = M4.transpose . M4.inverseTransform $ m
15 in M3.mat3
16 (M4.m00 m') (M4.m10 m') (M4.m20 m')
17 (M4.m01 m') (M4.m11 m') (M4.m21 m')
18 (M4.m02 m') (M4.m12 m') (M4.m22 m')
diff --git a/Spear/Math/Octree.hs b/Spear/Math/Octree.hs
new file mode 100644
index 0000000..74689a0
--- /dev/null
+++ b/Spear/Math/Octree.hs
@@ -0,0 +1,282 @@
1module Spear.Math.Octree
2(
3 Octree
4, makeOctree
5, clone
6, Spear.Math.Octree.insert
7, insertl
8, Spear.Math.Octree.map
9, gmap
10, population
11)
12where
13
14import Spear.Collision.AABB as AABB
15import Spear.Collision.Types
16import Spear.Math.Vector3 as Vector
17
18import Control.Applicative ((<*>))
19import Data.List
20import Data.Functor
21import Data.Monoid
22import qualified Data.Foldable as F
23
24
25-- | Represents an Octree.
26data Octree e
27 = Octree
28 {
29 root :: AABB,
30 ents :: [e],
31 c1 :: Octree e,
32 c2 :: Octree e,
33 c3 :: Octree e,
34 c4 :: Octree e,
35 c5 :: Octree e,
36 c6 :: Octree e,
37 c7 :: Octree e,
38 c8 :: Octree e
39 }
40 |
41 Leaf
42 {
43 root :: AABB,
44 ents :: [e]
45 }
46
47
48-- | Builds an Octree using the specified AABB as the root and having the specified depth.
49makeOctree :: Int -> AABB -> Octree e
50makeOctree d root@(AABB min max)
51 | d == 0 = Leaf root []
52 | otherwise = Octree root [] c1 c2 c3 c4 c5 c6 c7 c8
53 where
54 boxes = subdivide root
55 c1 = makeOctree (d-1) $ boxes !! 0
56 c2 = makeOctree (d-1) $ boxes !! 1
57 c3 = makeOctree (d-1) $ boxes !! 2
58 c4 = makeOctree (d-1) $ boxes !! 3
59 c5 = makeOctree (d-1) $ boxes !! 4
60 c6 = makeOctree (d-1) $ boxes !! 5
61 c7 = makeOctree (d-1) $ boxes !! 6
62 c8 = makeOctree (d-1) $ boxes !! 7
63
64
65subdivide :: AABB -> [AABB]
66subdivide (AABB min max) = [a1, a2, a3, a4, a5, a6, a7, a8]
67 where
68 v = (max-min) / 2
69 c = vec3 (x min + x v) (y min + y v) (z min + z v)
70 a1 = AABB min c
71 a2 = AABB ( vec3 (x min) (y min) (z c) ) ( vec3 (x c) (y c) (z max) )
72 a3 = AABB ( vec3 (x min) (y c) (z min) ) ( vec3 (x c) (y max) (z c) )
73 a4 = AABB ( vec3 (x min) (y c) (z c) ) ( vec3 (x c) (y max) (z max) )
74 a5 = AABB ( vec3 (x c) (y min) (z min) ) ( vec3 (x max) (y c) (z c) )
75 a6 = AABB ( vec3 (x c) (y min) (z c) ) ( vec3 (x max) (y c) (z max) )
76 a7 = AABB ( vec3 (x c) (y c) (z min) ) ( vec3 (x max) (y max) (z c) )
77 a8 = AABB c max
78
79
80-- | Clones the structure of an octree. The new octree has no entities.
81clone :: Octree e -> Octree e
82clone (Leaf root ents) = Leaf root []
83clone (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) = Octree root [] c1' c2' c3' c4' c5' c6' c7' c8'
84 where
85 c1' = clone c1
86 c2' = clone c2
87 c3' = clone c3
88 c4' = clone c4
89 c5' = clone c5
90 c6' = clone c6
91 c7' = clone c7
92 c8' = clone c8
93
94
95keep :: (e -> AABB -> CollisionType) -> AABB -> e -> Bool
96keep testAABB aabb e = test == FullyContainedBy || test == Equal
97 where test = e `testAABB` aabb
98
99
100-- | Inserts an entity into the given octree.
101insert :: (e -> AABB -> CollisionType) -> Octree e -> e -> Octree e
102insert testAABB octree e = octree' where (octree', _) = insert' testAABB e octree
103
104
105insert' :: (e -> AABB -> CollisionType) -> e -> Octree e -> (Octree e, Bool)
106
107
108insert' testAABB e l@(Leaf root ents)
109 | test == True = (Leaf root (e:ents), True)
110 | otherwise = (l, False)
111 where
112 test = keep testAABB root e
113
114
115insert' testAABB e o@(Octree root ents c1 c2 c3 c4 c5 c6 c7 c8)
116 | test == False = (o, False)
117 | otherwise =
118 if isContainedInChild then (Octree root ents c1' c2' c3' c4' c5' c6' c7' c8', True)
119 else (Octree root (e:ents) c1 c2 c3 c4 c5 c6 c7 c8, True)
120 where
121 children = [c1,c2,c3,c4,c5,c6,c7,c8]
122 test = keep testAABB root e
123 descend = fmap (Spear.Math.Octree.insert' testAABB e) children
124 (children', results) = unzip descend
125 isContainedInChild = or results
126 c1' = children' !! 0
127 c2' = children' !! 1
128 c3' = children' !! 2
129 c4' = children' !! 3
130 c5' = children' !! 4
131 c6' = children' !! 5
132 c7' = children' !! 6
133 c8' = children' !! 7
134
135
136-- | Inserts a list of entities into the given octree.
137insertl :: (e -> AABB -> CollisionType) -> Octree e -> [e] -> Octree e
138insertl testAABB octree es = octree' where (octree', _) = insertl' testAABB es octree
139
140
141insertl' :: (e -> AABB -> CollisionType) -> [e] -> Octree e -> (Octree e, [e])
142
143insertl' testAABB es (Leaf root ents) = (Leaf root ents', outliers)
144 where
145 ents' = ents ++ ents_kept
146 ents_kept = filter (keep testAABB root) es
147 outliers = filter (not . keep testAABB root) es
148
149insertl' testAABB es (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) =
150 (Octree root ents' c1' c2' c3' c4' c5' c6' c7' c8', outliers)
151 where
152 ents' = ents ++ ents_kept
153 new_ents = es ++ ents1 ++ ents2 ++ ents3 ++ ents4 ++ ents5 ++ ents6 ++ ents7 ++ ents8
154 ents_kept = filter (keep testAABB root) new_ents
155 outliers = filter (not . keep testAABB root) new_ents
156 (c1', ents1) = insertl' testAABB es c1
157 (c2', ents2) = insertl' testAABB es c2
158 (c3', ents3) = insertl' testAABB es c3
159 (c4', ents4) = insertl' testAABB es c4
160 (c5', ents5) = insertl' testAABB es c5
161 (c6', ents6) = insertl' testAABB es c6
162 (c7', ents7) = insertl' testAABB es c7
163 (c8', ents8) = insertl' testAABB es c8
164
165
166-- | Extracts all entities from an octree. The resulting octree has no entities.
167extract :: Octree e -> (Octree e, [e])
168extract (Leaf root ents) = (Leaf root [], ents)
169extract (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) = (Octree root [] c1' c2' c3' c4' c5' c6' c7' c8', ents')
170 where
171 (c1', ents1) = extract c1
172 (c2', ents2) = extract c2
173 (c3', ents3) = extract c3
174 (c4', ents4) = extract c4
175 (c5', ents5) = extract c5
176 (c6', ents6) = extract c6
177 (c7', ents7) = extract c7
178 (c8', ents8) = extract c8
179 ents' = ents ++ ents1 ++ ents2 ++ ents3 ++ ents4 ++ ents5 ++ ents6 ++ ents7 ++ ents8
180
181
182-- | Applies the given function to the entities in the octree.
183-- Entities that break out of their cell are reallocated appropiately.
184map :: (e -> AABB -> CollisionType) -> (e -> e) -> Octree e -> Octree e
185map testAABB f o = let (o', outliers) = map' testAABB f o in insertl testAABB o' outliers
186
187
188map' :: (e -> AABB -> CollisionType) -> (e -> e) -> Octree e -> (Octree e, [e])
189
190
191map' testAABB f (Leaf root ents) = (Leaf root ents_kept, outliers)
192 where
193 ents' = fmap f ents
194 ents_kept = filter (keep testAABB root) ents'
195 outliers = filter (not . keep testAABB root) ents'
196
197
198map' testAABB f (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) =
199 (Octree root ents_kept c1' c2' c3' c4' c5' c6' c7' c8', outliers)
200 where
201 ents' = (fmap f ents) ++ out1 ++ out2 ++ out3 ++ out4 ++ out5 ++ out6 ++ out7 ++ out8
202 ents_kept = filter (keep testAABB root) ents'
203 outliers = filter (not . keep testAABB root) ents'
204 (c1', out1) = map' testAABB f c1
205 (c2', out2) = map' testAABB f c2
206 (c3', out3) = map' testAABB f c3
207 (c4', out4) = map' testAABB f c4
208 (c5', out5) = map' testAABB f c5
209 (c6', out6) = map' testAABB f c6
210 (c7', out7) = map' testAABB f c7
211 (c8', out8) = map' testAABB f c8
212
213
214-- | Applies a function to the entity groups in the octree.
215-- Entities that break out of their cell are reallocated appropiately.
216gmap :: (e -> AABB -> CollisionType) -> (e -> e -> e) -> Octree e -> Octree e
217gmap testAABB f o = let (o', outliers) = gmap' testAABB f o in insertl testAABB o' outliers
218
219
220gmap' :: (e -> AABB -> CollisionType) -> (e -> e -> e) -> Octree e -> (Octree e, [e])
221
222gmap' testAABB f (Leaf root ents) = (Leaf root ents_kept, outliers)
223 where
224 ents' = f <$> ents <*> ents
225 ents_kept = filter (keep testAABB root) ents'
226 outliers = filter (not . keep testAABB root) ents'
227
228gmap' testAABB f (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) =
229 (Octree root ents_kept c1' c2' c3' c4' c5' c6' c7' c8', outliers)
230 where
231 ents' = (f <$> ents <*> ents) ++ out1 ++ out2 ++ out3 ++ out4 ++ out5 ++ out6 ++ out7 ++ out8
232 ents_kept = filter (keep testAABB root) ents'
233 outliers = filter (not . keep testAABB root) ents'
234 (c1', out1) = gmap' testAABB f c1
235 (c2', out2) = gmap' testAABB f c2
236 (c3', out3) = gmap' testAABB f c3
237 (c4', out4) = gmap' testAABB f c4
238 (c5', out5) = gmap' testAABB f c5
239 (c6', out6) = gmap' testAABB f c6
240 (c7', out7) = gmap' testAABB f c7
241 (c8', out8) = gmap' testAABB f c8
242
243
244population :: Octree e -> Int
245population = F.foldr (\_ acc -> acc+1) 0
246
247
248
249
250instance Functor Octree where
251 fmap f (Leaf root ents) = Leaf root $ fmap f ents
252
253 fmap f (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) =
254 Octree root (fmap f ents) c1' c2' c3' c4' c5' c6' c7' c8'
255 where
256 c1' = fmap f c1
257 c2' = fmap f c2
258 c3' = fmap f c3
259 c4' = fmap f c4
260 c5' = fmap f c5
261 c6' = fmap f c6
262 c7' = fmap f c7
263 c8' = fmap f c8
264
265
266
267instance F.Foldable Octree where
268 foldMap f (Leaf root ents) = mconcat . fmap f $ ents
269
270 foldMap f (Octree root ents c1 c2 c3 c4 c5 c6 c7 c8) =
271 mconcat (fmap f ents) `mappend`
272 c1' `mappend` c2' `mappend` c3' `mappend` c4' `mappend`
273 c5' `mappend` c6' `mappend` c7' `mappend` c8'
274 where
275 c1' = F.foldMap f c1
276 c2' = F.foldMap f c2
277 c3' = F.foldMap f c3
278 c4' = F.foldMap f c4
279 c5' = F.foldMap f c5
280 c6' = F.foldMap f c6
281 c7' = F.foldMap f c7
282 c8' = F.foldMap f c8
diff --git a/Spear/Math/Plane.hs b/Spear/Math/Plane.hs
new file mode 100644
index 0000000..0f5829b
--- /dev/null
+++ b/Spear/Math/Plane.hs
@@ -0,0 +1,33 @@
1module Spear.Math.Plane
2(
3 Plane
4, plane
5, classify
6)
7where
8
9
10import Spear.Math.Vector3 as Vector
11
12
13data PointPlanePos = Front | Back | Contained deriving (Eq, Ord, Show)
14
15
16data Plane = Plane {
17 n :: !Vector3,
18 d :: !Float
19} deriving(Eq, Show)
20
21
22-- | Create a plane given a normal vector and a distance from the origin.
23plane :: Vector3 -> Float -> Plane
24plane n d = Plane (normalise n) d
25
26
27-- | Classify the given point's relative position with respect to the given plane.
28classify :: Plane -> Vector3 -> PointPlanePos
29classify (Plane n d) pt = case (n `dot` pt - d) `compare` 0 of
30 GT -> Front
31 LT -> Back
32 EQ -> Contained
33 \ No newline at end of file
diff --git a/Spear/Math/Quaternion.hs b/Spear/Math/Quaternion.hs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Spear/Math/Quaternion.hs
diff --git a/Spear/Math/Spatial.hs b/Spear/Math/Spatial.hs
new file mode 100644
index 0000000..d925f6f
--- /dev/null
+++ b/Spear/Math/Spatial.hs
@@ -0,0 +1,84 @@
1module Spear.Math.Spatial
2where
3
4
5import Spear.Math.Vector3
6import Spear.Math.Matrix4 as M
7
8
9class Spatial s where
10 -- | Move the 'Spatial'.
11 move :: Vector3 -> s -> s
12
13 -- | Move the 'Spatial' forwards.
14 moveFwd :: Float -> s -> s
15
16 -- | Move the 'Spatial' backwards.
17 moveBack :: Float -> s -> s
18
19 -- | Make the 'Spatial' strafe left.
20 strafeLeft :: Float -> s -> s
21
22 -- | Make the 'Spatial' Strafe right.
23 strafeRight :: Float -> s -> s
24
25 -- | Rotate the 'Spatial' about its local X axis.
26 pitch :: Float -> s -> s
27
28 -- | Rotate the 'Spatial' about its local Y axis.
29 yaw :: Float -> s -> s
30
31 -- | Rotate the 'Spatial' about its local Z axis.
32 roll :: Float -> s -> s
33
34 -- | Get the 'Spatial''s position.
35 pos :: s -> Vector3
36
37 -- | Get the 'Spatial''s forward vector.
38 fwd :: s -> Vector3
39
40 -- | Get the 'Spatial''s up vector.
41 up :: s -> Vector3
42
43 -- | Get the 'Spatial''s right vector.
44 right :: s -> Vector3
45
46 -- | Get the 'Spatial''s transform.
47 transform :: s -> Matrix4
48
49 -- | Set the 'Spatial''s transform.
50 setTransform :: Matrix4 -> s -> s
51
52 -- | Make the 'Spatial' look at the given point.
53 lookAt :: Vector3 -> s -> s
54 lookAt pt s =
55 let position = pos s
56 fwd = normalise $ pt - position
57 r = fwd `cross` unitY
58 u = r `cross` fwd
59 in
60 setTransform (M.transform r u (-fwd) position) s
61
62 -- | Make the 'Spatial' orbit around the given point
63 orbit :: Vector3 -- ^ Target point
64 -> Float -- ^ Horizontal angle
65 -> Float -- ^ Vertical angle
66 -> Float -- ^ Orbit radius.
67 -> s
68 -> s
69
70 orbit pt anglex angley radius s =
71 let ax = anglex * pi / 180
72 ay = angley * pi / 180
73 sx = sin ax
74 sy = sin ay
75 cx = cos ax
76 cy = cos ay
77 px = (x pt) + radius*cy*sx
78 py = (y pt) + radius*sy
79 pz = (z pt) + radius*cx*cy
80 r = Spear.Math.Spatial.right s
81 u = Spear.Math.Spatial.up s
82 f = Spear.Math.Spatial.fwd s
83 in
84 setTransform (M.transform u r f (vec3 px py pz)) s
diff --git a/Spear/Math/Vector3.hs b/Spear/Math/Vector3.hs
new file mode 100644
index 0000000..fad6e01
--- /dev/null
+++ b/Spear/Math/Vector3.hs
@@ -0,0 +1,217 @@
1module Spear.Math.Vector3
2(
3 Vector3
4 -- * Accessors
5, x
6, y
7, z
8 -- * Construction
9, unitX
10, unitY
11, unitZ
12, zero
13, fromList
14, vec3
15, orbit
16 -- * Operations
17, Spear.Math.Vector3.min
18, Spear.Math.Vector3.max
19, Spear.Math.Vector3.zipWith
20, Spear.Math.Vector3.map
21, dot
22, cross
23, normSq
24, norm
25, scale
26, normalise
27, neg
28)
29where
30
31import Foreign.C.Types (CFloat)
32import Foreign.Storable
33
34
35-- | Represents a vector in 3D.
36data Vector3 = Vector3 !Float !Float !Float deriving (Eq, Show)
37
38
39instance Num Vector3 where
40 Vector3 ax ay az + Vector3 bx by bz = Vector3 (ax + bx) (ay + by) (az + bz)
41 Vector3 ax ay az - Vector3 bx by bz = Vector3 (ax - bx) (ay - by) (az - bz)
42 Vector3 ax ay az * Vector3 bx by bz = Vector3 (ax * bx) (ay * by) (az * bz)
43 abs (Vector3 ax ay az) = Vector3 (abs ax) (abs ay) (abs az)
44 signum (Vector3 ax ay az) = Vector3 (signum ax) (signum ay) (signum az)
45 fromInteger i = Vector3 i' i' i' where i' = fromInteger i
46
47
48instance Fractional Vector3 where
49 Vector3 ax ay az / Vector3 bx by bz = Vector3 (ax / bx) (ay / by) (az / bz)
50 fromRational r = Vector3 r' r' r' where r' = fromRational r
51
52
53instance Ord Vector3 where
54 Vector3 ax ay az <= Vector3 bx by bz
55 = (ax <= bx)
56 || (az == bx && ay <= by)
57 || (ax == bx && ay == by && az <= bz)
58
59 Vector3 ax ay az >= Vector3 bx by bz
60 = (ax >= bx)
61 || (ax == bx && ay >= by)
62 || (ax == bx && ay == by && az >= bz)
63
64 Vector3 ax ay az < Vector3 bx by bz
65 = (ax < bx)
66 || (az == bx && ay < by)
67 || (ax == bx && ay == by && az < bz)
68
69 Vector3 ax ay az > Vector3 bx by bz
70 = (ax > bx)
71 || (ax == bx && ay > by)
72 || (ax == bx && ay == by && az > bz)
73
74
75sizeFloat = sizeOf (undefined :: CFloat)
76
77
78instance Storable Vector3 where
79 sizeOf _ = 3*sizeFloat
80 alignment _ = alignment (undefined :: CFloat)
81
82 peek ptr = do
83 ax <- peekByteOff ptr 0
84 ay <- peekByteOff ptr $ 1*sizeFloat
85 az <- peekByteOff ptr $ 2*sizeFloat
86 return (Vector3 ax ay az)
87
88 poke ptr (Vector3 ax ay az) = do
89 pokeByteOff ptr 0 ax
90 pokeByteOff ptr (1*sizeFloat) ay
91 pokeByteOff ptr (2*sizeFloat) az
92
93
94x (Vector3 ax _ _ ) = ax
95y (Vector3 _ ay _ ) = ay
96z (Vector3 _ _ az) = az
97
98
99-- | Unit vector along the X axis.
100unitX :: Vector3
101unitX = Vector3 1 0 0
102
103
104-- | Unit vector along the Y axis.
105unitY :: Vector3
106unitY = Vector3 0 1 0
107
108
109-- | Unit vector along the Z axis.
110unitZ :: Vector3
111unitZ = Vector3 0 0 1
112
113
114-- | Zero vector.
115zero :: Vector3
116zero = Vector3 0 0 0
117
118
119-- | Create a vector from the given list.
120fromList :: [Float] -> Vector3
121fromList (ax:ay:az:_) = Vector3 ax ay az
122
123
124-- | Create a 3D vector from the given values.
125vec3 :: Float -> Float -> Float -> Vector3
126vec3 ax ay az = Vector3 ax ay az
127
128
129-- | Create a 3D vector as a point on a sphere.
130orbit :: Vector3 -- ^ Sphere center.
131 -> Float -- ^ Sphere radius
132 -> Float -- ^ Azimuth angle.
133 -> Float -- ^ Zenith angle.
134 -> Vector3
135
136orbit center radius anglex angley =
137 let ax = anglex * pi / 180
138 ay = angley * pi / 180
139 sx = sin ax
140 sy = sin ay
141 cx = cos ax
142 cy = cos ay
143 px = (x center) + radius*cy*sx
144 py = (y center) + radius*sy
145 pz = (z center) + radius*cx*cy
146 in
147 vec3 px py pz
148
149
150-- | Create a vector with components set to the minimum of each of the given vectors'.
151min :: Vector3 -> Vector3 -> Vector3
152min (Vector3 ax ay az) (Vector3 bx by bz) = Vector3 (Prelude.min ax bx) (Prelude.min ay by) (Prelude.min az bz)
153
154
155-- | Create a vector with components set to the maximum of each of the given vectors'.
156max :: Vector3 -> Vector3 -> Vector3
157max (Vector3 ax ay az) (Vector3 bx by bz) = Vector3 (Prelude.max ax bx) (Prelude.max ay by) (Prelude.max az bz)
158
159
160-- | Zip two vectors with the given function.
161zipWith :: (Float -> Float -> Float) -> Vector3 -> Vector3 -> Vector3
162zipWith f (Vector3 ax ay az) (Vector3 bx by bz) = Vector3 (f ax bx) (f ay by) (f az bz)
163
164
165-- | Folds a vector from the left.
166{-foldl :: (UV.Unbox b) => (a -> b -> a) -> a -> Vector3 b -> a
167foldl f acc (Vector3 v) = UV.foldl f acc v
168
169
170-- | Folds a vector from the right.
171foldr :: (UV.Unbox b) => (b -> a -> a) -> a -> Vector3 b -> a
172foldr f acc (Vector3 v) = UV.foldr f acc v-}
173
174
175-- | Map the given function over the given vector.
176map :: (Float -> Float) -> Vector3 -> Vector3
177map f (Vector3 ax ay az) = Vector3 (f ax) (f ay) (f az)
178
179
180-- | Compute the given vectors' dot product.
181dot :: Vector3 -> Vector3 -> Float
182Vector3 ax ay az `dot` Vector3 bx by bz = ax*bx + ay*by + az*bz
183
184
185-- | Compute the given vectors' cross product.
186cross :: Vector3 -> Vector3 -> Vector3
187(Vector3 ax ay az) `cross` (Vector3 bx by bz) =
188 Vector3 (ay * bz - az * by) (az * bx - ax * bz) (ax * by - ay * bx)
189
190
191-- | Compute the given vector's squared norm.
192normSq :: Vector3 -> Float
193normSq (Vector3 ax ay az) = ax*ax + ay*ay + az*az
194
195
196-- | Compute the given vector's norm.
197norm :: Vector3 -> Float
198norm = sqrt . normSq
199
200
201-- | Multiply the given vector with the given scalar.
202scale :: Float -> Vector3 -> Vector3
203scale s (Vector3 ax ay az) = Vector3 (s*ax) (s*ay) (s*az)
204
205
206-- | Normalise the given vector.
207normalise :: Vector3 -> Vector3
208normalise v =
209 let n' = norm v
210 n = if n' == 0 then 1 else n'
211 in
212 scale (1.0 / n) v
213
214
215-- | Negate the given vector.
216neg :: Vector3 -> Vector3
217neg (Vector3 ax ay az) = Vector3 (-ax) (-ay) (-az)
diff --git a/Spear/Math/Vector4.hs b/Spear/Math/Vector4.hs
new file mode 100644
index 0000000..2dd852a
--- /dev/null
+++ b/Spear/Math/Vector4.hs
@@ -0,0 +1,200 @@
1module Spear.Math.Vector4
2(
3 Vector4
4 -- * Accessors
5, x
6, y
7, z
8, w
9 -- * Construction
10, unitX
11, unitY
12, unitZ
13, fromList
14, vec4
15 -- * Operations
16, Spear.Math.Vector4.min
17, Spear.Math.Vector4.max
18, Spear.Math.Vector4.zipWith
19, Spear.Math.Vector4.map
20, dot
21, normSq
22, norm
23, scale
24, normalise
25, neg
26)
27where
28
29
30import Foreign.C.Types (CFloat)
31import Foreign.Storable
32
33
34-- | Represents a vector in 3D.
35data Vector4 = Vector4 !Float !Float !Float !Float deriving (Eq, Show)
36
37
38instance Num Vector4 where
39 Vector4 ax ay az aw + Vector4 bx by bz bw = Vector4 (ax + bx) (ay + by) (az + bz) (aw + bw)
40 Vector4 ax ay az aw - Vector4 bx by bz bw = Vector4 (ax - bx) (ay - by) (az - bz) (aw - bw)
41 Vector4 ax ay az aw * Vector4 bx by bz bw = Vector4 (ax * bx) (ay * by) (az * bz) (aw * bw)
42 abs (Vector4 ax ay az aw) = Vector4 (abs ax) (abs ay) (abs az) (abs aw)
43 signum (Vector4 ax ay az aw) = Vector4 (signum ax) (signum ay) (signum az) (signum aw)
44 fromInteger i = Vector4 i' i' i' i' where i' = fromInteger i
45
46
47instance Fractional Vector4 where
48 Vector4 ax ay az aw / Vector4 bx by bz bw = Vector4 (ax / bx) (ay / by) (az / bz) (aw / bw)
49 fromRational r = Vector4 r' r' r' r' where r' = fromRational r
50
51
52instance Ord Vector4 where
53 Vector4 ax ay az aw <= Vector4 bx by bz bw
54 = (ax <= bx)
55 || (az == bx && ay <= by)
56 || (ax == bx && ay == by && az <= bz)
57 || (ax == bx && ay == by && az == bz && aw <= bw)
58
59 Vector4 ax ay az aw >= Vector4 bx by bz bw
60 = (ax >= bx)
61 || (ax == bx && ay >= by)
62 || (ax == bx && ay == by && az >= bz)
63 || (ax == bx && ay == by && az == bz && aw >= bw)
64
65 Vector4 ax ay az aw < Vector4 bx by bz bw
66 = (ax < bx)
67 || (az == bx && ay < by)
68 || (ax == bx && ay == by && az < bz)
69 || (ax == bx && ay == by && az == bz && aw < bw)
70
71 Vector4 ax ay az aw > Vector4 bx by bz bw
72 = (ax > bx)
73 || (ax == bx && ay > by)
74 || (ax == bx && ay == by && az > bz)
75 || (ax == bx && ay == by && az == bz && aw > bw)
76
77
78sizeFloat = sizeOf (undefined :: CFloat)
79
80
81instance Storable Vector4 where
82 sizeOf _ = 4*sizeFloat
83 alignment _ = alignment (undefined :: CFloat)
84
85 peek ptr = do
86 ax <- peekByteOff ptr 0
87 ay <- peekByteOff ptr $ 1 * sizeFloat
88 az <- peekByteOff ptr $ 2 * sizeFloat
89 aw <- peekByteOff ptr $ 3 * sizeFloat
90 return (Vector4 ax ay az aw)
91
92 poke ptr (Vector4 ax ay az aw) = do
93 pokeByteOff ptr 0 ax
94 pokeByteOff ptr (1 * sizeFloat) ay
95 pokeByteOff ptr (2 * sizeFloat) az
96 pokeByteOff ptr (3 * sizeFloat) aw
97
98
99x (Vector4 ax _ _ _ ) = ax
100y (Vector4 _ ay _ _ ) = ay
101z (Vector4 _ _ az _ ) = az
102w (Vector4 _ _ _ aw) = aw
103
104
105-- | Unit vector along the X axis.
106unitX :: Vector4
107unitX = Vector4 1 0 0 0
108
109
110-- | Unit vector along the Y axis.
111unitY :: Vector4
112unitY = Vector4 0 1 0 0
113
114
115-- | Unit vector along the Z axis.
116unitZ :: Vector4
117unitZ = Vector4 0 0 1 0
118
119
120-- | Create a vector from the given list.
121fromList :: [Float] -> Vector4
122fromList (ax:ay:az:aw:_) = Vector4 ax ay az aw
123
124
125-- | Create a 4D vector from the given values.
126vec4 :: Float -> Float -> Float -> Float -> Vector4
127vec4 ax ay az aw = Vector4 ax ay az aw
128
129
130-- | Create a vector whose components are the minimum of each of the given vectors'.
131min :: Vector4 -> Vector4 -> Vector4
132min (Vector4 ax ay az aw) (Vector4 bx by bz bw) =
133 Vector4 (Prelude.min ax bx) (Prelude.min ay by) (Prelude.min az bz) (Prelude.min aw bw)
134
135
136-- | Create a vector whose components are the maximum of each of the given vectors'.
137max :: Vector4 -> Vector4 -> Vector4
138max (Vector4 ax ay az aw) (Vector4 bx by bz bw) =
139 Vector4 (Prelude.max ax bx) (Prelude.max ay by) (Prelude.max az bz) (Prelude.min aw bw)
140
141
142-- | Zip two vectors with the given function.
143zipWith :: (Float -> Float -> Float) -> Vector4 -> Vector4 -> Vector4
144zipWith f (Vector4 ax ay az aw) (Vector4 bx by bz bw) = Vector4 (f ax bx) (f ay by) (f az bz) (f aw bw)
145
146
147-- | Folds a vector from the left.
148{-foldl :: (UV.Unbox b) => (a -> b -> a) -> a -> Vector4 b -> a
149foldl f acc (Vector4 v) = UV.foldl f acc v
150
151
152-- | Folds a vector from the right.
153foldr :: (UV.Unbox b) => (b -> a -> a) -> a -> Vector4 b -> a
154foldr f acc (Vector4 v) = UV.foldr f acc v-}
155
156
157-- | Map the given function over the given vector.
158map :: (Float -> Float) -> Vector4 -> Vector4
159map f (Vector4 ax ay az aw) = Vector4 (f ax) (f ay) (f az) (f aw)
160
161
162-- | Compute the given vectors' dot product.
163dot :: Vector4 -> Vector4 -> Float
164Vector4 ax ay az aw `dot` Vector4 bx by bz bw = ax*bx + ay*by + az*bz + aw*bw
165
166
167-- | Compute the given vectors' cross product.
168-- The vectors are projected to 3D space. The resulting vector is the cross product of the vectors' projections with w=0.
169cross :: Vector4 -> Vector4 -> Vector4
170(Vector4 ax ay az _) `cross` (Vector4 bx by bz _) =
171 Vector4 (ay * bz - az * by) (az * bx - ax * bz) (ax * by - ay * bx) 0
172
173
174-- | Compute the given vector's squared norm.
175normSq :: Vector4 -> Float
176normSq (Vector4 ax ay az aw) = ax*ax + ay*ay + az*az + aw*aw
177
178
179-- | Compute the given vector's norm.
180norm :: Vector4 -> Float
181norm = sqrt . normSq
182
183
184-- | Multiply the given vector with the given scalar.
185scale :: Float -> Vector4 -> Vector4
186scale s (Vector4 ax ay az aw) = Vector4 (s*ax) (s*ay) (s*az) (s*aw)
187
188
189-- | Normalise the given vector.
190normalise :: Vector4 -> Vector4
191normalise v =
192 let n' = norm v
193 n = if n' == 0 then 1 else n'
194 in
195 scale (1.0 / n) v
196
197
198-- | Negate the given vector.
199neg :: Vector4 -> Vector4
200neg (Vector4 ax ay az aw) = Vector4 (-ax) (-ay) (-az) (-aw)
diff --git a/Spear/Render/AnimatedModel.hs b/Spear/Render/AnimatedModel.hs
new file mode 100644
index 0000000..cc31f12
--- /dev/null
+++ b/Spear/Render/AnimatedModel.hs
@@ -0,0 +1,183 @@
1module Spear.Render.AnimatedModel
2(
3 AnimatedModelResource
4, AnimatedModelRenderer
5, animatedModelResource
6, animatedModelRenderer
7, Spear.Render.AnimatedModel.release
8, setAnimation
9, currentAnimation
10, bind
11, render
12)
13where
14
15
16import Spear.Assets.Model
17import Spear.Render.Model
18import Spear.GLSL
19import Spear.Render.Material
20import Spear.Render.Program
21import Spear.Updatable
22import Spear.Setup as Setup
23
24import Control.Applicative ((<$>), (<*>))
25import Graphics.Rendering.OpenGL.Raw.Core31
26import Unsafe.Coerce (unsafeCoerce)
27
28
29-- | An animated model resource.
30--
31-- Contains model data necessary to render an animated model.
32data AnimatedModelResource = AnimatedModelResource
33 { model :: Model
34 , vao :: VAO
35 , nFrames :: Int
36 , nVertices :: Int
37 , material :: Material
38 , texture :: Texture
39 , rkey :: Resource
40 }
41
42
43instance Eq AnimatedModelResource where
44 m1 == m2 = vao m1 == vao m2
45
46
47instance Ord AnimatedModelResource where
48 m1 < m2 = vao m1 < vao m2
49
50
51-- | An animated model renderer.
52--
53-- Holds animation data necessary to render an animated model and a reference
54-- to an 'AnimatedModelResource'.
55--
56-- Model data is kept separate from animation data. This allows instances
57-- of 'AnimatedModelRenderer' to share the underlying 'AnimatedModelResource',
58-- minimising the amount of data in memory and allowing one to minimise OpenGL
59-- state changes by sorting 'AnimatedModelRenderer's by their underlying
60-- 'AnimatedModelResource' when rendering the scene.
61data AnimatedModelRenderer = AnimatedModelRenderer
62 { modelResource :: AnimatedModelResource
63 , currentAnim :: Int
64 , frameStart :: Int
65 , frameEnd :: Int
66 , currentFrame :: Int
67 , frameProgress :: Float
68 }
69
70
71instance Eq AnimatedModelRenderer where
72 m1 == m2 = modelResource m1 == modelResource m2
73
74
75instance Ord AnimatedModelRenderer where
76 m1 < m2 = modelResource m1 < modelResource m2
77
78
79-- | Create an 'AnimatedModelResource' from the given 'Model'.
80animatedModelResource :: AnimatedProgramChannels
81 -> Material
82 -> Texture
83 -> Model
84 -> Setup AnimatedModelResource
85
86animatedModelResource
87 (AnimatedProgramChannels vertChan1 vertChan2 normChan1 normChan2 texChan)
88 material texture model = do
89 RenderModel elements numFrames numVertices <- setupIO . renderModelFromModel $ model
90 elementBuf <- newBuffer
91 vao <- newVAO
92
93 setupIO $ do
94
95 let elemSize = 56
96 elemSize' = fromIntegral elemSize
97 n = numVertices * numFrames
98
99 bindVAO vao
100
101 bindBuffer elementBuf ArrayBuffer
102 bufferData ArrayBuffer (unsafeCoerce $ elemSize * n) elements StaticDraw
103
104 attribVAOPointer vertChan1 3 gl_FLOAT False elemSize' 0
105 attribVAOPointer vertChan2 3 gl_FLOAT False elemSize' 12
106 attribVAOPointer normChan1 3 gl_FLOAT False elemSize' 24
107 attribVAOPointer normChan2 3 gl_FLOAT False elemSize' 36
108 attribVAOPointer texChan 2 gl_FLOAT False elemSize' 48
109
110 enableVAOAttrib vertChan1
111 enableVAOAttrib vertChan2
112 enableVAOAttrib normChan1
113 enableVAOAttrib normChan2
114 enableVAOAttrib texChan
115
116 rkey <- register . runSetup_ $ do
117 setupIO $ putStrLn "Releasing animated model resource"
118 releaseVAO vao
119 releaseBuffer elementBuf
120
121 return $ AnimatedModelResource
122 model vao (unsafeCoerce numFrames) (unsafeCoerce numVertices) material texture rkey
123
124
125-- | Release the given 'AnimatedModelResource'.
126release :: AnimatedModelResource -> Setup ()
127release = Setup.release . rkey
128
129
130-- | Create an 'AnimatedModelRenderer' from the given 'AnimatedModelResource'.
131animatedModelRenderer :: AnimatedModelResource -> AnimatedModelRenderer
132animatedModelRenderer modelResource = AnimatedModelRenderer modelResource 0 0 0 0 0
133
134
135instance Updatable AnimatedModelRenderer where
136
137 update dt (AnimatedModelRenderer model curAnim startFrame endFrame curFrame fp) =
138 AnimatedModelRenderer model curAnim startFrame endFrame curFrame' fp'
139 where f = fp + dt
140 nextFrame = f >= 1.0
141 fp' = if nextFrame then f - 1.0 else f
142 curFrame' = if nextFrame
143 then let x = curFrame + 1 in if x > endFrame then startFrame else x
144 else curFrame
145
146
147-- | Set the active animation to the given one.
148setAnimation :: Enum a => a -> AnimatedModelRenderer -> AnimatedModelRenderer
149setAnimation anim modelRend =
150 let (Animation _ f1 f2) = animation (model . modelResource $ modelRend) anim'
151 anim' = fromEnum anim
152 in
153 modelRend { currentAnim = anim', frameStart = f1, frameEnd = f2, currentFrame = f1 }
154
155
156-- | Get the renderer's current animation.
157currentAnimation :: Enum a => AnimatedModelRenderer -> a
158currentAnimation = toEnum . currentAnim
159
160
161-- | Bind the given 'AnimatedModelRenderer' to prepare it for rendering.
162bind :: AnimatedProgramUniforms -> AnimatedModelRenderer -> IO ()
163bind (AnimatedProgramUniforms kaLoc kdLoc ksLoc shiLoc texLoc _ _ _ _) modelRend =
164 let model' = modelResource modelRend
165 in do
166 bindVAO . vao $ model'
167 bindTexture $ texture model'
168 activeTexture $= gl_TEXTURE0
169 glUniform1i texLoc 0
170
171
172-- | Render the model described by the given 'AnimatedModelRenderer'.
173render :: AnimatedProgramUniforms -> AnimatedModelRenderer -> IO ()
174render uniforms (AnimatedModelRenderer model _ _ _ curFrame fp) =
175 let n = nVertices model
176 (Material _ ka kd ks shi) = material model
177 in do
178 uniformVec4 (kaLoc uniforms) ka
179 uniformVec4 (kdLoc uniforms) kd
180 uniformVec4 (ksLoc uniforms) ks
181 glUniform1f (shiLoc uniforms) $ unsafeCoerce shi
182 glUniform1f (fpLoc uniforms) (unsafeCoerce fp)
183 drawArrays gl_TRIANGLES (n*curFrame) n
diff --git a/Spear/Render/Box.hs b/Spear/Render/Box.hs
new file mode 100644
index 0000000..5da6fa8
--- /dev/null
+++ b/Spear/Render/Box.hs
@@ -0,0 +1,193 @@
1module Spear.Render.Box
2(
3 render
4, renderOutwards
5, renderInwards
6, renderEdges
7)
8where
9
10
11import Spear.Math.Vector3
12import Spear.Math.Matrix
13import Graphics.Rendering.OpenGL.Raw
14import Unsafe.Coerce
15import Control.Monad.Instances
16
17type Center = Vector3
18type Colour = Vector4
19type Length = Float
20type Normals = [Vector3]
21type GenerateTexCoords = Bool
22
23
24applyColour :: Colour -> IO ()
25--applyColour col = glColor4f (unsafeCoerce $ x col) (unsafeCoerce $ y col) (unsafeCoerce $ z col) (unsafeCoerce $ w col)
26applyColour = do
27 ax <- unsafeCoerce . x
28 ay <- unsafeCoerce . y
29 az <- unsafeCoerce . z
30 aw <- unsafeCoerce . w
31 glColor4f ax ay az aw
32
33
34applyNormal :: Vector3 -> IO ()
35--applyNormal v = glNormal3f (unsafeCoerce $ x v) (unsafeCoerce $ y v) (unsafeCoerce $ z v)
36applyNormal = do
37 nx <- unsafeCoerce . x
38 ny <- unsafeCoerce . y
39 nz <- unsafeCoerce . z
40 glNormal3f nx ny nz
41
42
43-- | Renders a box.
44render :: Center -- ^ The box's center.
45 -> Length -- ^ The perpendicular distance from the box's center to any of its sides.
46 -> Colour -- ^ The box's colour.
47 -> Normals -- ^ The box's normals, of the form [front, back, right, left, top, bottom].
48 -> IO ()
49render c l col normals = do
50 glPushMatrix
51 glTranslatef (unsafeCoerce $ x c) (unsafeCoerce $ y c) (unsafeCoerce $ z c)
52 applyColour col
53
54 let d = unsafeCoerce l
55 glBegin gl_QUADS
56
57 --Front
58 --glNormal3f 0 0 (-1)
59 applyNormal $ normals !! 0
60 glVertex3f d (-d) (-d)
61 glVertex3f d d (-d)
62 glVertex3f (-d) d (-d)
63 glVertex3f (-d) (-d) (-d)
64
65 --Back
66 --glNormal3f 0 0 1
67 applyNormal $ normals !! 1
68 glVertex3f (-d) (-d) d
69 glVertex3f (-d) d d
70 glVertex3f d d d
71 glVertex3f d (-d) d
72
73 --Right
74 --glNormal3f 1 0 0
75 applyNormal $ normals !! 2
76 glVertex3f d (-d) (-d)
77 glVertex3f d (-d) d
78 glVertex3f d d d
79 glVertex3f d d (-d)
80
81 --Left
82 --glNormal3f (-1) 0 0
83 applyNormal $ normals !! 3
84 glVertex3f (-d) (-d) (-d)
85 glVertex3f (-d) d (-d)
86 glVertex3f (-d) d d
87 glVertex3f (-d) (-d) d
88
89 --Top
90 --glNormal3f 0 1 0
91 applyNormal $ normals !! 4
92 glVertex3f (-d) d (-d)
93 glVertex3f d d (-d)
94 glVertex3f d d d
95 glVertex3f (-d) d d
96
97 --Bottom
98 --glNormal3f 0 (-1) 0
99 applyNormal $ normals !! 5
100 glVertex3f d (-d) d
101 glVertex3f d (-d) (-d)
102 glVertex3f (-d) (-d) (-d)
103 glVertex3f (-d) (-d) d
104
105 glEnd
106
107 glPopMatrix
108
109
110normals = [vec3 0 0 (-1), vec3 0 0 1, vec3 1 0 0, vec3 (-1) 0 0, vec3 0 1 0, vec3 0 (-1) 0]
111
112
113-- | Renders a box with normals facing outwards.
114renderOutwards :: Center -- ^ The box's center.
115 -> Length -- ^ The perpendicular distance from the box's center to any of its sides.
116 -> Colour -- ^ The box's colour.
117 -> IO ()
118renderOutwards c l col = render c l col normals
119
120
121-- | Renders a box with normals facing inwards.
122renderInwards :: Center -- ^ The box's center.
123 -> Length -- ^ The perpendicular distance from the box's center to any of its sides.
124 -> Colour -- ^ The box's colour.
125 -> IO ()
126renderInwards c l col = do
127 glFrontFace gl_CW
128 render c l col $ Prelude.map neg normals
129 glFrontFace gl_CCW
130
131
132renderEdges :: Center -- ^ The box's center.
133 -> Length -- ^ The perpendicular distance from the box's center to any of its sides.
134 -> Colour -- ^ The box's colour.
135 -> IO ()
136renderEdges c l col = do
137 glPushMatrix
138 glTranslatef (unsafeCoerce $ x c) (unsafeCoerce $ y c) (unsafeCoerce $ z c)
139 applyColour col
140
141 let d = unsafeCoerce l
142
143 --Front
144 glBegin gl_LINE_STRIP
145 glVertex3f d (-d) (-d)
146 glVertex3f d d (-d)
147 glVertex3f (-d) d (-d)
148 glVertex3f (-d) (-d) (-d)
149 glEnd
150
151 --Back
152 glBegin gl_LINE_STRIP
153 glVertex3f (-d) (-d) d
154 glVertex3f (-d) d d
155 glVertex3f d d d
156 glVertex3f d (-d) d
157 glVertex3f (-d) (-d) d
158 glEnd
159
160 --Right
161 glBegin gl_LINE_STRIP
162 glVertex3f d (-d) (-d)
163 glVertex3f d (-d) d
164 glVertex3f d d d
165 glVertex3f d d (-d)
166 glEnd
167
168 --Left
169 glBegin gl_LINE_STRIP
170 glVertex3f (-d) (-d) (-d)
171 glVertex3f (-d) d (-d)
172 glVertex3f (-d) d d
173 glVertex3f (-d) (-d) d
174 glEnd
175
176 --Top
177 glBegin gl_LINE_STRIP
178 glVertex3f (-d) d (-d)
179 glVertex3f d d (-d)
180 glVertex3f d d d
181 glVertex3f (-d) d d
182 glEnd
183
184 --Bottom
185 glBegin gl_LINE_STRIP
186 glVertex3f d (-d) d
187 glVertex3f d (-d) (-d)
188 glVertex3f (-d) (-d) (-d)
189 glVertex3f (-d) (-d) d
190 glEnd
191
192 glPopMatrix
193 \ No newline at end of file
diff --git a/Spear/Render/Light.hs b/Spear/Render/Light.hs
new file mode 100644
index 0000000..cc1de1f
--- /dev/null
+++ b/Spear/Render/Light.hs
@@ -0,0 +1,25 @@
1module Spear.Render.Light
2where
3
4
5import Spear.Vector
6import Graphics.Rendering.OpenGL.Raw
7
8
9data LightData = LightData {
10 ambient :: Vector Float,
11 diffuse :: Vector Float,
12 spec :: Vector Float
13}
14
15
16data Light =
17 ParallelLight {
18 ld :: LightData
19 }
20| PointLight {
21 ld :: LightData
22 }
23| SpotLight {
24 ld :: LightData
25 }
diff --git a/Spear/Render/Material.hs b/Spear/Render/Material.hs
new file mode 100644
index 0000000..f504036
--- /dev/null
+++ b/Spear/Render/Material.hs
@@ -0,0 +1,16 @@
1module Spear.Render.Material
2( Material(..)
3)
4where
5
6
7import Spear.Math.Vector4
8
9
10data Material = Material
11 { ke :: Vector4
12 , ka :: Vector4
13 , kd :: Vector4
14 , ks :: Vector4
15 , shininess :: Float
16 }
diff --git a/Spear/Render/Model.hsc b/Spear/Render/Model.hsc
new file mode 100644
index 0000000..02a37ae
--- /dev/null
+++ b/Spear/Render/Model.hsc
@@ -0,0 +1,61 @@
1{-# LANGUAGE CPP, ForeignFunctionInterface #-}
2
3module Spear.Render.Model
4(
5 RenderModel(..)
6, renderModelFromModel
7)
8where
9
10
11import qualified Spear.Assets.Model as Assets
12import Spear.Setup
13
14import Foreign.Ptr
15import Foreign.C.Types
16import Foreign.Marshal.Alloc
17import Foreign.Marshal.Array
18import Foreign.Marshal.Utils (with)
19import Foreign.Storable
20
21
22#include "RenderModel.h"
23
24
25data Vec3 = Vec3 !CFloat !CFloat !CFloat
26
27data TexCoord = TexCoord !CFloat !CFloat
28
29
30data RenderModel = RenderModel
31 { elements :: Ptr CChar
32 , numFrames :: CUInt
33 , numVertices :: CUInt -- ^ Number of vertices per frame.
34 }
35
36
37instance Storable RenderModel where
38 sizeOf _ = #{size RenderModel}
39 alignment _ = alignment (undefined :: CUInt)
40
41 peek ptr = do
42 elements <- #{peek RenderModel, elements} ptr
43 numFrames <- #{peek RenderModel, numFrames} ptr
44 numVertices <- #{peek RenderModel, numVertices} ptr
45 return $ RenderModel elements numFrames numVertices
46
47 poke ptr (RenderModel elements numFrames numVertices) = do
48 #{poke RenderModel, elements} ptr elements
49 #{poke RenderModel, numFrames} ptr numFrames
50 #{poke RenderModel, numVertices} ptr numVertices
51
52
53foreign import ccall "RenderModel.h render_model_from_model_asset"
54 render_model_from_model_asset :: Ptr Assets.CModel -> Ptr RenderModel -> IO Int
55
56
57-- | Convert the given 'Model' to a 'ModelData' instance.
58renderModelFromModel :: Assets.Model -> IO RenderModel
59renderModelFromModel m = with (Assets.cmodel m) $ \mPtr -> alloca $ \mdPtr -> do
60 render_model_from_model_asset mPtr mdPtr
61 peek mdPtr
diff --git a/Spear/Render/Program.hs b/Spear/Render/Program.hs
new file mode 100644
index 0000000..9755aa3
--- /dev/null
+++ b/Spear/Render/Program.hs
@@ -0,0 +1,119 @@
1module Spear.Render.Program
2(
3 StaticProgram(..)
4, AnimatedProgram(..)
5, Program(..)
6, ProgramUniforms(..)
7, StaticProgramChannels(..)
8, StaticProgramUniforms(..)
9, AnimatedProgramChannels(..)
10, AnimatedProgramUniforms(..)
11)
12where
13
14
15import Spear.GLSL.Management (GLSLProgram)
16
17
18import Graphics.Rendering.OpenGL.Raw.Core31
19
20
21data StaticProgram = StaticProgram
22 { staticProgram :: GLSLProgram
23 , staticProgramChannels :: StaticProgramChannels
24 , staticProgramUniforms :: StaticProgramUniforms
25 }
26
27
28data AnimatedProgram = AnimatedProgram
29 { animatedProgram :: GLSLProgram
30 , animatedProgramChannels :: AnimatedProgramChannels
31 , animatedProgramUniforms :: AnimatedProgramUniforms
32 }
33
34
35data StaticProgramChannels = StaticProgramChannels
36 { vertexChannel :: GLuint -- ^ Vertex channel.
37 , normalChannel :: GLuint -- ^ Normal channel.
38 , stexChannel :: GLuint -- ^ Texture channel.
39 }
40
41
42data AnimatedProgramChannels = AnimatedProgramChannels
43 { vertexChannel1 :: GLuint -- ^ Vertex channel 1.
44 , vertexChannel2 :: GLuint -- ^ Vertex channel 2.
45 , normalChannel1 :: GLuint -- ^ Normal channel 1.
46 , normalChannel2 :: GLuint -- ^ Normal channel 2.
47 , atexChannel :: GLuint -- ^ Texture channel.
48 }
49
50
51data StaticProgramUniforms = StaticProgramUniforms
52 { skaLoc :: GLint -- ^ Material ambient uniform location.
53 , skdLoc :: GLint -- ^ Material diffuse uniform location.
54 , sksLoc :: GLint -- ^ Material specular uniform location.
55 , sshiLoc :: GLint -- ^ Material shininess uniform location.
56 , stexLoc :: GLint -- ^ Texture sampler location.
57 , smodelviewLoc :: GLint -- ^ Modelview matrix location.
58 , snormalmatLoc :: GLint -- ^ Normal matrix location.
59 , sprojLoc :: GLint -- ^ Projection matrix location.
60 }
61
62
63data AnimatedProgramUniforms = AnimatedProgramUniforms
64 { akaLoc :: GLint -- ^ Material ambient uniform location.
65 , akdLoc :: GLint -- ^ Material diffuse uniform location.
66 , aksLoc :: GLint -- ^ Material specular uniform location.
67 , ashiLoc :: GLint -- ^ Material shininess uniform location.
68 , atexLoc :: GLint -- ^ Texture sampler location.
69 , fpLoc :: GLint -- ^ Frame progress uniform location.
70 , amodelviewLoc :: GLint -- ^ Modelview matrix location.
71 , anormalmatLoc :: GLint -- ^ Normal matrix location.
72 , aprojLoc :: GLint -- ^ Projection matrix location.
73 }
74
75
76class Program a where
77 program :: a -> GLSLProgram
78
79
80instance Program StaticProgram where
81 program = staticProgram
82
83
84instance Program AnimatedProgram where
85 program = animatedProgram
86
87
88class ProgramUniforms a where
89 kaLoc :: a -> GLint
90 kdLoc :: a -> GLint
91 ksLoc :: a -> GLint
92 shiLoc :: a -> GLint
93 texLoc :: a -> GLint
94 modelviewLoc :: a -> GLint
95 normalmatLoc :: a -> GLint
96 projLoc :: a -> GLint
97
98
99instance ProgramUniforms StaticProgramUniforms where
100 kaLoc = skaLoc
101 kdLoc = skdLoc
102 ksLoc = sksLoc
103 shiLoc = sshiLoc
104 texLoc = stexLoc
105 modelviewLoc = smodelviewLoc
106 normalmatLoc = snormalmatLoc
107 projLoc = sprojLoc
108
109
110
111instance ProgramUniforms AnimatedProgramUniforms where
112 kaLoc = akaLoc
113 kdLoc = akdLoc
114 ksLoc = aksLoc
115 shiLoc = ashiLoc
116 texLoc = atexLoc
117 modelviewLoc = amodelviewLoc
118 normalmatLoc = anormalmatLoc
119 projLoc = aprojLoc
diff --git a/Spear/Render/RenderModel.c b/Spear/Render/RenderModel.c
new file mode 100644
index 0000000..3d18a4b
--- /dev/null
+++ b/Spear/Render/RenderModel.c
@@ -0,0 +1,232 @@
1#include "RenderModel.h"
2#include <stdlib.h> // free
3#include <string.h> // memcpy
4#include <stdio.h>
5
6
7static void safe_free (void* ptr)
8{
9 if (ptr)
10 {
11 free (ptr);
12 ptr = 0;
13 }
14}
15
16
17/// Populate elements of an animated model to be rendered from
18/// start to end in a loop.
19/*int populate_elements_animated (Model* model_asset, RenderModel* model)
20{
21 size_t nverts = model_asset->numVertices;
22 size_t ntriangles = model_asset->numTriangles;
23 size_t nframes = model_asset->numFrames;
24 size_t n = nframes * ntriangles * 3;
25
26 model->elements = malloc (56 * n);
27 if (!model->elements) return -1;
28
29 // Populate elements.
30
31 size_t f, i;
32
33 char* elem = (char*) model->elements;
34 vec3* v1 = model_asset->vertices;
35 vec3* v2 = v1 + nverts;
36 vec3* n1 = model_asset->normals;
37 vec3* n2 = n1 + nverts;
38 texCoord* tex = model_asset->texCoords;
39
40 for (f = 0; f < nframes; ++f)
41 {
42 triangle* t = model_asset->triangles;
43
44 for (i = 0; i < ntriangles; ++i)
45 {
46 *((vec3*) elem) = v1[t->vertexIndices[0]];
47 *((vec3*) (elem + 12)) = v2[t->vertexIndices[0]];
48 *((vec3*) (elem + 24)) = n1[t->vertexIndices[0]];
49 *((vec3*) (elem + 36)) = n2[t->vertexIndices[0]];
50 *((texCoord*) (elem + 48)) = tex[t->textureIndices[0]];
51 elem += 56;
52
53 *((vec3*) elem) = v1[t->vertexIndices[1]];
54 *((vec3*) (elem + 12)) = v2[t->vertexIndices[1]];
55 *((vec3*) (elem + 24)) = n1[t->vertexIndices[1]];
56 *((vec3*) (elem + 36)) = n2[t->vertexIndices[1]];
57 *((texCoord*) (elem + 48)) = tex[t->textureIndices[1]];
58 elem += 56;
59
60 *((vec3*) elem) = v1[t->vertexIndices[2]];
61 *((vec3*) (elem + 12)) = v2[t->vertexIndices[2]];
62 *((vec3*) (elem + 24)) = n1[t->vertexIndices[2]];
63 *((vec3*) (elem + 36)) = n2[t->vertexIndices[2]];
64 *((texCoord*) (elem + 48)) = tex[t->textureIndices[2]];
65 elem += 56;
66
67 t++;
68 }
69
70 v1 += nverts;
71 v2 += nverts;
72 n1 += nverts;
73 n2 += nverts;
74
75 if (f == nframes-2)
76 {
77 v2 = model_asset->vertices;
78 n2 = model_asset->normals;
79 }
80 }
81
82 return 0;
83}*/
84
85
86/// Populate elements of an animated model according to its frames
87/// of animation.
88int populate_elements_animated (Model* model_asset, RenderModel* model)
89{
90 size_t nverts = model_asset->numVertices;
91 size_t ntriangles = model_asset->numTriangles;
92 size_t nframes = model_asset->numFrames;
93 size_t n = nframes * ntriangles * 3;
94
95 model->elements = malloc (56 * n);
96 if (!model->elements) return -1;
97
98 // Populate elements.
99
100 unsigned f, i, j, u;
101
102 char* elem = (char*) model->elements;
103 animation* anim = model_asset->animations;
104
105 for (i = 0; i < model_asset->numAnimations; ++i, anim++)
106 {
107 unsigned start = anim->start;
108 unsigned end = anim->end;
109
110 char singleFrameAnim = start == end;
111
112 vec3* v1 = model_asset->vertices + start*nverts;
113 vec3* v2 = singleFrameAnim ? v1 : v1 + nverts;
114 vec3* n1 = model_asset->normals + start*nverts;
115 vec3* n2 = singleFrameAnim ? n1 : n1 + nverts;
116 texCoord* tex = model_asset->texCoords;
117
118 for (u = start; u <= end; ++u)
119 {
120 triangle* t = model_asset->triangles;
121
122 for (j = 0; j < ntriangles; ++j, t++)
123 {
124 *((vec3*) elem) = v1[t->vertexIndices[0]];
125 *((vec3*) (elem + 12)) = v2[t->vertexIndices[0]];
126 *((vec3*) (elem + 24)) = n1[t->vertexIndices[0]];
127 *((vec3*) (elem + 36)) = n2[t->vertexIndices[0]];
128 *((texCoord*) (elem + 48)) = tex[t->textureIndices[0]];
129 elem += 56;
130
131 *((vec3*) elem) = v1[t->vertexIndices[1]];
132 *((vec3*) (elem + 12)) = v2[t->vertexIndices[1]];
133 *((vec3*) (elem + 24)) = n1[t->vertexIndices[1]];
134 *((vec3*) (elem + 36)) = n2[t->vertexIndices[1]];
135 *((texCoord*) (elem + 48)) = tex[t->textureIndices[1]];
136 elem += 56;
137
138 *((vec3*) elem) = v1[t->vertexIndices[2]];
139 *((vec3*) (elem + 12)) = v2[t->vertexIndices[2]];
140 *((vec3*) (elem + 24)) = n1[t->vertexIndices[2]];
141 *((vec3*) (elem + 36)) = n2[t->vertexIndices[2]];
142 *((texCoord*) (elem + 48)) = tex[t->textureIndices[2]];
143 elem += 56;
144 }
145
146 // Advance to the next frame of animation of the current
147 // animation.
148 v1 += nverts;
149 v2 += nverts;
150 n1 += nverts;
151 n2 += nverts;
152
153 // Reset the secondary pointers to the beginning of the
154 // animation when we are about to reach the last frame.
155 if (u == end-1)
156 {
157 v2 = model_asset->vertices + start*nverts;
158 n2 = model_asset->normals + start*nverts;
159 }
160 }
161 }
162
163 return 0;
164}
165
166
167int populate_elements_static (Model* model_asset, RenderModel* model)
168{
169 size_t nverts = model_asset->numVertices;
170 size_t ntriangles = model_asset->numTriangles;
171 size_t n = ntriangles * 3;
172
173 model->elements = malloc (32 * n);
174 if (!model->elements) return -1;
175
176 // Populate elements.
177
178 size_t f, i;
179
180 char* elem = (char*) model->elements;
181 vec3* vert = model_asset->vertices;
182 vec3* norm = model_asset->normals;
183 texCoord* tex = model_asset->texCoords;
184
185 triangle* t = model_asset->triangles;
186
187 for (i = 0; i < ntriangles; ++i)
188 {
189 *((vec3*) elem) = vert[t->vertexIndices[0]];
190 *((vec3*) (elem + 12)) = norm[t->vertexIndices[0]];
191 *((texCoord*) (elem + 24)) = tex[t->textureIndices[0]];
192 elem += 32;
193
194 *((vec3*) elem) = vert[t->vertexIndices[1]];
195 *((vec3*) (elem + 12)) = norm[t->vertexIndices[1]];
196 *((texCoord*) (elem + 24)) = tex[t->textureIndices[1]];
197 elem += 32;
198
199 *((vec3*) elem) = vert[t->vertexIndices[2]];
200 *((vec3*) (elem + 12)) = norm[t->vertexIndices[2]];
201 *((texCoord*) (elem + 24)) = tex[t->textureIndices[2]];
202 elem += 32;
203
204 t++;
205 }
206
207 return 0;
208}
209
210
211int render_model_from_model_asset (Model* model_asset, RenderModel* model)
212{
213 U32 ntriangles = model_asset->numTriangles;
214 U32 nframes = model_asset->numFrames;
215
216 int result;
217 if (nframes > 1) result = populate_elements_animated (model_asset, model);
218 else result = populate_elements_static (model_asset, model);
219
220 if (result != 0) return result;
221
222 model->numFrames = nframes;
223 model->numVertices = ntriangles * 3; // Number of vertices per frame.
224
225 return 0;
226}
227
228
229void render_model_free (RenderModel* model)
230{
231 safe_free (model->elements);
232}
diff --git a/Spear/Render/RenderModel.h b/Spear/Render/RenderModel.h
new file mode 100644
index 0000000..cb70a19
--- /dev/null
+++ b/Spear/Render/RenderModel.h
@@ -0,0 +1,49 @@
1#ifndef _SPEAR_RENDER_MODEL_H
2#define _SPEAR_RENDER_MODEL_H
3
4#include "Model.h"
5
6
7/// Represents a renderable model.
8/**
9 * If the model is animated:
10 *
11 * Buffer layout:
12 * vert1 vert2 norm1 norm2 texc
13 *
14 * element size = (3 + 3 + 3 + 3 + 2)*4 = 56 B
15 * buffer size = element size * num vertices = 56n
16 *
17 * If the model is static:
18 *
19 * Buffer layout:
20 * vert norm texc
21 *
22 * element size = (3 + 3 + 2)*4 = 32 B
23 * buffer size = element size * num vertices = 32n
24 *
25 **/
26typedef struct
27{
28 void* elements;
29 U32 numFrames;
30 U32 numVertices; // Number of vertices per frame.
31}
32RenderModel;
33
34
35#ifdef __cplusplus
36extern "C" {
37#endif
38
39int render_model_from_model_asset (Model* model_asset, RenderModel* render_model);
40
41void render_model_free (RenderModel* model);
42
43#ifdef __cplusplus
44}
45#endif
46
47
48#endif // _SPEAR_RENDER_MODEL_H
49
diff --git a/Spear/Render/Renderable.hs b/Spear/Render/Renderable.hs
new file mode 100644
index 0000000..a3d08f9
--- /dev/null
+++ b/Spear/Render/Renderable.hs
@@ -0,0 +1,8 @@
1module Spear.Render.Renderable
2where
3
4
5class Renderable a where
6
7 -- | Renders the given 'Renderable'.
8 render :: a -> IO ()
diff --git a/Spear/Render/Sphere.hs b/Spear/Render/Sphere.hs
new file mode 100644
index 0000000..25d775a
--- /dev/null
+++ b/Spear/Render/Sphere.hs
@@ -0,0 +1,45 @@
1module Spear.Render.Sphere
2(
3 render
4)
5where
6
7
8import Spear.Math.Vector as Vector
9import Spear.Math.Matrix
10import Graphics.Rendering.OpenGL.Raw
11import Graphics.Rendering.OpenGL.GL.Colors
12import qualified Graphics.Rendering.OpenGL.GLU as GLU
13import Unsafe.Coerce
14
15
16type Center = Vector R
17type Radius = R
18type Colour = Vector R
19
20
21applyColour :: Colour -> IO ()
22applyColour col =
23 if Vector.length col == 4 then
24 glColor4f (unsafeCoerce $ x col) (unsafeCoerce $ y col) (unsafeCoerce $ z col)
25 (unsafeCoerce $ w col)
26 else
27 glColor3f (unsafeCoerce $ x col) (unsafeCoerce $ y col) (unsafeCoerce $ z col)
28
29
30-- | Renders a sphere.
31-- Center is the sphere's center.
32-- Radius is the sphere's radius.
33-- Colour is a Vector representing the sphere's colour. Colour may hold an alpha channel.
34render :: Center -> Radius -> Colour -> IO ()
35render c radius col = do
36 glPushMatrix
37 glTranslatef (unsafeCoerce $ x c) (unsafeCoerce $ y c) (unsafeCoerce $ z c)
38 applyColour col
39
40 let r = unsafeCoerce $ (realToFrac radius :: Double)
41 let style = GLU.QuadricStyle (Just Smooth) GLU.NoTextureCoordinates GLU.Outside GLU.FillStyle
42 GLU.renderQuadric style $ GLU.Sphere r 16 16
43
44 glPopMatrix
45 \ No newline at end of file
diff --git a/Spear/Render/StaticModel.hs b/Spear/Render/StaticModel.hs
new file mode 100644
index 0000000..05e80e4
--- /dev/null
+++ b/Spear/Render/StaticModel.hs
@@ -0,0 +1,123 @@
1module Spear.Render.StaticModel
2(
3 StaticModelResource
4, StaticModelRenderer
5, staticModelResource
6, staticModelRenderer
7, Spear.Render.StaticModel.release
8, bind
9, render
10)
11where
12
13
14import Spear.Assets.Model
15import Spear.Render.Model
16import Spear.GLSL
17import Spear.Render.Material
18import Spear.Render.Program
19import Spear.Setup as Setup
20
21import Graphics.Rendering.OpenGL.Raw.Core31
22import Unsafe.Coerce (unsafeCoerce)
23
24
25data StaticModelResource = StaticModelResource
26 { vao :: VAO
27 , nVertices :: Int
28 , material :: Material
29 , texture :: Texture
30 , rkey :: Resource
31 }
32
33
34instance Eq StaticModelResource where
35 m1 == m2 = vao m1 == vao m2
36
37
38instance Ord StaticModelResource where
39 m1 < m2 = vao m1 < vao m2
40
41
42data StaticModelRenderer = StaticModelRenderer { model :: StaticModelResource }
43
44
45instance Eq StaticModelRenderer where
46 m1 == m2 = model m1 == model m2
47
48
49instance Ord StaticModelRenderer where
50 m1 < m2 = model m1 < model m2
51
52
53-- | Create a 'StaticModelResource' from the given 'Model'.
54staticModelResource :: StaticProgramChannels
55 -> Material
56 -> Texture
57 -> Model
58 -> Setup StaticModelResource
59
60staticModelResource (StaticProgramChannels vertChan normChan texChan) material texture model = do
61 RenderModel elements _ numVertices <- setupIO . renderModelFromModel $ model
62 elementBuf <- newBuffer
63 vao <- newVAO
64
65 setupIO $ do
66
67 let elemSize = 32
68 elemSize' = fromIntegral elemSize
69 n = numVertices
70
71 bindVAO vao
72
73 bindBuffer elementBuf ArrayBuffer
74 bufferData ArrayBuffer (fromIntegral $ elemSize*n) elements StaticDraw
75
76 attribVAOPointer vertChan 3 gl_FLOAT False elemSize' 0
77 attribVAOPointer normChan 3 gl_FLOAT False elemSize' 12
78 attribVAOPointer texChan 2 gl_FLOAT False elemSize' 24
79
80 enableVAOAttrib vertChan
81 enableVAOAttrib normChan
82 enableVAOAttrib texChan
83
84 rkey <- register . runSetup_ $ do
85 setupIO $ putStrLn "Releasing static model resource"
86 releaseVAO vao
87 releaseBuffer elementBuf
88 --sequence_ . fmap releaseBuffer $ [elementBuf, indexBuf]
89
90 return $ StaticModelResource vao (unsafeCoerce numVertices) material texture rkey
91
92
93-- | Release the given 'StaticModelResource'.
94release :: StaticModelResource -> Setup ()
95release = Setup.release . rkey
96
97
98-- | Create a 'StaticModelRenderer' from the given 'StaticModelResource'.
99staticModelRenderer :: StaticModelResource -> StaticModelRenderer
100staticModelRenderer = StaticModelRenderer
101
102
103-- | Bind the given 'StaticModelRenderer' to prepare it for rendering.
104bind :: StaticProgramUniforms -> StaticModelRenderer -> IO ()
105bind (StaticProgramUniforms kaLoc kdLoc ksLoc shiLoc texLoc _ _ _) (StaticModelRenderer model) =
106 let (Material _ ka kd ks shi) = material model
107 in do
108 bindVAO . vao $ model
109 bindTexture $ texture model
110 activeTexture $= gl_TEXTURE0
111 glUniform1i texLoc 0
112
113
114-- | Render the given 'StaticModelRenderer'.
115render :: StaticProgramUniforms -> StaticModelRenderer -> IO ()
116render uniforms (StaticModelRenderer model) =
117 let (Material _ ka kd ks shi) = material model
118 in do
119 uniformVec4 (kaLoc uniforms) ka
120 uniformVec4 (kdLoc uniforms) kd
121 uniformVec4 (ksLoc uniforms) ks
122 glUniform1f (shiLoc uniforms) $ unsafeCoerce shi
123 drawArrays gl_TRIANGLES 0 $ nVertices model
diff --git a/Spear/Render/Texture.hs b/Spear/Render/Texture.hs
new file mode 100644
index 0000000..59e7797
--- /dev/null
+++ b/Spear/Render/Texture.hs
@@ -0,0 +1,34 @@
1module Spear.Render.Texture
2(
3 loadTextureImage
4)
5where
6
7
8import Spear.Setup
9import Spear.Assets.Image
10import Spear.GLSL.Texture
11import Data.StateVar (($=))
12import Graphics.Rendering.OpenGL.Raw.Core31
13
14
15-- | Load the 'Texture' specified by the given file.
16loadTextureImage :: FilePath
17 -> GLenum -- ^ Texture's min filter.
18 -> GLenum -- ^ Texture's mag filter.
19 -> Setup Texture
20loadTextureImage file minFilter magFilter = do
21 image <- loadImage file
22 tex <- newTexture
23 setupIO $ do
24 let w = width image
25 h = height image
26 pix = pixels image
27 rgb = fromIntegral . fromEnum $ gl_RGB
28
29 bindTexture tex
30 loadTextureData gl_TEXTURE_2D 0 rgb w h 0 gl_RGB gl_UNSIGNED_BYTE pix
31 texParami gl_TEXTURE_2D gl_TEXTURE_MIN_FILTER $= minFilter
32 texParami gl_TEXTURE_2D gl_TEXTURE_MAG_FILTER $= magFilter
33
34 return tex
diff --git a/Spear/Render/Triangle.hs b/Spear/Render/Triangle.hs
new file mode 100644
index 0000000..296349a
--- /dev/null
+++ b/Spear/Render/Triangle.hs
@@ -0,0 +1,10 @@
1module Spear.Render.Triangle
2(
3)
4where
5
6
7import Graphics.Rendering.OpenGL.Raw.Core31
8
9
10
diff --git a/Spear/Scene/Graph.hs b/Spear/Scene/Graph.hs
new file mode 100644
index 0000000..a91fc89
--- /dev/null
+++ b/Spear/Scene/Graph.hs
@@ -0,0 +1,143 @@
1module Spear.Scene.Graph
2(
3 Property
4, SceneGraph(..)
5, ParseError
6, loadSceneGraph
7, loadSceneGraphFromFile
8, node
9)
10where
11
12
13import qualified Data.ByteString.Char8 as B
14import Data.List (find, intersperse)
15import Data.Maybe (isJust)
16import Text.Parsec.Char
17import Text.Parsec.Combinator
18import Text.Parsec.Error
19import Text.Parsec.Prim
20import qualified Text.Parsec.ByteString as P
21import qualified Text.Parsec.Token as PT
22
23
24type Property = (String, [String])
25
26
27data SceneGraph
28 = SceneNode
29 { nodeID :: String
30 , properties :: [Property]
31 , children :: [SceneGraph]
32 }
33 | SceneLeaf
34 { nodeID :: String
35 , properties :: [Property]
36 }
37
38
39instance Show SceneGraph where
40 show sceneGraph = show' "" sceneGraph
41 where
42 show' tab (SceneNode nid props children) =
43 tab ++ nid ++ "\n" ++ tab ++ "{\n" ++ (printProps tab props) ++
44 (concat . fmap (show' $ " " ++ tab) $ children) ++ '\n':tab ++ "}\n"
45
46 show' tab (SceneLeaf nid props) =
47 tab ++ nid ++ '\n':tab ++ "{\n" ++ tab ++ (printProps tab props) ++ '\n':tab ++ "}\n"
48
49
50printProp :: Property -> String
51printProp (key, vals) = key ++ " = " ++ (concat $ intersperse ", " vals)
52
53
54printProps :: String -> [Property] -> String
55printProps tab props =
56 let
57 tab' = '\n':(tab ++ tab)
58 longestKeyLen = maximum . fmap (length . fst) $ props
59
60 align :: Int -> String -> String
61 align len str =
62 let (key, vals) = break ((==) '=') str
63 thisLen = length key
64 padLen = len - thisLen + 1
65 pad = replicate padLen ' '
66 in
67 key ++ pad ++ vals
68 in
69 case concat . intersperse tab' . fmap (align longestKeyLen . printProp) $ props of
70 [] -> []
71 xs -> tab ++ xs
72
73
74-- | Load the scene graph from the given string.
75loadSceneGraph :: String -> Either ParseError SceneGraph
76loadSceneGraph str = parse sceneGraph "(unknown)" $ B.pack str
77
78
79-- | Load the scene graph specified by the given file.
80loadSceneGraphFromFile :: FilePath -> IO (Either ParseError SceneGraph)
81loadSceneGraphFromFile = P.parseFromFile sceneGraph
82
83
84-- | Get the node identified by the given string from the given scene graph.
85node :: String -> SceneGraph -> Maybe SceneGraph
86node str SceneLeaf {} = Nothing
87node str n@(SceneNode nid _ children)
88 | str == nid = Just n
89 | otherwise = case find isJust $ fmap (node str) children of
90 Nothing -> Nothing
91 Just x -> x
92
93
94sceneGraph :: P.Parser SceneGraph
95sceneGraph = do
96 g <- graph
97 whitespace
98 eof
99 return g
100
101
102graph :: P.Parser SceneGraph
103graph = do
104 nid <- name
105 whitespace
106 char '{'
107 props <- many . try $ whitespace >> property
108 children <- many . try $ whitespace >> graph
109 whitespace
110 char '}'
111
112 return $ case null children of
113 True -> SceneLeaf nid props
114 False -> SceneNode nid props children
115
116
117property :: P.Parser Property
118property = do
119 key <- name
120 spaces >> char '=' >> spaces
121 vals <- cells name
122 return (key, vals)
123
124
125cells :: P.Parser String -> P.Parser [String]
126cells p = do
127 val <- p
128 vals <- remainingCells p
129 return $ val:vals
130
131
132remainingCells :: P.Parser String -> P.Parser [String]
133remainingCells p =
134 try (whitespace >> char ',' >> whitespace >> cells p)
135 <|> (return [])
136
137
138name :: P.Parser String
139name = many1 $ choice [oneOf "-/.()?_", alphaNum]
140
141
142whitespace :: P.Parser ()
143whitespace = skipMany $ choice [space, newline]
diff --git a/Spear/Scene/Light.hs b/Spear/Scene/Light.hs
new file mode 100644
index 0000000..76ff074
--- /dev/null
+++ b/Spear/Scene/Light.hs
@@ -0,0 +1,82 @@
1module Spear.Scene.Light
2(
3 Light(..)
4)
5where
6
7
8import qualified Spear.Math.Matrix4 as M
9import qualified Spear.Math.Spatial as S
10import Spear.Math.Vector3
11import qualified Spear.Math.Vector4 as V4
12
13
14data Light
15 = PointLight
16 { ambient :: Vector3
17 , diffuse :: Vector3
18 , specular :: Vector3
19 , transform :: M.Matrix4
20 }
21 | DirectionalLight
22 { ambient :: Vector3
23 , diffuse :: Vector3
24 , specular :: Vector3
25 , direction :: Vector3
26 }
27 | SpotLight
28 { ambient :: Vector3
29 , diffuse :: Vector3
30 , specular :: Vector3
31 , transform :: M.Matrix4
32 }
33
34
35instance S.Spatial Light where
36 move _ l@DirectionalLight {} = l
37 move v l = l { transform = M.translv v * transform l}
38
39 moveFwd _ l@DirectionalLight {} = l
40 moveFwd f l = l { transform = M.translv (scale f $ S.fwd l) * transform l }
41
42 moveBack _ l@DirectionalLight {} = l
43 moveBack f l = l { transform = M.translv (scale (-f) $ S.fwd l) * transform l }
44
45 strafeLeft _ l@DirectionalLight {} = l
46 strafeLeft f l = l { transform = M.translv (scale (-f) $ S.right l) * transform l }
47
48 strafeRight _ l@DirectionalLight {} = l
49 strafeRight f l = l { transform = M.translv (scale f $ S.right l) * transform l }
50
51 pitch _ l@DirectionalLight {} = l
52 pitch a l = l { transform = transform l * M.axisAngle (S.right l) a }
53
54 yaw _ l@DirectionalLight {} = l
55 yaw a l = l { transform = transform l * M.axisAngle (S.up l) a }
56
57 roll _ l@DirectionalLight {} = l
58 roll a l = l { transform = transform l * M.axisAngle (S.fwd l) a }
59
60 pos l@DirectionalLight {} = vec3 0 0 0
61 pos l = M.position . transform $ l
62
63 fwd (DirectionalLight _ _ _ f) = f
64 fwd l = M.forward . transform $ l
65
66 up l@DirectionalLight {} = vec3 0 1 0
67 up l = M.up . transform $ l
68
69 right l@DirectionalLight {} = vec3 1 0 0
70 right l = M.right . transform $ l
71
72 transform (PointLight _ _ _ transf) = transf
73 transform (DirectionalLight _ _ _ fwd) =
74 let up' = vec3 0 1 0
75 right = up `cross` fwd
76 up = fwd `cross` right
77 in
78 M.transform up right fwd (vec3 0 0 0)
79 transform (SpotLight _ _ _ transf) = transf
80
81 setTransform _ l@DirectionalLight {} = l
82 setTransform t l = l { Spear.Scene.Light.transform = t }
diff --git a/Spear/Scene/Loader.hs b/Spear/Scene/Loader.hs
new file mode 100644
index 0000000..32aba45
--- /dev/null
+++ b/Spear/Scene/Loader.hs
@@ -0,0 +1,414 @@
1module Spear.Scene.Loader
2(
3 SceneResources(..)
4, CreateStaticObject
5, CreateAnimatedObject
6, loadScene
7, validate
8, resourceMap
9, loadObjects
10)
11where
12
13
14import Spear.Assets.Model as Model
15import qualified Spear.GLSL as GLSL
16import Spear.Math.Matrix4 as M4
17import Spear.Math.Vector3 as V3
18import Spear.Math.Vector4
19import Spear.Render.AnimatedModel
20import Spear.Render.Material
21import Spear.Render.Program
22import Spear.Render.StaticModel
23import Spear.Render.Texture
24import Spear.Scene.Light
25import Spear.Scene.Graph
26import Spear.Scene.SceneResources
27import Spear.Setup
28
29import Control.Monad.State.Strict
30import Control.Monad.Trans (lift)
31import Data.List as L (find)
32import Data.Map as M
33import qualified Data.StateVar as SV (get)
34import Graphics.Rendering.OpenGL.Raw.Core31
35import Text.Printf (printf)
36
37
38type Loader = StateT SceneResources Setup
39
40
41loaderSetup = lift
42loaderIO = loaderSetup . setupIO
43loaderError = loaderSetup . setupError
44
45
46type CreateStaticObject a = String -> Matrix4 -> StaticModelResource -> a
47type CreateAnimatedObject a = String -> Matrix4 -> AnimatedModelResource -> a
48
49
50-- | Load the scene specified by the given file.
51loadScene :: FilePath -> Setup (SceneResources, SceneGraph)
52loadScene file = do
53 result <- setupIO $ loadSceneGraphFromFile file
54 case result of
55 Left err -> setupError $ show err
56 Right g -> case validate g of
57 Nothing -> do
58 sceneRes <- resourceMap g
59 return (sceneRes, g)
60 Just err -> setupError err
61
62
63-- | Validate the given SceneGraph.
64validate :: SceneGraph -> Maybe String
65validate _ = Nothing
66
67
68-- | Load the scene described by the given 'SceneGraph'.
69resourceMap :: SceneGraph -> Setup SceneResources
70resourceMap g = execStateT (resourceMap' g) emptySceneResources
71
72
73resourceMap' :: SceneGraph -> Loader ()
74resourceMap' node@(SceneLeaf nid props) = do
75 case nid of
76 "shader-program" -> newShaderProgram node
77 "model" -> newModel node
78 "light" -> newLight node
79 x -> return ()
80
81resourceMap' node@(SceneNode nid props children) = do
82 mapM_ resourceMap' children
83
84
85-- Lookup the given resource in the data pool. Load it if it is not present, otherwise return it.
86loadResource :: String -- ^ Resource name.
87 -> (SceneResources -> Map String a) -- ^ Map getter.
88 -> (String -> a -> Loader ()) -- ^ Function to modify resources.
89 -> Setup a -- ^ Resource loader.
90 -> Loader a
91loadResource key field modifyResources load = do
92 sceneData <- get
93 case M.lookup key $ field sceneData of
94 Just val -> return val
95 Nothing -> do
96 loaderIO $ printf "Loading %s..." key
97 resource <- loaderSetup load
98 loaderIO $ printf "done\n"
99 modifyResources key resource
100 return resource
101
102
103addShader name shader =
104 modify $ \sceneData -> sceneData { shaders = M.insert name shader $ shaders sceneData }
105
106
107addStaticProgram name prog =
108 modify $ \sceneData -> sceneData { staticPrograms = M.insert name prog $ staticPrograms sceneData }
109
110
111addAnimatedProgram name prog =
112 modify $ \sceneData -> sceneData { animatedPrograms = M.insert name prog $ animatedPrograms sceneData }
113
114
115addTexture name tex =
116 modify $ \sceneData -> sceneData { textures = M.insert name tex $ textures sceneData }
117
118
119addStaticModel name model =
120 modify $ \sceneData -> sceneData { staticModels = M.insert name model $ staticModels sceneData }
121
122
123addAnimatedModel name model =
124 modify $ \sceneData -> sceneData { animatedModels = M.insert name model $ animatedModels sceneData }
125
126
127-- Get the given resource from the data pool.
128getResource :: (SceneResources -> Map String a) -> String -> Loader a
129getResource field key = do
130 sceneData <- get
131 case M.lookup key $ field sceneData of
132 Just val -> return val
133 Nothing -> loaderSetup . setupError $ "Oops, the given resource has not been loaded: " ++ key
134
135
136
137
138----------------------
139-- Resource Loading --
140----------------------
141
142newModel :: SceneGraph -> Loader ()
143newModel (SceneLeaf _ props) = do
144 name <- asString $ mandatory "name" props
145 file <- asString $ mandatory "file" props
146 tex <- asString $ mandatory "texture" props
147 prog <- asString $ mandatory "shader-program" props
148 ke <- asVec4 $ mandatory "ke" props
149 ka <- asVec4 $ mandatory "ka" props
150 kd <- asVec4 $ mandatory "kd" props
151 ks <- asVec4 $ mandatory "ks" props
152 shi <- asFloat $ mandatory "shi" props
153
154 let rotation = asRotation $ value "rotation" props
155 scale = asVec3 $ value "scale" props
156
157 loaderIO $ printf "Loading model %s..." name
158 model <- loaderSetup $ loadModel' file rotation scale
159 loaderIO . putStrLn $ "done"
160 texture <- loadTexture tex
161 sceneRes <- get
162
163 let material = Material ke ka kd ks shi
164
165 case animated model of
166 False ->
167 case M.lookup prog $ staticPrograms sceneRes of
168 Nothing -> (loaderError $ "Static shader program " ++ prog ++ " does not exist") >> return ()
169 Just p ->
170 let StaticProgram _ channels _ = p
171 in do
172 model' <- loaderSetup $ staticModelResource channels material texture model
173 loadResource name staticModels addStaticModel (return model')
174 return ()
175 True ->
176 case M.lookup prog $ animatedPrograms sceneRes of
177 Nothing -> (loaderError $ "Animated shader program " ++ prog ++ " does not exist") >> return ()
178 Just p ->
179 let AnimatedProgram _ channels _ = p
180 in do
181 model' <- loaderSetup $ animatedModelResource channels material texture model
182 loadResource name animatedModels addAnimatedModel (return model')
183 return ()
184
185
186loadModel' :: FilePath -> Maybe Rotation -> Maybe Vector3 -> Setup Model
187loadModel' file rotation scale = do
188 model <- Model.loadModel file
189 case rotation of
190 Just rot -> setupIO $ rotateModel model rot
191 Nothing -> return ()
192 case scale of
193 Just s -> setupIO $ Model.transform (scalev s) model
194 Nothing -> return ()
195 return model
196
197
198rotateModel :: Model -> Rotation -> IO ()
199rotateModel model (Rotation x y z order) = case order of
200 XYZ -> Model.transform (rotZ z * rotY y * rotX x) model
201 XZY -> Model.transform (rotY y * rotZ z * rotX x) model
202 YXZ -> Model.transform (rotZ z * rotX x * rotY y) model
203 YZX -> Model.transform (rotX x * rotZ z * rotY y) model
204 ZXY -> Model.transform (rotY y * rotX x * rotZ z) model
205 ZYX -> Model.transform (rotX x * rotY y * rotZ z) model
206
207
208loadTexture :: FilePath -> Loader GLSL.Texture
209loadTexture file = loadResource file textures addTexture $ loadTextureImage file gl_LINEAR gl_LINEAR
210
211
212newShaderProgram :: SceneGraph -> Loader ()
213newShaderProgram (SceneLeaf _ props) = do
214 (vsName, vertShader) <- Spear.Scene.Loader.loadShader GLSL.VertexShader props
215 (fsName, fragShader) <- Spear.Scene.Loader.loadShader GLSL.FragmentShader props
216 name <- asString $ mandatory "name" props
217 stype <- asString $ mandatory "type" props
218 texChan <- fmap read $ asString $ mandatory "texture-channel" props
219 ambient <- asString $ mandatory "ambient" props
220 diffuse <- asString $ mandatory "diffuse" props
221 specular <- asString $ mandatory "specular" props
222 shininess <- asString $ mandatory "shininess" props
223 texture <- asString $ mandatory "texture" props
224 modelview <- asString $ mandatory "modelview" props
225 normalmat <- asString $ mandatory "normalmat" props
226 projection <- asString $ mandatory "projection" props
227 prog <- loaderSetup $ GLSL.newProgram [vertShader, fragShader]
228
229 let getUniformLoc name =
230 loaderSetup $ (setupIO . SV.get $ GLSL.uniformLocation prog name) `GLSL.assertGL` name
231
232 ka <- getUniformLoc ambient
233 kd <- getUniformLoc diffuse
234 ks <- getUniformLoc specular
235 shi <- getUniformLoc shininess
236 tex <- getUniformLoc texture
237 mview <- getUniformLoc modelview
238 nmat <- getUniformLoc normalmat
239 proj <- getUniformLoc projection
240
241 case stype of
242 "static" -> do
243 vertChan <- fmap read $ asString $ mandatory "vertex-channel" props
244 normChan <- fmap read $ asString $ mandatory "normal-channel" props
245
246 let channels = StaticProgramChannels vertChan normChan texChan
247 uniforms = StaticProgramUniforms ka kd ks shi tex mview nmat proj
248
249 loadResource name staticPrograms addStaticProgram $
250 return $ StaticProgram prog channels uniforms
251 return ()
252
253 "animated" -> do
254 vertChan1 <- fmap read $ asString $ mandatory "vertex-channel1" props
255 vertChan2 <- fmap read $ asString $ mandatory "vertex-channel2" props
256 normChan1 <- fmap read $ asString $ mandatory "normal-channel1" props
257 normChan2 <- fmap read $ asString $ mandatory "normal-channel2" props
258 fp <- asString $ mandatory "fp" props
259 p <- getUniformLoc fp
260
261 let channels = AnimatedProgramChannels vertChan1 vertChan2 normChan1 normChan2 texChan
262 uniforms = AnimatedProgramUniforms ka kd ks shi tex p mview nmat proj
263
264 loadResource name animatedPrograms addAnimatedProgram $
265 return $ AnimatedProgram prog channels uniforms
266 return ()
267
268
269loadShader :: GLSL.ShaderType -> [Property] -> Loader (String, GLSL.GLSLShader)
270loadShader _ [] = loaderSetup . setupError $ "Loader::vertexShader: empty list"
271loadShader shaderType ((stype, file):xs) =
272 if shaderType == GLSL.VertexShader && stype == "vertex-shader" ||
273 shaderType == GLSL.FragmentShader && stype == "fragment-shader"
274 then let f = concat file
275 in loadShader' f shaderType >>= \shader -> return (f, shader)
276 else Spear.Scene.Loader.loadShader shaderType xs
277
278
279loadShader' :: String -> GLSL.ShaderType -> Loader GLSL.GLSLShader
280loadShader' file shaderType = loadResource file shaders addShader $ GLSL.loadShader file shaderType
281
282
283newLight :: SceneGraph -> Loader ()
284newLight _ = return ()
285
286
287
288
289--------------------
290-- Object Loading --
291--------------------
292
293
294-- | Load objects from the given 'SceneGraph'.
295loadObjects :: CreateStaticObject a -> CreateAnimatedObject a -> SceneResources -> SceneGraph -> Setup [a]
296loadObjects newSO newAO sceneRes g =
297 case node "layout" g of
298 Nothing -> return []
299 Just n -> do
300 let gos = concat . fmap (newObject newSO newAO sceneRes) $ children n
301 forM gos $ \go -> case go of
302 Left err -> setupError err
303 Right go -> return go
304
305
306-- to-do: use a strict accumulator and make loadObjects tail recursive.
307newObject :: CreateStaticObject a -> CreateAnimatedObject a -> SceneResources -> SceneGraph -> [Either String a]
308newObject newSO newAO sceneRes (SceneNode nid props children) =
309 let o = newObject' newSO newAO sceneRes nid props
310 in o : (concat $ fmap (newObject newSO newAO sceneRes) children)
311
312newObject newSO newAO sceneRes (SceneLeaf nid props) = [newObject' newSO newAO sceneRes nid props]
313
314
315newObject' :: CreateStaticObject a -> CreateAnimatedObject a -> SceneResources
316 -> String -> [Property] -> Either String a
317newObject' newSO newAO sceneRes nid props = do
318 -- Optional properties.
319 let name = (asString $ value "name" props) `unspecified` "unknown"
320 model = (asString $ value "model" props) `unspecified` "ghost"
321 position = (asVec3 $ value "position" props) `unspecified` vec3 0 0 0
322 rotation = (asVec3 $ value "rotation" props) `unspecified` vec3 0 0 0
323 right' = (asVec3 $ value "right" props) `unspecified` vec3 1 0 0
324 up' = (asVec3 $ value "up" props) `unspecified` vec3 0 1 0
325 forward' = asVec3 $ value "forward" props
326 scale = (asVec3 $ value "scale" props) `unspecified` vec3 1 1 1
327
328 -- Compute the object's vectors if a forward vector has been specified.
329 let (right, up, forward) = vectors forward'
330
331 case M.lookup model $ staticModels sceneRes of
332 Just m -> Right $ newSO name (M4.transform right up forward position) m
333 Nothing -> case M.lookup model $ animatedModels sceneRes of
334 Just m -> Right $ newAO name (M4.transform right up forward position) m
335 Nothing -> Left $ "Loader::newObject: model " ++ model ++ " has not been loaded."
336
337
338vectors :: Maybe Vector3 -> (Vector3, Vector3, Vector3)
339vectors forward = case forward of
340 Nothing -> (V3.unitX, V3.unitY, V3.unitZ)
341 Just f ->
342 let r = f `cross` V3.unitY
343 u = r `cross` f
344 in
345 (r, u, f)
346
347
348
349
350----------------------
351-- Helper functions --
352----------------------
353
354-- Get the value of the given key.
355value :: String -> [Property] -> Maybe [String]
356value name props = case L.find ((==) name . fst) props of
357 Nothing -> Nothing
358 Just prop -> Just . snd $ prop
359
360
361unspecified :: Maybe a -> a -> a
362unspecified (Just x) _ = x
363unspecified Nothing x = x
364
365
366mandatory :: String -> [Property] -> Loader [String]
367mandatory name props = case value name props of
368 Nothing -> loaderError $ "Loader::mandatory: key not found: " ++ name
369 Just x -> return x
370
371
372asString :: Functor f => f [String] -> f String
373asString = fmap concat
374
375
376asFloat :: Functor f => f [String] -> f Float
377asFloat = fmap (read . concat)
378
379
380asVec4 :: Functor f => f [String] -> f Vector4
381asVec4 val = fmap toVec4 val
382 where toVec4 (x:y:z:w:_) = vec4 (read x) (read y) (read z) (read w)
383 toVec4 (x:[]) = let x' = read x in vec4 x' x' x' x'
384
385
386asVec3 :: Functor f => f [String] -> f Vector3
387asVec3 val = fmap toVec3 val
388 where toVec3 (x:y:z:_) = vec3 (read x) (read y) (read z)
389 toVec3 (x:[]) = let x' = read x in vec3 x' x' x'
390
391
392asRotation :: Functor f => f [String] -> f Rotation
393asRotation val = fmap parseRotation val
394 where parseRotation (ax:ay:az:order:_) = Rotation (read ax) (read ay) (read az) (readOrder order)
395
396
397data Rotation = Rotation
398 { ax :: Float
399 , ay :: Float
400 , az :: Float
401 , order :: RotationOrder
402 }
403
404
405data RotationOrder = XYZ | XZY | YXZ | YZX | ZXY | ZYX deriving Eq
406
407
408readOrder :: String -> RotationOrder
409readOrder "xyz" = XYZ
410readOrder "xzy" = XZY
411readOrder "yxz" = YXZ
412readOrder "yzx" = YZX
413readOrder "zxy" = ZXY
414readOrder "zyx" = ZYX
diff --git a/Spear/Scene/Scene.hs b/Spear/Scene/Scene.hs
new file mode 100644
index 0000000..94c2f6f
--- /dev/null
+++ b/Spear/Scene/Scene.hs
@@ -0,0 +1,152 @@
1module Spear.Scene.Scene
2(
3 -- * Data types
4 Scene
5 -- * Construction
6, listScene
7 -- * Insertion and deletion
8, add
9, addl
10, remove
11, Spear.Scene.Scene.filter
12 -- * Queries
13, find
14 -- * Update and render
15, update
16, updateM
17, collide
18, collideM
19, render
20)
21where
22
23
24import Spear.Collision.AABB
25import Spear.Collision.Types
26import Spear.Game (Game)
27import Spear.Math.Octree as Octree
28
29import Control.Applicative ((<*>))
30import Control.Monad (foldM)
31import Data.Foldable as F (foldl', mapM_)
32import Data.Functor ((<$>))
33import qualified Data.List as L (delete, filter, find)
34
35
36data Scene obj =
37 ListScene
38 { objects :: [obj]
39 }
40 |
41 OctreeScene
42 { collideAABB :: obj -> AABB -> CollisionType
43 , world :: Octree obj
44 }
45
46
47-- | Create a list-based scene.
48listScene :: [obj] -> Scene obj
49listScene = ListScene
50
51
52-- Create an octree-based scene.
53--octreeScene :: (obj -> AABB -> CollisionType) -> (obj -> AABB) -> [obj] -> Scene obj msg
54--octreeScene collide getAABB objs = OctreeScene [] collide $ makeOctree
55
56
57-- | Add a game object to the given 'Scene'.
58add :: Scene obj -> obj -> Scene obj
59add (scene@ListScene {}) o = scene { objects = o : objects scene }
60add (scene@OctreeScene {}) o = scene { world = insert (collideAABB scene) (world scene) o }
61
62
63-- | Add a list of game objects to the given 'Scene'.
64addl :: Scene obj -> [obj] -> Scene obj
65addl (scene@ListScene {}) l = scene { objects = l ++ objects scene }
66addl (scene@OctreeScene {}) l = scene { world = insertl (collideAABB scene) (world scene) l }
67
68
69-- | Remove a game object from the given 'Scene'.
70remove :: Eq obj => Scene obj -> obj -> Scene obj
71remove (scene@ListScene {}) o = scene { objects = L.delete o (objects scene) }
72--remove (scene@OctreeScene {}) o =
73
74
75-- | Remove those game objects that do not satisfy the given predicate from the 'Scene'.
76filter :: (obj -> Bool) -> Scene obj -> Scene obj
77filter pred (scene@ListScene {}) = scene { objects = L.filter pred (objects scene) }
78
79
80-- | Search for an object in the 'Scene'.
81find :: (obj -> Bool) -> Scene obj -> Maybe obj
82find pred (scene@ListScene {}) = L.find pred $ objects scene
83
84
85type Update obj = obj -> obj
86
87
88-- | Update the given scene.
89update :: (obj -> obj) -> Scene obj -> Scene obj
90update updt (scene@ListScene {}) = scene { objects = fmap updt $ objects scene }
91update updt (scene@OctreeScene {}) = scene { world = Octree.map (collideAABB scene) updt $ world scene }
92
93
94-- | Update the given scene.
95updateM :: Monad m => (obj -> m obj) -> Scene obj -> m (Scene obj)
96updateM updt scene@ListScene {} = mapM updt (objects scene) >>= return . ListScene
97
98
99{-update' :: (obj -> (obj, [a])) -> Scene obj -> (Scene obj, [a])
100
101update' updt (scene@ListScene {}) =
102 let (objs, msgs) = unzip . fmap updt $ objects scene
103 in (scene { objects = objs }, concat msgs)-}
104
105
106-- | Perform collisions.
107collide :: (obj -> obj -> obj) -> Scene obj -> Scene obj
108
109collide col scene@ListScene {} =
110 let objs = objects scene
111 objs' = fmap col' objs
112 col' o = foldl' col o objs
113 in
114 scene { objects = objs' }
115
116collide col scene@OctreeScene {} =
117 scene { world = gmap (collideAABB scene) col $ world scene }
118
119
120-- | Perform collisions.
121collideM :: Monad m => (obj -> obj -> m obj) -> Scene obj -> m (Scene obj)
122collideM col scene@ListScene {} =
123 let objs = objects scene
124
125 col' o = foldM f o objs
126 f o p = col o p
127
128 objs' = sequence . fmap col' $ objs
129 in
130 objs' >>= return . ListScene
131
132
133{-collide' :: (obj -> obj -> (obj, [a])) -> Scene obj -> (Scene obj, [a])
134
135collide' col scene@ListScene {} =
136 let objs = objects scene
137
138 --col' :: obj -> (obj, [a])
139 col' o = foldl' f (o, []) objs
140
141 --f :: (obj, [a]) -> obj -> (obj, [a])
142 f (o, msgs) p = let (o', msgs') = col o p in (o', msgs' ++ msgs)
143
144 (objs', msgs) = let (os, ms) = (unzip . fmap col' $ objs) in (os, concat ms)
145 in
146 (scene { objects = objs' }, msgs)-}
147
148
149-- | Render the given 'Scene'.
150render :: (obj -> Game s ()) -> Scene obj -> Game s ()
151render rend (scene@ListScene {}) = Prelude.mapM_ rend $ objects scene
152render rend (scene@OctreeScene {}) = F.mapM_ rend $ world scene
diff --git a/Spear/Scene/SceneResources.hs b/Spear/Scene/SceneResources.hs
new file mode 100644
index 0000000..e54f385
--- /dev/null
+++ b/Spear/Scene/SceneResources.hs
@@ -0,0 +1,72 @@
1module Spear.Scene.SceneResources
2(
3 SceneResources(..)
4, StaticProgram(..)
5, AnimatedProgram(..)
6, emptySceneResources
7, getShader
8, getStaticProgram
9, getAnimatedProgram
10, getTexture
11, getStaticModel
12, getAnimatedModel
13)
14where
15
16
17import Spear.Assets.Model as Model
18import Spear.GLSL as GLSL
19import Spear.Math.Vector3
20import Spear.Render.AnimatedModel
21import Spear.Render.Material
22import Spear.Render.Program
23import Spear.Render.StaticModel
24import Spear.Render.Texture
25import Spear.Scene.Light
26
27import Data.Map as M
28
29
30data SceneResources = SceneResources
31 { shaders :: Map String GLSLShader
32 , staticPrograms :: Map String StaticProgram
33 , animatedPrograms :: Map String AnimatedProgram
34 , textures :: Map String Texture
35 , staticModels :: Map String StaticModelResource
36 , animatedModels :: Map String AnimatedModelResource
37 , lights :: [Light]
38 }
39
40
41-- | Build an empty instance of 'SceneResources'.
42emptySceneResources = SceneResources M.empty M.empty M.empty M.empty M.empty M.empty []
43
44
45-- | Get the 'GLSLShader' specified by the given 'String' from the given 'SceneResources'.
46getShader :: SceneResources -> String -> Maybe GLSLShader
47getShader res key = M.lookup key $ shaders res
48
49
50-- | Get the 'StaticProgram' specified by the given 'String' from the given 'SceneResources'.
51getStaticProgram :: SceneResources -> String -> Maybe StaticProgram
52getStaticProgram res key = M.lookup key $ staticPrograms res
53
54
55-- | Get the 'AnimatedProgram' specified by the given 'String' from the given 'SceneResources'.
56getAnimatedProgram :: SceneResources -> String -> Maybe AnimatedProgram
57getAnimatedProgram res key = M.lookup key $ animatedPrograms res
58
59
60-- | Get the 'Texture' specified by the given 'String' from the given 'SceneResources'.
61getTexture :: SceneResources -> String -> Maybe Texture
62getTexture res key = M.lookup key $ textures res
63
64
65-- | Get the 'StaticModelResource' specified by the given 'String' from the given 'SceneResources'.
66getStaticModel :: SceneResources -> String -> Maybe StaticModelResource
67getStaticModel res key = M.lookup key $ staticModels res
68
69
70-- | Get the 'AnimatedModelResource' specified by the given 'String' from the given 'SceneResources'.
71getAnimatedModel :: SceneResources -> String -> Maybe AnimatedModelResource
72getAnimatedModel res key = M.lookup key $ animatedModels res
diff --git a/Spear/Setup.hs b/Spear/Setup.hs
new file mode 100644
index 0000000..2f16c54
--- /dev/null
+++ b/Spear/Setup.hs
@@ -0,0 +1,52 @@
1module Spear.Setup
2(
3 Setup
4, Resource
5, register
6, release
7, runSetup
8, runSetup_
9, setupError
10, setupIO
11)
12where
13
14
15import Control.Monad.Error
16import qualified Control.Monad.Resource as R
17import qualified Control.Monad.Trans.Class as MT (lift)
18
19
20type Setup = R.ResourceT (ErrorT String IO)
21
22type Resource = R.ReleaseKey
23
24
25-- | Register the given cleaner.
26register :: IO () -> Setup Resource
27register = R.register
28
29
30-- | Release the given 'Resource'.
31release :: Resource -> Setup ()
32release = R.release
33
34
35-- | Run the given 'Setup', freeing all of its allocated resources.
36runSetup :: Setup a -> IO (Either String a)
37runSetup = runErrorT . R.runResourceT
38
39
40-- | Run the given 'Setup', freeing all of its allocated resources.
41runSetup_ :: Setup a -> IO ()
42runSetup_ s = (runErrorT . R.runResourceT) s >> return ()
43
44
45-- | Throw an error from the 'Setup' monad.
46setupError :: String -> Setup a
47setupError = MT.lift . throwError
48
49
50-- | Lift the given IO action into the 'Setup' monad.
51setupIO :: IO a -> Setup a
52setupIO = MT.lift . MT.lift
diff --git a/Spear/Sys/Timer.hs b/Spear/Sys/Timer.hs
new file mode 100644
index 0000000..a44f7f9
--- /dev/null
+++ b/Spear/Sys/Timer.hs
@@ -0,0 +1,194 @@
1{-# INCLUDE "Timer/Timer.h" #-}
2{-# LINE 1 "Timer.hsc" #-}
3{-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns #-}
4{-# LINE 2 "Timer.hsc" #-}
5module Spear.Sys.Timer
6(
7 Timer
8, initialiseTimingSubsystem
9, newTimer
10, tick
11, reset
12, stop
13, start
14, sleep
15, getTime
16, getDelta
17, isRunning
18)
19where
20
21
22import Foreign
23import Foreign.C.Types
24import Control.Monad
25import System.IO.Unsafe
26
27
28
29{-# LINE 28 "Timer.hsc" #-}
30type TimeReading = CDouble
31
32{-# LINE 30 "Timer.hsc" #-}
33
34data Timer = Timer {
35 getBaseTime :: TimeReading
36, getPausedTime :: TimeReading
37, getStopTime :: TimeReading
38, getPrevTime :: TimeReading
39, getCurTime :: TimeReading
40, getDeltaTime :: CFloat
41, getRunning :: CChar
42}
43
44
45
46{-# LINE 43 "Timer.hsc" #-}
47
48
49instance Storable Timer where
50 sizeOf _ = (48)
51{-# LINE 47 "Timer.hsc" #-}
52 alignment _ = alignment (undefined :: TimeReading)
53
54 peek ptr = do
55 baseTime <- (\hsc_ptr -> peekByteOff hsc_ptr 0) ptr
56{-# LINE 51 "Timer.hsc" #-}
57 pausedTime <- (\hsc_ptr -> peekByteOff hsc_ptr 8) ptr
58{-# LINE 52 "Timer.hsc" #-}
59 stopTime <- (\hsc_ptr -> peekByteOff hsc_ptr 16) ptr
60{-# LINE 53 "Timer.hsc" #-}
61 prevTime <- (\hsc_ptr -> peekByteOff hsc_ptr 24) ptr
62{-# LINE 54 "Timer.hsc" #-}
63 curTime <- (\hsc_ptr -> peekByteOff hsc_ptr 32) ptr
64{-# LINE 55 "Timer.hsc" #-}
65 deltaTime <- (\hsc_ptr -> peekByteOff hsc_ptr 40) ptr
66{-# LINE 56 "Timer.hsc" #-}
67 stopped <- (\hsc_ptr -> peekByteOff hsc_ptr 44) ptr
68{-# LINE 57 "Timer.hsc" #-}
69 return $ Timer baseTime pausedTime stopTime prevTime curTime deltaTime stopped
70
71 poke ptr (Timer baseTime pausedTime stopTime prevTime curTime deltaTime stopped) = do
72 (\hsc_ptr -> pokeByteOff hsc_ptr 0) ptr baseTime
73{-# LINE 61 "Timer.hsc" #-}
74 (\hsc_ptr -> pokeByteOff hsc_ptr 8) ptr pausedTime
75{-# LINE 62 "Timer.hsc" #-}
76 (\hsc_ptr -> pokeByteOff hsc_ptr 16) ptr stopTime
77{-# LINE 63 "Timer.hsc" #-}
78 (\hsc_ptr -> pokeByteOff hsc_ptr 24) ptr prevTime
79{-# LINE 64 "Timer.hsc" #-}
80 (\hsc_ptr -> pokeByteOff hsc_ptr 32) ptr curTime
81{-# LINE 65 "Timer.hsc" #-}
82 (\hsc_ptr -> pokeByteOff hsc_ptr 40) ptr deltaTime
83{-# LINE 66 "Timer.hsc" #-}
84 (\hsc_ptr -> pokeByteOff hsc_ptr 44) ptr stopped
85{-# LINE 67 "Timer.hsc" #-}
86
87
88foreign import ccall "Timer.h timer_initialise_subsystem"
89 c_timer_initialise_subsystem :: IO ()
90
91foreign import ccall "Timer.h timer_initialise_timer"
92 c_timer_initialise_timer :: Ptr Timer -> IO ()
93
94foreign import ccall "Timer.h timer_tick"
95 c_timer_tick :: Ptr Timer -> IO ()
96
97foreign import ccall "Timer.h timer_reset"
98 c_timer_reset :: Ptr Timer -> IO ()
99
100foreign import ccall "Timer.h timer_stop"
101 c_timer_stop :: Ptr Timer -> IO ()
102
103foreign import ccall "Timer.h timer_start"
104 c_timer_start :: Ptr Timer -> IO ()
105
106foreign import ccall "Timer.h timer_sleep"
107 c_timer_sleep :: CFloat -> IO ()
108
109foreign import ccall "Timer.h timer_get_time"
110 c_timer_get_time :: Ptr Timer -> IO (CFloat)
111
112foreign import ccall "Timer.h timer_get_delta"
113 c_timer_get_delta :: Ptr Timer -> IO (CFloat)
114
115foreign import ccall "Timer.h timer_is_running"
116 c_timer_is_running :: Ptr Timer -> IO (CChar)
117
118
119-- | Initialises the timing subsystem.
120initialiseTimingSubsystem :: IO ()
121initialiseTimingSubsystem = c_timer_initialise_subsystem
122
123
124-- | Creates a timer.
125newTimer :: Timer
126newTimer = unsafePerformIO . alloca $ \tptr -> do
127 c_timer_initialise_timer tptr
128 t <- peek tptr
129 return t
130
131
132-- | Updates the timer.
133tick :: Timer -> IO (Timer)
134tick t = alloca $ \tptr -> do
135 poke tptr t
136 c_timer_tick tptr
137 t' <- peek tptr
138 return t'
139
140
141-- | Resets the timer.
142reset :: Timer -> IO (Timer)
143reset t = alloca $ \tptr -> do
144 poke tptr t
145 c_timer_reset tptr
146 t' <- peek tptr
147 return t'
148
149
150-- | Stops the timer.
151stop :: Timer -> IO (Timer)
152stop t = alloca $ \tptr -> do
153 poke tptr t
154 c_timer_stop tptr
155 t' <- peek tptr
156 return t'
157
158
159-- | Starts the timer.
160start :: Timer -> IO (Timer)
161start t = alloca $ \tptr -> do
162 poke tptr t
163 c_timer_start tptr
164 t' <- peek tptr
165 return t'
166
167
168-- | Puts the caller thread to sleep for the given number of seconds.
169sleep :: Float -> IO ()
170sleep = c_timer_sleep . realToFrac
171
172
173-- | Gets the timer's total running time.
174getTime :: Timer -> Float
175getTime t = unsafePerformIO . alloca $ \tptr -> do
176 poke tptr t
177 time <- c_timer_get_time tptr
178 return (realToFrac time)
179
180
181-- | Gets the timer's delta since the last tick.
182getDelta :: Timer -> Float
183getDelta t = unsafePerformIO . alloca $ \tptr -> do
184 poke tptr t
185 dt <- c_timer_get_delta tptr
186 return (realToFrac dt)
187
188
189-- | Returns true if the timer is running, false otherwise.
190isRunning :: Timer -> Bool
191isRunning t = unsafePerformIO . alloca $ \tptr -> do
192 poke tptr t
193 running <- c_timer_is_running tptr
194 return (running /= 0)
diff --git a/Spear/Sys/Timer.hsc b/Spear/Sys/Timer.hsc
new file mode 100644
index 0000000..c800c8d
--- /dev/null
+++ b/Spear/Sys/Timer.hsc
@@ -0,0 +1,175 @@
1{-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns #-}
2module Spear.Sys.Timer
3(
4 Timer
5, initialiseTimingSubsystem
6, newTimer
7, tick
8, reset
9, stop
10, start
11, sleep
12, getTime
13, getDelta
14, isRunning
15)
16where
17
18
19import Foreign hiding (unsafePerformIO)
20import Foreign.C.Types
21import Control.Monad
22import System.IO.Unsafe
23
24
25#ifdef WIN32
26type TimeReading = CULLong
27#else
28type TimeReading = CDouble
29#endif
30
31data Timer = Timer {
32 getBaseTime :: TimeReading
33, getPausedTime :: TimeReading
34, getStopTime :: TimeReading
35, getPrevTime :: TimeReading
36, getCurTime :: TimeReading
37, getDeltaTime :: CFloat
38, getRunning :: CChar
39}
40
41
42#include "Timer/Timer.h"
43
44
45instance Storable Timer where
46 sizeOf _ = #{size timer}
47 alignment _ = alignment (undefined :: TimeReading)
48
49 peek ptr = do
50 baseTime <- #{peek timer, baseTime} ptr
51 pausedTime <- #{peek timer, pausedTime} ptr
52 stopTime <- #{peek timer, stopTime} ptr
53 prevTime <- #{peek timer, prevTime} ptr
54 curTime <- #{peek timer, curTime} ptr
55 deltaTime <- #{peek timer, deltaTime} ptr
56 stopped <- #{peek timer, stopped} ptr
57 return $ Timer baseTime pausedTime stopTime prevTime curTime deltaTime stopped
58
59 poke ptr (Timer baseTime pausedTime stopTime prevTime curTime deltaTime stopped) = do
60 #{poke timer, baseTime} ptr baseTime
61 #{poke timer, pausedTime} ptr pausedTime
62 #{poke timer, stopTime} ptr stopTime
63 #{poke timer, prevTime} ptr prevTime
64 #{poke timer, curTime} ptr curTime
65 #{poke timer, deltaTime} ptr deltaTime
66 #{poke timer, stopped} ptr stopped
67
68
69foreign import ccall "Timer.h timer_initialise_subsystem"
70 c_timer_initialise_subsystem :: IO ()
71
72foreign import ccall "Timer.h timer_initialise_timer"
73 c_timer_initialise_timer :: Ptr Timer -> IO ()
74
75foreign import ccall "Timer.h timer_tick"
76 c_timer_tick :: Ptr Timer -> IO ()
77
78foreign import ccall "Timer.h timer_reset"
79 c_timer_reset :: Ptr Timer -> IO ()
80
81foreign import ccall "Timer.h timer_stop"
82 c_timer_stop :: Ptr Timer -> IO ()
83
84foreign import ccall "Timer.h timer_start"
85 c_timer_start :: Ptr Timer -> IO ()
86
87foreign import ccall "Timer.h timer_sleep"
88 c_timer_sleep :: CFloat -> IO ()
89
90foreign import ccall "Timer.h timer_get_time"
91 c_timer_get_time :: Ptr Timer -> IO (CFloat)
92
93foreign import ccall "Timer.h timer_get_delta"
94 c_timer_get_delta :: Ptr Timer -> IO (CFloat)
95
96foreign import ccall "Timer.h timer_is_running"
97 c_timer_is_running :: Ptr Timer -> IO (CChar)
98
99
100-- | Initialises the timing subsystem.
101initialiseTimingSubsystem :: IO ()
102initialiseTimingSubsystem = c_timer_initialise_subsystem
103
104
105-- | Creates a timer.
106newTimer :: Timer
107newTimer = unsafePerformIO . alloca $ \tptr -> do
108 c_timer_initialise_timer tptr
109 t <- peek tptr
110 return t
111
112
113-- | Updates the timer.
114tick :: Timer -> IO (Timer)
115tick t = alloca $ \tptr -> do
116 poke tptr t
117 c_timer_tick tptr
118 t' <- peek tptr
119 return t'
120
121
122-- | Resets the timer.
123reset :: Timer -> IO (Timer)
124reset t = alloca $ \tptr -> do
125 poke tptr t
126 c_timer_reset tptr
127 t' <- peek tptr
128 return t'
129
130
131-- | Stops the timer.
132stop :: Timer -> IO (Timer)
133stop t = alloca $ \tptr -> do
134 poke tptr t
135 c_timer_stop tptr
136 t' <- peek tptr
137 return t'
138
139
140-- | Starts the timer.
141start :: Timer -> IO (Timer)
142start t = alloca $ \tptr -> do
143 poke tptr t
144 c_timer_start tptr
145 t' <- peek tptr
146 return t'
147
148
149-- | Puts the caller thread to sleep for the given number of seconds.
150sleep :: Float -> IO ()
151sleep = c_timer_sleep . realToFrac
152
153
154-- | Gets the timer's total running time.
155getTime :: Timer -> Float
156getTime t = unsafePerformIO . alloca $ \tptr -> do
157 poke tptr t
158 time <- c_timer_get_time tptr
159 return (realToFrac time)
160
161
162-- | Gets the timer's delta since the last tick.
163getDelta :: Timer -> Float
164getDelta t = unsafePerformIO . alloca $ \tptr -> do
165 poke tptr t
166 dt <- c_timer_get_delta tptr
167 return (realToFrac dt)
168
169
170-- | Returns true if the timer is running, false otherwise.
171isRunning :: Timer -> Bool
172isRunning t = unsafePerformIO . alloca $ \tptr -> do
173 poke tptr t
174 running <- c_timer_is_running tptr
175 return (running /= 0)
diff --git a/Spear/Sys/Timer/Timer.h b/Spear/Sys/Timer/Timer.h
new file mode 100644
index 0000000..60b81f7
--- /dev/null
+++ b/Spear/Sys/Timer/Timer.h
@@ -0,0 +1,73 @@
1#ifndef _SPEAR_TIMER_H
2#define _SPEAR_TIMER_H
3
4#ifdef _MSC_VER
5 #ifdef DLL_EXPORT
6 #define DECLDIR __declspec(dllexport)
7 #else
8 #define DECLDIR __declspec(dllimport)
9 #endif
10#else
11 #define DECLDIR
12#endif
13
14#ifdef WIN32
15 #ifdef _MSC_VER
16 typedef __int64 timeReading;
17 #else
18 typedef __UINT64_TYPE__ timeReading;
19 #endif
20#else
21 typedef double timeReading;
22#endif
23
24#ifdef __cplusplus
25extern C {
26#endif
27
28typedef struct
29{
30 timeReading baseTime;
31 timeReading pausedTime;
32 timeReading stopTime;
33 timeReading prevTime;
34 timeReading curTime;
35 float deltaTime;
36 char stopped;
37} timer;
38
39/// Initialises the timing subsystem.
40void DECLDIR timer_initialise_subsystem ();
41
42/// Initialises a timer.
43void DECLDIR timer_initialise_timer (timer* t);
44
45/// Call every frame.
46void DECLDIR timer_tick (timer* t);
47
48/// Call before message loop.
49void DECLDIR timer_reset (timer* t);
50
51/// Call when paused.
52void DECLDIR timer_stop (timer* t);
53
54/// Call when unpaused.
55void DECLDIR timer_start (timer* t);
56
57/// Puts the caller thread to sleep for the given number of seconds.
58void DECLDIR timer_sleep (float seconds);
59
60/// Returns total running time in seconds.
61float DECLDIR timer_get_time (timer* t);
62
63/// Returns the elapsed time in seconds.
64float DECLDIR timer_get_delta (timer* t);
65
66/// Gets the timer's running state.
67char DECLDIR timer_is_running (timer* t);
68
69#ifdef __cplusplus
70}
71#endif
72
73#endif // _SPEAR_TIMER_H
diff --git a/Spear/Sys/Timer/ctimer.c b/Spear/Sys/Timer/ctimer.c
new file mode 100644
index 0000000..7f7ffe0
--- /dev/null
+++ b/Spear/Sys/Timer/ctimer.c
@@ -0,0 +1,172 @@
1#include "Timer.h"
2#include <stdlib.h>
3
4#ifdef __APPLE__
5 #include <mach/mach_time.h>
6#elif WIN32
7 #define WIN32_LEAN_AND_MEAN
8 #include <Windows.h>
9#else // Linux
10 #include <time.h>
11 const double NSEC_TO_SEC = 1.0f/1000000000.0f;
12 const double SEC_TO_NSEC = 1000000000.0f;
13#endif
14
15
16static double secondsPerCount;
17
18
19void timer_initialise_subsystem ()
20{
21#ifdef WIN32
22 __int64 countsPerSec;
23 QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
24 secondsPerCount = 1.0 / (double)countsPerSec;
25#else
26 /*struct timespec ts;
27 clock_getres(CLOCK_REALTIME, &ts);
28 secondsPerCount = (double)ts.tv_sec + ((double)ts.tv_nsec * NSEC_TO_SEC);*/
29 secondsPerCount = 1.0f;
30#endif
31}
32
33
34timeReading now ()
35{
36 timeReading t;
37
38#ifdef __APPLE__
39 t = mach_absolute_time();
40#elif WIN32
41 QueryPerformanceCounter((LARGE_INTEGER*)&t);
42#else
43 struct timespec ts;
44 clock_gettime(CLOCK_REALTIME, &ts);
45 t = ts.tv_sec + ((double)ts.tv_nsec * NSEC_TO_SEC);
46#endif
47
48 return t;
49}
50
51
52void DECLDIR timer_initialise_timer (timer* t)
53{
54 t->baseTime = 0;
55 t->pausedTime = 0;
56 t->stopTime = 0;
57 t->prevTime = 0;
58 t->curTime = 0;
59 t->deltaTime = 0;
60 t->stopped = 1;
61}
62
63
64void timer_tick (timer* t)
65{
66 if (t->stopped)
67 {
68 t->deltaTime = 0.0;
69 return;
70 }
71
72 //Get the time on this frame.
73 t->curTime = now();
74
75 //Time delta between the current frame and the previous.
76 t->deltaTime = (float) ((t->curTime - t->prevTime) * secondsPerCount);
77
78 //Update for next frame.
79 t->prevTime = t->curTime;
80
81 // Force nonnegative. The DXSDK's CDXUTTimer mentions that if the
82 // processor goes into a power save mode or we get shuffled to
83 // another processor, then mDeltaTime can be negative.
84 if(t->deltaTime < 0.0)
85 {
86 t->deltaTime = 0.0;
87 }
88}
89
90
91void timer_reset (timer* t)
92{
93 t->curTime = now();
94 t->baseTime = t->curTime;
95 t->prevTime = t->curTime;
96 t->stopTime = 0;
97 t->stopped = 0;
98}
99
100
101void timer_stop (timer* t)
102{
103 // Don't do anything if we are already stopped.
104 if (!t->stopped)
105 {
106 // Grab the stop time.
107 t->stopTime = now();
108
109 // Now we are stopped.
110 t->stopped = 1;
111 }
112}
113
114
115void timer_start (timer* t)
116{
117 // Only start if we are stopped.
118 if (t->stopped)
119 {
120 timeReading startTime = now();
121
122 // Accumulate the paused time.
123 t->pausedTime = t->pausedTime + startTime - t->stopTime;
124
125 // Make the previous time valid.
126 t->prevTime = startTime;
127
128 //Now we are running.
129 t->stopTime = 0;
130 t->stopped = 0;
131 }
132}
133
134
135void timer_sleep (float seconds)
136{
137#ifdef WIN32
138 Sleep((DWORD)(seconds * 1000));
139#else
140 struct timespec ts;
141 ts.tv_sec = 0;
142 ts.tv_nsec = seconds * SEC_TO_NSEC;
143 nanosleep(&ts, NULL);
144#endif
145}
146
147
148float timer_get_time (timer* t)
149{
150 // If we are stopped, we do not count the time we have been stopped for.
151 if (t->stopped)
152 {
153 return (float)((t->stopTime - t->baseTime) * secondsPerCount);
154 }
155 // Otherwise return the time elapsed since the start of the game without counting the time we have been paused for.
156 else
157 {
158 return (float)((t->curTime - t->baseTime - t->pausedTime) * secondsPerCount);
159 }
160}
161
162
163float timer_get_delta (timer* t)
164{
165 return t->deltaTime;
166}
167
168
169char timer_is_running (timer* t)
170{
171 return !t->stopped;
172}
diff --git a/Spear/Sys/Timer/main.hs b/Spear/Sys/Timer/main.hs
new file mode 100644
index 0000000..8cf1a76
--- /dev/null
+++ b/Spear/Sys/Timer/main.hs
@@ -0,0 +1,22 @@
1import Spear.Sys.Timer
2
3main = do
4 initialiseTimingSubsystem
5 wait 3
6 putStrLn "Done"
7
8
9wait secs = do
10 timer <- start newTimer
11 wait' secs timer
12
13
14wait' secs timer = do
15 timer' <- tick timer
16 let t = getTime timer'
17
18 putStrLn $ show t
19
20 if t >= secs then return ()
21 else wait' secs timer'
22 \ No newline at end of file
diff --git a/Spear/Updatable.hs b/Spear/Updatable.hs
new file mode 100644
index 0000000..2c7a7e9
--- /dev/null
+++ b/Spear/Updatable.hs
@@ -0,0 +1,9 @@
1module Spear.Updatable
2where
3
4
5-- | A type class for types that can update themselves given a time delta.
6class Updatable a where
7
8 -- | Updates the given 'Updatable'.
9 update :: Float -> a -> a