diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | CHANGELOG.md | 107 | ||||
| -rw-r--r-- | LICENSE | 11 | ||||
| -rw-r--r-- | README.md | 30 | ||||
| -rw-r--r-- | cabal.project | 7 | ||||
| -rw-r--r-- | flake.lock | 26 | ||||
| -rw-r--r-- | flake.nix | 41 | ||||
| -rw-r--r-- | hs-rgfw-example.cabal | 34 | ||||
| -rw-r--r-- | src/Main.hs | 120 |
9 files changed, 378 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd4112d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist-newstyle +result diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d413bc8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,107 @@ + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Eventual] + +### Added + +- test suite for non-`IO` functions + +### Changed + +- use rotors rather than `Linear.Quaternion` for rotation; easily extended to 4D +- move `Player` out of `Camera` +- configure with `Properties` objects in the Model +- use `Double` rather than `FLoat` for internal calculations + - `cursorPos`, `dt` natively `Double` already + +## [0.4.0] - 2025-12-21 + +### Added + +- cube as array of points +- debug feature where `t` and `g` raise and lower speed respectively + +### Changed + +- `initResources` now takes `[[V3 GL.GLfloat]]` rather than `[V3 GL.GLfloat]` + - to migrate: rather than passing `a` pass `[a]`; behaviour is the same +- reduce view distance to 1'000 from 10'000 +- color pixels based on normalized coordinates +- use `haskellPackages.callCabal2nix` in `flake.nix` for brevity, same behaviour + +## [0.3.0] - 2025-12-08 + +### Added + +- Cabal build system +- `.gitignore` against build artifacts + +### Changed + +- versioning using the [PVP standard](https://pvp.haskell.org/), though it will remain SemVer compliant + - SemVer version A.B.C will become PVP version A.B.C.0 +- `README.md` overhauled to reflect new build system + +### Fixed + +- a couple non-impactful typos + +### Removed + +- `Game` module -> moved to `Main` + +## [0.2.1] - 2025-12-08 + +### Changed + +- break Git by using it wrong +- fix Git (maybe) + +## [0.2.0] - 2025-12-07 + +### Added + +- 100% documentation coverage +- 4 outputs of Nix flake + - `debug`: compiles fast, no docs + - `release`: runs fast, no docs + - `docs`: only docs + - `default`: release binary with docs + +### Changed + +- `initResources` no longer takes a `GLFW.Window` argument +- BSD 3-clause license adopted rather than WTFPL + - reasoning: More professional, widely recognised, effectively identical +- `CHANGELOG.md` has more formatting, namely inline code +- clarify a couple entries in the `[0.1.0] - 2025-12-07 Changed` entry + +### Fixed + +- semantic issues; no warnings are thrown + +## [0.1.0] - 2025-12-07 + +### Added + +- added `CHANGELOG.md` +- layer correctly drawn objects in `view` + +### Changed + +- 8xMSAA rather than 4xMSAA window hint to improve AA +- todo and changelog in `CHANGELOG.md` rather than `README.md` +- a nubmer fo functions from `Game` now in `Game.Internal` +- `initResources` takes an `[V3 GL.GLfloat]` to draw rather than hardcoded arrays +- square the distance of the far plane of the perspective transform +- `loop` function takes delta time `dt :: Float` + +### Fixed + +- constrained pitch to disallow gimbal lock and an inverted view @@ -0,0 +1,11 @@ +Copyright 2025 Andromeda + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3065c4 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +see CHANGELOG.md + +to run without nix: +- get build tools: + - with apt: `apt install cabal-install ghc git` +- get source code: `git clone https://git.mtgmonkey.net/Andromeda/hs-game --depth 1; cd hs-game` +- get dependencies + - with apt on x86-64: `apt install g++-x86-64_linux-gnu libgl-dev libx11-dev libxi-dev libxrandr-dev libxxf86vm-dev libxcursor-dev libxinerama-dev libglu1-mesa-dev` +- run with `cabal run` or build with `cabal build` + +to run with nix: +`nix run git+https://git.mtgmonkey.net/Andromeda/hs-game` + +to enter nix development shell: +`nix develop git+https://git.mtgmonkey.net/Andromeda/hs-game` + +build tested on +- nix +- Kubuntu 25.10 + +to release: +- update CHANGELOG.md with new version +- update version in hs-game.cabal +- update version in flake.nix +- check that it builds +- `git add -A` +- `git status` make sure there aren't random files +- `git status -v` make sure all additions are in CHANGELOG.md +- double check that flake, .cabal, and CHANGELOG.md all have the same version +- release diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..6fa7c79 --- /dev/null +++ b/cabal.project @@ -0,0 +1,7 @@ +packages: + ./ + +source-repository-package + type: git + location: https://git.mtgmonkey.net/andromeda/hs-rgfw.git + tag: d9d14bdc9448fe909bc1ce13c9ec3a94b3b1d5b7 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9386f3f --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1764611609, + "narHash": "sha256-yU9BNcP0oadUKupw0UKmO9BKDOVIg9NStdJosEbXf8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8c29968b3a942f2903f90797f9623737c215737c", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixpkgs-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6413015 --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixpkgs-unstable"; + }; + outputs = { + nixpkgs, + self, + ... + }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system} = { + default = + pkgs.haskell.packages.ghc912.callCabal2nix "hs-rgfw-example" ./. { + }; + }; + devShells.${system} = { + default = pkgs.mkShell { + packages = [ + # dev stuff + pkgs.haskellPackages.ghcide + pkgs.haskellPackages.ormolu + + pkgs.cabal-install + pkgs.libGL + pkgs.xorg.libX11 + pkgs.xorg.libXi + pkgs.xorg.libXrandr + pkgs.xorg.libXxf86vm + pkgs.xorg.libXcursor + pkgs.xorg.libXinerama + pkgs.libGLU + ]; + inputsFrom = [ + self.packages.${system}.default + ]; + }; + }; + }; +} diff --git a/hs-rgfw-example.cabal b/hs-rgfw-example.cabal new file mode 100644 index 0000000..6cbbfc5 --- /dev/null +++ b/hs-rgfw-example.cabal @@ -0,0 +1,34 @@ +cabal-version: 3.0 +name: hs-game +version: 0.5.0 +-- synopsis: +-- description: +homepage: https://git.mtgmonkey.net/Andromeda/hs-rgfw-example +license: BSD-3-Clause +license-file: LICENSE +author: andromeda +maintainer: @andromeda:tchncs.de +-- copyright: +category: Game +build-type: Simple +extra-doc-files: CHANGELOG.md +-- extra-source-files: + +common warnings + ghc-options: -Wall + +common optimizations + ghc-options: -O2 + +executable hs-game + import: optimizations + main-is: Main.hs + other-modules: + build-depends: + base >= 4.18, + bytestring >= 0.12, + rgfw >= 0.6, + lens >= 5.3, + linear >= 1.23, + OpenGL >= 3.0, + hs-source-dirs: src diff --git a/src/Main.hs b/src/Main.hs new file mode 100644 index 0000000..b0dccb9 --- /dev/null +++ b/src/Main.hs @@ -0,0 +1,120 @@ +{-# LANGUAGE DisambiguateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedRecordDot #-} + +-- | +-- - Module : Game +-- - Description : runs game +-- - Copyright : 2025 Andromeda +-- - License : BSD 3-clause +-- - Maintainer : Matrix @Andromeda:tchncs.de +-- - Stability : Experimental +module Main + ( main, + ) +where + +import Control.Lens ((^.)) +import Data.IORef (newIORef) +import Data.List (nub) +import GHC.Float (double2Float) +import Game.Internal +import Game.Internal.Types +import Graphics.Rendering.OpenGL (($=)) +import qualified Graphics.Rendering.OpenGL as GL +import qualified Graphics.UI.GLFW as GLFW +import Linear (V3 (..), V4 (..), (*^), _w, _xyz, _y) +import qualified Linear as L + +-- | Main function runs game +main :: IO () +main = do + _ <- GLFW.init + GLFW.defaultWindowHints + -- OpenGL core >=3.3 + GLFW.windowHint $ GLFW.WindowHint'ContextVersionMajor 3 + GLFW.windowHint $ GLFW.WindowHint'ContextVersionMinor 3 + GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core + -- MSAA + GLFW.windowHint $ GLFW.WindowHint'Samples $ Just 8 + -- alpha + -- create window + monitor <- GLFW.getPrimaryMonitor + Just window <- GLFW.createWindow 256 256 "hs-game" monitor Nothing + GLFW.makeContextCurrent $ Just window + -- add callbacks + GLFW.setWindowCloseCallback window $ Just shutdownWindow + GLFW.setWindowSizeCallback window $ Just resizeWindow + GLFW.setKeyCallback window $ Just (keyPressed Nothing) + GLFW.setCursorInputMode window GLFW.CursorInputMode'Hidden + GLFW.setCursorPosCallback window $ Just (cursorPosHandler Nothing) + + loop window 0 update view modelRef + +-------------------------------------------------------------------------------- +-- Elm-like data structures +-------------------------------------------------------------------------------- + +-- | update function +update :: Float -> Model -> Model +update dt model = model + +-- | views the model +view :: GLFW.Window -> Model -> IO () +view window model = do + 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 + + -- TODO optimise this inefficient pos + let objarr = map (v3tov4 camw) $ sliceW camw $ initArr + (obj, program) <- initResources objarr + + -- fit viewport to window + (w, h) <- GLFW.getFramebufferSize window + GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h)) + -- clear screen + GL.clearColor $= GL.Color4 1 0 1 1 + GL.clear [GL.ColorBuffer, GL.DepthBuffer] + -- depth + GL.depthFunc $= Just GL.Less + -- apply transforms + let pitch = model.camera.camPitch + yaw = model.camera.camYaw + forward = V3 (cos pitch * sin yaw) (sin pitch) (cos pitch * cos yaw) + viewMatrix = + L.lookAt + (model.camera.camPos ^. _xyz) + ((model.camera.camPos ^. _xyz) - forward) + model.wprop.up + projectionMatrix = + L.perspective 1.2 (fromIntegral w / fromIntegral h) 0.01 1000 + + viewGLMatrix <- + GL.newMatrix GL.RowMajor $ toGLMatrix viewMatrix :: + IO + (GL.GLmatrix GL.GLfloat) + + -- load 3d view matrix + viewLocation <- GL.get $ GL.uniformLocation program "u_view" + GL.uniform viewLocation $= viewGLMatrix + + projectionGLMatrix <- + GL.newMatrix GL.RowMajor $ toGLMatrix projectionMatrix :: + IO + (GL.GLmatrix GL.GLfloat) + + -- load 3d projection matrix + projectionLocation <- GL.get $ GL.uniformLocation program "u_projection" + GL.uniform projectionLocation $= projectionGLMatrix + + camWLocation <- GL.get $ GL.uniformLocation program "u_cam" + GL.uniform camWLocation $= GL.Vector4 camx camy camz camw + + -- draw objects; returns IO [] + _ <- drawObjects [obj] + -- swap to current buffer + GLFW.swapBuffers window + -- check for interrupts + GLFW.pollEvents |
