Skip to content

Commit

Permalink
Merge pull request #290 from Tresjs/feature/170-use-gltf-exporter
Browse files Browse the repository at this point in the history
feat: 170 use gltf exporter
  • Loading branch information
JaimeTorrealba authored Jan 29, 2024
2 parents dffae6b + 571c554 commit 411d631
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export default defineConfig({
{ text: 'Stats', link: '/guide/misc/stats' },
{ text: 'Html', link: '/guide/misc/html-component' },
{ text: 'StatsGl', link: '/guide/misc/stats-gl' },
{ text: 'useGLTFExporter', link: '/guide/misc/use-gltf-exporter' },
{ text: 'BakeShadows', link: '/guide/misc/bake-shadows' },
],
},
Expand Down
70 changes: 70 additions & 0 deletions docs/guide/misc/use-gltf-exporter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# useGLTFExporter

[GLTFExporter](https://threejs.org/docs/index.html?q=expo#examples/en/exporters/GLTFExporter) is an addon in threeJs that allows you to download any object3D in a [GLTF](https://www.khronos.org/gltf) format. **TresJS** provides a composable that simplifies this process with just a few lines of code.

## Basic usage

```vue{3,6,10,20}
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import { useGLTFExporter } from '@tresjs/cientos'
import { shallowRef } from 'vue'
const boxRef = shallowRef()
// the second argument – options – is not required
const downloadBox = () => {
useGLTFExporter(boxRef.value, { fileName: 'cube', binary: true })
}
</script>
<template>
<TresCanvas clear-color="#82DBC5" window-size >
<TresPerspectiveCamera :position="[0, 2, 5]" />
<TresMesh
ref="boxRef"
:position-z="30"
:scale="10"
@click="downloadBox"
>
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshStandardMaterial :color="0x00ff00" />
</TresMesh>
<TresDirectionalLight :position="[0, 10, 10]" />
</TresCanvas>
</template>
```

### Download the scene: a quick tip

In the previous example, if we want to download the whole scene, we can easily do it by just accessing the `parent` property.
```ts{3}
...
const downloadBox = () => {
useGLTFExporter(boxRef.value.parent) // As the parent is the current scene
}
...
```
_This may vary in your scene please first check what is in your `parent` property_

Otherwise, you can access your scene using the [useTresContext](https://docs.tresjs.org/api/composables.html#usetrescontext)


## Arguments

| Name | Type | Default | Description |
| :----------- | ---------- | ----------- | ---------------------------------------------------- |
| **Selector** | `Object3D` | Required | The object to download. Could be an array of objects |
| **Options** | `Options` | `undefined` | Description below |

## Options

| Name | Type | Default | Description |
| :-------------------------- | ---------------------- | ------------- | --------------------------------------------------------------------------- |
| **trs** | `bool` | `false` | Export position, rotation and scale instead of matrix per node |
| **onlyVisible** | `bool` | `true` | Export only visible objects |
| **binary** | `bool` | `false` | Export in binary (.glb) format, returning an ArrayBuffer |
| **maxTextureSize** | `number` | `Infinity` | Restricts the image maximum size (both width and height) to the given value |
| **animations** | `Array<AnimationClip>` | `undefined` | List of animations to be included in the export |
| **includeCustomExtensions** | `bool` | `Infinity` | Export custom glTF extensions defined on an object's |
| **fileName** | `string` | `Object name` | name of the generated fil |
72 changes: 72 additions & 0 deletions playground/src/pages/misc/GLTFExporterDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<script setup lang="ts">
import { TresCanvas, useRenderLoop } from '@tresjs/core'
import { CameraControls, useGLTFExporter } from '@tresjs/cientos'
import { MeshStandardMaterial, TorusGeometry } from 'three'
import { shallowRef } from 'vue'
const donutMaterial = new MeshStandardMaterial({})
const donutGeometry = new TorusGeometry(1, 0.5, 16, 32)
const boxRef = shallowRef()
const downloadScene = () => useGLTFExporter(boxRef.value.parent)
const downloadCube = () => useGLTFExporter(boxRef.value, { fileName: 'cube', binary: true })
const { onLoop } = useRenderLoop()
onLoop(({ elapsed }) => {
if (boxRef.value) {
boxRef.value.rotation.x = elapsed
boxRef.value.rotation.y = elapsed
}
})
</script>

<template>
<div>
<div class="downloads-buttons">
<button @click="downloadScene">
Download Scene
</button>
<button @click="downloadCube">
Download Cube
</button>
</div>
<TresCanvas
clear-color="#82DBC5"
window-size
>
<TresPerspectiveCamera :position="[0, 2, 5]" />
<TresMesh
v-for="i in 20"
:key="i"
:geometry="donutGeometry"
:material="donutMaterial"
:position="[0.5 + (i * (Math.random() - 0.5) * 5),
0.5 + (i * (Math.random() - 0.5) * 5),
0.5 + (i * (Math.random() - 0.5) * 5)]"
:scale="(i * 0.5) + 1"
/>
<TresMesh
ref="boxRef"
:position-z="30"
:scale="10"
>
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshStandardMaterial :color="0x00ff00" />
</TresMesh>
<CameraControls :distance="75" />
<TresDirectionalLight :position="[0, 10, 10]" />
</TresCanvas>
</div>
</template>

<style scoped>
.downloads-buttons {
position: absolute;
top: 0;
left: 0;
z-index: 100;
display: flex;
justify-content: space-evenly;
width: 100%;
}
</style>
5 changes: 5 additions & 0 deletions playground/src/router/routes/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ export const miscRoutes = [
name: 'BakeShadows',
component: () => import('../../pages/misc/BakeShadows.vue'),
},
{
path: '/misc/gltfExporter',
name: 'GLTFExporter',
component: () => import('../../pages/misc/GLTFExporterDemo.vue'),
},
]
3 changes: 2 additions & 1 deletion src/core/misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { Stats } from './Stats'
import { StatsGl } from './StatsGl'
import { BakeShadows } from './BakeShadows'
import Html from './html/HTML.vue'
import { useGLTFExporter } from './useGLTFExporter'

export { Html, Stats, StatsGl, BakeShadows }
export { Html, Stats, StatsGl, BakeShadows, useGLTFExporter }
58 changes: 58 additions & 0 deletions src/core/misc/useGLTFExporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { GLTFExporter } from 'three-stdlib'
import type { AnimationClip, Object3D } from 'three'
import { useLogger } from '@tresjs/core'

const { logError } = useLogger()

interface gltfExporterOptions {
fileName?: string
trs?: boolean
onlyVisible?: boolean
binary?: boolean
maxTextureSize?: number
animations?: AnimationClip[]
includeCustomExtensions?: boolean
}

export async function useGLTFExporter(
object3D: Object3D | Object3D[],
options?: gltfExporterOptions,
) {
const exporter = new GLTFExporter()
const name = options?.fileName || 'scene'

exporter.parse(
object3D,
(gltf) => {
if (gltf instanceof ArrayBuffer) {
saveArrayBuffer(gltf, `${name}.glb`)
}
else {
const output = JSON.stringify(gltf, null, 2)
saveString(output, `${name}.gltf`)
}
},
(error: any) => {
logError('An error happened while exporting the GLTF', error)
},
options,
)
}

function saveString(text: string, filename: string) {
save(new Blob([text], { type: 'text/plain' }), filename)
}

function saveArrayBuffer(buffer: any, filename: string) {
save(new Blob([buffer], { type: 'application/octet-stream' }), filename)
}

function save(blob: Blob, filename: string) {
const link = document.createElement('a')
link.style.display = 'none'
document.body.appendChild(link)
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
link.remove()
}

0 comments on commit 411d631

Please sign in to comment.