feat: 3d model and serious bloat incoming
This commit is contained in:
parent
b903f3755d
commit
0f4436787c
7 changed files with 57712 additions and 14 deletions
BIN
assets/3d/macg/image0.jpg
Normal file
BIN
assets/3d/macg/image0.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 MiB |
23
assets/3d/macg/macgregor-house-small.mtl
Normal file
23
assets/3d/macg/macgregor-house-small.mtl
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# File generated by ImageToStl.com - Free Image and 3D model conversion tools
|
||||||
|
|
||||||
|
newmtl mat0
|
||||||
|
Ns 0
|
||||||
|
Ka 1.0 1.0 1.0
|
||||||
|
Kd 1 1 1
|
||||||
|
Ks 0.5 0.5 0.5
|
||||||
|
Ke 0.0 0.0 0.0
|
||||||
|
Ni 1.0
|
||||||
|
d 1
|
||||||
|
illum 2
|
||||||
|
map_Kd image0.jpg
|
||||||
|
|
||||||
|
newmtl mat1
|
||||||
|
Ns 250
|
||||||
|
Ka 1.0 1.0 1.0
|
||||||
|
Kd 0.85 0.85 0.85
|
||||||
|
Ks 0 0 0
|
||||||
|
Ke 0.0 0.0 0.0
|
||||||
|
Ni 1.0
|
||||||
|
d 1
|
||||||
|
illum 1
|
||||||
|
|
57388
assets/3d/macg/macgregor.obj.txt
Normal file
57388
assets/3d/macg/macgregor.obj.txt
Normal file
File diff suppressed because it is too large
Load diff
BIN
assets/3d/macgregor.zip
Normal file
BIN
assets/3d/macgregor.zip
Normal file
Binary file not shown.
25
elm.json
25
elm.json
|
@ -6,16 +6,35 @@
|
||||||
"elm-version": "0.19.1",
|
"elm-version": "0.19.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"direct": {
|
"direct": {
|
||||||
|
"andrewMacmurray/elm-simple-animation": "2.3.2",
|
||||||
|
"avh4/elm-color": "1.0.0",
|
||||||
"elm/browser": "1.0.2",
|
"elm/browser": "1.0.2",
|
||||||
"elm/core": "1.0.5",
|
"elm/core": "1.0.5",
|
||||||
"elm/html": "1.0.0",
|
"elm/html": "1.0.0",
|
||||||
"mdgriffith/elm-ui": "1.1.8"
|
"elm/http": "2.0.0",
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm-explorations/webgl": "1.1.3",
|
||||||
|
"ianmackenzie/elm-3d-camera": "3.1.0",
|
||||||
|
"ianmackenzie/elm-3d-scene": "1.0.2",
|
||||||
|
"ianmackenzie/elm-geometry": "3.11.0",
|
||||||
|
"ianmackenzie/elm-triangular-mesh": "1.1.0",
|
||||||
|
"ianmackenzie/elm-units": "2.10.0",
|
||||||
|
"mdgriffith/elm-ui": "1.1.8",
|
||||||
|
"w0rm/elm-obj-file": "1.2.1"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
"elm/bytes": "1.0.8",
|
||||||
|
"elm/file": "1.0.5",
|
||||||
"elm/json": "1.1.3",
|
"elm/json": "1.1.3",
|
||||||
"elm/time": "1.0.0",
|
"elm/random": "1.0.0",
|
||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
"elm/virtual-dom": "1.0.3"
|
"elm/virtual-dom": "1.0.3",
|
||||||
|
"elm-explorations/linear-algebra": "1.0.3",
|
||||||
|
"ianmackenzie/elm-1d-parameter": "1.0.1",
|
||||||
|
"ianmackenzie/elm-float-extra": "1.1.0",
|
||||||
|
"ianmackenzie/elm-geometry-linear-algebra-interop": "2.0.2",
|
||||||
|
"ianmackenzie/elm-interval": "3.1.0",
|
||||||
|
"ianmackenzie/elm-units-interval": "3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test-dependencies": {
|
"test-dependencies": {
|
||||||
|
|
282
src/Main.elm
282
src/Main.elm
|
@ -1,11 +1,69 @@
|
||||||
module Main exposing (main)
|
module Main exposing (main)
|
||||||
|
|
||||||
|
import Angle
|
||||||
|
import Array
|
||||||
import Browser
|
import Browser
|
||||||
import Browser.Events as Events
|
import Browser.Events as Events
|
||||||
|
import Camera3d
|
||||||
|
import Color
|
||||||
|
import Direction3d
|
||||||
import Element exposing (..)
|
import Element exposing (..)
|
||||||
import Element.Background as Background
|
import Element.Background as Background
|
||||||
import Element.Font as Font
|
import Element.Font as Font
|
||||||
import Html exposing (Html)
|
import Html exposing (Html)
|
||||||
|
import Http exposing (Error)
|
||||||
|
import Length
|
||||||
|
import Obj.Decode
|
||||||
|
import Pixels
|
||||||
|
import Point3d exposing (Point3d)
|
||||||
|
import Scene3d exposing (Entity)
|
||||||
|
import Scene3d.Material as Material
|
||||||
|
import Scene3d.Mesh as Mesh
|
||||||
|
import Simple.Animation as Animation exposing (Animation)
|
||||||
|
import Simple.Animation.Animated as Animated
|
||||||
|
import Simple.Animation.Property as P
|
||||||
|
import Task
|
||||||
|
import Time
|
||||||
|
import TriangularMesh exposing (TriangularMesh)
|
||||||
|
import Viewpoint3d
|
||||||
|
import WebGL.Texture
|
||||||
|
|
||||||
|
|
||||||
|
getMesh : Cmd Msg
|
||||||
|
getMesh =
|
||||||
|
Http.get
|
||||||
|
{ url = "../assets/3d/macg/macgregor.obj.txt"
|
||||||
|
, expect = Obj.Decode.expectObj GotMesh Length.meters Obj.Decode.texturedTriangles
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getTexture : Cmd Msg
|
||||||
|
getTexture =
|
||||||
|
Material.loadWith Material.nearestNeighborFiltering "../assets/3d/macg/image0.jpg" |> Task.attempt GotTexture
|
||||||
|
|
||||||
|
|
||||||
|
animatedUi :
|
||||||
|
(List (Attribute msg) -> children -> Element msg)
|
||||||
|
-> Animation
|
||||||
|
-> List (Attribute msg)
|
||||||
|
-> children
|
||||||
|
-> Element msg
|
||||||
|
animatedUi =
|
||||||
|
Animated.ui
|
||||||
|
{ behindContent = Element.behindContent
|
||||||
|
, htmlAttribute = Element.htmlAttribute
|
||||||
|
, html = Element.html
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
animatedEl : Animation -> List (Element.Attribute msg) -> Element msg -> Element msg
|
||||||
|
animatedEl =
|
||||||
|
animatedUi Element.el
|
||||||
|
|
||||||
|
|
||||||
|
animatedCol : Animation -> List (Element.Attribute msg) -> List (Element msg) -> Element msg
|
||||||
|
animatedCol =
|
||||||
|
animatedUi Element.column
|
||||||
|
|
||||||
|
|
||||||
main : Program Flags Model Msg
|
main : Program Flags Model Msg
|
||||||
|
@ -14,7 +72,7 @@ main =
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ w : Int, h : Int }
|
{ w : Int, h : Int, mesh : Maybe Object3d, textures : Maybe (Material.Textured Obj.Decode.ObjCoordinates), angle : Float }
|
||||||
|
|
||||||
|
|
||||||
type alias Flags =
|
type alias Flags =
|
||||||
|
@ -25,23 +83,76 @@ init : Flags -> ( Model, Cmd Msg )
|
||||||
init flags =
|
init flags =
|
||||||
case flags of
|
case flags of
|
||||||
( width, height ) ->
|
( width, height ) ->
|
||||||
( { w = width, h = height }, Cmd.none )
|
( { w = width, h = height, mesh = Nothing, textures = Nothing, angle = 0 }, Cmd.batch [ getMesh, getTexture ] )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- type alias Object3d =
|
||||||
|
-- TriangularMesh
|
||||||
|
-- { position : Point3d Length.Meters Obj.Decode.ObjCoordinates
|
||||||
|
-- , normal : Vector3d Quantity.Unitless Obj.Decode.ObjCoordinates
|
||||||
|
-- , uv : ( Float, Float )
|
||||||
|
-- }
|
||||||
|
-- type alias Object3d =
|
||||||
|
-- TriangularMesh (Point3d Length.Meters Obj.Decode.ObjCoordinates)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Object3d =
|
||||||
|
TriangularMesh { position : Point3d Length.Meters Obj.Decode.ObjCoordinates, uv : ( Float, Float ) }
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= Resize Int Int
|
= Resize Int Int
|
||||||
|
| GotMesh (Result Http.Error Object3d)
|
||||||
|
| GotTexture (Result WebGL.Texture.Error (Material.Texture Color.Color))
|
||||||
|
| Rotate Time.Posix
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
update msg model =
|
update msg model =
|
||||||
|
let
|
||||||
|
wrap : Model -> ( Model, Cmd Msg )
|
||||||
|
wrap data =
|
||||||
|
( data, Cmd.none )
|
||||||
|
|
||||||
|
pass : ( Model, Cmd Msg )
|
||||||
|
pass =
|
||||||
|
wrap model
|
||||||
|
in
|
||||||
case msg of
|
case msg of
|
||||||
Resize width height ->
|
Resize width height ->
|
||||||
( { model | w = width, h = height }, Cmd.none )
|
wrap { model | w = width, h = height }
|
||||||
|
|
||||||
|
GotMesh response ->
|
||||||
|
case response of
|
||||||
|
Err _ ->
|
||||||
|
pass
|
||||||
|
|
||||||
|
Ok object ->
|
||||||
|
wrap { model | mesh = Just object }
|
||||||
|
|
||||||
|
GotTexture result ->
|
||||||
|
case result of
|
||||||
|
Err _ ->
|
||||||
|
pass
|
||||||
|
|
||||||
|
Ok texture ->
|
||||||
|
wrap
|
||||||
|
{ model
|
||||||
|
| textures =
|
||||||
|
Just (Material.texturedMatte texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rotate time ->
|
||||||
|
wrap { model | angle = model.angle + 2 * (2 + sin (toFloat (Time.posixToMillis time) / 1000)) }
|
||||||
|
|
||||||
|
|
||||||
subscribe : Model -> Sub Msg
|
subscribe : Model -> Sub Msg
|
||||||
subscribe _ =
|
subscribe _ =
|
||||||
Events.onResize Resize
|
Sub.batch
|
||||||
|
[ Events.onResize Resize
|
||||||
|
, Time.every (1000 / 30) Rotate
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
htmlify : List (Element Msg) -> List (Html Msg)
|
htmlify : List (Element Msg) -> List (Html Msg)
|
||||||
|
@ -64,19 +175,170 @@ view model =
|
||||||
{ title = "MacGregor House"
|
{ title = "MacGregor House"
|
||||||
, body =
|
, body =
|
||||||
htmlify
|
htmlify
|
||||||
[ el
|
[ column
|
||||||
[ width fill
|
[ width fill
|
||||||
, height fill
|
, height fill
|
||||||
, Background.color (rgb255 0 0 0)
|
, spacing (round (vh model -100))
|
||||||
]
|
]
|
||||||
(el
|
[ el
|
||||||
|
[ width fill
|
||||||
|
, height (px (round (vh model 100)))
|
||||||
|
, Background.color (rgb255 0 0 0)
|
||||||
|
]
|
||||||
|
Element.none
|
||||||
|
, animatedEl crossfadeIn
|
||||||
|
[ width fill
|
||||||
|
, height (px (round (vh model 100)))
|
||||||
|
, Background.gradient { angle = 45, steps = [ rgb255 200 0 100, rgb255 100 0 200 ] }
|
||||||
|
]
|
||||||
|
Element.none
|
||||||
|
, animatedEl crossfadeOut
|
||||||
|
[ width fill
|
||||||
|
, height (px (round (vh model 100)))
|
||||||
|
, Background.gradient { angle = 45, steps = [ rgb255 0 100 200, rgb255 0 200 100 ] }
|
||||||
|
]
|
||||||
|
Element.none
|
||||||
|
, el
|
||||||
[ alignLeft
|
[ alignLeft
|
||||||
, alignTop
|
, alignTop
|
||||||
, moveRight (vw model 10)
|
, width (px (round (vw model 50)))
|
||||||
, moveDown (vh model 50)
|
, height (px (round (vh model 100)))
|
||||||
|
, paddingEach
|
||||||
|
{ top = round (vh model 50) - 96
|
||||||
|
, bottom = 0
|
||||||
|
, left = round (vw model 10)
|
||||||
|
, right = 0
|
||||||
|
}
|
||||||
, Font.color (rgb255 255 255 255)
|
, Font.color (rgb255 255 255 255)
|
||||||
|
, Font.family [ Font.typeface "Imbue" ]
|
||||||
|
, Font.size 96
|
||||||
]
|
]
|
||||||
(text "MacGregor House")
|
(text "MacGregor House")
|
||||||
)
|
, el
|
||||||
|
[ alignRight
|
||||||
|
, alignTop
|
||||||
|
, width (px (round (vw model 60)))
|
||||||
|
, height (px (round (vh model 100)))
|
||||||
|
, paddingEach
|
||||||
|
{ top = round (vh model 25)
|
||||||
|
, bottom = round (vh model 25)
|
||||||
|
, left = 0
|
||||||
|
, right = 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
(view3D model)
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pyramidMesh : Mesh.Uniform coordinates
|
||||||
|
pyramidMesh =
|
||||||
|
let
|
||||||
|
-- Define the vertices of our pyramid
|
||||||
|
frontLeft =
|
||||||
|
Point3d.centimeters 250 500 0
|
||||||
|
|
||||||
|
frontRight =
|
||||||
|
Point3d.centimeters 400 0 -500
|
||||||
|
|
||||||
|
backLeft =
|
||||||
|
Point3d.centimeters -250 500 -500
|
||||||
|
|
||||||
|
backRight =
|
||||||
|
Point3d.centimeters -250 0 0
|
||||||
|
|
||||||
|
tip =
|
||||||
|
Point3d.centimeters 0 0 500
|
||||||
|
|
||||||
|
-- Create a TriangularMesh value from an array of vertices and list
|
||||||
|
-- of index triples defining faces (see https://package.elm-lang.org/packages/ianmackenzie/elm-triangular-mesh/latest/TriangularMesh#indexed)
|
||||||
|
triangularMesh =
|
||||||
|
TriangularMesh.indexed
|
||||||
|
(Array.fromList
|
||||||
|
[ frontLeft -- 0
|
||||||
|
, frontRight -- 1
|
||||||
|
, backLeft -- 2
|
||||||
|
, backRight -- 3
|
||||||
|
, tip -- 4
|
||||||
|
]
|
||||||
|
)
|
||||||
|
[ ( 1, 0, 4 ) -- front
|
||||||
|
, ( 0, 2, 4 ) -- left
|
||||||
|
, ( 2, 3, 4 ) -- back
|
||||||
|
, ( 3, 1, 4 ) -- right
|
||||||
|
, ( 1, 3, 0 ) -- bottom
|
||||||
|
, ( 0, 3, 2 ) -- bottom
|
||||||
|
]
|
||||||
|
in
|
||||||
|
-- Create a elm-3d-scene Mesh value from the TriangularMesh; we use
|
||||||
|
-- Mesh.indexedFacets so that normal vectors will be generated for each face
|
||||||
|
Mesh.indexedFacets triangularMesh
|
||||||
|
|
||||||
|
|
||||||
|
view3D : Model -> Element msg
|
||||||
|
view3D model =
|
||||||
|
Element.html
|
||||||
|
(let
|
||||||
|
entity : Entity Obj.Decode.ObjCoordinates
|
||||||
|
entity =
|
||||||
|
case model.mesh of
|
||||||
|
Nothing ->
|
||||||
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) pyramidMesh
|
||||||
|
|
||||||
|
Just mesh ->
|
||||||
|
case model.textures of
|
||||||
|
Nothing ->
|
||||||
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) (Mesh.texturedFacets mesh)
|
||||||
|
|
||||||
|
Just textures ->
|
||||||
|
Scene3d.mesh textures (Mesh.texturedFacets mesh)
|
||||||
|
|
||||||
|
camera : Camera3d.Camera3d Length.Meters coordinates
|
||||||
|
camera =
|
||||||
|
Camera3d.perspective
|
||||||
|
{ viewpoint =
|
||||||
|
Viewpoint3d.lookAt
|
||||||
|
{ focalPoint = Point3d.origin
|
||||||
|
, eyePoint =
|
||||||
|
let
|
||||||
|
theta =
|
||||||
|
Angle.degrees model.angle
|
||||||
|
in
|
||||||
|
Point3d.meters (10 * Angle.cos theta) 2 (10 * Angle.sin theta)
|
||||||
|
, upDirection = Direction3d.xy (Angle.degrees 90)
|
||||||
|
}
|
||||||
|
, verticalFieldOfView = Angle.degrees 100
|
||||||
|
}
|
||||||
|
in
|
||||||
|
Scene3d.sunny
|
||||||
|
{ entities = [ entity ]
|
||||||
|
, camera = camera
|
||||||
|
, upDirection = Direction3d.z
|
||||||
|
, sunlightDirection = Direction3d.yz (Angle.degrees -120)
|
||||||
|
, background = Scene3d.transparentBackground
|
||||||
|
, clipDepth = Length.centimeters 1
|
||||||
|
, shadows = False
|
||||||
|
, dimensions = ( Pixels.int (round (vw model 60)), Pixels.int (round (vh model 100)) )
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
crossfadeIn : Animation
|
||||||
|
crossfadeIn =
|
||||||
|
Animation.fromTo
|
||||||
|
{ duration = 2017
|
||||||
|
, options = [ Animation.yoyo, Animation.loop ]
|
||||||
|
}
|
||||||
|
[ P.opacity 0 ]
|
||||||
|
[ P.opacity 1 ]
|
||||||
|
|
||||||
|
|
||||||
|
crossfadeOut : Animation
|
||||||
|
crossfadeOut =
|
||||||
|
Animation.fromTo
|
||||||
|
{ duration = 2027
|
||||||
|
, options = [ Animation.yoyo, Animation.loop ]
|
||||||
|
}
|
||||||
|
[ P.opacity 1 ]
|
||||||
|
[ P.opacity 0 ]
|
||||||
|
|
|
@ -3,10 +3,16 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>MacGregor House</title>
|
<title>MacGregor House</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Imbue:opsz,wght@10..100,100..900&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script src="../result/Main.js"></script>
|
<script src="../compiled/Main.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var app = Elm.Main.init({
|
var app = Elm.Main.init({
|
||||||
node: document.getElementById("app"),
|
node: document.getElementById("app"),
|
||||||
|
|
Loading…
Reference in a new issue