3 Commits

Author SHA1 Message Date
andromeda
602507a605 better projection, transparency; 4d camera, controls r/f for +/-w 2026-02-08 15:08:46 +01:00
andromeda
4d59cd7569 better colors 2026-02-07 18:59:23 +01:00
andromeda
5474012f89 hypercubes but actually 2026-02-07 17:37:05 +01:00
4 changed files with 218 additions and 108 deletions

View File

@@ -11,7 +11,9 @@
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
in { in {
packages.${system} = { packages.${system} = {
default = pkgs.haskellPackages.callCabal2nix "hs-game" ./. {}; default =
pkgs.haskell.packages.ghc912.callCabal2nix "hs-game" ./. {
};
}; };
devShells.${system} = { devShells.${system} = {
default = pkgs.mkShell { default = pkgs.mkShell {

View File

@@ -1,4 +1,5 @@
{-# LANGUAGE DisambiguateRecordFields #-} {-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE MultilineStrings #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedRecordDot #-}
@@ -42,19 +43,24 @@ import Linear (V3 (..), V4 (..))
-- Shader creation and object initialisation -- Shader creation and object initialisation
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
initResources :: [[V4 GL.GLfloat]] -> IO ([Object], GL.Program) initResources :: [V4 GL.GLfloat] -> IO (Object, GL.Program)
initResources arrays = do initResources arr = do
objects <- object <-
listIOsToIOlist createObject arr 4 GL.Triangles (GL.AttribLocation 0)
[createObject arr 4 GL.TriangleStrip (GL.AttribLocation 0) | arr <- arrays]
[] -- compile shader program
program <- program <-
loadShaders loadShaders
[ ShaderInfo GL.VertexShader (StringSource vertShader), [ ShaderInfo GL.VertexShader (StringSource vertShader),
ShaderInfo GL.FragmentShader (StringSource fragShader) ShaderInfo GL.FragmentShader (StringSource fragShader)
] ]
GL.currentProgram $= Just program GL.currentProgram $= Just program
return (objects, program)
-- alpha
GL.blend $= GL.Enabled
GL.blendFunc $= (GL.SrcAlpha, GL.OneMinusSrcAlpha)
return (object, program)
listIOsToIOlist :: [IO a] -> [a] -> IO [a] listIOsToIOlist :: [IO a] -> [a] -> IO [a]
listIOsToIOlist [] out = return out listIOsToIOlist [] out = return out
@@ -67,39 +73,83 @@ listIOsToIOlist (io : ios) out = do
-- u_ uniform -- u_ uniform
-- o_ fragment shader output -- o_ fragment shader output
-- | vertex shader
vertShader :: String vertShader :: String
vertShader = vertShader =
"#version 330 core\n" """
++ "layout (location = 0) in vec4 a_vPos;\n" #version 330 core
++ "uniform mat4 u_view;\n"
++ "uniform mat4 u_projection;\n" layout (location = 0) in vec4 a_vPos;
++ "out vec3 v_pos;\n"
++ glslProjectTo3d uniform mat4 u_view;
++ "void main()\n" uniform mat4 u_projection;
++ "{\n" uniform vec4 u_cam;
++ " gl_Position = u_projection * u_view * vec4(projectTo3d(a_vPos), 1.0);\n"
++ " v_pos = a_vPos.xyz;\n" out vec3 v_pos;
++ "}\n" out float v_w;
out float v_alpha;
vec3 orthoFrom4d(vec4 point)
{
return point.xyz;
}
// creates a simple 3d coordinate from a 4d
vec3 projectFrom4d(vec4 point)
{
// TODO don't do camera ops in shader, prefer linear algebra
// also use a reasonable projection for god's sake
vec4 view = abs(u_cam - point);
float perspective = 1.0 / abs(u_cam.w - view.w);
return perspective * (point.xyz);
}
void main()
{
vec3 vPos = orthoFrom4d(a_vPos);
// TODO don't set constant inside of shader :/
float wHorizon = 3;
float alpha = (wHorizon - abs(u_cam.w - a_vPos.w)) / wHorizon;
// cull invisible things
if (alpha < -1) {
gl_Position = vec4(0.0);
alpha = 0.0;
} else {
alpha = max(alpha, 0.0);
gl_Position = u_projection * u_view * vec4(vPos, 1.0);
}
v_pos = vPos;
v_w = a_vPos.w;
v_alpha = alpha;
}
"""
-- | fragment shader
fragShader :: String fragShader :: String
fragShader = fragShader =
"#version 330 core\n" """
++ "out vec4 o_vColor;\n" #version 330 core
++ "in vec3 v_pos;\n"
++ "void main()\n"
++ "{\n"
++ " o_vColor = vec4(0.5 + 0.5 * normalize(v_pos), 1);\n"
++ "}\n"
glslProjectTo3d :: String uniform vec4 u_cam;
glslProjectTo3d =
"vec3 projectTo3d(vec4 point)\n" out vec4 o_vColor;
++ "{\n"
++ " float perspective = 1.0 / (1.0 + point.w);\n" in vec3 v_pos;
++ " return perspective * point.xyz;\n" in float v_w;
++ "}\n" in float v_alpha;
void main()
{
// the normal vector of the face
// yoinked from https://stackoverflow.com/questions/14980712/how-to-get-flat-normals-on-a-cube/14981446#14981446
vec3 norm = normalize(cross(dFdx(v_pos), dFdy(v_pos)));
// creates a color based on the normal direction
o_vColor = vec4((0.5 + 0.5 * norm) / 2, v_alpha);
}
"""
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Objects -- Objects

View File

@@ -11,7 +11,7 @@
module Game.Internal.Types module Game.Internal.Types
( Object (..), ( Object (..),
toGLMatrix, toGLMatrix,
Model (camera, objects, cursorDeltaPos, cursorPos, program, keys, wprop), Model (camera, objects, cursorDeltaPos, cursorPos, keys, program, wprop),
mkModel, mkModel,
Camera (camPos, camPitch, camYaw, camReference, mouseSensitivity, camVel, strafeStrength, jumpStrength, hasJumped, airTime), Camera (camPos, camPitch, camYaw, camReference, mouseSensitivity, camVel, strafeStrength, jumpStrength, hasJumped, airTime),
mkCamera, mkCamera,
@@ -70,7 +70,6 @@ data Model = Model
keys :: [GLFW.Key], keys :: [GLFW.Key],
-- | draw calls -- | draw calls
objects :: [Object], objects :: [Object],
-- | shader program
program :: GL.Program, program :: GL.Program,
wprop :: WorldProperties wprop :: WorldProperties
} }
@@ -84,7 +83,7 @@ mkModel camera objects program wprop =
-- | camera -- | camera
data Camera = Camera data Camera = Camera
{ -- | position in world space { -- | position in world space
camPos :: V3 Float, camPos :: V4 Float,
-- | pitch in radians, up positive -- | pitch in radians, up positive
camPitch :: Float, camPitch :: Float,
-- | yaw in radians, right positive -- | yaw in radians, right positive
@@ -108,7 +107,7 @@ data Camera = Camera
-- | smart constructor for Camera -- | smart constructor for Camera
mkCamera :: mkCamera ::
V3 Float -> V4 Float ->
Float -> Float ->
Float -> Float ->
V3 Float -> V3 Float ->

View File

@@ -22,7 +22,7 @@ import Game.Internal.Types
import Graphics.Rendering.OpenGL (($=)) import Graphics.Rendering.OpenGL (($=))
import qualified Graphics.Rendering.OpenGL as GL import qualified Graphics.Rendering.OpenGL as GL
import qualified Graphics.UI.GLFW as GLFW import qualified Graphics.UI.GLFW as GLFW
import Linear (V3 (..), V4 (..), _y) import Linear (V3 (..), V4 (..), (*^), _w, _xyz, _y)
import qualified Linear as L import qualified Linear as L
-- | Main function runs game -- | Main function runs game
@@ -36,6 +36,7 @@ main = do
GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core
-- MSAA -- MSAA
GLFW.windowHint $ GLFW.WindowHint'Samples $ Just 8 GLFW.windowHint $ GLFW.WindowHint'Samples $ Just 8
-- alpha
-- create window -- create window
monitor <- GLFW.getPrimaryMonitor monitor <- GLFW.getPrimaryMonitor
Just window <- GLFW.createWindow 256 256 "hs-game" monitor Nothing Just window <- GLFW.createWindow 256 256 "hs-game" monitor Nothing
@@ -46,15 +47,22 @@ main = do
GLFW.setKeyCallback window $ Just (keyPressed Nothing) GLFW.setKeyCallback window $ Just (keyPressed Nothing)
GLFW.setCursorInputMode window GLFW.CursorInputMode'Hidden GLFW.setCursorInputMode window GLFW.CursorInputMode'Hidden
GLFW.setCursorPosCallback window $ Just (cursorPosHandler Nothing) GLFW.setCursorPosCallback window $ Just (cursorPosHandler Nothing)
(objects, program) <- (object, program) <-
initResources $ initResources $
-- [map (v3tov4 0) $ map (+ V3 a 0 0) cube | a <- take 100 [0, 2 ..]] concat
[hCube] ( [hCube]
++ [map (\v -> (V4 a 0 0 a) + (rotate4 0 1 (a * g90 / 24) v)) hCube | a <- take 1000 [0, 2 ..]]
++ [map (\v -> (V4 a 2 0 a) + (rotate4 0 2 (a * g90 / 24) v)) hCube | a <- take 1000 [0, 2 ..]]
++ [map (\v -> (V4 a 4 0 a) + (rotate4 0 3 (a * g90 / 24) v)) hCube | a <- take 1000 [0, 2 ..]]
++ [map (\v -> (V4 a (-2) 0 a) + (rotate4 1 0 (a * g90 / 24) v)) hCube | a <- take 1000 [0, 2 ..]]
++ [map (\v -> (V4 a (-4) 0 a) + (rotate4 2 0 (a * g90 / 24) v)) hCube | a <- take 1000 [0, 2 ..]]
++ [map (\v -> (V4 a (-6) 0 a) + (rotate4 3 0 (a * g90 / 24) v)) hCube | a <- take 1000 [0, 2 ..]]
)
let model = let model =
mkModel mkModel
( mkCamera ( mkCamera
(V3 0 0 3) -- camPos (V4 0 0 3 0) -- camPos
0 -- pitch 0 -- pitch
0 -- yaw 0 -- yaw
(V3 0 0 (-1)) -- reference vector (V3 0 0 (-1)) -- reference vector
@@ -63,7 +71,7 @@ main = do
16 -- strafe strength 16 -- strafe strength
12 -- jump strength 12 -- jump strength
) )
objects [object]
program program
(mkWorldProperties 2 0.16 (V3 0 1 0)) (mkWorldProperties 2 0.16 (V3 0 1 0))
modelRef <- newIORef model modelRef <- newIORef model
@@ -75,34 +83,13 @@ main = do
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Arrays -- Arrays
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
top :: [V3 GL.GLfloat]
top =
[ V3 p 0 p,
V3 p 0 m,
V3 m 0 p,
V3 m 0 m
]
side :: [V3 GL.GLfloat]
side =
[ V3 0 p p,
V3 0 p m,
V3 0 m p,
V3 0 m m
]
front :: [V3 GL.GLfloat]
front =
[ V3 p p 0,
V3 p m 0,
V3 m p 0,
V3 m m 0
]
m = (0 - p) m = (0 - p)
p = 0.5 p = 0.5
g90 = pi / 2
v3tov4 :: GL.GLfloat -> V3 GL.GLfloat -> V4 GL.GLfloat v3tov4 :: GL.GLfloat -> V3 GL.GLfloat -> V4 GL.GLfloat
v3tov4 w (V3 x y z) = V4 x y z w v3tov4 w (V3 x y z) = V4 x y z w
@@ -115,41 +102,77 @@ v3tov4'' w (V3 x y z) = V4 x w y z
v3tov4''' :: GL.GLfloat -> V3 GL.GLfloat -> V4 GL.GLfloat v3tov4''' :: GL.GLfloat -> V3 GL.GLfloat -> V4 GL.GLfloat
v3tov4''' w (V3 x y z) = V4 w x y z v3tov4''' w (V3 x y z) = V4 w x y z
-- | TODO optimise cube rotate :: V3 GL.GLfloat -> GL.GLfloat -> V3 GL.GLfloat -> V3 GL.GLfloat
cube :: [V3 GL.GLfloat] rotate axis angle point = L.fromQuaternion (L.axisAngle axis angle) L.!* point
cube =
[ V3 p p p, -- front rotate4 :: Int -> Int -> GL.GLfloat -> V4 GL.GLfloat -> V4 GL.GLfloat
V3 p m p, rotate4 i0 i1 angle (V4 x y z w) =
V3 m p p, let coords = [x, y, z, w]
V3 m m p, -- down cos' = cos angle
V3 m m m, sin' = sin angle
V3 p m p, xi = coords !! i0
V3 p m m, -- right xj = coords !! i1
V3 p p m, coords' =
V3 p m p, [ if k == i0
V3 p p p, -- up then cos' * xi - sin' * xj
V3 m p p, else
V3 p p m, if k == i1
V3 m p m, -- back then sin' * xi + cos' * xj
V3 p m m, else coords !! k
V3 p p m, | k <- [0 .. 3]
V3 m m m, -- left ]
V3 m p m, in V4 (coords' !! 0) (coords' !! 1) (coords' !! 2) (coords' !! 3)
V3 m m p,
V3 m p p cycle3r :: V3 GL.GLfloat -> V3 GL.GLfloat
cycle3r (V3 a b c) = V3 c a b
cycle3l :: V3 GL.GLfloat -> V3 GL.GLfloat
cycle3l (V3 a b c) = V3 b c a
face :: [V3 GL.GLfloat]
face =
[ V3 m m 0,
V3 p m 0,
V3 m p 0,
V3 m p 0,
V3 p m 0,
V3 p p 0
] ]
-- | TODO optimise hCube -- | cube, side length 1, centered on 0 0 0
cube :: [V3 GL.GLfloat]
cube =
concatMap
( \faceSpec ->
map
(\v -> (rotate (fst faceSpec) g90 v) L.^+^ (rotate (fst faceSpec) g90 (snd faceSpec)))
face
)
[ (V3 0 0 1, V3 0 0 p),
(V3 0 1 0, V3 0 0 p),
(V3 1 0 0, V3 0 0 p),
(V3 0 0 (-1), V3 0 0 m), -- no clue
(V3 0 (-1) 0, V3 0 0 p),
(V3 (-1) 0 0, V3 0 0 p)
]
hCube :: [V4 GL.GLfloat] hCube :: [V4 GL.GLfloat]
hCube = hCube =
(map (v3tov4 m) cube) concatMap
++ (map (v3tov4 p) cube) ( \(w, i0, i1) ->
++ (map (v3tov4' m) cube) map
++ (map (v3tov4' p) cube) (rotate4 i0 i1 g90 . v3tov4 w)
++ (map (v3tov4'' m) cube) cube
++ (map (v3tov4'' p) cube) )
++ (map (v3tov4''' m) cube) [ (p, 3, 0),
++ (map (v3tov4''' p) cube) (p, 0, 3),
(p, 1, 3),
(p, 2, 3),
(m, 3, 0),
(m, 0, 3),
(m, 1, 3),
(m, 2, 3)
]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Elm-like data structures -- Elm-like data structures
@@ -158,10 +181,32 @@ hCube =
-- | update function -- | update function
update :: Float -> Model -> Model update :: Float -> Model -> Model
update dt model = update dt model =
updateVelocity dt $ updateW dt $
updateAcceleration dt $ updateVelocity dt $
updateSpeed dt $ updateAcceleration dt $
updateCameraAngle dt model updateSpeed dt $
updateCameraAngle dt model
updateW :: Float -> Model -> Model
updateW dt model =
if elem GLFW.Key'R model.keys
then
model
{ camera =
model.camera
{ camPos = model.camera.camPos + (V4 0 0 0 (dt * dt * model.camera.strafeStrength))
}
}
else
if elem GLFW.Key'F model.keys
then
model
{ camera =
model.camera
{ camPos = model.camera.camPos - (V4 0 0 0 (dt * dt * model.camera.strafeStrength))
}
}
else model
updateSpeed :: Float -> Model -> Model updateSpeed :: Float -> Model -> Model
updateSpeed dt model = updateSpeed dt model =
@@ -215,7 +260,7 @@ updateAcceleration dt model =
then V3 0 (0 - model.wprop.g * model.camera.airTime) 0 then V3 0 (0 - model.wprop.g * model.camera.airTime) 0
else V3 0 0 0 else V3 0 0 0
camVel' = friction * (model.camera.camVel + movement' + jump) camVel' = friction * (model.camera.camVel + movement' + jump)
aboveGround = (model.camera.camPos + dt L.*^ camVel') ^. _y > 0 aboveGround = ((model.camera.camPos ^. _xyz) + dt L.*^ camVel') ^. _y > 0
in if (elem GLFW.Key'Space model.keys) && (model.camera.hasJumped == False) in if (elem GLFW.Key'Space model.keys) && (model.camera.hasJumped == False)
then then
updateAcceleration dt $ updateAcceleration dt $
@@ -246,7 +291,7 @@ updateAcceleration dt model =
model.camera model.camera
{ airTime = 0, { airTime = 0,
camVel = camVel' * (V3 1 0 1), camVel = camVel' * (V3 1 0 1),
camPos = model.camera.camPos * (V3 1 0 1), camPos = model.camera.camPos * (V4 1 0 1 1),
hasJumped = aboveGround hasJumped = aboveGround
} }
} }
@@ -256,7 +301,7 @@ updateVelocity dt model =
model model
{ camera = { camera =
model.camera model.camera
{ camPos = model.camera.camPos + dt L.*^ model.camera.camVel { camPos = V4 1 1 1 (model.camera.camPos ^. _w) * (L.point $ (model.camera.camPos ^. _xyz) + dt L.*^ model.camera.camVel)
} }
} }
@@ -299,23 +344,37 @@ view window model = do
forward = V3 (cos pitch * sin yaw) (sin pitch) (cos pitch * cos yaw) forward = V3 (cos pitch * sin yaw) (sin pitch) (cos pitch * cos yaw)
viewMatrix = viewMatrix =
L.lookAt L.lookAt
model.camera.camPos (model.camera.camPos ^. _xyz)
(model.camera.camPos - forward) ((model.camera.camPos ^. _xyz) - forward)
model.wprop.up model.wprop.up
projectionMatrix = projectionMatrix =
L.perspective 1.2 (fromIntegral w / fromIntegral h) 0.01 1000 L.perspective 1.2 (fromIntegral w / fromIntegral h) 0.01 1000
viewGLMatrix <- viewGLMatrix <-
GL.newMatrix GL.RowMajor $ toGLMatrix viewMatrix :: GL.newMatrix GL.RowMajor $ toGLMatrix viewMatrix ::
IO IO
(GL.GLmatrix GL.GLfloat) (GL.GLmatrix GL.GLfloat)
-- load 3d view matrix
viewLocation <- GL.get $ GL.uniformLocation model.program "u_view" viewLocation <- GL.get $ GL.uniformLocation model.program "u_view"
GL.uniform viewLocation $= viewGLMatrix GL.uniform viewLocation $= viewGLMatrix
projectionGLMatrix <- projectionGLMatrix <-
GL.newMatrix GL.RowMajor $ toGLMatrix projectionMatrix :: GL.newMatrix GL.RowMajor $ toGLMatrix projectionMatrix ::
IO IO
(GL.GLmatrix GL.GLfloat) (GL.GLmatrix GL.GLfloat)
-- load 3d projection matrix
projectionLocation <- GL.get $ GL.uniformLocation model.program "u_projection" projectionLocation <- GL.get $ GL.uniformLocation model.program "u_projection"
GL.uniform projectionLocation $= projectionGLMatrix GL.uniform projectionLocation $= projectionGLMatrix
let camx = (\(V4 x _ _ _) -> x) model.camera.camPos
camy = (\(V4 _ y _ _) -> y) model.camera.camPos
camz = (\(V4 _ _ z _) -> z) model.camera.camPos
camw = (\(V4 _ _ _ w) -> w) model.camera.camPos
camWLocation <- GL.get $ GL.uniformLocation model.program "u_cam"
GL.uniform camWLocation $= GL.Vector4 camx camy camz camw
-- draw objects; returns IO [] -- draw objects; returns IO []
_ <- drawObjects model.objects _ <- drawObjects model.objects
-- swap to current buffer -- swap to current buffer