Skip to content

Commit

Permalink
Document the code
Browse files Browse the repository at this point in the history
  • Loading branch information
w0rm committed Dec 8, 2023
1 parent 900d5f4 commit e60b896
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 168 deletions.
22 changes: 9 additions & 13 deletions src/Ball.elm
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Quantity
import Scene3d exposing (Entity)
import Scene3d.Material as Material
import SketchPlane3d
import Sphere3d exposing (Sphere3d)
import Sphere3d
import Vector3d


Expand All @@ -38,27 +38,21 @@ damping =
{ linear = 0.4, angular = 0.4 }


ballMaterial : Material
ballMaterial =
material : Material
material =
Material.custom
{ friction = 0.06
, bounciness = 0.6
}


sphere : Sphere3d Meters BodyCoordinates
sphere =
Sphere3d.atOrigin radius


body : id -> Body id
body id =
Body.sphere sphere
id
|> Body.withMaterial ballMaterial
Body.sphere (Sphere3d.atOrigin radius) id
|> Body.withMaterial material
|> Body.withDamping damping
|> Body.withBehavior (Body.dynamic weight)
-- rotate to see the numbers
-- rotate to see the numbers on the balls
|> Body.rotateAround Axis3d.x (Angle.degrees 90)


Expand All @@ -71,9 +65,11 @@ entity baseColor roughnessTexture =
, metallic = Material.constant 0
}
)
sphere
(Sphere3d.atOrigin radius)


{-| Rack the balls at the foot spot on the table
-}
rack : Point2d Meters WorldCoordinates -> List (Body Id)
rack footSpot =
let
Expand Down
8 changes: 8 additions & 0 deletions src/Bodies.elm
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
module Bodies exposing (Id(..))

{-|
@docs Id
-}

import EightBall exposing (Ball)


{-| Identify the different bodies in the physical simulation
-}
type Id
= Floor
| Numbered Ball
Expand Down
172 changes: 108 additions & 64 deletions src/Camera.elm
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
module Camera exposing
( Camera
, ScreenCoordinates
, animate
, azimuth
, camera3d
, focusOn
, initial
, mouseOrbiting
, mouseWheelZoom
, orbitingPrecision
, ray
, zoomOut
( ScreenCoordinates, Camera, initial
, camera3d, azimuth, orbitingPrecision
, ray, mouseOrbiting, mouseWheelZoom
, focusOn, zoomOut, animate
)

{-| Animated 3d camera controls
@docs ScreenCoordinates, Camera, initial
# Current state
@docs camera3d, azimuth, orbitingPrecision
# Interaction
@docs ray, mouseOrbiting, mouseWheelZoom
# Animation
@docs focusOn, zoomOut, animate
-}

import Angle exposing (Angle)
import Animator exposing (Timeline)
import Axis3d exposing (Axis3d)
Expand All @@ -30,10 +43,13 @@ import Vector2d
import Viewpoint3d


{-| Screen space coordinate system
-}
type ScreenCoordinates
= ScreenCoordinates Never


{-| -}
type Camera
= Camera
{ zoom : Timeline Float -- also used for orbiting precision
Expand All @@ -43,6 +59,8 @@ type Camera
}


{-| Initial look at the table
-}
initial : Camera
initial =
Camera
Expand All @@ -53,26 +71,30 @@ initial =
}


ray :
Camera
-> Rectangle2d Pixels ScreenCoordinates
-> Point2d Pixels ScreenCoordinates
-> Axis3d Meters WorldCoordinates
ray camera =
Camera3d.ray (camera3d camera)

-- CURRENT STATE


{-| Get the currrent Camera3d for rendering with elm-3d-scene
-}
camera3d : Camera -> Camera3d Meters WorldCoordinates
camera3d (Camera camera) =
let
distance =
Animator.move camera.zoom Animator.at
|> Quantity.interpolateFrom (Length.meters 0.5) (Length.meters 6)

focalPoint =
Point3d.fromRecord Length.meters <|
Animator.xyz camera.focalPoint
(Point3d.toMeters
>> (\p -> { x = Animator.at p.x, y = Animator.at p.y, z = Animator.at p.z })
)
in
Camera3d.perspective
{ viewpoint =
Viewpoint3d.orbit
{ focalPoint = pointFromTimeline camera.focalPoint
{ focalPoint = focalPoint
, groundPlane = SketchPlane3d.xy
, azimuth = camera.azimuth
, elevation = angleFromTimeline camera.elevation
Expand All @@ -82,30 +104,41 @@ camera3d (Camera camera) =
}


{-| Get the currrent azimuth used for aiming the cue
-}
azimuth : Camera -> Angle
azimuth (Camera camera) =
camera.azimuth


animate : Posix -> Camera -> Camera
animate time (Camera camera) =
Camera
{ camera
| elevation = Animator.updateTimeline time camera.elevation
, zoom = Animator.updateTimeline time camera.zoom
, focalPoint = Animator.updateTimeline time camera.focalPoint
}
{-| Make orbiting precision depend on zoom level.
Controls how much radians correspond to the change in mouse offset.
-}
orbitingPrecision : Camera -> Quantity Float (Quantity.Rate Angle.Radians Pixels)
orbitingPrecision (Camera camera) =
Quantity.rate
(Angle.radians (0.2 + Animator.move camera.zoom Animator.at / 0.8))
(Pixels.pixels (180 / pi))


mouseWheelZoom : Float -> Camera -> Camera
mouseWheelZoom deltaY (Camera camera) =
let
newZoom =
clamp 0 1 (Animator.move camera.zoom Animator.at - deltaY * 0.002)
in
Camera { camera | zoom = Animator.go Animator.immediately newZoom camera.zoom }

-- INTERACTION


{-| Get the ray from the camera into the viewplane,
useful for mouse interactions with the 3d objects
-}
ray :
Camera
-> Rectangle2d Pixels ScreenCoordinates
-> Point2d Pixels ScreenCoordinates
-> Axis3d Meters WorldCoordinates
ray camera =
Camera3d.ray (camera3d camera)


{-| Orbit the camera with mouse
-}
mouseOrbiting : Point2d Pixels ScreenCoordinates -> Point2d Pixels ScreenCoordinates -> Camera -> Camera
mouseOrbiting originalPosition newPosition (Camera camera) =
let
Expand Down Expand Up @@ -134,15 +167,30 @@ mouseOrbiting originalPosition newPosition (Camera camera) =
}


zoomOut : Camera -> Camera
zoomOut (Camera camera) =
Camera
{ camera
| zoom = Animator.go Animator.verySlowly 1 camera.zoom
, elevation = Animator.go Animator.verySlowly (Angle.degrees 50) camera.elevation
}
{-| Zoom in/out by mouse wheel delta
-}
mouseWheelZoom : Float -> Camera -> Camera
mouseWheelZoom deltaY (Camera camera) =
let
newZoom =
clamp 0 1 (Animator.move camera.zoom Animator.at - deltaY * 0.002)
in
Camera { camera | zoom = Animator.go Animator.immediately newZoom camera.zoom }


{-| Read the angle value from the timeline
-}
angleFromTimeline : Timeline Angle -> Angle
angleFromTimeline angleTimeline =
Angle.radians (Animator.move angleTimeline (Angle.inRadians >> Animator.at))



-- ANIMATION


{-| Animate the focal point of the camera to the new position
-}
focusOn : Point3d Meters WorldCoordinates -> Camera -> Camera
focusOn focalPoint (Camera camera) =
Camera
Expand All @@ -151,29 +199,25 @@ focusOn focalPoint (Camera camera) =
}


{-| Make orbiting precision depend on zoom level.
Controls how much radians correspond to the change in mouse offset.
{-| Zoom out the camera to look over the table from the top
-}
orbitingPrecision : Camera -> Quantity Float (Quantity.Rate Angle.Radians Pixels)
orbitingPrecision (Camera camera) =
Quantity.rate
(Angle.radians (0.2 + Animator.move camera.zoom Animator.at / 0.8))
(Pixels.pixels (180 / pi))


{-| Read the angle value from the timeline
-}
angleFromTimeline : Timeline Angle -> Angle
angleFromTimeline angleTimeline =
Angle.radians (Animator.move angleTimeline (Angle.inRadians >> Animator.at))
zoomOut : Camera -> Camera
zoomOut (Camera camera) =
Camera
{ camera
| zoom = Animator.go Animator.verySlowly 1 camera.zoom
, elevation = Animator.go Animator.verySlowly (Angle.degrees 50) camera.elevation
}


{-| Read the point value from the timeline
{-| Update the camera animation state, this needs to be called
from the animation frame subscription
-}
pointFromTimeline : Timeline (Point3d Meters WorldCoordinates) -> Point3d Meters WorldCoordinates
pointFromTimeline pointTimeline =
Point3d.fromRecord Length.meters <|
Animator.xyz pointTimeline
(Point3d.toMeters
>> (\p -> { x = Animator.at p.x, y = Animator.at p.y, z = Animator.at p.z })
)
animate : Posix -> Camera -> Camera
animate time (Camera camera) =
Camera
{ camera
| elevation = Animator.updateTimeline time camera.elevation
, zoom = Animator.updateTimeline time camera.zoom
, focalPoint = Animator.updateTimeline time camera.focalPoint
}
12 changes: 11 additions & 1 deletion src/Cue.elm
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module Cue exposing (canShoot, entity)
module Cue exposing (entity, canShoot)

{-| The cue is reperensented as an `Axis3d Meters WorldCoordinates`
that points away from the hit point on the cue ball.
@docs entity, canShoot
-}

import Angle
import Axis3d exposing (Axis3d)
Expand Down Expand Up @@ -36,6 +43,9 @@ offset =
Length.centimeters 2


{-| Render the cue as a cylinder, make sure it is trimmed with the end cap
when interecting with the view plane.
-}
entity : Camera3d Meters WorldCoordinates -> Length -> Color -> Axis3d Meters WorldCoordinates -> Entity WorldCoordinates
entity camera3d clipDepth color axis =
case cylinder camera3d clipDepth axis of
Expand Down
Loading

0 comments on commit e60b896

Please sign in to comment.