drei-vanilla icon indicating copy to clipboard operation
drei-vanilla copied to clipboard

Bundle less CDN example?

Open jikkuatwork opened this issue 1 year ago • 8 comments

Is there a possibility for adding a vanila/bundleless/CDN example?

jikkuatwork avatar Jul 18 '24 18:07 jikkuatwork

search for @pmndrs/vanilla instead of drei-vanilla then it should come up

https://unpkg.com/browse/@pmndrs/vanilla/

https://www.jsdelivr.com/package/npm/@pmndrs/vanilla

was not able to setup the imports correctly ,will look at it again later

vis-prime avatar Jul 19 '24 16:07 vis-prime

Somehow didn't work. Anyways, I will be keen to see an example without bundling. Thanks for replying.

jikkuatwork avatar Jul 19 '24 19:07 jikkuatwork

check this fiddle , is this what you meant ?

https://jsfiddle.net/f7o0zsrg/15/

vis-prime avatar Nov 19 '24 16:11 vis-prime

Thank you for the example. All seems to work except for the "Outlines" helper.

Simply executing the Outlines() function causes a different typeerror for different browsers.

I'm wondering if my implementation is incorrect.

I've forked your fiddle and imported the Outlines helper, example here: https://jsfiddle.net/jnitafan/2mokvrty/8/

jnitafan avatar Nov 22 '24 03:11 jnitafan

Ok, i've managed to get the Outlines() function on a CDN only instance working by fixing all the issues that propped up.

Originally I imported vanilla-drei only for the outlines feature since no other implementation is good as react-three-drei's. Since that was the case I decided to just extract the code I needed.

The application I was dealing with required VanillaJS so I had to transpile it with Babel. It should not return "TypeError: Right side of assignment cannot be destructured" anymore.

If anyone else comes across this searching for a super hyperspecific scenario of where you are only allowed CDNs, VanillaJS and want some nice outlines, here are the snippets.

Heres the HTML:

<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>

<script type="importmap">
	{
		"imports": {
			"three": "https://unpkg.com/three/build/three.module.js",
                        "three/examples/jsm/": "https://unpkg.com/three/examples/jsm/",
		}
	}
</script>

<script type="module" src="./loadObject.js"></script>

And the Javascript:
loadObject.js

    import * as THREE from "three"
    import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
    import { toCreasedNormals } from 'three/examples/jsm/utils/BufferGeometryUtils.js';

    let renderer, scene, camera, controls

    init()
    animate()

    function init() {
        // renderer
        renderer = new THREE.WebGLRenderer()
        renderer.setSize(window.innerWidth, window.innerHeight)
        renderer.setPixelRatio(window.devicePixelRatio)
        document.body.appendChild(renderer.domElement)

        // scene
        scene = new THREE.Scene()

        // camera
        camera = new THREE.PerspectiveCamera(
            40,
            window.innerWidth / window.innerHeight,
            1,
            10000,
        )
        camera.position.set(20, 20, 20)

        // controls
        controls = new OrbitControls(camera, renderer.domElement)

        // ambient
        scene.add(new THREE.AmbientLight(0x222222))
        scene.background = new THREE.Color('grey')
        // light
        const light = new THREE.DirectionalLight(0xffffff, 1)
        light.position.set(20, 20, 0)
        scene.add(light)

        // axes
        scene.add(new THREE.AxesHelper(20))

        const geometry = new THREE.BoxGeometry(15, 32, 32)

        const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: 0x00ff00 }))
        mesh.scale.set(0.25, 0.25, 0.25)

        const outlines = Outlines({ color: new THREE.Color("blue"), thickness: 10 });
        mesh.add(outlines.group)
        outlines.generate()

        scene.add(mesh)
    }

    function animate(time) {
        requestAnimationFrame(animate)

        renderer.render(scene, camera)
    }

    function Outlines({
        color = new THREE.Color("black"),
        opacity = 1,
        transparent = false,
        screenspace = false,
        toneMapped = true,
        polygonOffset = false,
        polygonOffsetFactor = 0,
        renderOrder = 0,
        thickness = 0.05,
        angle = Math.PI,
        gl
    } = {}) {

        const OutlinesMaterial = shaderMaterial(
            {
                screenspace: false,
                color: new THREE.Color('black'),
                opacity: 1,
                thickness: 100.0,
                size: new THREE.Vector2(),
            },
            /* glsl */ `
            #include <common>
            #include <morphtarget_pars_vertex>
            #include <skinning_pars_vertex>
            uniform float thickness;
            uniform float screenspace;
            uniform vec2 size;
            void main() {
            #if defined (USE_SKINNING)
                #include <beginnormal_vertex>
                #include <morphnormal_vertex>
                #include <skinbase_vertex>
                #include <skinnormal_vertex>
                #include <defaultnormal_vertex>
            #endif
            #include <begin_vertex>
                #include <morphtarget_vertex>
                #include <skinning_vertex>
            #include <project_vertex>
            vec4 tNormal = vec4(normal, 0.0);
            vec4 tPosition = vec4(transformed, 1.0);
            #ifdef USE_INSTANCING
                tNormal = instanceMatrix * tNormal;
                tPosition = instanceMatrix * tPosition;
            #endif
            if (screenspace == 0.0) {
                vec3 newPosition = tPosition.xyz + tNormal.xyz * thickness;
                gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0); 
            } else {
                vec4 clipPosition = projectionMatrix * modelViewMatrix * tPosition;
                vec4 clipNormal = projectionMatrix * modelViewMatrix * tNormal;
                vec2 offset = normalize(clipNormal.xy) * thickness / size * clipPosition.w * 2.0;
                clipPosition.xy += offset;
                gl_Position = clipPosition;
            }
            }`,
            /* glsl */ `
            uniform vec3 color;
            uniform float opacity;
            void main(){
            gl_FragColor = vec4(color, opacity);
            #include <tonemapping_fragment>
            #include <${parseInt(THREE.REVISION.replace(/\D+/g, '')) >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}>
            }`
        )

        const group = new THREE.Group()

        let shapeProps = {
            color,
            opacity,
            transparent,
            screenspace,
            toneMapped,
            polygonOffset,
            polygonOffsetFactor,
            renderOrder,
            thickness,
            angle
        }

        function updateMesh(angle) {
            const parent = group.parent
            group.clear()
            if (parent && parent.geometry) {
                let mesh
                const material = new OutlinesMaterial({ side: THREE.BackSide });
                if (parent.skeleton) {
                    mesh = new THREE.SkinnedMesh()
                    mesh.material = material
                    mesh.bind(parent.skeleton, parent.bindMatrix)
                    group.add(mesh)
                } else if (parent.isInstancedMesh) {
                    mesh = new THREE.InstancedMesh(parent.geometry, material, parent.count)
                    mesh.instanceMatrix = parent.instanceMatrix
                    group.add(mesh)
                } else {
                    mesh = new THREE.Mesh()
                    mesh.material = material
                    group.add(mesh)
                }
                mesh.geometry = angle
                    ? toCreasedNormals(parent.geometry, angle)
                    : parent.geometry
            }
        }

        function updateProps(newProps) {
            shapeProps = { ...shapeProps, ...newProps }
            const mesh = group.children[0]
            if (mesh) {
                const {
                    transparent,
                    thickness,
                    color,
                    opacity,
                    screenspace,
                    toneMapped,
                    polygonOffset,
                    polygonOffsetFactor,
                    renderOrder
                } = shapeProps
                const contextSize = new THREE.Vector2()
                if (!gl && shapeProps.screenspace) {
                    console.warn(
                        'Outlines: "screenspace" requires a WebGLRenderer instance to calculate the outline size'
                    )
                }
                if (gl) gl.getSize(contextSize)

                Object.assign(mesh.material, {
                    transparent,
                    thickness,
                    color,
                    opacity,
                    size: contextSize,
                    screenspace,
                    toneMapped,
                    polygonOffset,
                    polygonOffsetFactor
                })
                if (renderOrder !== undefined) mesh.renderOrder = renderOrder
            }
        }

        return {
            group,
            updateProps(props) {
                const angle = props.angle ?? shapeProps.angle
                if (angle !== shapeProps.angle) {
                    updateMesh(angle)
                }
                updateProps(props)
            },
            generate() {
                updateMesh(shapeProps.angle)
                updateProps(shapeProps)
            }
        }
    }

    export function shaderMaterial(uniforms, vertexShader, fragmentShader, onInit) {
        const entries = Object.entries(uniforms)

        class Material extends THREE.ShaderMaterial {
            static key = THREE.MathUtils.generateUUID()

            constructor(parameters) {
                super({
                    uniforms: entries.reduce((acc, [name, value]) => {
                        const uniform = THREE.UniformsUtils.clone({ [name]: { value } })
                        return {
                            ...acc,
                            ...uniform
                        }
                    }, {}),
                    vertexShader,
                    fragmentShader
                })

                for (const [name] of entries) {
                    Object.defineProperty(this, name, {
                        get: () => this.uniforms[name].value,
                        set: v => (this.uniforms[name].value = v)
                    })
                }

                Object.assign(this, parameters)

                onInit?.(this)
            }
        }

        return Material
    }

Hope this helps someone else.

jnitafan avatar Nov 23 '24 12:11 jnitafan

On jsfiddle i was able to get outline working without any changes. so the problem might be specific to your build environment, Thanks for sharing

https://jsfiddle.net/jq8dpnoy/

image

vis-prime avatar Nov 24 '24 15:11 vis-prime

Thanks for checking, I was able to reproduce the destructured TypeError by going on jsfiddle by doing:

const outlines = Outlines()

instead of:

const outlines = Outlines({})

Screenshot 2024-11-25 at 3 53 04 PM

Putting in an placeholder object inside the Outlines() function properties fixes the issue. I'm assuming this isn't intended and can be fixed by going into Outlines.ts and editing line 101: }: Partial<OutlinesProps> = {}): OutlinesType { to include a = {}

I should of tested more with some placeholder values 😭

jnitafan avatar Nov 25 '24 04:11 jnitafan

cool, will add that in the upcoming updates

vis-prime avatar Nov 25 '24 15:11 vis-prime