mischief-math (empty) → 0.1.0.0
raw patch · 11 files changed
+566/−0 lines, 11 filesdep +basedep +containersdep +data-default
Dependencies added: base, containers, data-default, linear, mischief-ecs, mischief-math, primitive, text
Files
- CHANGELOG.md +5/−0
- LICENSE +29/−0
- mischief-math.cabal +134/−0
- src/Mischief/Math.hs +61/−0
- src/Mischief/Math/Quat.hs +65/−0
- src/Mischief/Math/Transform.hs +90/−0
- src/Mischief/Math/Transform/Plugin.hs +56/−0
- src/Mischief/Math/Vec.hs +36/−0
- src/Mischief/Math/Vec/Orphans/Default.hs +12/−0
- src/Mischief/Math/Vec/Orphans/FieldAccessors.hs +26/−0
- test/Main.hs +52/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for mischief-math++## 0.1.0.0 ++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (c) 2026, Damon+++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * 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.
+ mischief-math.cabal view
@@ -0,0 +1,134 @@+cabal-version: 3.0+-- The cabal-version field refers to the version of the .cabal specification,+-- and can be different from the cabal-install (the tool) version and the+-- Cabal (the library) version you are using. As such, the Cabal (the library)+-- version used must be equal or greater than the version stated in this field.+-- Starting from the specification version 2.2, the cabal-version field must be+-- the first thing in the cabal file.+-- Initial package description 'mischief' generated by+-- 'cabal init'. For further documentation, see:+-- http://haskell.org/cabal/users-guide/+--+-- The name of the package.+name: mischief-math+-- The package version.+-- See the Haskell package versioning policy (PVP) for standards+-- guiding when and how versions should be incremented.+-- https://pvp.haskell.org+-- PVP summary: +-+------- breaking API changes+-- | | +----- non-breaking API additions+-- | | | +--- code changes with no API change+version: 0.1.0.0+-- A short (one-line) description of the package.+-- synopsis:+-- A longer description of the package.+-- description:+-- The license under which the package is released.+license: BSD-3-Clause+-- The file containing the license text.+license-file: LICENSE+-- The package author(s).+author: Damon+-- An email address to which users can send suggestions, bug reports, and patches.+maintainer: pvdoriginal2019@gmail.com+homepage: https://codeberg.org/mischief/mischief+synopsis: Transform and Algebra for the Mischief game engine.+-- A copyright notice.+-- copyright:+build-type: Simple+-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.+extra-doc-files: CHANGELOG.md++-- Extra source files to be distributed with the package, such as examples, or a tutorial module.+-- extra-source-files:+common warnings+ ghc-options:+ "-Wall,"+ -Wno-name-shadowing++library+ -- Import common warning flags.+ import: warnings+ -- Modules exported by the library.+ exposed-modules:+ Mischief.Math+ Mischief.Math.Quat+ Mischief.Math.Transform+ Mischief.Math.Transform.Plugin+ Mischief.Math.Vec+ Mischief.Math.Vec.Orphans.Default+ Mischief.Math.Vec.Orphans.FieldAccessors++ -- Modules included in this library but not exported.+ -- other-modules:+ other-modules:+ -- LANGUAGE extensions used by modules in this package.z+ default-extensions:+ DefaultSignatures+ DeriveAnyClass+ DuplicateRecordFields+ FunctionalDependencies+ ImportQualifiedPost+ MagicHash+ NoFieldSelectors+ OverloadedRecordDot+ QuasiQuotes+ RequiredTypeArguments+ TemplateHaskell+ TypeFamilyDependencies+ UndecidableInstances++ -- other-extensions:+ -- Other library packages from which modules are imported.+ build-depends:+ base >=4.21.0 && <4.22,+ containers >=0.7 && <0.8,+ data-default >=0.8.0 && <0.9,+ linear >=1.23.3 && <1.24,+ mischief-ecs >=0.2.1 && <0.3,+ primitive >=0.9.1 && <0.10,+ text >=2.1.2 && <2.2,++ -- Directories containing source files.+ hs-source-dirs:+ src++ -- Base language which the package is written in.+ default-language:+ GHC2024++ ghc-options: -fno-omit-yields++test-suite mischief-test+ -- Import common warning flags.+ import: warnings+ ghc-options: -fno-omit-yields+ -- Base language which the package is written in.+ default-language: GHC2024+ -- Modules included in this executable, other than Main.+ -- other-modules:+ -- LANGUAGE extensions used by modules in this package.+ default-extensions:+ DeriveAnyClass+ DuplicateRecordFields+ NoFieldSelectors+ NoGeneralizedNewtypeDeriving+ OverloadedRecordDot+ RequiredTypeArguments+ TypeFamilyDependencies++ -- other-extensions:+ -- The interface type and version of the test suite.+ type: exitcode-stdio-1.0+ -- Directories containing source files.+ hs-source-dirs: test+ -- The entrypoint to the test suite.+ main-is: Main.hs+ -- Test dependencies.+ build-depends:+ base >=4.21.0.0 && <4.22,+ containers,+ data-default,+ mischief-ecs,+ mischief-math,+ primitive,
+ src/Mischief/Math.hs view
@@ -0,0 +1,61 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++module Mischief.Math+ ( -- * Dependencies+ -- $deps++ -- * How to Use+ -- $howtouse++ -- * Transform Propagation+ -- $prop+ module Linear,+ module Mischief.Math.Quat,+ module Mischief.Math.Transform,+ module Mischief.Math.Transform.Plugin,+ )+where++import Linear+import Mischief.ECS+import Mischief.Math.Quat (Quat)+import Mischief.Math.Transform (GlobalTransform (..), Transform (..))+import Mischief.Math.Transform qualified as Transform+import Mischief.Math.Transform.Plugin (TransformPlugin, propTransform)+import Mischief.Math.Vec.Orphans.Default ()+import Mischief.Math.Vec.Orphans.FieldAccessors ()++-- $deps+-- * [mischief-ecs])("Mischief.ECS")++-- $howtouse+-- This library is mostly a wrapper around the "Linear" library, which it also re-exports types, functions and operators from.+--+-- The most notable addition is 'Transform'. A component that encodes the translation (position), rotation and scale of entities.+--+-- Transform is usually constructed with its 'Default' instance, optionally specifying some of its fields:+--+-- @+-- insert ('def' {'translation' = 'V3' 5 1 0})+-- @+--+-- To manipulate Transforms, you can import the following module:+--+-- @+-- import "Mischief.Math.Transform" qualified as Transform+-- @+--+-- This module contains a few useful functions, such as @move@ which is used to update the translation:+--+-- @+-- Transform.'Transform.move' ('V3' 5 0 0) transform+-- @+--+-- Other notable functions are @Transform.'Transform.rotate'@. There is also @Transform.'Transform.rotateZ'@ if you wish to rotate+-- a 2d object.++-- $prop+-- You can add 'TransformPlugin' to your app. This will propagate the transform of all entities through the 'ChildOf' component.+-- Basically, each entity's 'Transform' will become their position relative to their parent.+--+-- This plugin also inserts the 'GlobalTransform' component to all relevant entities, which is a wrapper around a Transform that's global.
+ src/Mischief/Math/Quat.hs view
@@ -0,0 +1,65 @@+module Mischief.Math.Quat where++import Data.Default (Default (def))+import Linear (M33, Quaternion (Quaternion), V3 (V3), axisAngle, fromQuaternion)+import Mischief.Math.Vec (Dir3 (Dir3))+import Mischief.Math.Vec.Orphans.FieldAccessors ()++newtype Quat = Quat (Quaternion Float) deriving newtype (Show, Eq, Ord, Num)++instance Default Quat where+ def = identity++identity :: Quat+identity = Quat $ Quaternion 1 $ V3 0 0 0++fromAxisAngle :: Dir3 Float -> Float -> Quat+fromAxisAngle (Dir3 dir) angle = Quat $ axisAngle dir angle++fromXYZW :: (Float, Float, Float, Float) -> Quat+fromXYZW (x, y, z, w) = Quat $ Quaternion w (V3 x y z)++fromRotationX :: Float -> Quat+fromRotationX angle =+ let (s, c) = (sin angle, cos angle)+ in fromXYZW (s, 0, 0, c)++fromRotationY :: Float -> Quat+fromRotationY angle =+ let (s, c) = (sin angle, cos angle)+ in fromXYZW (0, s, 0, c)++fromRotationZ :: Float -> Quat+fromRotationZ angle =+ let (s, c) = (sin angle, cos angle)+ in fromXYZW (0, 0, s, c)++fromAngles :: V3 Float -> Quat+fromAngles vec3 = rotateX vec3.x $ rotateY vec3.y $ rotateZ vec3.z identity++fromEuler :: Euler -> Quat+fromEuler Euler {x, y, z} = fromAngles $ V3 x y z++rotateAxis :: Dir3 Float -> Float -> Quat -> Quat+rotateAxis dir angle quat = quat * fromAxisAngle dir angle++rotateX :: Float -> Quat -> Quat+rotateX angle quat = quat * fromRotationX angle++rotateY :: Float -> Quat -> Quat+rotateY angle quat = quat * fromRotationY angle++rotateZ :: Float -> Quat -> Quat+rotateZ angle quat = quat * fromRotationZ angle++toMat3 :: Quat -> M33 Float+toMat3 (Quat quat) = fromQuaternion quat++toEuler :: Quat -> Euler+toEuler = m33ToEuler . toMat3++data Euler = Euler {x :: Float, y :: Float, z :: Float} deriving (Show, Eq)++m33ToEuler :: M33 Float -> Euler+m33ToEuler (V3 (V3 m11 m12 m13) (V3 _ _ m23) (V3 _ _ m33)) =+ Euler {x = atan2 (-m23) m33 * 0.5, y = asin m13 * 0.5, z = atan2 (-m12) m11 * 0.5}
+ src/Mischief/Math/Transform.hs view
@@ -0,0 +1,90 @@+{-# OPTIONS_GHC -Wno-missing-methods #-}++module Mischief.Math.Transform where++import Data.Default (Default)+import GHC.Generics (Generic)+import Linear.Quaternion qualified as Quaternion+import Mischief.ECS (Tick, def)+import Mischief.ECS.Components (Component (onAdd))+import Mischief.ECS.Components.HooksDef+ ( HookContext (HookContext, entity),+ )+import Mischief.ECS.Exports (Default, Generic)+import Mischief.ECS.Hooks (hook)+import Mischief.ECS.World.Insert (insert)+import Mischief.ECS.World.Query (single)+import Mischief.ECS.World.Query.TH (q)+import Mischief.Math.Quat (Quat (Quat))+import Mischief.Math.Quat qualified as Quat+import Mischief.Math.Vec+import Mischief.Math.Vec.Orphans.Default ()++newtype GlobalTransform = GlobalTransform {transform :: Transform}+ deriving anyclass (Component, Default)+ deriving stock (Generic, Show)++data Transform = Transform {translation :: V3 Float, rotation :: Quat, scale :: V3 Float} deriving (Generic, Show, Eq)++instance Default Transform where+ def = Transform {translation = V3 0 0 0, rotation = def, scale = V3 1 1 1}++instance Component Transform where+ onAdd =+ [ hook $ \HookContext {entity} -> do+ Just transform <- single [q|entity. Transform|]+ insert (GlobalTransform transform) entity+ ]++composeTransforms :: Transform -> Transform -> Transform+composeTransforms t1 t2 =+ let Quat quat1 = t1.rotation+ in Transform+ { translation = t1.translation + (quat1 `Quaternion.rotate` (t1.scale * t2.translation)),+ rotation = t1.rotation * t2.rotation,+ scale = t1.scale * t2.scale+ }++setRotation :: Quat -> Transform -> Transform+setRotation rotation Transform {translation, scale} =+ Transform {translation, rotation, scale}++setRotationAxis :: Dir3 Float -> Float -> Transform -> Transform+setRotationAxis dir angle Transform {translation, scale} =+ Transform {translation, rotation = Quat.fromAxisAngle dir angle, scale}++setTranslation :: V3 Float -> Transform -> Transform+setTranslation translation Transform {rotation, scale} =+ Transform {translation, rotation, scale}++setScale :: V3 Float -> Transform -> Transform+setScale scale Transform {rotation, translation} =+ Transform {translation, rotation, scale}++move :: V3 Float -> Transform -> Transform+move trans Transform {translation, rotation, scale} =+ Transform {translation = translation + trans, rotation, scale}++scale :: V3 Float -> Transform -> Transform+scale sc Transform {translation, rotation, scale} =+ Transform {translation, rotation, scale = scale * sc}++rotate :: Quat -> Transform -> Transform+rotate rot Transform {translation, rotation, scale} =+ Transform {translation, rotation = rotation * rot, scale}++rotateAxis :: Dir3 Float -> Float -> Transform -> Transform+rotateAxis dir angle Transform {translation, rotation, scale} =+ Transform {translation, rotation = Quat.rotateAxis dir angle rotation, scale}++rotateX :: Float -> Transform -> Transform+rotateX angle Transform {translation, rotation, scale} =+ Transform {translation, rotation = Quat.rotateX angle rotation, scale}++rotateY :: Float -> Transform -> Transform+rotateY angle Transform {translation, rotation, scale} =+ Transform {translation, rotation = Quat.rotateY angle rotation, scale}++rotateZ :: Float -> Transform -> Transform+rotateZ angle Transform {translation, rotation, scale} =+ Transform {translation, rotation = Quat.rotateZ angle rotation, scale}
+ src/Mischief/Math/Transform/Plugin.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}++module Mischief.Math.Transform.Plugin where++import Mischief.ECS+import Mischief.ECS.App.Schedules (First)+import Mischief.ECS.Relationships.ChildOf qualified as ChildOf+import Mischief.Math.Transform (GlobalTransform (GlobalTransform), Transform, composeTransforms)++data TransformPlugin++instance Plugin TransformPlugin where+ init = do+ schedule @First $ systems propTransform++propTransform :: System ()+propTransform = do+ query_ qiterTransform++qiterTransform :: Query System ()+qiterTransform = do+ root <- [q|Entity / Without ChildOf -> *|]+ dirty <- qlift $ check [qf|Changed Transform|] root+ if dirty+ then do+ trans <- qget root [qd|Transform|]+ qlift $ insert (GlobalTransform trans) root+ child <- qiterE =<< qlift (ChildOf.children root)+ qpropTransformRec child (GlobalTransform trans)+ else+ qiterTransformRec root++-- | Recursively iterates downwards and starts propagating the transform when finding a dirty entity.+qiterTransformRec :: Entity -> Query System ()+qiterTransformRec entity = do+ child <- qiterE =<< qlift (ChildOf.children entity)+ dirty <- qlift $ check [qf|Changed Transform|] child++ if dirty+ then do+ global <- qget entity [qd|GlobalTransform|]+ qpropTransformRec child global+ else do+ qiterTransformRec child++-- | Given an entity and its parent's Global Transform, propagates the Global Transform downwards.+qpropTransformRec :: Entity -> GlobalTransform -> Query System ()+qpropTransformRec entity (GlobalTransform global) = do+ transform <- qget entity [qd|Transform|]++ let global' = global `composeTransforms` transform+ qlift $ insert (GlobalTransform global') entity++ child <- qiterE =<< qlift (ChildOf.children entity)+ qpropTransformRec child (GlobalTransform global')
+ src/Mischief/Math/Vec.hs view
@@ -0,0 +1,36 @@+module Mischief.Math.Vec+ ( V2 (..),+ V3 (..),+ V4 (..),+ ext2,+ trunc3,+ Dir3 (Dir3),+ dir3,+ )+where++import GHC.Records+import Linear++ext2 :: a -> V2 a -> V3 a+ext2 z (V2 x y) = V3 x y z++trunc3 :: V3 a -> V2 a+trunc3 (V3 x y _) = V2 x y++newtype Dir3 a = Dir3 (V3 a) deriving newtype (Show, Eq, Ord)++instance (Floating a, Epsilon a) => Num (Dir3 a) where+ (+) (Dir3 a) (Dir3 b) = Dir3 (normalize $ a + b)+ (*) (Dir3 a) (Dir3 b) = Dir3 (normalize $ a - b)+ abs (Dir3 a) = Dir3 (abs a)+ signum (Dir3 a) = Dir3 (signum a)+ fromInteger = Dir3 . fromInteger+ negate (Dir3 a) = Dir3 (negate a)++instance HasField "vec" (Dir3 a) (V3 a) where+ getField :: Dir3 a -> V3 a+ getField (Dir3 v3) = v3++dir3 :: (Floating a, Epsilon a) => a -> a -> a -> Dir3 a+dir3 x y z = Dir3 $ normalize $ V3 x y z
+ src/Mischief/Math/Vec/Orphans/Default.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Mischief.Math.Vec.Orphans.Default where++import Data.Default+import Linear++instance (Default a) => Default (V2 a) where+ def = V2 (def @a) (def @a)++instance (Default a) => Default (V3 a) where+ def = V3 (def @a) (def @a) (def @a)
+ src/Mischief/Math/Vec/Orphans/FieldAccessors.hs view
@@ -0,0 +1,26 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Mischief.Math.Vec.Orphans.FieldAccessors where++import GHC.Records+import Linear++instance HasField "x" (V2 a) a where+ getField :: V2 a -> a+ getField (V2 x _) = x++instance HasField "y" (V2 a) a where+ getField :: V2 a -> a+ getField (V2 _ y) = y++instance HasField "x" (V3 a) a where+ getField :: V3 a -> a+ getField (V3 x _ _) = x++instance HasField "y" (V3 a) a where+ getField :: V3 a -> a+ getField (V3 _ y _) = y++instance HasField "z" (V3 a) a where+ getField :: V3 a -> a+ getField (V3 _ _ z) = z
+ test/Main.hs view
@@ -0,0 +1,52 @@+module Main where++import Control.Monad.IO.Class+import Data.Default+import Mischief.ECS+import Mischief.Math+import Mischief.Math.Mat qualified as Mat+import Mischief.Math.Quat qualified as Quat+import Mischief.Math.Transform (GlobalTransform (..), Transform)+import Mischief.Math.Transform qualified as Transform+import Mischief.Math.Vec (vec2, vec3)++quat = Quat.rotateX 1.7 Quat.identity++main :: IO ()+main = undefined++-- main :: IO ()+-- main = do+-- app <- newApp [transformPlugin, testPropagation]+-- runApp app++-- data Parent = Parent deriving (Component, Queryable)++-- data Child = Child deriving (Component, Queryable)++-- testPropagation :: Plugin ()+-- testPropagation = do+-- addSystems Startup setup+-- addSystems Update showTransforms++-- setup :: System ()+-- setup = do+-- let parent = Transform.setRotation (Quat.fromRotationZ 0.5) $ Transform.setTranslation (vec3 (0, 1, 0)) $ def @Transform+-- p <- spawn (Parent, parent)++-- let child = Transform.setRotation (Quat.fromRotationZ $ -0.2) $ Transform.setTranslation (vec3 (0, 2, 0)) $ def @Transform+-- _ <- spawn (Child, (child, R (ChildOf, p)))+-- return ()++-- showTransforms :: System ()+-- showTransforms = do+-- Just p <- single' @GlobalTransform $ with @Parent+-- Just c <- single' @GlobalTransform $ with @Child++-- let rot1 = Quat.toEuler p.value.transform.rotation+-- let rot2 = Quat.toEuler c.value.transform.rotation+-- liftIO $ print p+-- liftIO $ print c+-- liftIO $ print rot1+-- liftIO $ print rot2+-- return ()