diff --git a/README.org b/README.org index fbd99c2..27a3b83 100644 --- a/README.org +++ b/README.org @@ -1,39 +1,105 @@ -### FPS Scene +#+OPTIONS: toc:nil -You can walk around the scene with simple FPS controls. -Open `scene.html` with an http server, you can use the following; +* FP Rendering Engine +** TL;DR +Graphics + Web + Functional Programming -```python3 -m http.server 8000``` +[[https://ferano.io/3d-fp/][Play it here]] -Unfortunately, elm-reactor has issues with loading obj files so that's why the -python server is needed. +** About +The goal of this project is to learn about the intersection of 3 different +topics; graphics, web, and functional programming. The FP acronym refers to both +the fact that it's a *F*irst *P*erson camera, as well as being implemented using +*F*unctional *P*rogramming. It uses Elm with WebGL to create a 3D first-person +scene implementing several lower level graphics techniques including matrix +transformations to model parent/child local transforms, texture mapping, and +basic lighting with shaders. -![alt text](screenshots/elm-fps.png "FPS Scene") +I worked on this project for a few weeks in 2019, however, it was just thrown +into a git repo without much consideration, along with [[https://git.ferano.io/JosephFerano/pqtorusknot][this]] project. I felt this +deserved a little bit more TLC, so I decided to spruce it up a little bit and +move it to its own repository. Here's the original [[https://github.com/JosephFerano/elm-graphics][project]]. + +** Showcase + +*** First Person Camera +[[file:screenshots/play1.gif]] + +[[https://ferano.io/3d-fp/play1.mp4][Higher Resolution Version]] +*** Sextus +[[file:screenshots/sextus.gif]] + +[[https://ferano.io/3d-fp/sextus.mp4][Higher Resolution Version]] -##### Controls +** 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]] -Mouse - Look Rotation +If you want to use =npm= instead; - =npm install -g elm@elm0.18.0= -WASD - Player movement +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. -◀ ▼ ▲ ▶ - Move the Robot +Once you have the Elm compiler, go ahead and run -N and M - Rotate robot left and right +=elm make Scene.elm= -Y and H - Rotate robot arm up and down +And that should first pull in all the dependencies then generate an =index.html= +file. -U and J - Rotate robot hand up and down +*** Issues with dependencies +One of the dependencies of this project, =Zinggi/elm-obj-loader=, is getting a 404 +when Elm tries to pull in all the deps during compilation. Therefore this +dependency will be included in version control, on the off chance someone +actually wants to build this. +** Running +In order to be able to load the textures and models, the files must be served by +an HTTP server because of browser security, see [[https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy][SOP]] and [[https://developer.mozilla.org/en-US/docs/Glossary/CORS][CORS]] if curious. -### PQTorusknot +You can use ~elm-reactor~, run the command and open up your browser to +~https://localhost:8000~ and then click on ~Scene.elm~ in the nice project webview +provided. If you don't want to do that and have Python 3 installed, run =python3 +-m http.server=, but make sure to compile with the ~elm make Scene.elm~ command +first. -Either open up `torus.html`, or use the command `elm-reactor`, if you want to be able to modify the source file and compile; +** How to Play +You can walk around the scene and also control the robot which I have named +Emperor Sextus, Conqueror of the Luddites. -https://guide.elm-lang.org/install.html +*** Keybindings +- Look Around: =Mouse= -Alternatively, here's the Ellie link +- FPS Movement: =WASD= -https://ellie-app.com/vVTgpBj77ra1 +- Robot Movement: =◀ ▼ ▲ ▶= -![alt text](screenshots/elm-knot.png "PQ Torus Knot") +- Rotate Robot: =N | M= + +- Rotate Robot Arm: =H | J= + +- Rotate Robot Hand: =Y | U= + +** Takeaways +The Elm WebGL library provides a few abstractions (namely =WebGL.Entity=) that +help reduce some of the boilerplate usually associated with creating a WebGL +application from scratch. While it was useful for the purposes of this +prototype, it remains to be seen if it would scale if this would become a proper +3D rendering engine. + +Despite what might be potentially undiscovered performance issues, Elm was +pleasant to work with, and it sold me two ideas; functional programming and +minimalism. The touted safety and correctness were noticeable, the learning +curve wasn't as steep, and using the Elm debugger to be able to step back in +time through the game was eye-opening in showing what's possible when you +architect your state management with immutability in mind. It would be +interesting to see if a modern game engine could switch between the functional, +immutable data structures in a debug context and the cache-friendly mutable data +structures for improved performance in a release build. Thus boosting +productivity during development, while still remaining performant in the hands +of players. + +** License +This project is licensed under the terms of the MIT license. For more +information, see the included LICENSE file. diff --git a/Scene.elm b/Scene.elm index 2d12c91..c1f3643 100644 --- a/Scene.elm +++ b/Scene.elm @@ -107,7 +107,7 @@ update msg model = (model.keys.a , model.keys.s , model.keys.w , model.keys.d) model.lookDir model.pos - 0.08 + 0.11 , robot = updateRobot model dt , rot = model.rot + 0.001 * dt} WindowResized size -> { model | winSize = size } @@ -126,15 +126,16 @@ update msg model = updateRobot: Model -> Time -> Robot updateRobot { robot , keys } dt = - let rr part = part + dt * 0.005 - rl part = part - dt * 0.005 + let rotSpeed = 0.003 + rr part = part + dt * rotSpeed + rl part = part - dt * rotSpeed rot = radians robot.rot in { pos = movePos (keys.left, keys.down, keys.up, keys.right) (vec3 (sin rot) (sin rot) (cos rot)) robot.pos - 0.03 + 0.05 , rot = if keys.n then rr robot.rot else if keys.m then rl robot.rot else robot.rot , armRot = let angle = if keys.j then rr robot.armRot else if keys.h then rl robot.armRot else robot.armRot @@ -168,8 +169,8 @@ getRobot model = |> Mat4.inverse |> Maybe.withDefault Mat4.identity |> Mat4.mul (Mat4.makeTranslate3 0 0 0.6) |> Mat4.mul arm - in [ getEntity3 model (body |> Mat4.scale3 0.5 0.5 0.5) cube Color.blue - , getEntity3 model (arm |> Mat4.scale3 0.2 0.2 0.5) cube Color.green + in [ getEntity3 model (body |> Mat4.scale3 0.5 0.5 0.5) cube Color.blue + , getEntity3 model (arm |> Mat4.scale3 0.2 0.2 0.5) cube Color.green , getEntity3 model (hand |> Mat4.scale3 0.15 0.15 0.45) cube Color.red ] |> List.concat diff --git a/screenshots/elm-fps.png b/screenshots/elm-fps.png deleted file mode 100644 index 232fc11..0000000 Binary files a/screenshots/elm-fps.png and /dev/null differ diff --git a/screenshots/play1.gif b/screenshots/play1.gif new file mode 100644 index 0000000..2ce0d15 Binary files /dev/null and b/screenshots/play1.gif differ diff --git a/screenshots/sextus.gif b/screenshots/sextus.gif new file mode 100644 index 0000000..c8731a3 Binary files /dev/null and b/screenshots/sextus.gif differ