Rich-Harris/svelte-cubed

Object/Texture Loaders

Rigo-m opened this issue · 3 comments

Is this already implemented and not documented or is the implementation lacking/won't be implemented by design?

https://threejs.org/docs/#examples/en/loaders/OBJLoader

Another good name for the issue would be: how do I load an external .obj file via Svelte Cubed API?

I put together a simple working example in a project I'm working on. I converted my .obj file to a .js file, wrapped everything in a template literal, and exported it. To avoid having to do that, there's room to potentially sweeten this up with a custom vite plugin that imports .obj files.

<script lang="ts">
	import * as THREE from 'three';
	import * as SC from 'svelte-cubed';
	import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
	import obj from '$lib/components/models/Lowpoly_tree_sample.js';
</script>

<SC.Canvas antialias background={new THREE.Color('papayawhip')} shadows>
	<SC.Primitive object={new OBJLoader().parse(obj)} />

	<SC.PerspectiveCamera position={[50, 50, 50]} />
	<SC.OrbitControls enableZoom={true} maxPolarAngle={Math.PI * 0.51} />
	<SC.AmbientLight intensity={0.6} />
	<SC.DirectionalLight intensity={0.6} position={[-2, 3, 2]} shadow={{ mapSize: [2048, 2048] }} />
</SC.Canvas>

image

Using SvelteKit, I hypothesize performance may be improved here by parsing the .obj file contents during prerendering (in the load function of <script context="module">) and passing both a BufferGeometry and Material through the props. I attempted that without success but I'm pretty green to three.js, so someone more knowledgeable may know how to extract those from the Object3D that new OBJLoader().parse(obj) returns.

I just created that vite plugin I mentioned above so you don't have to convert your the .obj files https://github.com/tonyketcham/unplugin-obj

Using that, you just have to do

import obj from '$lib/components/models/Lowpoly_tree_sample.obj';

Alternatively (and probably more performantly albeit more verbose) you can place your .obj/.mtl files within your static folder and do something like:

<script lang="ts">
	import * as THREE from 'three';
	import * as SC from 'svelte-cubed';
	import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
	import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
        import { assets } from '$app/paths';
	import { browser } from '$app/env';

	let mesh: THREE.Mesh;
	const mtlSrc = `${assets}/Lowpoly_tree_sample.mtl`;

	const mtlLoader = new MTLLoader();
	const objLoader = new OBJLoader();

        if (browser) {
	  // Load the object and material resources
	  mtlLoader.load(mtlSrc, (mtl) => {
                   const objSrc = `${assets}/Lowpoly_tree_sample.obj`;
		  mtl.preload();
		  objLoader.setMaterials(mtl);
  
		  objLoader.load(objSrc, (event) => {
			  mesh = event.children[0] as THREE.Mesh;
			  console.log(mesh);
			  return mesh;
		  });
	  });
        }

	async function loadMesh(mesh) {
		return new Promise((resolve: (value: THREE.Mesh) => any) => {
			if (mesh) {
				resolve(mesh);
			}
		});
	}

	// Wait for the mesh to load before adding it to the scene
	$: meshPromise = loadMesh(mesh);
</script>

<SC.Canvas antialias background={new THREE.Color('papayawhip')} shadows>
	{#await meshPromise then mesh}
		<SC.Mesh geometry={mesh.geometry} material={mesh.material} />
	{/await}
	<SC.PerspectiveCamera position={[50, 50, 50]} />
	<SC.OrbitControls enableZoom={true} maxPolarAngle={Math.PI * 0.51} />
	<SC.AmbientLight intensity={0.6} />
	<SC.DirectionalLight intensity={0.6} position={[-2, 3, 2]} shadow={{ mapSize: [2048, 2048] }} />
</SC.Canvas>