Compare commits
5 Commits
bcccfc6673
...
13d7e1bdaf
Author | SHA1 | Date | |
---|---|---|---|
13d7e1bdaf | |||
4cebcebfa3 | |||
84e29eb59e | |||
c66b503df6 | |||
b8d3c40413 |
54
README.org
54
README.org
@ -1,39 +1,49 @@
|
|||||||
### FPS Scene
|
#+OPTIONS: toc:nil
|
||||||
|
|
||||||
You can walk around the scene with simple FPS controls.
|
* PQ Torus Knot
|
||||||
Open `scene.html` with an http server, you can use the following;
|
|
||||||
|
|
||||||
```python3 -m http.server 8000```
|
** About
|
||||||
|
|
||||||
Unfortunately, elm-reactor has issues with loading obj files so that's why the
|
[[https://ferano.io/pqtk/][Live Link]]
|
||||||
python server is needed.
|
|
||||||
|
|
||||||

|
This is an implementation of the [[https://en.wikipedia.org/wiki/Torus_knot][PQ Torus Knot]] in the Elm programming language
|
||||||
|
using WebGL.
|
||||||
|
|
||||||
|
** Showcase
|
||||||
|
|
||||||
##### Controls
|
[[file:./media/red-p2q5.gif]]
|
||||||
|
[[file:./media/blue-p3q5.gif]]
|
||||||
|
[[file:./media/green-p2q9.gif]]
|
||||||
|
[[file:./media/yellow-p8q9.gif]]
|
||||||
|
|
||||||
Mouse - Look Rotation
|
** Keybindings
|
||||||
|
|
||||||
WASD - Player movement
|
You can increment P or Q with the keyboard by pressing ~p~ or ~q~ and decrement with
|
||||||
|
the neighboring letter ~o~ and ~w~ respectively.
|
||||||
|
|
||||||
◀ ▼ ▲ ▶ - Move the Robot
|
- Incr/Decr P: ~p/o~
|
||||||
|
- Incr/Decr Q: ~q/w~
|
||||||
|
|
||||||
N and M - Rotate robot left and right
|
** Building with Elm 0.18.0
|
||||||
|
To install the binaries manually, follow this short guide; - [[https://sirfitz.medium.com/install-elm-0-18-0-in-2021-3f64ce298801][Install Binaries]]
|
||||||
|
|
||||||
Y and H - Rotate robot arm up and down
|
If you want to use =npm= instead; - =npm install -g elm@elm0.18.0=
|
||||||
|
|
||||||
U and J - Rotate robot hand up and down
|
If you use =npm=, note that you will likely need an older version of Node.js, so
|
||||||
|
it is recommended to use [[https://github.com/nvm-sh/nvm][nvm]] for that. I have not attempted this method
|
||||||
|
personally, fyi.
|
||||||
|
|
||||||
|
Once you have the Elm compiler, go ahead and run
|
||||||
|
|
||||||
### PQTorusknot
|
=elm make Torus.elm=
|
||||||
|
|
||||||
Either open up `torus.html`, or use the command `elm-reactor`, if you want to be able to modify the source file and compile;
|
And that should first pull in all the dependencies then generate an =index.html=
|
||||||
|
file.
|
||||||
|
|
||||||
https://guide.elm-lang.org/install.html
|
Alternatively, you can use ~elm-reactor~, run the command and open up your browser
|
||||||
|
to ~https://localhost:8000~ and then click on ~Torus.elm~ in the nice project
|
||||||
|
webview provided.
|
||||||
|
|
||||||
Alternatively, here's the Ellie link
|
** License
|
||||||
|
This project is licensed under the terms of the MIT license. For more
|
||||||
https://ellie-app.com/vVTgpBj77ra1
|
information, see the included LICENSE file.
|
||||||
|
|
||||||

|
|
||||||
|
106
Torus.elm
106
Torus.elm
@ -3,6 +3,7 @@ module Torus exposing (..)
|
|||||||
import Html exposing (Html)
|
import Html exposing (Html)
|
||||||
import Html.Attributes exposing (width, height, style)
|
import Html.Attributes exposing (width, height, style)
|
||||||
import AnimationFrame
|
import AnimationFrame
|
||||||
|
import Keyboard exposing (..)
|
||||||
import Time exposing (Time)
|
import Time exposing (Time)
|
||||||
import Math.Matrix4 as Mat4 exposing (Mat4)
|
import Math.Matrix4 as Mat4 exposing (Mat4)
|
||||||
import Math.Vector2 as Vec2 exposing (vec2, Vec2)
|
import Math.Vector2 as Vec2 exposing (vec2, Vec2)
|
||||||
@ -25,19 +26,26 @@ type alias Attributes = { position : Vec3 , normal : Vec3 }
|
|||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= Animate Time
|
= Animate Time
|
||||||
|
| KeyChange Bool Keyboard.KeyCode
|
||||||
| WindowResized Window.Size
|
| WindowResized Window.Size
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ p : Float
|
{ p : Float
|
||||||
, q : Float
|
, q : Float
|
||||||
-- , mesh : Mesh Attributes
|
, torus : Mesh Attributes
|
||||||
, time : Float
|
, time : Float
|
||||||
, winSize : Window.Size }
|
, winSize : Window.Size }
|
||||||
|
|
||||||
|
totalLinePoints = 250
|
||||||
|
ringRadius = 0.1
|
||||||
|
ringVerts = 40
|
||||||
|
knotScale = 0.2
|
||||||
|
|
||||||
init: ( Model , Cmd Msg )
|
init: ( Model , Cmd Msg )
|
||||||
init =
|
init =
|
||||||
-- ( { p = 1 , q = 0 , time = 0.0 , winSize = (Window.Size 1 1)
|
( { p = 2 , q = 9 , time = 0.0 , winSize = (Window.Size 1 1)
|
||||||
( { p = 12 , q = 29 , time = 0.0 , winSize = (Window.Size 1 1)
|
, torus = constructTorus 2 9
|
||||||
|
-- ( { p = 8 , q = 9 , time = 0.0 , winSize = (Window.Size 1 1)
|
||||||
-- , mesh = (torusPoints 2 15 |> torusShell)
|
-- , mesh = (torusPoints 2 15 |> torusShell)
|
||||||
}
|
}
|
||||||
, Cmd.batch [ Task.perform WindowResized Window.size ] )
|
, Cmd.batch [ Task.perform WindowResized Window.size ] )
|
||||||
@ -47,14 +55,25 @@ subscriptions: Model -> Sub Msg
|
|||||||
subscriptions _ =
|
subscriptions _ =
|
||||||
Sub.batch
|
Sub.batch
|
||||||
[ AnimationFrame.diffs Animate
|
[ AnimationFrame.diffs Animate
|
||||||
|
, Keyboard.downs (KeyChange True)
|
||||||
, Window.resizes WindowResized ]
|
, Window.resizes WindowResized ]
|
||||||
|
|
||||||
update: Msg -> Model -> (Model , Cmd Msg)
|
update: Msg -> Model -> (Model , Cmd Msg)
|
||||||
update msg model =
|
update msg model =
|
||||||
let m = case msg of
|
let m = case msg of
|
||||||
|
KeyChange b k ->
|
||||||
|
let (p,q) = case (b, k) of
|
||||||
|
-- pq / wo
|
||||||
|
(True, 80) -> (model.p+1, model.q )
|
||||||
|
(True, 81) -> (model.p , model.q+1)
|
||||||
|
(True, 79) -> (model.p-1, model.q )
|
||||||
|
(True, 87) -> (model.p , model.q-1)
|
||||||
|
_ -> (model.p, model.q)
|
||||||
|
_ = Debug.log "P Q: " (p, q)
|
||||||
|
in { model | torus = constructTorus p q , p = p , q = q }
|
||||||
Animate dt ->
|
Animate dt ->
|
||||||
-- { model | time = model.time + dt * 0.001 }
|
{ model | time = model.time + dt * 0.001 }
|
||||||
{ model | time = model.time + dt * 0.001 , p = model.p + 0.008 , q = model.q + 0.004}
|
-- { model | time = model.time + dt * 0.001 , p = model.p + 0.008 , q = model.q + 0.004}
|
||||||
-- Animate dt -> model
|
-- Animate dt -> model
|
||||||
|
|
||||||
WindowResized size -> { model | winSize = size }
|
WindowResized size -> { model | winSize = size }
|
||||||
@ -62,16 +81,33 @@ update msg model =
|
|||||||
|
|
||||||
view: Model -> Html Msg
|
view: Model -> Html Msg
|
||||||
view model =
|
view model =
|
||||||
WebGL.toHtml
|
Html.div []
|
||||||
|
[ Html.div [ style [ ("position", "absolute")
|
||||||
|
, ("top", "10px")
|
||||||
|
, ("left", "10px")
|
||||||
|
, ("background-color", "rgba(128, 128, 128, 1)")
|
||||||
|
, ("padding", "5px")
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ Html.text ("P: " ++ toString model.p) ]
|
||||||
|
, Html.div [ style [ ("position", "absolute")
|
||||||
|
, ("top", "10px")
|
||||||
|
, ("left", "40px")
|
||||||
|
, ("background-color", "rgba(128, 128, 128, 1)")
|
||||||
|
, ("padding", "5px")
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ Html.text ("Q: " ++ toString model.q) ]
|
||||||
|
, WebGL.toHtml
|
||||||
[ width model.winSize.width
|
[ width model.winSize.width
|
||||||
, height model.winSize.height
|
, height model.winSize.height
|
||||||
, style [ ( "display" , "block") , ( "background" , "black" ) ] ]
|
, style [ ( "display" , "block") , ( "background" , "#151515" ) ] ]
|
||||||
([ WebGL.entity
|
([ WebGL.entity
|
||||||
diffuseVS
|
diffuseVS
|
||||||
diffuseFS
|
diffuseFS
|
||||||
-- model.mesh
|
-- model.mesh
|
||||||
-- (constructTorus model)
|
-- (constructTorusMesh model)
|
||||||
(constructTorus2 model)
|
model.torus
|
||||||
(DiffuseColor
|
(DiffuseColor
|
||||||
(Mat4.makePerspective
|
(Mat4.makePerspective
|
||||||
50
|
50
|
||||||
@ -79,37 +115,35 @@ view model =
|
|||||||
0.01
|
0.01
|
||||||
1000)
|
1000)
|
||||||
(Mat4.makeLookAt (vec3 0 0 5) (vec3 0 0 0) (vec3 0 1 0))
|
(Mat4.makeLookAt (vec3 0 0 5) (vec3 0 0 0) (vec3 0 1 0))
|
||||||
(Mat4.makeRotate (model.time * 0.5) (vec3 1 1 1 ) )
|
(Mat4.makeRotate (model.time * 0.9) (vec3 1 1 1 ) )
|
||||||
-- (Mat4.makeRotate (pi) (vec3 0.3 0.5 1 ) )
|
-- (Mat4.makeRotate (pi) (vec3 0.3 0.5 1 ) )
|
||||||
-- Mat4.identity
|
-- Mat4.identity
|
||||||
(colorToVec3 Color.red)
|
-- (colorToVec3 Color.red)
|
||||||
-- (colorToVec3 Color.darkGrey)
|
(colorToVec3 Color.green)
|
||||||
-- (colorToVec3 Color.white)
|
-- (colorToVec3 Color.blue)
|
||||||
|
-- (colorToVec3 Color.yellow)
|
||||||
(vec3 1 1 1)
|
(vec3 1 1 1)
|
||||||
(vec3 1 1 1)
|
(vec3 1 1 1)
|
||||||
(vec3 1 1 1)
|
(vec3 1 1 1)
|
||||||
1.0) ] )
|
1.0)
|
||||||
|
])]
|
||||||
|
|
||||||
totalLinePoints = 100
|
constructTorusMesh: Model -> Mesh Attributes
|
||||||
ringRadius = 0.15
|
constructTorusMesh model =
|
||||||
ringVerts = 18
|
|
||||||
|
|
||||||
constructTorus: Model -> Mesh Attributes
|
|
||||||
constructTorus model =
|
|
||||||
let points = torusPoints model.p model.q |> makePairs
|
let points = torusPoints model.p model.q |> makePairs
|
||||||
rings = torusRings points |> List.concatMap makePairs
|
rings = torusRings points |> List.concatMap makePairs
|
||||||
in points ++ rings |> toLines
|
in points ++ rings |> toLines
|
||||||
-- in points |> toLines
|
-- in points |> toLines
|
||||||
|
|
||||||
|
|
||||||
constructTorus2: Model -> Mesh Attributes
|
constructTorus: Float -> Float -> Mesh Attributes
|
||||||
constructTorus2 model =
|
constructTorus p q =
|
||||||
torusPoints model.p model.q
|
torusPoints p q
|
||||||
|> makePairs
|
|> makePairs
|
||||||
|> torusRings
|
|> torusRings
|
||||||
|> torusTris
|
|> torusTris
|
||||||
|> withTris
|
|> withTris
|
||||||
-- |> wireframe |> List.map (\ x -> toAttributes x Vec3.i) |> WebGL.lineStrip
|
-- |> wireframe |> List.map (\ x -> toAttributes x Vec3.i) |> WebGL.lineStrip
|
||||||
|
|
||||||
withTris: List (Vec3 , Vec3 , Vec3) -> Mesh Attributes
|
withTris: List (Vec3 , Vec3 , Vec3) -> Mesh Attributes
|
||||||
withTris tris =
|
withTris tris =
|
||||||
@ -124,10 +158,10 @@ torusPoints p q =
|
|||||||
interpolatedCircle totalLinePoints
|
interpolatedCircle totalLinePoints
|
||||||
|> List.map
|
|> List.map
|
||||||
(\ t ->
|
(\ t ->
|
||||||
let r = 0.5 * (2 + sin (q * t))
|
let r = 2 + sin (q * t)
|
||||||
in vec3
|
in vec3
|
||||||
(cos (t * p) * r)
|
(cos (t * p) * r)
|
||||||
(cos (t * q) * r * 0.5)
|
(cos (t * q) * r * 0.25)
|
||||||
(sin (t * p) * r) )
|
(sin (t * p) * r) )
|
||||||
|
|
||||||
|
|
||||||
@ -136,9 +170,11 @@ torusRings verts =
|
|||||||
verts
|
verts
|
||||||
|> List.map
|
|> List.map
|
||||||
(\ (p1, p2)->
|
(\ (p1, p2)->
|
||||||
(List.map circlePoint <| interpolatedCircle ringVerts)
|
ringVerts
|
||||||
|
|> interpolatedCircle
|
||||||
|
|> List.map circlePoint
|
||||||
|> List.map (\ p ->
|
|> List.map (\ p ->
|
||||||
let (mid , dir) = (Vec3.add p1 p2 |> Vec3.scale 0.5 , Vec3.sub p2 p1 |> Vec3.normalize)
|
let (mid , dir) = (Vec3.add p1 p2 |> Vec3.scale knotScale , Vec3.sub p2 p1 |> Vec3.normalize)
|
||||||
p_ = Vec3.toRecord p
|
p_ = Vec3.toRecord p
|
||||||
dir_ = Vec3.toRecord dir
|
dir_ = Vec3.toRecord dir
|
||||||
u = Vec3.cross dir Vec3.j |> Vec3.normalize
|
u = Vec3.cross dir Vec3.j |> Vec3.normalize
|
||||||
@ -168,7 +204,12 @@ circlePoint: Float -> Vec3
|
|||||||
circlePoint x = vec3 (cos x) (sin x) 0
|
circlePoint x = vec3 (cos x) (sin x) 0
|
||||||
|
|
||||||
makePairs: List a -> List (a ,a)
|
makePairs: List a -> List (a ,a)
|
||||||
makePairs ps = List.map2 (,) ps (List.drop 1 ps)
|
makePairs ps1 =
|
||||||
|
case ps1 of
|
||||||
|
[] -> []
|
||||||
|
h::tail ->
|
||||||
|
let ps2 = tail ++ [h]
|
||||||
|
in List.map2 (,) ps1 ps2
|
||||||
|
|
||||||
closedPairs: List a -> List (a ,a)
|
closedPairs: List a -> List (a ,a)
|
||||||
closedPairs xs =
|
closedPairs xs =
|
||||||
@ -197,6 +238,10 @@ colorToVec3 color =
|
|||||||
c = Color.toRgb color
|
c = Color.toRgb color
|
||||||
in vec3 (to01 c.red) (to01 c.green) (to01 c.blue)
|
in vec3 (to01 c.red) (to01 c.green) (to01 c.blue)
|
||||||
|
|
||||||
|
-------------
|
||||||
|
-- Shaders
|
||||||
|
-------------
|
||||||
|
|
||||||
type alias DiffuseColor =
|
type alias DiffuseColor =
|
||||||
{ projection : Mat4
|
{ projection : Mat4
|
||||||
, view : Mat4
|
, view : Mat4
|
||||||
@ -248,6 +293,3 @@ diffuseFS =
|
|||||||
gl_FragColor = vec4(color * vlightWeight, 1.0);
|
gl_FragColor = vec4(color * vlightWeight, 1.0);
|
||||||
}
|
}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
8
build.sh
Executable file
8
build.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
elm make Torus.elm
|
||||||
|
sed -i 's/<title>Torus<\/title>/<title>PQ Torus Knot<\/title>/g' index.html
|
||||||
|
mkdir -p ./public
|
||||||
|
mv index.html ./public/
|
||||||
|
cd public
|
||||||
|
zip -r ../pqtk.zip .
|
4
deploy.sh
Executable file
4
deploy.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
scp pqtk.zip joe-vps:~
|
||||||
|
ssh joe-vps 'unzip -o pqtk.zip -d ~/websites/pqtk/ && rm pqtk.zip'
|
@ -13,6 +13,7 @@
|
|||||||
"elm-lang/animation-frame": "1.0.1 <= v < 2.0.0",
|
"elm-lang/animation-frame": "1.0.1 <= v < 2.0.0",
|
||||||
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
||||||
"elm-lang/window": "1.0.1 <= v < 2.0.0",
|
"elm-lang/window": "1.0.1 <= v < 2.0.0",
|
||||||
|
"elm-lang/keyboard": "1.0.1 <= v < 2.0.0",
|
||||||
"elm-lang/html": "2.0.0 <= v < 3.0.0"
|
"elm-lang/html": "2.0.0 <= v < 3.0.0"
|
||||||
},
|
},
|
||||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||||
|
BIN
media/blue-p3q5.gif
Normal file
BIN
media/blue-p3q5.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 691 KiB |
BIN
media/green-p2q9.gif
Normal file
BIN
media/green-p2q9.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 906 KiB |
BIN
media/red-p2q5.gif
Normal file
BIN
media/red-p2q5.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 602 KiB |
BIN
media/yellow-p8q9.gif
Normal file
BIN
media/yellow-p8q9.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 850 KiB |
Loading…
x
Reference in New Issue
Block a user