Rich-Harris / svelte-cubed

Svelte ❤️ Three
svelte-cubed.vercel.app
MIT License
1.19k stars 76 forks source link

Enable use of custom cameras or accessing root #47

Open nilskjQL opened 2 years ago

nilskjQL commented 2 years ago

Awesome library!

I have a scene exported from blender (a .gltf file) that has a camera and some animations I would like to use. Since this library is using context im struggling to extend root to use my own camera. Example: If I copy the logic from PerspectiveCamera to a Custom camera it cannot access the context properly when getting root from setup: const { root, self } = setup(new PerspectiveCamera());

Errors:

three.module.js:26548 Uncaught TypeError: Cannot read properties of null (reading 'isCamera')
    at WebGLRenderer.render (three.module.js:26548:39)
    at Canvas.svelte? [sm]:94:18
WebGLRenderer.render @ three.module.js:26548

context.js?v=7cfba4c5:17 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'scene')
    at setup (context.js?v=7cfba4c5:17:44)
    at instance (GLTFCamera.svelte:20:25)
    at init (index.mjs:1809:11)
    at new GLTFCamera (GLTFCamera.svelte:45:18)
    at createProxiedComponent (svelte-hooks.js:266:9)
    at new ProxyComponent (proxy.js:239:20)
    at new Proxy<GLTFCamera> (proxy.js:346:11)
    at Array.create_default_slot (index.svelte? [sm]:54:94)
    at create_slot (index.mjs:69:27)
    at create_if_block (Canvas.svelte? [sm]:4:32)

From the source I see that only set_root is exposed, not get_root. Is there a reason for this?

I built a wrapping GLTFModel loader component that loads the scene and exposes its data to its children with slot props:

<script>
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
    import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
    import { Mesh } from 'svelte-cubed';
    import { onMount } from 'svelte';

    let loader = new GLTFLoader();
    let dracoLoader = new DRACOLoader();
    dracoLoader.setDecoderPath(
        'DECODER_PATH'
    );
    loader.setDRACOLoader(dracoLoader);

    export let url = '';
    let load = loadModel(url);

    async function loadModel(model) {
        try {
            const gltf = await loader.loadAsync(model);
            console.log('loaded', gltf);
            return gltf;
        } catch (e) {
            console.error(e);
        }
    }
</script>

{#await load then model}
    <slot {model} />
{/await}

Using it I can read the scene camera and materials and use it like so:

<GLTFModel url="/model.glb" let:model>
    <Mesh geometry={model.scene.children[1].geometry} position={[0, $height, 0]} />
</GLTFModel>

How can I use the camera inside that model and replace the root camera with it?