Created
July 24, 2025 12:42
-
-
Save juliendehos/2fa5f2d175c9cb56a352f7163b2691c1 to your computer and use it in GitHub Desktop.
typesafe texture/material in three.hs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In three.js, there are several classes implementing textures: | |
// - Texture (base class, for classic textures) | |
// - DepthTexture, VideoTexture... (which extend Texture) | |
// Then materials use such textures as properties (map, normalmap...). | |
// So we can write something like: | |
const material1 = new THREE.MeshLambertianMaterial(); | |
material1.map = new THREE.Texture(); // a Texture | |
const texture1 = material1.map; | |
// ... | |
material1.map = new THREE.VideoTexture(); // a VideoTexture | |
const videotexture1 = material1.map; | |
// ... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- In three.hs, we use typeclasses to wrap three.js base classes. | |
-- So, to implement textures, we have: | |
-- - TextureClass (typeclass) | |
-- - Texture (newtype) | |
-- - DepthTexture, VideoTexture... (newtypes) | |
-- In materials (for example, MeshLambertMaterial), we can define properties using TextureClass: | |
map :: (TextureClass texture, FromJSVal texture) => Property MeshLambertMaterial (Maybe texture) | |
map = optional "map" | |
-- Thus, we can set the map property using a Texture or a VideoTexture: | |
material1 <- THREE.MeshLambertMaterial.new () | |
texture1 <- THREE.Texture.new () | |
material1 & THREE.MeshLambertMaterial.map .= Just texture1 | |
-- ... | |
videotexture1 <- THREE.VideoTexture.new () | |
material1 & THREE.MeshLambertMaterial.map .= Just videotexture1 | |
-- But this isn't typesafe, since we can get a texture property with a potentially incorrect type: | |
(Just t1 :: Maybe Texture) <- material1 ^. THREE.MeshLambertMaterial.map -- ok (base class) | |
(Just t2 :: Maybe VideoTexture) <- material1 ^. THREE.MeshLambertMaterial.map -- ok (inherited class) | |
(Just t3 :: Maybe DepthTexture) <- material1 ^. THREE.MeshLambertMaterial.map -- not ok (but compiles) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Another solution is to define the property using Texture, instead of TextureClass: | |
map :: Property MeshLambertMaterial (Maybe Texture) | |
map = optional "map" | |
-- Thus, we can set this property using Texture or any "inherited" types (after coercion, still): | |
material1 <- THREE.MeshLambertMaterial.new () | |
texture1 <- THREE.Texture.new () | |
material1 & THREE.MeshLambertMaterial.map .= Just texture1 | |
-- ... | |
texture2 <- unsafeCoerceTexture =<< THREE.VideoTexture.new () | |
material1 & THREE.MeshLambertMaterial.map .= Just texture2 | |
-- We get a texture property using the (correct) base type only: | |
(Just t1 :: Maybe Texture) <- material1 ^. THREE.MeshLambertMaterial.map | |
-- (Just t2 :: Maybe VideoTexture) <- material1 ^. THREE.MeshLambertMaterial.map -- does not compile | |
-- (Just t3 :: Maybe DepthTexture) <- material1 ^. THREE.MeshLambertMaterial.map -- does not compile | |
-- But we can coerce a texture to get the inherited type, without breaking typesafety: | |
mvt <- coerceVideoTexture t1 | |
case mvt of | |
Just videotexture1 -> consoleLog "t1 is a VideoTexture" | |
Nothing -> consoleLog "t1 is not a VideoTexture" | |
-- helper functions: | |
unsafeCoerceTexture :: (ToJSVal a) => a -> JSM Texture | |
unsafeCoerceTexture = fmap Texture . toJSVal | |
coerceVideoTexture :: (MakeObject a, ToJSVal a) => a -> JSM (Maybe VideoTexture) | |
coerceVideoTexture v = do | |
r <- fromJSVal =<< v ! "isVideoTexture" | |
case r of | |
Just True -> Just . VideoTexture <$> toJSVal v | |
_ -> pure Nothing |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment