diff --git a/docs/examples/text-3d.md b/docs/examples/text-3d.md
index 422c53d1c..0f806c789 100644
--- a/docs/examples/text-3d.md
+++ b/docs/examples/text-3d.md
@@ -95,7 +95,7 @@ So the final code would be something like this:
-
-
-
+
diff --git a/packages/tres/src/components/Shapes.vue b/packages/tres/src/components/Shapes.vue
deleted file mode 100644
index dedeb6511..000000000
--- a/packages/tres/src/components/Shapes.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/tres/src/components/TestSphere.vue b/packages/tres/src/components/TestSphere.vue
deleted file mode 100644
index 327de11d8..000000000
--- a/packages/tres/src/components/TestSphere.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
diff --git a/packages/tres/src/components/TresCanvas.ts b/packages/tres/src/components/TresCanvas.ts
new file mode 100644
index 000000000..ee2db2d69
--- /dev/null
+++ b/packages/tres/src/components/TresCanvas.ts
@@ -0,0 +1,125 @@
+import { defineComponent, h, PropType, ref, watch } from 'vue'
+import * as THREE from 'three'
+import { ShadowMapType, TextureEncoding, ToneMapping } from 'three'
+import { createTres } from '/@/core/renderer'
+import { useLogger } from '/@/composables'
+import { useCamera, useRenderer, useRenderLoop, useRaycaster, useTres } from '/@/composables'
+import { TresObject } from '../types'
+import { extend } from '../core/catalogue'
+import { RendererPresetsType } from '../composables/useRenderer/const'
+
+export interface TresCanvasProps {
+ shadows?: boolean
+ shadowMapType?: ShadowMapType
+ physicallyCorrectLights?: boolean
+ useLegacyLights?: boolean
+ outputEncoding?: TextureEncoding
+ toneMapping?: ToneMapping
+ toneMappingExposure?: number
+ context?: WebGLRenderingContext
+ powerPreference?: 'high-performance' | 'low-power' | 'default'
+ preserveDrawingBuffer?: boolean
+ clearColor?: string
+ windowSize?: boolean
+ preset?: RendererPresetsType
+}
+/**
+ * Vue component for rendering a Tres component.
+ */
+
+const { logWarning } = useLogger()
+
+export const TresCanvas = defineComponent({
+ name: 'TresCanvas',
+ props: [
+ 'shadows',
+ 'shadowMapType',
+ 'physicallyCorrectLights',
+ 'useLegacyLights',
+ 'outputEncoding',
+ 'toneMapping',
+ 'toneMappingExposure',
+ 'context',
+ 'powerPreference',
+ 'preserveDrawingBuffer',
+ 'clearColor',
+ 'windowSize',
+ 'preset',
+ ] as unknown as undefined,
+ setup(props, { slots, expose }) {
+ if (props.physicallyCorrectLights === true) {
+ logWarning('physicallyCorrectLights is deprecated, useLegacyLights is now false by default')
+ }
+ const container = ref()
+ const canvas = ref()
+ const scene = new THREE.Scene()
+
+ watch(canvas, () => {
+ const { renderer } = useRenderer(canvas, container, props)
+ const { activeCamera } = useCamera()
+
+ const { onLoop } = useRenderLoop()
+
+ const { raycaster, pointer } = useRaycaster()
+
+ onLoop(() => {
+ if (!activeCamera.value) return
+
+ raycaster.value.setFromCamera(pointer.value, activeCamera.value)
+ renderer.value?.render(scene, activeCamera.value)
+ })
+ })
+
+ const app = createTres(slots)
+ app.provide('useTres', useTres())
+ app.provide('extend', extend)
+ app.mount(scene as unknown as TresObject)
+ expose({
+ scene,
+ })
+
+ return () => {
+ return h(
+ h(
+ 'div',
+ {
+ ref: container,
+ style: {
+ position: 'relative',
+ width: '100%',
+ height: '100%',
+ pointerEvents: 'auto',
+ touchAction: 'none',
+ },
+ },
+ [
+ h(
+ 'div',
+ {
+ style: {
+ width: '100%',
+ height: '100%',
+ },
+ },
+ [
+ h('canvas', {
+ ref: canvas,
+ style: {
+ display: 'block',
+ width: '100%',
+ height: '100%',
+ position: props.windowSize ? 'fixed' : 'absolute',
+ top: 0,
+ left: 0,
+ },
+ }),
+ ],
+ ),
+ ],
+ ),
+ )
+ }
+ },
+})
+
+export default TresCanvas
diff --git a/packages/tres/src/composables/index.ts b/packages/tres/src/composables/index.ts
index 93a88d834..c6c2d8ffa 100644
--- a/packages/tres/src/composables/index.ts
+++ b/packages/tres/src/composables/index.ts
@@ -1 +1,8 @@
+export * from './useCamera'
+export * from './useRenderLoop/'
+export * from './useRenderer/'
+export * from './useLoader'
+export * from './useTexture'
+export * from './useTres'
+export * from './useRaycaster'
export * from './useLogger'
diff --git a/packages/tres/src/core/useCamera/index.ts b/packages/tres/src/composables/useCamera/index.ts
similarity index 94%
rename from packages/tres/src/core/useCamera/index.ts
rename to packages/tres/src/composables/useCamera/index.ts
index 2c18232e5..fe1238fff 100644
--- a/packages/tres/src/core/useCamera/index.ts
+++ b/packages/tres/src/composables/useCamera/index.ts
@@ -1,7 +1,7 @@
-import { useTres } from '/@/core/'
+import { useTres } from '/@/composables/'
import { PerspectiveCamera, OrthographicCamera } from 'three'
-import { toRef, watch, Ref, inject } from 'vue'
+import { toRef, Ref, watchEffect } from 'vue'
export enum CameraType {
Perspective = 'Perspective',
@@ -104,8 +104,8 @@ let camera: Camera
* @return {*} {UseCameraReturn}
*/
export function useCamera(): UseCameraReturn {
- const { state, setState } = useTres()
- const aspectRatio = inject('aspect-ratio')
+ const { state, setState, aspectRatio } = useTres()
+ /* const aspectRatio = inject('aspect-ratio') */
/**
* Create camera and push to Tres `state.cameras` array
*
@@ -182,9 +182,12 @@ export function useCamera(): UseCameraReturn {
function clearCameras() {
state.cameras = []
}
- if (aspectRatio) {
- watch(aspectRatio, updateCamera)
- }
+
+ watchEffect(() => {
+ if (aspectRatio?.value) {
+ updateCamera()
+ }
+ })
return {
activeCamera: toRef(state, 'camera'),
diff --git a/packages/tres/src/core/useCamera/useCamera.test.ts b/packages/tres/src/composables/useCamera/useCamera.test.ts
similarity index 100%
rename from packages/tres/src/core/useCamera/useCamera.test.ts
rename to packages/tres/src/composables/useCamera/useCamera.test.ts
diff --git a/packages/tres/src/core/useLoader/index.ts b/packages/tres/src/composables/useLoader/index.ts
similarity index 100%
rename from packages/tres/src/core/useLoader/index.ts
rename to packages/tres/src/composables/useLoader/index.ts
diff --git a/packages/tres/src/core/useLoader/useLoader.test.ts b/packages/tres/src/composables/useLoader/useLoader.test.ts
similarity index 100%
rename from packages/tres/src/core/useLoader/useLoader.test.ts
rename to packages/tres/src/composables/useLoader/useLoader.test.ts
diff --git a/packages/tres/src/core/useRaycaster/index.ts b/packages/tres/src/composables/useRaycaster/index.ts
similarity index 84%
rename from packages/tres/src/core/useRaycaster/index.ts
rename to packages/tres/src/composables/useRaycaster/index.ts
index d3d7f31c9..05a146832 100644
--- a/packages/tres/src/core/useRaycaster/index.ts
+++ b/packages/tres/src/composables/useRaycaster/index.ts
@@ -1,6 +1,6 @@
import { Raycaster, Vector2 } from 'three'
-import { onUnmounted, provide, Ref, ref, ShallowRef, shallowRef } from 'vue'
-import { useTres } from '/@/core'
+import { Ref, ref, ShallowRef, shallowRef } from 'vue'
+import { useTres } from '/@/composables'
const raycaster = shallowRef(new Raycaster())
const pointer = ref(new Vector2())
@@ -42,10 +42,6 @@ export function useRaycaster(): UseRaycasterReturn {
setState('pointer', pointer)
setState('currentInstance', currentInstance)
- provide('raycaster', raycaster)
- provide('pointer', pointer)
- provide('currentInstance', currentInstance)
-
function onPointerMove(event: MouseEvent) {
pointer.value.x = (event.clientX / window.innerWidth) * 2 - 1
pointer.value.y = -(event.clientY / window.innerHeight) * 2 + 1
@@ -53,9 +49,9 @@ export function useRaycaster(): UseRaycasterReturn {
window.addEventListener('pointermove', onPointerMove)
- onUnmounted(() => {
+ /* onUnmounted(() => {
window.removeEventListener('pointermove', onPointerMove)
- })
+ }) */
return {
raycaster,
pointer,
diff --git a/packages/tres/src/core/useRaycaster/useRaycaster.test.ts b/packages/tres/src/composables/useRaycaster/useRaycaster.test.ts
similarity index 100%
rename from packages/tres/src/core/useRaycaster/useRaycaster.test.ts
rename to packages/tres/src/composables/useRaycaster/useRaycaster.test.ts
diff --git a/packages/tres/src/core/useRenderLoop/index.ts b/packages/tres/src/composables/useRenderLoop/index.ts
similarity index 100%
rename from packages/tres/src/core/useRenderLoop/index.ts
rename to packages/tres/src/composables/useRenderLoop/index.ts
diff --git a/packages/tres/src/core/useRenderer/const.ts b/packages/tres/src/composables/useRenderer/const.ts
similarity index 100%
rename from packages/tres/src/core/useRenderer/const.ts
rename to packages/tres/src/composables/useRenderer/const.ts
diff --git a/packages/tres/src/core/useRenderer/index.ts b/packages/tres/src/composables/useRenderer/index.ts
similarity index 99%
rename from packages/tres/src/core/useRenderer/index.ts
rename to packages/tres/src/composables/useRenderer/index.ts
index a2e5b28a7..8d463ee05 100644
--- a/packages/tres/src/core/useRenderer/index.ts
+++ b/packages/tres/src/composables/useRenderer/index.ts
@@ -19,7 +19,7 @@ import {
Clock,
} from 'three'
import type { TextureEncoding, ToneMapping } from 'three'
-import { useRenderLoop, useTres } from '/@/core/'
+import { useRenderLoop, useTres } from '/@/composables/'
import { normalizeColor } from '/@/utils/normalize'
import { TresColor } from '/@/types'
import { rendererPresets, RendererPresetsType } from './const'
diff --git a/packages/tres/src/core/useTexture/index.ts b/packages/tres/src/composables/useTexture/index.ts
similarity index 100%
rename from packages/tres/src/core/useTexture/index.ts
rename to packages/tres/src/composables/useTexture/index.ts
diff --git a/packages/tres/src/core/useTexture/useTexture.test.ts b/packages/tres/src/composables/useTexture/useTexture.test.ts
similarity index 100%
rename from packages/tres/src/core/useTexture/useTexture.test.ts
rename to packages/tres/src/composables/useTexture/useTexture.test.ts
diff --git a/packages/tres/src/core/useTres/index.ts b/packages/tres/src/composables/useTres/index.ts
similarity index 98%
rename from packages/tres/src/core/useTres/index.ts
rename to packages/tres/src/composables/useTres/index.ts
index 22dcee14e..2fc05b117 100644
--- a/packages/tres/src/core/useTres/index.ts
+++ b/packages/tres/src/composables/useTres/index.ts
@@ -1,6 +1,6 @@
import { Clock, EventDispatcher, Raycaster, Scene, Vector2, WebGLRenderer } from 'three'
import { computed, ComputedRef, shallowReactive, toRefs } from 'vue'
-import { Camera } from '/@/core'
+import { Camera } from '/@/composables'
export interface TresState {
/**
diff --git a/packages/tres/src/core/useTres/useTres.test.ts b/packages/tres/src/composables/useTres/useTres.test.ts
similarity index 100%
rename from packages/tres/src/core/useTres/useTres.test.ts
rename to packages/tres/src/composables/useTres/useTres.test.ts
diff --git a/packages/tres/src/core/catalogue.test.ts b/packages/tres/src/core/catalogue.test.ts
new file mode 100644
index 000000000..242de926d
--- /dev/null
+++ b/packages/tres/src/core/catalogue.test.ts
@@ -0,0 +1,15 @@
+import { useTres } from '.'
+import { catalogue, extend } from './catalogue'
+import * as THREE from 'three'
+
+describe('catalog', () => {
+ it('should return a autogenerated uuid', () => {
+ expect(catalogue.value.uuid).toBeDefined()
+ })
+ it('should return a catalog of objects when extended', () => {
+ extend(THREE)
+
+ expect(catalogue.value).toHaveProperty('Mesh')
+ expect(catalogue.value).toHaveProperty('MeshBasicMaterial')
+ })
+})
diff --git a/packages/tres/src/core/catalogue.ts b/packages/tres/src/core/catalogue.ts
new file mode 100644
index 000000000..bc62f45d1
--- /dev/null
+++ b/packages/tres/src/core/catalogue.ts
@@ -0,0 +1,9 @@
+import { MathUtils } from 'three'
+import { Ref, ref } from 'vue'
+import { TresCatalogue } from '../types'
+
+export const catalogue: Ref = ref({ uuid: MathUtils.generateUUID() })
+
+export const extend = (objects: any) => void Object.assign(catalogue.value, objects)
+
+export default { catalogue, extend }
diff --git a/packages/tres/src/core/index.ts b/packages/tres/src/core/index.ts
deleted file mode 100644
index c2df1a6ba..000000000
--- a/packages/tres/src/core/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export * from './useCamera'
-export * from './useCatalogue'
-export * from './useInstanceCreator'
-export * from './useRenderLoop/'
-export * from './useRenderer/'
-export * from './useScene/'
-export * from './useLoader'
-export * from './useTexture'
-export * from './useTres'
-export * from './useRaycaster'
diff --git a/packages/tres/src/core/nodeOps.ts b/packages/tres/src/core/nodeOps.ts
new file mode 100644
index 000000000..45ef51c0d
--- /dev/null
+++ b/packages/tres/src/core/nodeOps.ts
@@ -0,0 +1,175 @@
+import { Mesh } from 'three'
+import { useCamera, useRaycaster, useRenderLoop, useLogger } from '/@/composables'
+import { RendererOptions } from 'vue'
+import { catalogue } from './catalogue'
+import { isFunction, useEventListener } from '@vueuse/core'
+import { TresEvent, TresObject } from '../types'
+import { isHTMLTag, kebabToCamel } from '../utils'
+
+const { logWarning } = useLogger()
+
+function hasEvents(obj: any) {
+ for (const prop in obj) {
+ if (prop.indexOf('on') === 0) {
+ return true
+ }
+ }
+ return false
+}
+
+function noop(fn: string): any {
+ fn
+}
+
+let scene: TresObject | null = null
+
+export const nodeOps: RendererOptions = {
+ createElement(tag, _isSVG, _anchor, props) {
+ if (tag === 'template') return null
+ if (isHTMLTag(tag)) return null
+ let instance
+
+ if (props === null) props = {}
+
+ if (props?.args) {
+ instance = new catalogue.value[tag.replace('Tres', '')](...props.args)
+ } else {
+ instance = new catalogue.value[tag.replace('Tres', '')]()
+ }
+
+ if (instance.isCamera) {
+ // Let users know that camera is in the center of the scene
+ if (!props?.position || props?.position.every((v: number) => v == 0)) {
+ logWarning(
+ // eslint-disable-next-line max-len
+ 'Camera is positioned at the center of the scene [0,0,0], if this is not intentional try setting a position if your scene seems empty 🤗',
+ )
+ }
+ const { pushCamera } = useCamera()
+ pushCamera(instance)
+ }
+
+ if (props?.attach === undefined) {
+ if (instance.isMaterial) instance.attach = 'material'
+ else if (instance.isBufferGeometry) instance.attach = 'geometry'
+ }
+
+ return instance
+ },
+ insert(child, parent, anchor) {
+ if (scene === null && parent.isScene) scene = parent
+ if (parent === null) parent = scene as TresObject
+ //vue core
+ /* parent.insertBefore(child, anchor || null) */
+ if (parent?.isObject3D && child?.isObject3D) {
+ const index = anchor ? parent.children.indexOf(anchor) : 0
+ child.parent = parent
+ parent.children.splice(index, 0, child)
+ child.dispatchEvent({ type: 'added' })
+ } else if (typeof child?.attach === 'string') {
+ child.__previousAttach = child[parent?.attach]
+ if (parent) {
+ parent[child.attach] = child
+ }
+ }
+
+ const { onLoop } = useRenderLoop()
+
+ // RayCasting
+ let prevInstance: TresEvent | null = null
+ let currentInstance: TresEvent | null = null
+
+ const { raycaster } = useRaycaster()
+ if (child && child instanceof Mesh && hasEvents(child)) {
+ onLoop(() => {
+ if (parent?.children && child && raycaster) {
+ const intersects = raycaster.value.intersectObjects(parent.children)
+
+ if (intersects.length > 0 && intersects[0].object.uuid === child.uuid) {
+ currentInstance = intersects[0]
+
+ if (prevInstance === null || prevInstance.object.uuid !== currentInstance?.object.uuid) {
+ child.onPointerEnter?.(currentInstance)
+ }
+
+ child.onPointerMove?.(currentInstance)
+ } else {
+ currentInstance = null
+ if (prevInstance !== null) {
+ child.onPointerLeave?.(prevInstance)
+ }
+ }
+
+ prevInstance = currentInstance
+ }
+ })
+
+ useEventListener(window, 'click', () => {
+ if (currentInstance === null) return
+ child.onClick?.(currentInstance)
+ })
+ }
+ },
+ remove(node) {
+ if (!node) return
+ const parent = node.parentNode
+ if (parent) {
+ parent.removeChild(node)
+ }
+ },
+ patchProp(node, prop, _prevValue, nextValue) {
+ if (node) {
+ /* if (node.isCamera && prop === 'look-at') {
+ debugger
+ } */
+ let root = node
+ let key = prop
+ const camelKey = kebabToCamel(key)
+ let target = root?.[camelKey]
+
+ if (!node.parent) {
+ node.parent = scene as TresObject
+ }
+
+ // Traverse pierced props (e.g. foo-bar=value => foo.bar = value)
+ if (key.includes('-') && target === undefined) {
+ const chain = key.split('-')
+ target = chain.reduce((acc, key) => acc[kebabToCamel(key)], root)
+ key = chain.pop() as string
+
+ if (!target?.set) root = chain.reduce((acc, key) => acc[kebabToCamel(key)], root)
+ }
+ let value = nextValue
+ if (value === '') value = true
+ // Set prop, prefer atomic methods if applicable
+ if (isFunction(target)) {
+ /* if (Array.isArray(value)) target(...value)
+ else target(value) */
+ return
+ }
+ if (!target?.set && !isFunction(target)) root[camelKey] = value
+ else if (target.constructor === value.constructor && target?.copy) target?.copy(value)
+ else if (Array.isArray(value)) target.set(...value)
+ else if (!target.isColor && target.setScalar) target.setScalar(value)
+ else target.set(value)
+ }
+ },
+
+ parentNode(node) {
+ return node?.parent || null
+ },
+ createText: () => noop('createText'),
+
+ createComment: () => noop('createComment'),
+
+ setText: () => noop('setText'),
+
+ setElementText: () => noop('setElementText'),
+ nextSibling: () => noop('nextSibling'),
+
+ querySelector: () => noop('querySelector'),
+
+ setScopeId: () => noop('setScopeId'),
+ cloneNode: () => noop('cloneNode'),
+ insertStaticContent: () => noop('insertStaticContent'),
+}
diff --git a/packages/tres/src/core/nodeOpts.test.ts b/packages/tres/src/core/nodeOpts.test.ts
new file mode 100644
index 000000000..7ae4e4912
--- /dev/null
+++ b/packages/tres/src/core/nodeOpts.test.ts
@@ -0,0 +1,174 @@
+import * as THREE from 'three'
+import { nodeOps } from './nodeOps'
+import { TresObject } from '../types'
+import { extend } from './catalogue'
+import { Mesh, Scene } from 'three'
+
+describe('nodeOps', () => {
+ beforeAll(() => {
+ // Setup
+ extend(THREE)
+ })
+ it('createElement should create an instance with given tag', async () => {
+ // Setup
+ const tag = 'TresMesh'
+ const props = { args: [] }
+
+ // Test
+ const instance = nodeOps.createElement(tag, false, null, props)
+
+ // Assert
+ expect(instance.isObject3D).toBeTruthy()
+ expect(instance).toBeInstanceOf(Mesh)
+ })
+
+ it('createElement should create an instance with given tag and props', async () => {
+ // Setup
+ const tag = 'TresTorusGeometry'
+ const props = { args: [10, 3, 16, 100] }
+
+ // Test
+ const instance = nodeOps.createElement(tag, false, null, props)
+
+ // Assert
+ expect(instance.parameters.radius).toBe(10)
+ expect(instance.parameters.tube).toBe(3)
+ expect(instance.parameters.radialSegments).toBe(16)
+ expect(instance.parameters.tubularSegments).toBe(100)
+ })
+
+ it('createElement should create an camera instance', async () => {
+ // Setup
+ const tag = 'TresPerspectiveCamera'
+ const props = { args: [75, 2, 0.1, 5] }
+
+ // Test
+ const instance = nodeOps.createElement(tag, false, null, props)
+
+ // Assert
+ expect(instance.isCamera).toBeTruthy()
+ expect(instance).toBeInstanceOf(THREE.PerspectiveCamera)
+ })
+
+ it('createElement should log a warning if the camera doesnt have a position', async () => {
+ // Setup
+ const tag = 'TresPerspectiveCamera'
+ const props = { args: [75, 2, 0.1, 5] }
+
+ // Spy
+ const consoleWarnSpy = vi.spyOn(console, 'warn')
+ consoleWarnSpy.mockImplementation(() => {})
+
+ // Test
+ const instance = nodeOps.createElement(tag, false, null, props)
+
+ // Assert
+ expect(instance.isCamera).toBeTruthy()
+ expect(instance).toBeInstanceOf(THREE.PerspectiveCamera)
+ expect(consoleWarnSpy).toHaveBeenCalled()
+ })
+
+ it('createElement should add attach material propety if instance is a material', () => {
+ // Setup
+ const tag = 'TresMeshStandardMaterial'
+ const props = { args: [] }
+
+ // Test
+ const instance = nodeOps.createElement(tag, false, null, props)
+
+ // Assert
+ expect(instance.isMaterial).toBeTruthy()
+ expect(instance.attach).toBe('material')
+ })
+
+ it('createElement should add attach geometry propety if instance is a geometry', () => {
+ // Setup
+ const tag = 'TresTorusGeometry'
+ const props = { args: [] }
+
+ // Test
+ const instance = nodeOps.createElement(tag, false, null, props)
+
+ // Assert
+ expect(instance.isBufferGeometry).toBeTruthy()
+ expect(instance.attach).toBe('geometry')
+ })
+
+ it('insert should insert child into parent', async () => {
+ // Setup
+ const parent: TresObject = new Scene()
+ const child: TresObject = new Mesh()
+
+ // Test
+ nodeOps.insert(child, parent, null)
+
+ // Assert
+ expect(parent.children.includes(child)).toBeTruthy()
+ })
+
+ it('remove: removes child from parent', async () => {
+ // Setup
+ const parent: TresObject = new Scene()
+ const child: TresObject = new Mesh()
+ parent.children.push(child)
+
+ // Test
+ nodeOps.remove(child)
+
+ // Assert
+ expect(!parent.children.includes(child))
+ })
+
+ it('patchProp should patch property of node', async () => {
+ // Setup
+ const node: TresObject = new Mesh()
+ const prop = 'visible'
+ const nextValue = false
+
+ // Test
+ nodeOps.patchProp(node, prop, null, nextValue)
+
+ // Assert
+ expect(node.visible === nextValue)
+ })
+
+ it('patchProp should patch traverse pierced props', async () => {
+ // Setup
+ const node: TresObject = new Mesh()
+ const prop = 'position-x'
+ const nextValue = 5
+
+ // Test
+ nodeOps.patchProp(node, prop, null, nextValue)
+
+ // Assert
+ expect(node.position.x === nextValue)
+ })
+
+ it('patchProp it should not patch traverse pierced props of existing dashed properties', async () => {
+ // Setup
+ const node: TresObject = new Mesh()
+ const prop = 'cast-shadow'
+ const nextValue = true
+
+ // Test
+ nodeOps.patchProp(node, prop, null, nextValue)
+
+ // Assert
+ expect(node.castShadow === nextValue)
+ })
+
+ it('parentNode: returns parent of a node', async () => {
+ // Setup
+ const parent: TresObject = new Scene()
+ const child: TresObject = new Mesh()
+ parent.children.push(child)
+ child.parent = parent
+
+ // Test
+ const parentNode = nodeOps.parentNode(child)
+
+ // Assert
+ expect(parentNode === parent)
+ })
+})
diff --git a/packages/tres/src/core/renderer.ts b/packages/tres/src/core/renderer.ts
new file mode 100644
index 000000000..9e9893fe1
--- /dev/null
+++ b/packages/tres/src/core/renderer.ts
@@ -0,0 +1,19 @@
+import * as THREE from 'three'
+
+import { createRenderer, Slots } from 'vue'
+import { extend } from './catalogue'
+import { nodeOps } from './nodeOps'
+
+export const { createApp } = createRenderer(nodeOps)
+
+export const createTres = (slots: Slots) => {
+ const app = createApp(internalFnComponent)
+ function internalFnComponent() {
+ return slots && slots.default ? slots.default() : []
+ }
+ return app
+}
+
+extend(THREE)
+
+export default { createTres, extend }
diff --git a/packages/tres/src/core/useCatalogue/index.ts b/packages/tres/src/core/useCatalogue/index.ts
deleted file mode 100644
index dd8463397..000000000
--- a/packages/tres/src/core/useCatalogue/index.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { App, ref, Component, Ref } from 'vue'
-import * as THREE from 'three'
-import { useInstanceCreator } from '/@/core'
-import { useLogger } from '/@/composables'
-import { TresCatalogue } from '/@/types'
-
-const catalogue: Ref = ref({ ...THREE, uuid: THREE.MathUtils.generateUUID() })
-
-delete catalogue.value.Scene
-
-let localApp: App
-
-/**
- * State for the catalogue of THREE objects
- *
- * ```ts
- * const { catalogue } = useCatalogue()
- *
- * console.log(catalogue.value.Mesh) // Mesh
- * ```
- *
- * @export
- * @param {App} [app]
- * @param {string} [prefix='Tres']
- * @return {*}
- */
-export function useCatalogue(app?: App, prefix = 'Tres') {
- const { logError } = useLogger()
- if (!localApp && app) {
- localApp = app
- }
- const { createComponentInstances } = useInstanceCreator(prefix)
-
- /**
- * Extend the catalogue with new THREE objects
- *
- * ```ts
- * const { catalog, extend } = useCatalogue()
- *
- * extend({ MyObject: { foo: 'bar' } })
- *
- * console.log(catalog.value.MyObject.foo) // bar
- * ```
- *
- * @param {*} objects
- */
- const extend = (objects: any) => {
- if (!objects) {
- logError('No objects provided to extend catalogue')
- return
- }
- catalogue.value = Object.assign(catalogue.value, objects)
- const components = createComponentInstances(ref(objects))
-
- if (localApp) {
- components.forEach(([key, cmp]) => {
- // If the component is not already registered, register it
- if (!localApp._context.components[key as string]) {
- localApp.component(key as string, cmp as Component)
- }
- })
- }
- }
-
- return {
- extend,
- catalogue,
- }
-}
diff --git a/packages/tres/src/core/useCatalogue/useCatalogue.test.ts b/packages/tres/src/core/useCatalogue/useCatalogue.test.ts
deleted file mode 100644
index de557ba50..000000000
--- a/packages/tres/src/core/useCatalogue/useCatalogue.test.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { createApp } from 'vue'
-import { withSetup } from '/@/utils/test-utils'
-import { useCatalogue } from './'
-const [composable, app] = withSetup(() => useCatalogue())
-
-describe('useCatalogue', () => {
- it('should fill the catalogue with THREE objects', () => {
- const { catalogue } = composable
-
- expect(catalogue.value).toHaveProperty('Mesh')
- expect(catalogue.value).toHaveProperty('MeshBasicMaterial')
- })
- it('should skip Scene object', () => {
- const { catalogue } = composable
-
- expect(catalogue.value).not.toHaveProperty('Scene')
- })
- it('should extend the catalogue with objects', () => {
- const app = createApp({})
- const { extend, catalogue } = useCatalogue(app)
-
- extend({ MyObject: { foo: 'bar' } })
-
- expect(catalogue.value.MyObject.foo).toEqual('bar')
- })
-
- // TODO: find a way to mock createComponentInstances to test the component registration
-})
diff --git a/packages/tres/src/core/useInstanceCreator/index.ts b/packages/tres/src/core/useInstanceCreator/index.ts
deleted file mode 100644
index 6eda00bfc..000000000
--- a/packages/tres/src/core/useInstanceCreator/index.ts
+++ /dev/null
@@ -1,348 +0,0 @@
-/* eslint-disable new-cap */
-/* eslint-disable @typescript-eslint/no-empty-function */
-import { BufferAttribute, Fog, FogBase, Mesh, OrthographicCamera, PerspectiveCamera } from 'three'
-import { defineComponent, inject, onUnmounted, Ref } from 'vue'
-import { useEventListener } from '@vueuse/core'
-
-import { isArray, isDefined, isFunction } from '@alvarosabu/utils'
-import { normalizeVectorFlexibleParam } from '/@/utils/normalize'
-import { useCamera, useCatalogue, useRenderLoop, useTres } from '/@/core/'
-import { useLogger } from '/@/composables'
-import { TresAttributes, TresCatalogue, TresInstance, TresVNode, TresVNodeType, TresEvent } from '/@/types'
-
-const VECTOR3_PROPS = ['rotation', 'scale', 'position']
-const VECTOR3_AXIS = ['X', 'Y', 'Z']
-const COLOR_PROPS = ['color']
-const COLOR_KEYS = ['r', 'g', 'b']
-
-/**
- * Composable responsible for creating instances out of Three.js objects.
- *
- * @export
- * @param {string} prefix
- * @return {*}
- */
-export function useInstanceCreator(prefix: string) {
- const { /* logMessage, */ logError } = useLogger()
-
- /**
- * Process props to `.setAttribute` on instance.
- *
- * @example `position` prop will be converted to `setPosition` method call.
- *
- * @param {Record} props
- * @param {TresInstance} instance
- */
- function processSetAttributes(props: Record, instance: TresInstance) {
- if (!isDefined(props)) return
- if (!isDefined(instance)) return
-
- Object.entries(props).forEach(([key, value]) => {
- const camelKey = key.replace(/(-\w)/g, m => m[1].toUpperCase())
- instance.setAttribute(camelKey, new BufferAttribute(...(value as ConstructorParameters)))
- })
- }
-
- /**
- * Process props to set properties on instance.
- *
- * It will also normalize vector3 props and check if the instances property has a `set` method.
- * If it does, it will call the `set` method with the value, spread if it's an array.
- *
- * @example `position=[0,0,0]` prop will be converted to `instance.position.set(0,0,0)` property.
- *
- * @param {Record} props
- * @param {TresInstance} instance
- */
- function processProps(props: Record, instance: TresInstance) {
- if (!isDefined(props)) return
- if (!isDefined(instance)) return
-
- Object.entries(props).forEach(([key, value]) => {
- const camelKey = key.replace(/(-\w)/g, m => m[1].toUpperCase())
- let transformProps
- let transformAxis
- let colorProps
- let colorKey
- // Ignore property args which is use for initial instance construction
- if (camelKey === 'args' || value === undefined) return
-
- // Normalize vector3 props
- if (VECTOR3_PROPS.includes(camelKey) && value) {
- value = normalizeVectorFlexibleParam(value)
- } else {
- VECTOR3_PROPS.forEach(vecProps => {
- // Check if the props starts with one of the transform props
- // and is followed only with one of the axis
- if (camelKey.startsWith(vecProps) && camelKey.length === vecProps.length + 1) {
- transformProps = vecProps
- transformAxis = camelKey.substring(vecProps.length)
- if (!VECTOR3_AXIS.includes(transformAxis)) {
- logError(
- // eslint-disable-next-line max-len
- `There was an error setting ${key} property, ${transformAxis} is not a valid axis for ${transformProps}`,
- )
- }
- }
- })
- }
- COLOR_PROPS.forEach(props => {
- // Check if the props starts with one of the color props
- // and is followed only with one of the key
- if (camelKey.startsWith(props) && camelKey.length === props.length + 1) {
- colorProps = props
- colorKey = camelKey.substring(props.length).toLowerCase()
- if (!COLOR_KEYS.includes(colorKey)) {
- logError(`There was an error setting ${key} property , ${colorKey} is not a valid axis for ${colorProps}`)
- }
- }
- })
-
- if (props.ref) {
- props.ref = instance
- }
-
- try {
- // Check if the property has a "set" method
- if (instance[camelKey] && isDefined(instance[camelKey].set)) {
- // Call the "set" method with the value, spread if it's an array
- instance[camelKey].set(...(isArray(value) ? value : [value]))
- } else if (
- // Check if the property has a "setAxis" method
- transformProps &&
- instance[transformProps]
- ) {
- // Check if setAxis function exist
- // if it doesn't check if props is rotation
- if (isDefined(instance[transformProps][`set${transformAxis}`])) {
- instance[transformProps][`set${transformAxis}`](value)
- } else if (isDefined(instance[`rotate${transformAxis}`])) {
- instance[`rotate${transformAxis}`](value)
- }
- } else if (
- // Check if the instance has a "color" property
- colorProps &&
- colorKey &&
- instance[colorProps] &&
- instance[colorProps][colorKey]
- ) {
- instance[colorProps][colorKey] = value
- } else {
- // Convert empty strings to `true`
- if (value === '') {
- value = true
- }
-
- // Check if the property is a function
- if (isFunction(instance[camelKey])) {
- if (key === 'center' && !value) return
- // Call the function with the value, spread if it's an array
- instance[camelKey](...(isArray(value) ? value : [value]))
- return
- }
-
- // Set the property to the value
- instance[camelKey] = value
- }
- } catch (error: unknown) {
- logError(`There was an error setting ${camelKey} property`, error as Error)
- }
- })
- }
-
- /**
- * Proccess slots to add children to instance.
- *
- * @param {TresVNode} vnode
- * @return {*} {(TresInstance | TresInstance[] | undefined)}
- */
- function createInstanceFromVNode(vnode: TresVNode): TresInstance | TresInstance[] | undefined {
- const fragmentRegex = /^Symbol\(Fragment\)$/g
- const textRegex = /^Symbol\(Text\)$/g
- const commentRegex = /^Symbol\(Comment\)$/g
- // Check if the vnode is a Fragment
- if (fragmentRegex.test(vnode.type.toString())) {
- return vnode.children.map(child => createInstanceFromVNode(child as TresVNode)) as TresInstance[]
- } else if (textRegex.test(vnode.type.toString()) || commentRegex.test(vnode.type.toString())) {
- return
- } else {
- const vNodeType = ((vnode.type as TresVNodeType).name as string).replace(prefix, '')
-
- const catalogue = inject[>('catalogue')
-
- // check if args prop is defined on the vnode
- let internalInstance
- if (catalogue) {
- if ((vnode.children as unknown as { default: any })?.default) {
- const internal = (vnode.children as unknown as { default: any })
- .default()
- .map((child: TresVNode) => createInstanceFromVNode(child)) as TresInstance[]
-
- internalInstance = new catalogue.value[vNodeType](...internal.flat().filter(Boolean))
- } else if (vnode?.props?.args) {
- // if args prop is defined, create new instance of catalogue[vNodeType] with the provided arguments
- if (catalogue?.value[vNodeType]) {
- internalInstance = new catalogue.value[vNodeType](...vnode.props.args)
- } else {
- logError(`There is no ${vNodeType} in the catalogue`, catalogue?.value.uuid)
- }
- } else {
- // if args prop is not defined, create a new instance of catalogue[vNodeType] without arguments
- internalInstance = new catalogue.value[vNodeType]()
- }
- }
-
- // check if props is defined on the vnode
- if (vnode?.props) {
- // if props is defined, process the props and pass the internalInstance to update its properties
- if (vNodeType === 'BufferGeometry') {
- processSetAttributes(vnode.props, internalInstance)
- } else {
- processProps(vnode.props, internalInstance)
- }
- }
- return internalInstance
- }
- }
-
- /**
- * Create a new instance of a ThreeJS object based on the component attrs and slots.
- *
- * Checks if the component has slots,
- * if it does, it will create a new Object3D instance passing the slots instances as properties
- * Example:
- *
- * ```vue
- *
- *
- *
- *
- * ```
- *
- * will create a new Mesh instance with a BoxGeometry and a MeshBasicMaterial
- * const mesh = new Mesh(new BoxGeometry(), new MeshBasicMaterial())
- *
- * @param {*} threeObj
- * @param {TresAttributes} attrs
- * @param {Record} slots
- * @return {*} {TresInstance}
- */
- function createInstance(threeObj: any, attrs: TresAttributes, slots: Record): TresInstance {
- if (slots.default && slots?.default()) {
- const internal = slots.default().map((vnode: TresVNode) => createInstanceFromVNode(vnode))
- if (threeObj.name === 'Group') {
- const group = new threeObj()
- internal.forEach((child: TresInstance) => {
- group.add(child)
- })
- return group
- } else {
- return new threeObj(...internal.flat().filter(Boolean))
- }
- } else {
- // Creates a new THREE instance, if args is present, spread it on the constructor
- return attrs.args ? new threeObj(...attrs.args) : new threeObj()
- }
- }
-
- /**
- * Creates a new component instance for each object in the catalogue
- *
- * @param {Ref} catalogue
- * @return {*}
- */
- function createComponentInstances(catalogue: Ref) {
- return (
- Object.entries(catalogue.value)
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- .filter(([_key, value]) => (value as { prototype: any })?.prototype?.constructor?.toString().includes('class'))
- .map(([key, threeObj]) => {
- const name = `${prefix}${key}`
- const cmp = defineComponent({
- name,
- setup(_props, { slots, attrs, ...ctx }) {
- const { state } = useTres()
- const { onLoop } = useRenderLoop()
- const scene = state.scene
- const raycaster = state.raycaster
-
- let instance = createInstance(threeObj, attrs, slots)
- processProps(attrs, instance)
- // If the instance is a camera, push it to the camera stack
- if (instance instanceof PerspectiveCamera || instance instanceof OrthographicCamera) {
- const { pushCamera } = useCamera()
- pushCamera(instance)
- }
-
- // If the instance is a valid Object3D, add it to the scene
- if (instance.isObject3D) {
- scene?.add(instance)
- }
-
- let prevInstance: TresEvent | null = null
- let currentInstance: TresEvent | null = null
- if (instance instanceof Mesh) {
- onLoop(() => {
- if (instance && raycaster && scene?.children) {
- const intersects = raycaster.intersectObjects(scene?.children)
-
- if (intersects.length > 0) {
- currentInstance = intersects[0]
-
- if (prevInstance === null || prevInstance.object.uuid !== currentInstance?.object.uuid) {
- ctx.emit('pointer-enter', currentInstance)
- }
-
- ctx.emit('pointer-move', currentInstance)
- } else {
- currentInstance = null
- if (prevInstance !== null) {
- ctx.emit('pointer-leave', prevInstance)
- }
- }
-
- prevInstance = currentInstance
- }
- })
-
- const clickEventListener = useEventListener(window, 'click', () => {
- ctx.emit('click', prevInstance)
- })
-
- onUnmounted(() => {
- clickEventListener()
- })
- }
-
- if (scene && instance instanceof Fog) {
- scene.fog = instance as unknown as FogBase
- }
-
- if (import.meta.hot) {
- import.meta.hot.on('vite:afterUpdate', () => {
- instance = createInstance(threeObj, attrs, slots)
- processProps(attrs, instance)
-
- if (instance.isObject3D) {
- scene?.add(instance)
- }
- })
- }
-
- ctx.expose(instance)
-
- return () => {}
- },
- })
-
- return [name, cmp]
- })
- )
- }
-
- return {
- createComponentInstances,
- processProps,
- createInstanceFromVNode,
- }
-}
diff --git a/packages/tres/src/core/useInstanceCreator/useInstanceCreator.test.ts b/packages/tres/src/core/useInstanceCreator/useInstanceCreator.test.ts
deleted file mode 100644
index 298ca77d3..000000000
--- a/packages/tres/src/core/useInstanceCreator/useInstanceCreator.test.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { describe } from 'vitest'
-import { shallowRef } from 'vue'
-import { useInstanceCreator } from '.'
-import { useTres } from '../useTres'
-import { withSetup } from '/@/utils/test-utils'
-
-const [composable, app] = withSetup(() => useInstanceCreator('Tres'))
-
-describe('useInstanceCreator', () => {
- // TODO: understand why this is not working
- it.todo('should create component instances', () => {
- const { createComponentInstances } = composable
- const catalogue = shallowRef({
- TresBox: { name: 'TresBox' },
- TresSphere: { name: 'TresSphere' },
- TresPlane: { name: 'TresPlane' },
- })
- app.provide('catalogue', catalogue)
-
- app.provide('useTres', useTres())
- const components = createComponentInstances(catalogue)
- expect(components).toHaveLength(3)
- expect(components[0][0]).toBe('TresBox')
- expect(components[1][0]).toBe('TresSphere')
- expect(components[2][0]).toBe('TresPlane')
- })
-})
diff --git a/packages/tres/src/core/useRenderer/component.ts b/packages/tres/src/core/useRenderer/component.ts
deleted file mode 100644
index 919019e2e..000000000
--- a/packages/tres/src/core/useRenderer/component.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { RendererPresetsType } from './const'
-import { ShadowMapType, TextureEncoding, ToneMapping } from 'three'
-import { h, defineComponent, ref, provide, onBeforeUnmount, PropType } from 'vue'
-import { useRenderer } from '.'
-import { useLogger } from '/@/composables'
-import { TresVNodeType } from '/@/types'
-
-/**
- * Vue component for rendering a Tres component.
- */
-
-const { logError, logWarning } = useLogger()
-
-export const TresCanvas = defineComponent({
- name: 'TresCanvas',
- props: {
- shadows: Boolean,
- shadowMapType: Number as PropType,
- physicallyCorrectLights: {
- type: Boolean,
- default: false,
- validator: (value: boolean) => {
- if (value) {
- logWarning('physicallyCorrectLights is deprecated. Use useLegacyLights instead.')
- }
- return true
- },
- },
- useLegacyLights: Boolean,
- outputEncoding: Number as PropType,
- toneMapping: Number as PropType,
- toneMappingExposure: Number,
- context: Object as PropType,
- powerPreference: String as PropType<'high-performance' | 'low-power' | 'default'>,
- preserveDrawingBuffer: Boolean,
- clearColor: String,
- windowSize: { type: Boolean, default: false },
- preset: String as PropType,
- },
- setup(props, { slots, attrs }) {
- const canvas = ref()
- const container = ref()
-
- const { renderer, dispose, aspectRatio } = useRenderer(canvas, container, props)
-
- provide('aspect-ratio', aspectRatio)
- provide('renderer', renderer)
-
- if (slots.default && !slots.default().some(node => (node.type as TresVNodeType).name === 'Scene')) {
- logError('TresCanvas must contain a Scene component.')
- }
- if (slots.default && !slots.default().some(node => (node.type as TresVNodeType).name?.includes('Camera'))) {
- logError('Scene must contain a Camera component.')
- }
-
- onBeforeUnmount(() => dispose())
-
- return () => {
- if (slots.default) {
- return h(
- 'div',
- {
- ref: container,
- style: {
- position: 'relative',
- width: '100%',
- height: '100%',
- overflow: 'hidden',
- pointerEvents: 'auto',
- touchAction: 'none',
- ...(attrs.style as Record),
- },
- },
- [
- h(
- 'div',
- {
- style: {
- width: '100%',
- height: '100%',
- },
- },
- [
- h('canvas', {
- ref: canvas,
- style: {
- display: 'block',
- width: '100%',
- height: '100%',
- position: props.windowSize ? 'fixed' : 'absolute',
- top: 0,
- left: 0,
- },
- }),
- slots.default(),
- ],
- ),
- ],
- )
- }
- }
- },
-})
diff --git a/packages/tres/src/core/useScene/component.ts b/packages/tres/src/core/useScene/component.ts
deleted file mode 100644
index 2b707e55e..000000000
--- a/packages/tres/src/core/useScene/component.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { defineComponent, inject, provide, Ref } from 'vue'
-import type { Renderer } from 'three'
-import { useCamera, useTres, useRenderLoop, useScene, useRaycaster } from '/@/core/'
-
-/**
- * Vue component for rendering a Tres component.
- */
-export const Scene = defineComponent({
- name: 'Scene',
- setup(_props, { slots }) {
- const { setState } = useTres()
- const { scene } = useScene()
- const renderer = inject][>('renderer')
- const { activeCamera } = useCamera()
- const { raycaster, pointer } = useRaycaster()
- const { onLoop } = useRenderLoop()
-
- provide('local-scene', scene)
- setState('scene', scene.value)
-
- onLoop(() => {
- if (!activeCamera.value) return
- raycaster.value.setFromCamera(pointer.value, activeCamera.value)
-
- if (renderer?.value && activeCamera && scene?.value) {
- renderer.value.render(scene?.value, activeCamera.value)
- }
- })
-
- if (import.meta.hot) {
- import.meta.hot.on('vite:afterUpdate', () => {
- scene.value.children = []
- })
- }
-
- return () => {
- if (slots.default) {
- return slots.default()
- }
- }
- },
-})
diff --git a/packages/tres/src/core/useScene/index.ts b/packages/tres/src/core/useScene/index.ts
deleted file mode 100644
index 10e2ebfce..000000000
--- a/packages/tres/src/core/useScene/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Scene } from 'three'
-import { shallowRef } from 'vue'
-
-const scene = shallowRef(new Scene())
-
-/**
- * Composable for accessing the scene.
- *
- * @export
- * @return {*} {ShallowRef}
- */
-export function useScene() {
- return {
- scene,
- }
-}
diff --git a/packages/tres/src/core/useScene/useScene.test.ts b/packages/tres/src/core/useScene/useScene.test.ts
deleted file mode 100644
index 7d3131b08..000000000
--- a/packages/tres/src/core/useScene/useScene.test.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Scene } from 'three'
-import { describe, test, expect } from 'vitest'
-import { useScene } from './'
-
-describe('useScene()', () => {
- test('should init a scene', () => {
- const { scene } = useScene()
- expect(scene.value).toBeInstanceOf(Scene)
- })
-})
diff --git a/packages/tres/src/demos/AkuAku.vue b/packages/tres/src/demos/AkuAku.vue
new file mode 100644
index 000000000..689d08e09
--- /dev/null
+++ b/packages/tres/src/demos/AkuAku.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
diff --git a/packages/tres/src/components/AnimatedModel.vue b/packages/tres/src/demos/AnimatedModel.vue
similarity index 100%
rename from packages/tres/src/components/AnimatedModel.vue
rename to packages/tres/src/demos/AnimatedModel.vue
diff --git a/packages/tres/src/components/FBXModels.vue b/packages/tres/src/demos/FBXModels.vue
similarity index 51%
rename from packages/tres/src/components/FBXModels.vue
rename to packages/tres/src/demos/FBXModels.vue
index 8a44d60da..59fcea378 100644
--- a/packages/tres/src/components/FBXModels.vue
+++ b/packages/tres/src/demos/FBXModels.vue
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/tres/src/components/Responsiveness.vue b/packages/tres/src/demos/Responsiveness.vue
similarity index 100%
rename from packages/tres/src/components/Responsiveness.vue
rename to packages/tres/src/demos/Responsiveness.vue
diff --git a/packages/tres/src/demos/Shapes.vue b/packages/tres/src/demos/Shapes.vue
new file mode 100644
index 000000000..0329396e0
--- /dev/null
+++ b/packages/tres/src/demos/Shapes.vue
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/tres/src/demos/TestSphere.vue b/packages/tres/src/demos/TestSphere.vue
new file mode 100644
index 000000000..67d63e2eb
--- /dev/null
+++ b/packages/tres/src/demos/TestSphere.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/packages/tres/src/components/Text3D.vue b/packages/tres/src/demos/Text3D.vue
similarity index 88%
rename from packages/tres/src/components/Text3D.vue
rename to packages/tres/src/demos/Text3D.vue
index 5057b6634..c0a89cb61 100644
--- a/packages/tres/src/components/Text3D.vue
+++ b/packages/tres/src/demos/Text3D.vue
@@ -1,10 +1,10 @@
]