feat: 3d model and serious bloat incoming

This commit is contained in:
Ananth Venkatesh 2025-01-26 04:17:06 -05:00
parent b903f3755d
commit 0f4436787c
Signed by: ananthv
GPG key ID: 4BB578E748CFE4FF
7 changed files with 57712 additions and 14 deletions

BIN
assets/3d/macg/image0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 MiB

View 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

File diff suppressed because it is too large Load diff

BIN
assets/3d/macgregor.zip Normal file

Binary file not shown.

View file

@ -6,16 +6,35 @@
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"andrewMacmurray/elm-simple-animation": "2.3.2",
"avh4/elm-color": "1.0.0",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"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": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/json": "1.1.3",
"elm/time": "1.0.0",
"elm/random": "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": {

View file

@ -1,11 +1,69 @@
module Main exposing (main)
import Angle
import Array
import Browser
import Browser.Events as Events
import Camera3d
import Color
import Direction3d
import Element exposing (..)
import Element.Background as Background
import Element.Font as Font
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
@ -14,7 +72,7 @@ main =
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 =
@ -25,23 +83,76 @@ init : Flags -> ( Model, Cmd Msg )
init flags =
case flags of
( 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
= 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 =
let
wrap : Model -> ( Model, Cmd Msg )
wrap data =
( data, Cmd.none )
pass : ( Model, Cmd Msg )
pass =
wrap model
in
case msg of
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 _ =
Events.onResize Resize
Sub.batch
[ Events.onResize Resize
, Time.every (1000 / 30) Rotate
]
htmlify : List (Element Msg) -> List (Html Msg)
@ -64,19 +175,170 @@ view model =
{ title = "MacGregor House"
, body =
htmlify
[ el
[ column
[ width 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
, alignTop
, moveRight (vw model 10)
, moveDown (vh model 50)
, width (px (round (vw 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.family [ Font.typeface "Imbue" ]
, Font.size 96
]
(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 ]

View file

@ -3,10 +3,16 @@
<head>
<meta charset="UTF-8" />
<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>
<body>
<div id="app"></div>
<script src="../result/Main.js"></script>
<script src="../compiled/Main.js"></script>
<script>
var app = Elm.Main.init({
node: document.getElementById("app"),