hgg-3d-0.1.0.0: src/Graphics/Hgg/ThreeD/Types.hs
-- |
-- Module : Graphics.Hgg.ThreeD.Types
-- Description : 3D の中核型 (Point3 / Vec3 / Mat4 / Camera3D / Projection3D)
-- Copyright : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License : BSD-3-Clause
--
-- Phase 3 A2 段階: 中核型のみ定義。 関数 (= project3D / lookAt 等) は A3
-- (Graphics.Hgg.ThreeD.Projection) で実装。
--
-- 設計判断: linear package 等の外部依存を入れず、 自前 Vec3 / Mat4 を持つ。
-- これで core (= base/vector/text/containers のみ) の依存戦略と整合する。
-- 行列演算は 4x4 で十分 (= camera + projection)、 性能不問の前提で simple list 実装。
{-# LANGUAGE DeriveGeneric #-}
module Graphics.Hgg.ThreeD.Types
( -- * 幾何プリミティブ
Point3 (..)
, Vec3 (..)
-- * 行列 (= 4x4 同次座標)
, Mat4 (..)
-- * Camera + Projection
, Camera3D (..)
, Projection3D (..)
-- * Convention (= z-up / y-up を両対応、 default は z-up)
-- $convention
, zUp
, yUp
, defaultCameraZUp
, defaultCameraYUp
-- * 視点 preset (Phase 24 A8・z-up)
, cameraIso
, cameraTop
, cameraFront
, cameraSide
, defaultPerspective
-- * helper: 基本ベクトル演算
, addV3
, subV3
, scaleV3
, dotV3
, crossV3
, lengthV3
, normalizeV3
) where
import Data.Aeson (FromJSON, ToJSON)
import GHC.Generics (Generic)
-- | 3D 点 (= world / camera space どちらでも使う)。
--
-- JSON: positional fields → array `[x, y, z]` (= aeson Generic デフォルト挙動)。
-- PS Argonaut 側も同形式で decode。
data Point3 = Point3 !Double !Double !Double
deriving (Show, Eq, Generic)
instance ToJSON Point3
instance FromJSON Point3
-- | 3D ベクトル (= 方向 / 法線)。 JSON は Point3 と同じく array `[x, y, z]`。
data Vec3 = Vec3 !Double !Double !Double
deriving (Show, Eq, Generic)
instance ToJSON Vec3
instance FromJSON Vec3
-- $convention
-- hgg-3d は **z-up と y-up の両方をサポート** する。 default は **z-up**
-- (= mplot3d / 工学慣例)。 user は用途に応じて切替可能。
--
-- == 業界の使い分け (= 2026 時点の調査)
--
-- +-------------------+-------------------------------------------------+------------+
-- | 分野 | 主要ライブラリ | up |
-- +===================+=================================================+============+
-- | __data viz__ | matplotlib mplot3d / plotly / Mathematica / | __z-up__ |
-- | | gnuplot splot / mayavi / R rgl | |
-- +-------------------+-------------------------------------------------+------------+
-- | __工学 / CAD__ | AutoCAD / SolidWorks / Inventor / Unreal Engine | __z-up__ |
-- +-------------------+-------------------------------------------------+------------+
-- | __game / CG__ | Unity / Godot 3D / OpenGL 慣例 / DirectX 慣例 | __y-up__ |
-- +-------------------+-------------------------------------------------+------------+
-- | __3D modeling__ | Blender (= z-up に切替済) / Maya (= y-up) | 分裂 |
-- +-------------------+-------------------------------------------------+------------+
--
-- == hgg-3d の default = z-up を選んだ理由
--
-- * メインターゲットが data viz (= hanalyze の HBM 等)
-- * mplot3d / plotly の慣例と整合 → 既存 user 知識が活きる
-- * @z = f(x, y)@ の数学慣例と一致 → 'Graphics.Hgg.ThreeD.Surface.Surface3D' が直感的
-- * z = 物理量の高さ (= 確率密度 / 計測値 / 標高) の伝統
--
-- == どちらを使うべきか
--
-- * __z-up__ ('zUp' / 'defaultCameraZUp'): data viz、 工学解析、 統計分布、 surface plot。 default 推奨
-- * __y-up__ ('yUp' / 'defaultCameraYUp'): game-like view、 OpenGL 系 sample との整合、 既存 CG 知識の流用
--
-- 両 helper とも up vector を切替えるだけで他は同じ。 任意の up vector が必要なら
-- 'Camera3D' を直接構築する。
-- | z-up convention の up vector (= 'Vec3' 0 0 1)。 data viz / mplot3d 慣例。
zUp :: Vec3
zUp = Vec3 0 0 1
-- | y-up convention の up vector (= 'Vec3' 0 1 0)。 OpenGL / game engine 慣例。
yUp :: Vec3
yUp = Vec3 0 1 0
-- | z-up convention の default camera。 'eye' を data の bounding box 寄りに配置し、
-- 'target' を原点、 up を 'zUp' に。 typical 3/4 view (= 上前方斜め見下ろし)。
--
-- @
-- defaultCameraZUp 5 -- camera at (5, -5, 3) → origin (= 上前方斜め)
-- @
defaultCameraZUp :: Double -> Camera3D
defaultCameraZUp dist = Camera3D
{ cameraEye = Point3 dist (negate dist) (dist * 0.6)
, cameraTarget = Point3 0 0 0
, cameraUp = zUp
}
-- | y-up convention の default camera。 OpenGL / Unity 慣例に整合する 3/4 view
-- (= y = 縦軸、 x/z = 床平面、 camera は y > 0 から見下ろし)。 game-like 表現や
-- OpenGL 系 sample との整合に。
--
-- @
-- defaultCameraYUp 5 -- camera at (5, 3, 5) → origin (= 上前方斜め、 y = up)
-- @
defaultCameraYUp :: Double -> Camera3D
defaultCameraYUp dist = Camera3D
{ cameraEye = Point3 dist (dist * 0.6) dist
, cameraTarget = Point3 0 0 0
, cameraUp = yUp
}
-- | Phase 24 A8: 視点 preset (z-up・target = 原点)。 'cameraIso' = 'defaultCameraZUp'
-- (上前方斜め見下ろし)。 @dist@ は eye と原点の距離スケール。
cameraIso :: Double -> Camera3D
cameraIso = defaultCameraZUp
-- | Phase 24 A8: 真上から見下ろす視点 (xy 平面を正対・z は奥行き)。 up は y 軸
-- (見下ろし時に zUp は eye 方向と平行になり退化するため)。
cameraTop :: Double -> Camera3D
cameraTop dist = Camera3D
{ cameraEye = Point3 0 0 dist
, cameraTarget = Point3 0 0 0
, cameraUp = yUp
}
-- | Phase 24 A8: 正面 (−y 方向から xz 面を正対・z = 縦)。
cameraFront :: Double -> Camera3D
cameraFront dist = Camera3D
{ cameraEye = Point3 0 (negate dist) 0
, cameraTarget = Point3 0 0 0
, cameraUp = zUp
}
-- | Phase 24 A8: 真横 (+x 方向から yz 面を正対・z = 縦)。
cameraSide :: Double -> Camera3D
cameraSide dist = Camera3D
{ cameraEye = Point3 dist 0 0
, cameraTarget = Point3 0 0 0
, cameraUp = zUp
}
-- | aspect 1:1 / fov 45° / near 0.1 / far 100 の sane default。
defaultPerspective :: Projection3D
defaultPerspective = Perspective (pi / 4) 1.0 0.1 100
-- | 4x4 同次座標行列 (= row-major、 16 Double を直接持つ)。
-- camera transform / perspective projection / model matrix で使う。
data Mat4 = Mat4
!Double !Double !Double !Double
!Double !Double !Double !Double
!Double !Double !Double !Double
!Double !Double !Double !Double
deriving (Show, Eq, Generic)
-- JSON: 16 要素 array (= aeson Generic で positional fields は array 化)。
instance ToJSON Mat4
instance FromJSON Mat4
-- | Camera 設定。 eye (= 視点)、 target (= 注視点)、 up (= 上方向、 通常 (0,1,0))。
-- 'Graphics.Hgg.ThreeD.Projection.lookAt' で view 行列を生成する。
data Camera3D = Camera3D
{ cameraEye :: !Point3
, cameraTarget :: !Point3
, cameraUp :: !Vec3
} deriving (Show, Eq, Generic)
instance ToJSON Camera3D
instance FromJSON Camera3D
-- | Projection 設定。 orthographic / perspective の 2 種。
--
-- * 'Orthographic': 平行投影。 box の半幅 (xHalf, yHalf, near, far) で領域指定
-- * 'Perspective': 透視投影。 fov (= 縦方向視野角、 radians) + aspect ratio +
-- near + far クリップ面
data Projection3D
= Orthographic
{ orthoXHalf :: !Double -- ^ x 方向半幅
, orthoYHalf :: !Double -- ^ y 方向半幅
, orthoNear :: !Double
, orthoFar :: !Double
}
| Perspective
{ perspFov :: !Double -- ^ 縦方向 FOV、 radians
, perspAspect :: !Double -- ^ aspect ratio (= width / height)
, perspNear :: !Double
, perspFar :: !Double
}
deriving (Show, Eq, Generic)
-- JSON: aeson Generic デフォルト (= `{"tag": "Orthographic", "orthoXHalf": ..., ...}` or
-- `{"tag": "Perspective", "perspFov": ..., ...}`)。 PS Argonaut 側で同形式 decode。
instance ToJSON Projection3D
instance FromJSON Projection3D
-- ===========================================================================
-- 基本ベクトル演算
-- ===========================================================================
-- | 加算。
addV3 :: Vec3 -> Vec3 -> Vec3
addV3 (Vec3 ax ay az) (Vec3 bx by bz) = Vec3 (ax + bx) (ay + by) (az + bz)
-- | 減算。
subV3 :: Vec3 -> Vec3 -> Vec3
subV3 (Vec3 ax ay az) (Vec3 bx by bz) = Vec3 (ax - bx) (ay - by) (az - bz)
-- | スカラー倍。
scaleV3 :: Double -> Vec3 -> Vec3
scaleV3 s (Vec3 x y z) = Vec3 (s * x) (s * y) (s * z)
-- | 内積。
dotV3 :: Vec3 -> Vec3 -> Double
dotV3 (Vec3 ax ay az) (Vec3 bx by bz) = ax * bx + ay * by + az * bz
-- | 外積 (= 右手系)。
crossV3 :: Vec3 -> Vec3 -> Vec3
crossV3 (Vec3 ax ay az) (Vec3 bx by bz) =
Vec3 (ay * bz - az * by) (az * bx - ax * bz) (ax * by - ay * bx)
-- | L2 ノルム。
lengthV3 :: Vec3 -> Double
lengthV3 v = sqrt (dotV3 v v)
-- | 正規化 (= 単位ベクトル化)。 ゼロベクトルは (0,0,0) 返す (= 例外無し)。
normalizeV3 :: Vec3 -> Vec3
normalizeV3 v =
let l = lengthV3 v
in if l < 1e-12 then Vec3 0 0 0 else scaleV3 (1 / l) v