Explorar el Código

Merge pull request #24 from Tresjs/feature/20-docs-core-examples

feat(core): docs core examples
Alvaro Saburido hace 2 años
padre
commit
777451c944

+ 34 - 6
docs/.vitepress/config.ts

@@ -1,5 +1,6 @@
 import { defineConfig } from 'vitepress'
 import { version } from '../../packages/tres/package.json'
+import { version as cientosVersion } from '../../packages/cientos/package.json'
 
 export default defineConfig({
   title: 'TresJS',
@@ -27,13 +28,25 @@ export default defineConfig({
           },
         ],
       },
-      /*{
+      {
         text: 'Examples',
-      }, */
+        items: [
+          { text: 'Orbit Controls', link: '/examples/orbit-controls' },
+          { text: 'Basic Animation', link: '/examples/basic-animations' },
+          { text: 'Load Textures', link: '/examples/load-textures' },
+          { text: 'Load Models', link: '/examples/load-models' },
+        ],
+      },
       {
         text: 'Advanced',
 
-        items: [{ text: 'Extending', link: '/advanced/extending' }],
+        items: [
+          { text: 'Extending', link: '/advanced/extending' },
+          {
+            text: 'Caveats',
+            link: '/advanced/caveats',
+          },
+        ],
       },
     ],
     nav: [
@@ -41,11 +54,26 @@ export default defineConfig({
       { text: 'API', link: '/api/' },
       { text: 'Config', link: '/config/' },
       {
-        text: `v${version}`,
+        text: 'Ecosystem',
+        activeMatch: `^/ecosystem/`,
         items: [
           {
-            text: 'Release Notes ',
-            link: 'https://github.com/Tresjs/tres/releases',
+            text: `Core v${version}`,
+            items: [
+              {
+                text: 'Release Notes ',
+                link: `https://github.com/Tresjs/tres/releases/tag/%40tresjs%2Fcore%40${version}`,
+              },
+            ],
+          },
+          {
+            text: `Cientos v${cientosVersion}`,
+            items: [
+              {
+                text: 'Release Notes ',
+                link: `https://github.com/Tresjs/tres/releases/tag/%40tresjs%2Fcientos%40${cientosVersion}`,
+              },
+            ],
           },
         ],
       },

+ 2 - 1
docs/.vitepress/theme/config.css

@@ -39,6 +39,7 @@
   --vp-button-sponsor-active-bg: transparent;
 }
 
-a {
+.vp-doc a {
   text-decoration: dashed;
+  font-weight: bold;
 }

+ 6 - 2
docs/.vitepress/theme/index.ts

@@ -11,8 +11,12 @@ export default {
     DefaultTheme.enhanceApp(ctx)
     ctx.app.component('FirstScene', FirstScene)
     ctx.app.component('StackBlitzEmbed', StackBlitzEmbed)
-    ctx.app.use(Tres)
-
+    /*  ctx.app.use(Tres)
+     */
+    if (import.meta.env.SSR) {
+      // ... server only logic
+      ctx.app.use(Tres)
+    }
     console.log(ctx)
   },
 }

+ 101 - 0
docs/advanced/caveats.md

@@ -0,0 +1,101 @@
+# Caveats 😱
+
+Our aim is to provide a simple way of using ThreeJS in VueJS with the best developer experience possible. However, there are some caveats that you should be aware of.
+
+## HMR and ThreeJS
+
+Hot module replacement (HMR) is a feature that allows you to update your code without reloading the page. This is a great feature that makes development much faster. **TresJS** uses [Vite](https://vitejs.dev/). However, is really tricky to make it work correctly with ThreeJS.
+
+Why? Because Tres builds the scene in a declarative way. This means that it creates the instance and add it to the scene when the component is mounted. The complexity comes to know when to remove the instance from the scene and when to add it again.
+
+Although a minimal disposal workflow is implemented, it is not perfect. This means that sometimes you will have to reload the page to see the changes correctly, specially when you are referencing an instances using [Template Refs](https://v3.vuejs.org/guide/component-template-refs.html)
+
+```vue
+<script setup lang="ts">
+const boxRef: Ref<TresInstance | null> = ref(null)
+
+onLoop(({ _delta, elapsed }) => {
+  if (boxRef.value) {
+    boxRef.value.rotation.y += 0.01
+    boxRef.value.rotation.z = elapsed * 0.2
+  }
+})
+</script>
+
+<template>
+  <TresMesh ref="boxRef" :scale="1" cast-shadow>
+    <TresBoxGeometry :args="[1, 1, 1]" />
+    <TresMeshStandardMaterial color="teal" />
+  </TresMesh>
+</template>
+```
+
+If you make a change on the `color` of the `TresMeshStandardMaterial` component, you will see that the change is applied but the rotation is not working anymore. This is because the instance is disposed and created again.
+
+:::tip
+So as **rule of thumb** you should reload the page whenever you don't see the changes you made.
+:::
+
+That being said we are working on a better solution for this 😁. If you have any idea how to solve this, please let us know.
+
+You can follow the discussion in [HMR Disposal Discussion](https://github.com/Tresjs/tres/issues/23)
+
+## Reactivity
+
+We all love reactivity 💚. It is one of the most powerful features of VueJS. However, we need to be mindful of it when using ThreeJS.
+
+Vue reactivity is based on [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). This allows Vue 3 to automatically track changes to data objects and update the corresponding DOM elements whenever the data changes.
+
+Since we are rendering an scene and updating it in every frame (60FPS), that means that we are updating the scene 60 times per second. If the object to be updated is reactive, Vue will try to update the that objectthat many times. This is not a good idea 😅 and will be detrimental for performance.
+
+Here is a benchmark of the difference between using a Proxy object and a plain object.
+
+<figure>
+  <img src="/proxy-benchmark.png" alt="Proxy vs Plain" style="width:100%">
+  <figcaption>Fig.1 - Executions per second Plan Object vs Proxy. </figcaption>
+</figure>
+
+Source: [Proxy vs Plain Object](https://www.measurethat.net/Benchmarks/Show/12503/0/object-vs-proxy-vs-proxy-setter)
+
+If you are forced to use reactivity, use [shallowRef](https://vuejs.org/api/reactivity-advanced.html#shallowref)
+
+Unlike `ref()`, the inner value of a shallow ref is stored and exposed as-is, and will not be made deeply reactive. Only the .value access is reactive. Source [VueJS Docs](https://vuejs.org/api/reactivity-advanced.html#shallowref)
+
+### Example
+
+❌ Incorrect
+
+```vue
+<script setup lang="ts">
+const position = reactive({ x: 0, y: 0, z: 0 })
+
+onLoop(({ _delta, elapsed }) => {
+  position.x = Math.sin(elapsed * 0.1) * 3
+})
+</script>
+<template>
+  <TresMesh :position="position" cast-shadow>
+    <TresBoxGeometry :args="[1, 1, 1]" />
+    <TresMeshStandardMaterial color="teal" />
+  </TresMesh>
+</template>
+```
+
+✅ Correct
+
+```vue
+<script setup lang="ts">
+const position = { x: 0, y: 0, z: 0 }
+const boxRef: ShallowRef<TresInstance | null> = shallowRef(null)
+
+onLoop(({ _delta, elapsed }) => {
+  boxRef.value.position.x = Math.sin(elapsed * 0.1) * 3
+})
+</script>
+<template>
+  <TresMesh ref="boxRef" :position="position" cast-shadow>
+    <TresBoxGeometry :args="[1, 1, 1]" />
+    <TresMeshStandardMaterial color="teal" />
+  </TresMesh>
+</template>
+```

+ 56 - 0
docs/examples/basic-animations.md

@@ -0,0 +1,56 @@
+# Basic Animations
+
+This guide will help you get started with basic animations in TresJS.
+
+We will build a simple scene with a cube. We will then animate the cube to rotate around the Y and Z axis.
+
+<StackBlitzEmbed projectId="tresjs-basic-animations" />
+
+## useRenderLoop
+
+The `useRenderLoop` composable is the core of TresJS animations. It allows you to register a callback that will be called every time the renderer updates the scene with the browser's refresh rate.
+
+To see a detailed explanation of how it works, please refer to the **useRenderLoop** documentation.
+
+```ts
+const { onLoop, resume } = useRenderLoop()
+
+resume()
+onLoop(({ _delta, elapsed }) => {
+  // I will run at every frame ~ 60FPS (depending of your monitor)
+})
+```
+
+## Getting the reference to the cube
+
+To animate the cube, we need to get a reference to it. We can do it by passing a [Template Ref](https://vuejs.org/guide/essentials/template-refs.html) using `ref` prop to the `TresMesh` component. This will return the THREE instance.
+
+To improve the performance, we will use a [Shallow Ref](https://v3.vuejs.org/guide/reactivity-fundamentals.html#shallow-reactivity) to store the reference instead of a regular Ref. See why [here](../advanced/caveats.md#reactivity)
+
+```vue
+<script setup lang="ts">
+const boxRef: ShallowRef<TresInstance | null> = shallowRef(null)
+</script>
+
+<template>
+  <TresMesh ref="boxRef" :scale="1" cast-shadow>
+    <TresBoxGeometry :args="[1, 1, 1]" />
+    <TresMeshStandardMaterial v-bind="pbrTexture" />
+  </TresMesh>
+</template>
+```
+
+## Animating the cube
+
+Now that we have a reference to the cube, we can animate it. We will use the `onLoop` callback to update the cube's rotation.
+
+```ts
+onLoop(({ _delta, elapsed }) => {
+  if (boxRef.value) {
+    boxRef.value.rotation.y += 0.01
+    boxRef.value.rotation.z = elapsed * 0.2
+  }
+})
+```
+
+You can also use the `delta` from the internal [THREE clock](https://threejs.org/docs/?q=clock#api/en/core/Clock) or the `elapsed` to animate the cube.

+ 82 - 0
docs/examples/load-models.md

@@ -0,0 +1,82 @@
+# Load Models
+
+> All models used in this guide are from [Alvaro Saburido](https://sketchfab.com/3d-models/aku-aku-7dfcb6edf10b4098bbb965c56fd3055c).
+
+3D models are available in hundreds of file formats, each with different purposes, assorted features, and varying complexity.
+
+For this guide we are going to focus on loading glTF (GL Transmission Format) models, which are the most common format for 3D models on the web.
+
+<StackBlitzEmbed projectId="tresjs-gltf-load-model" />
+
+There are several ways to load models on TresJS:
+
+## Using `useLoader`
+
+The `useLoader` composable allows you to pass any type of Three.js loader and a URL to load the resource from. It returns a `Promise` with the loaded resource.
+
+For a detailed explanation of how to use `useLoader`, check out the **useLoader** documentation.
+
+```ts
+import { useLoader } from '@tresjs/core'
+import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
+
+const { scene } = await useLoader(GLTFLoader, '/models/AkuAku.gltf')
+```
+
+Then you can pass the model scene to a `TresMesh` component:
+
+```html{4}
+<Suspense>
+  <TresCanvas>
+    <TresScene>
+      <TresMesh v-bind="scene" />
+    </TresScene>
+  </TresCanvas>
+</Suspense>
+```
+
+Notice in the example above that we are using the `Suspense` component to wrap the `TresCanvas` component. This is because `useLoader` returns a `Promise` and we need to wait for it to resolve before rendering the scene.
+
+## Using `useGLTF`
+
+A more convenient way of loading models is using the `useGLTF` composable available from [@tresjs/cientos](https://github.com/Tresjs/tres/tree/main/packages/cientos) package.
+
+To learn more about `useGLTF`, check out the **useGLTF** documentation.
+
+```ts
+import { useGLTF } from '@tresjs/cientos'
+
+const { scene } = await useGLTF('/models/AkuAku.gltf')
+```
+
+An advantage of using `useGLTF`is that you can pass a `draco` prop to enable [Draco compression](https://threejs.org/docs/index.html?q=drac#examples/en/loaders/DRACOLoader) for the model. This will reduce the size of the model and improve performance.
+
+```ts
+import { useGLTF } from '@tresjs/cientos'
+
+const { scene } = await useGLTF('/models/AkuAku.gltf', { draco: true })
+```
+
+## Using `GLTFModel`
+
+The `GLTFModel` component is a wrapper around `useGLTF` that's available from [@tresjs/cientos](https://github.com/Tresjs/tres/tree/main/packages/cientos) package.
+
+```vue{2,10}
+<script setup lang="ts">
+import { OrbitControls, GLTFModel } from '@tresjs/cientos'
+</script>
+<template>
+  <Suspense>
+    <TresCanvas clear-color="#82DBC5" shadows alpha>
+      <TresPerspectiveCamera :position="[11, 11, 11]" />
+      <OrbitControls />
+      <TresScene>
+        <GLTFModel path="/models/AkuAku.gltf" draco />
+        <TresDirectionalLight :position="[-4, 8, 4]" :intensity="1.5" cast-shadow />
+      </TresScene>
+    </TresCanvas>
+  </Suspense>
+</template>
+```
+
+This particular approach is more straightforward but gives you less control over the model.

+ 78 - 0
docs/examples/load-textures.md

@@ -0,0 +1,78 @@
+# Load Textures
+
+> All textures used in this example are from [ambientcg](https://ambientcg.com/).
+
+3D textures are textures that contain multiple layers of image data, allowing them to represent volumetric data or simulate three-dimensional structures. They are often used in 3D graphics and visual effects to add realism and complexity to scenes and objects.
+
+<StackBlitzEmbed projectId="tresjs-load-textures" />
+
+There are two ways of loading 3D textures in TresJS:
+
+## Using `useLoader`
+
+The `useLoader` composable allows you to pass any type of Three.js loader and a URL to load the resource from. It returns a `Promise` with the loaded resource.
+
+For a detailed explanation of how to use `useLoader`, check out the **useLoader** documentation.
+
+```ts
+import { useLoader } from '@tresjs/core'
+import { TextureLoader } from 'three/examples/jsm/loaders/TextureLoader'
+
+const texture = useLoader(TextureLoader, '/Rock035_2K_Color.jpg')
+```
+
+Then you can pass the texture to a material:
+
+```html
+<Suspense>
+  <TresCanvas>
+    <TresScene>
+      <TresMesh>
+        <TresSphereGeometry :args="[1,32,32]" />
+        <TresMeshStandardMaterial :map="texture" />
+      </TresMesh>
+    </TresScene>
+  </TresCanvas>
+</Suspense>
+```
+
+Notice in the example above that we are using the `Suspense` component to wrap the `TresCanvas` component. This is because `useLoader` returns a `Promise` and we need to wait for it to resolve before rendering the scene.
+
+## Using `useTexture`
+
+A more convenient way of loading textures is using the `useTexture` composable. It accepts both an array of URLs or a single object with the texture paths mapped.
+
+To learn more about `useTexture`, check out the **useTexture** documentation.
+
+```ts
+import { useTexture } from '@tresjs/core'
+
+const pbrTexture = await useTexture({
+  map: '/textures/black-rock/Rock035_2K_Displacement.jpg',
+  displacementMap: '/textures/black-rock/Rock035_2K_Displacement.jpg',
+  roughnessMap: '/textures/black-rock/Rock035_2K_Roughness.jpg',
+  normalMap: '/textures/black-rock/Rock035_2K_NormalDX.jpg',
+  ambientOcclusion: '/textures/black-rock/Rock035_2K_AmbientOcclusion.jpg',
+})
+```
+
+Similar to the previous example, we can pass all the textures to a material via props:
+
+```html
+<Suspense>
+  <TresCanvas>
+    <TresScene>
+      <TresMesh>
+        <TresSphereGeometry :args="[1,32,32]" />
+        <TresMeshStandardMaterial
+          :map="pbrTexture.map"
+          :displacementMap="pbrTexture.displacementMap"
+          :roughnessMap="pbrTexture.roughnessMap"
+          :normalMap="pbrTexture.normalMap"
+          :ambientOcclusionMap="pbrTexture.ambientOcclusionMap"
+        />
+      </TresMesh>
+    </TresScene>
+  </TresCanvas>
+</Suspense>
+```

+ 93 - 0
docs/examples/orbit-controls.md

@@ -0,0 +1,93 @@
+# OrbitControls
+
+<StackBlitzEmbed projectId="tresjs-orbit-controls" />
+
+[OrbitControls](https://threejs.org/docs/index.html?q=orbit#examples/en/controls/OrbitControls) is a camera controller that allows you to orbit around a target. It's a great way to explore your scene.
+
+However, it is not part of the core of ThreeJS. So to use it you would need to import it from the `three/examples/jsm/controls/OrbitControls` module.
+
+This creates a problem because **TresJS** automatically creates a catalog of the core of Three so you can use them as components.
+
+Fortunately, **TresJS** provides a way to extend the catalog of components. You can do it by using the `extend` method using the **useCatalogue** composable.
+
+For more information about extending your TresJS catalog, refer to the [extending](/advanced/extending.md) section.
+
+## Using OrbitControls
+
+To use `OrbitControls` you need to import it from the `three/examples/jsm/controls/OrbitControls` module.
+
+```js
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
+```
+
+Then you need to extend the catalogue of components using the `extend` method of the **useCatalogue** composable.
+
+```js
+import { useCatalogue } from '@tresjs/core'
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
+
+const { extend } = useCatalogue()
+
+extend({ OrbitControls })
+```
+
+Now you can use the `TresOrbitControls` component in your scene.
+
+```vue
+<template>
+  <TresCanvas shadows alpha>
+    <TresScene>
+      <TresOrbitControls v-if="state.renderer" :args="[state.camera, state.renderer?.domElement]" />
+    </TresScene>
+  </TresCanvas>
+</template>
+```
+
+Since [OrbitControls](https://threejs.org/docs/index.html?q=orbit#examples/en/controls/OrbitControls) needs a reference to the camera and the renderer, you need to pass them as arguments.
+
+You can use the **useThree** composable to get the camera and the renderer.
+
+```ts
+import { useThree } from '@tresjs/core'
+
+const { state } = useTres()
+```
+
+So the final code would be something like this:
+
+```vue
+<script setup lang="ts">
+import { useCatalogue } from '@tresjs/core'
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
+
+const { extend } = useCatalogue()
+extend({ OrbitControls })
+
+const { state } = useThree()
+</script>
+<template>
+  <TresCanvas shadows alpha>
+    <TresScene>
+      <TresOrbitControls v-if="state.renderer" :args="[state.camera, state.renderer?.domElement]" />
+      ...
+    </TresScene>
+  </TresCanvas>
+</template>
+```
+
+## OrbitControls from `cientos`
+
+Here is where the fancy part begins. ✨  
+The `cientos` package provides a component called `<OrbitControls />` that is a wrapper of the `OrbitControls` from the [`three-stdlib`](https://github.com/pmndrs/three-stdlib) module.
+
+The nicest part? You don't need to extend the catalog or pass any arguments.  
+It just works. 💯
+
+```vue
+<template>
+  <TresCanvas shadows alpha>
+    <OrbitControls />
+    <TresScene> ... </TresScene>
+  </TresCanvas>
+</template>
+```

+ 9 - 2
docs/guide/index.md

@@ -1,7 +1,8 @@
 # Introduction
 
-<FirstScene style="aspect-ratio: 16/9; height: auto; margin: 2rem 0; border-radius: 8px; overflow:hidden;"/>
-
+<ClientOnly>
+    <FirstScene style="aspect-ratio: 16/9; height: auto; margin: 2rem 0; border-radius: 8px; overflow:hidden;"/>
+</ClientOnly>
 ```
 npm install three @tresjs/core -D
 ```
@@ -18,6 +19,12 @@ pnpm users
 pnpm add three @tresjs/core -D
 ```
 
+## Try it online
+
+You can fork this template example on [StackBlitz](https://stackblitz.com/edit/tresjs-basic?file=src/App.vue) and play with it 😋 without installing anything locally.
+
+<StackBlitzEmbed projectId="tresjs-basic" />
+
 ## Motivation
 
 [ThreeJS](https://threejs.org/) is a wonderfull library to create awesome **WebGL** 3D websites. Is also a constantly updated library that makes hard for wrapper mantainers like [TroisJS](https://troisjs.github.io/) to keep up with all the enhancements.

BIN
docs/public/proxy-benchmark.png


+ 5 - 0
netlify.toml

@@ -0,0 +1,5 @@
+[build.environment]
+  NODE_VERSION = "16"
+[build]
+  publish = "docs/.vitepress/dist"
+  command = "pnpm docs:build"

+ 1 - 0
packages/tres/src/examples/models/gltf/GLTFModel.story.vue

@@ -9,6 +9,7 @@ import { OrbitControls, GLTFModel } from '@tresjs/cientos'
           <TresPerspectiveCamera :position="[11, 11, 11]" />
           <OrbitControls />
           <TresScene>
+            <TresAmbientLight :intensity="0.5" />
             <GLTFModel path="/models/AkuAku.gltf" />
             <TresDirectionalLight :position="[-4, 8, 4]" :intensity="1.5" cast-shadow />
           </TresScene>