Explorar o código

feat(core): brand new lib structure with components generated on build time

alvarosabu %!s(int64=2) %!d(string=hai) anos
pai
achega
dbd7c52159

+ 1 - 1
packages/tres/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@tresjs/core",
   "description": "Declarative ThreeJS using Vue Components",
-  "version": "1.8.1",
+  "version": "2.0.0",
   "type": "module",
   "author": "Alvaro Saburido <hola@alvarosaburido.dev> (https://github.com/alvarosabu/)",
   "files": [

+ 138 - 21
packages/tres/plugins/vite-plugin-tres.ts

@@ -1,48 +1,165 @@
 import { createUnplugin } from 'unplugin'
 import * as THREE from 'three'
+
 import fs from 'fs'
 import { join } from 'pathe'
 
-export const unplugin = createUnplugin((options: UserOptions) => {
+const blacklist = ['Scene', 'Color', 'Object3D']
+
+export const unplugin = createUnplugin(() => {
+  let modules = []
   return {
     name: 'unplugin-tres',
+
     buildStart() {
+      console.log('✨ Magically generating components...')
       const outputDir = join('.tres', 'components')
 
       if (!fs.existsSync(outputDir)) {
         fs.mkdirSync(outputDir, { recursive: true })
       }
 
+      let indexTemplate = ''
+
       for (const key in THREE) {
         const value = (THREE as any)[key]
-        if (key === 'Scene') continue
+        if (
+          blacklist.includes(key) ||
+          key.includes('Vector') || // Vector2, Vector3, Vector4
+          key.includes('BufferGeometry') || // Deprecated geometries
+          key.includes('Utils') || // Utils
+          key.includes('Curve') // Curves
+        )
+          continue
         if (typeof value === 'function' && /^\s*class\s+/.test(value.toString())) {
           const outputFilePath = join(outputDir, `${key}.ts`)
+
           const template = `
-          import { ref } from 'vue'
-          import { useInstanceCreator } from 'src/core'
-          const { createComponentInstances } = useInstanceCreator()
-        import { ${key} } from 'three'
-        const component = /* #__PURE__ */ createComponentInstances(ref({ ${key} }))
-        export default component[0][1]
-        `
-
-          /* const output = `
-      import { defineComponent } from 'vue';
-      import { ${key} } from 'three';
-
-      export default defineComponent({
-        name: '${componentName}',
-        props: ${key}.prototype,
-        setup(props) {
-          const ${key}Instance = new ${key}();
-          return () => ${key}Instance;
+      import { defineComponent, inject, ShallowRef } from 'vue';
+      import { ${key}, Scene, Color, Vector3, Object3D } from 'three';
+      /* import { useCamera } from '/@/core/' */
+      import { VectorFlexibleParams, normalizeVectorFlexibleParam, normalizeColor } from '/@/utils/normalize';
+
+      let ${key}Instance: ${key};
+      let instanceProps: string[] = [];
+  
+
+      /**
+       * Tres${key}Props
+       * @type {Partial<${key}> & {parentInstance?: ${key}}}
+       * @memberof Tres${key}
+       * @description This is a partial of the ${key} class, with the parentInstance property added.
+       * 
+       **/
+      export type Tres${key}Props = Partial<${key}> & {
+        parentInstance?: ${key},
+        /** 
+         * 
+         * Array of arguments to pass to the ${key} constructor
+         *  
+         * @type {Array<any>}
+         * @memberof Tres${key}Props
+         * @see https://threejs.org/docs/?q=${key}
+         *  
+         **/
+        args?: Array<any>,
+        /**
+         *
+         * Object's local position
+         * 
+         * @type {VectorFlexibleParams}
+         * @memberof Tres${key}Props
+        **/
+        position?: VectorFlexibleParams
+      }
+
+      try {
+        // @ts-ignore
+        ${key}Instance = new ${key}();
+        instanceProps = [...Object.keys(${key}Instance)]
+      } catch (e) {
+      }
+
+      export const Tres${key} = /* #__PURE__ */ defineComponent<Tres${key}Props>({
+        name: 'Tres${key}',
+        props: ['parentInstance', 'args', ...instanceProps] as unknown as undefined,
+        setup(props, { slots, expose }) {
+          if(props.args) {
+            // @ts-ignore
+            ${key}Instance = new ${key}(...props.args);
+          } else {
+            // @ts-ignore
+            ${key}Instance = new ${key}();
+          }
+          const scene = inject<ShallowRef<Scene>>('scene')
+         
+          expose({${key}Instance})
+
+          function append(parent, child) {
+            const regex = /[A-Z][a-z]+/g
+            const propName = child.type.match(regex).pop().toLowerCase()
+            if (parent[propName]) {
+              parent[propName] = child
+            }
+          }
+
+          function processProps() {
+            Object.keys(props).forEach((key) => {
+              if (props[key] !== undefined && key !== 'parentInstance' && key !== 'args') {
+                if( ${key}Instance[key] instanceof Color) {
+                  ${key}Instance[key] = normalizeColor(props[key])
+                } else if ( ${key}Instance[key] instanceof Vector3) {
+                  ${key}Instance[key].set(normalizeVectorFlexibleParam(props[key]))
+                } else {
+                  ${key}Instance[key] = props[key]
+                }
+              }
+            })
+          }
+
+          processProps()
+
+
+          /* if (${key}Instance.hasOwnProperty('isCamera')) {
+            ${key}Instance.position.set(0, 0, 5)
+            ${key}Instance.lookAt(0, 0, 0)
+            const { pushCamera } = useCamera()
+            pushCamera(${key}Instance)
+          } */
+      
+         
+          if(props.parentInstance) {
+            append(props.parentInstance, ${key}Instance)
+          }
+
+          if(scene && !props.parentInstance && ${key}Instance instanceof Object3D) {
+            scene.value.add(${key}Instance)
+          }
+
+          const preparedSlots = slots.default 
+            // eslint-disable-next-line max-len
+            ? slots.default().map((slot) => { slot.props = { 
+              ...slot.props, 
+              parentInstance: ${key}Instance }; 
+              return slot; 
+            })
+            : null
+          return () => {
+            return preparedSlots;
+          };
         },
       });
-    ` */
+
+      export default Tres${key};
+    `
+          indexTemplate += `export { default as Tres${key} } from './${key}'\n`
           fs.writeFileSync(outputFilePath, template)
+          modules.push(key)
         }
+
+        fs.writeFileSync(join(outputDir, `index.ts`), indexTemplate)
       }
+      console.log(`✨ Generated ${modules.length} components!`)
     },
   }
 })

+ 9 - 6
packages/tres/src/App.vue

@@ -1,14 +1,17 @@
 <script setup lang="ts">
-import { useTweakPane } from '@tresjs/cientos'
-import TheEnvironment from '/@/components/TheEnvironment.vue'
-// import TheEvents from '/@/components/TheEvents.vue'
-
-useTweakPane()
+import { TresMesh, TresSphereGeometry, TresMeshBasicMaterial, TresPerspectiveCamera } from '../.tres/components/'
+import { TresCanvas } from '/@/components/TresCanvas'
 </script>
 
 <template>
   <Suspense>
-    <TheEnvironment />
+    <TresCanvas clear-color="teal">
+      <TresPerspectiveCamera :position="[0, 3, 3]" />
+      <TresMesh>
+        <TresSphereGeometry :args="[1, 1, 32, 32]" />
+        <TresMeshBasicMaterial color="gold" />
+      </TresMesh>
+    </TresCanvas>
   </Suspense>
 </template>
 

+ 119 - 0
packages/tres/src/components/TresCanvas.ts

@@ -0,0 +1,119 @@
+import { Scene, ShadowMapType, TextureEncoding, ToneMapping } from 'three'
+import { h, defineComponent, ref, provide, onBeforeUnmount, PropType, shallowRef } from 'vue'
+import { useCamera } from '../core/useCamera'
+import { RendererPresetsType, useRenderer, useRenderLoop, useTres } from '/@/composables'
+import { useLogger } from '/@/composables/useLogger'
+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<ShadowMapType>,
+    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<TextureEncoding>,
+    toneMapping: Number as PropType<ToneMapping>,
+    toneMappingExposure: Number,
+    context: Object as PropType<WebGLRenderingContext>,
+    powerPreference: String as PropType<'high-performance' | 'low-power' | 'default'>,
+    preserveDrawingBuffer: Boolean,
+    clearColor: String,
+    windowSize: { type: Boolean, default: false },
+    preset: String as PropType<RendererPresetsType>,
+  },
+  setup(props, { slots, attrs }) {
+    const canvas = ref<HTMLCanvasElement>()
+    const container = ref<HTMLElement>()
+
+    const { renderer, dispose, aspectRatio } = useRenderer(canvas, container, props)
+
+    provide('aspect-ratio', aspectRatio)
+    provide('renderer', renderer)
+
+    const { setState } = useTres()
+    const scene = shallowRef(new Scene())
+    console.log('TresCanvas', scene)
+    const { activeCamera } = useCamera()
+    /*   const { raycaster, pointer } = useRaycaster() */
+    const { onLoop } = useRenderLoop()
+
+    provide('scene', scene)
+    setState('scene', scene)
+
+    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 (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<string, unknown>),
+            },
+          },
+          [
+            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(),
+              ],
+            ),
+          ],
+        )
+      }
+    }
+  },
+})

+ 1 - 0
packages/tres/src/components/index.ts

@@ -0,0 +1 @@
+export * from './TresCanvas'

+ 3 - 1
packages/tres/src/composables/index.ts

@@ -1 +1,3 @@
-export * from './useLogger'
+export * from './useRenderer'
+export * from './useRenderLoop'
+export * from './useTres'

+ 196 - 0
packages/tres/src/composables/useCamera.ts

@@ -0,0 +1,196 @@
+import { useTres } from '/@/composables'
+import { PerspectiveCamera, OrthographicCamera } from 'three'
+
+import { toRef, watch, Ref, inject } from 'vue'
+
+export enum CameraType {
+  Perspective = 'Perspective',
+  Orthographic = 'Orthographic',
+}
+
+export type Camera = PerspectiveCamera | OrthographicCamera
+
+export interface PerspectiveCameraOptions {
+  /**
+   * Camera frustum vertical field of view, from bottom to top of view, in degrees.
+   *
+   * @type {number}
+   * @memberof PerspectiveCameraOptions
+   */
+  fov?: number
+  /**
+   * Camera frustum near plane.
+   *
+   * @type {number}
+   * @memberof PerspectiveCameraOptions
+   */
+  near?: number
+  /**
+   * Camera frustum far plane.
+   *
+   * @type {number}
+   * @memberof PerspectiveCameraOptions
+   */
+  far?: number
+}
+
+export interface OrthographicCameraOptions {
+  /**
+   * Camera frustum left plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  left?: number
+  /**
+   * Camera frustum right plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  right?: number
+  /**
+   * Camera frustum top plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  top?: number
+  /**
+   * Camera frustum bottom plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  bottom?: number
+  /**
+   * Camera frustum near plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  near?: number
+  /**
+   * Camera frustum far plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  far?: number
+}
+
+interface UseCameraReturn {
+  activeCamera: Ref<Camera | undefined>
+  createCamera: (cameraType?: CameraType, options?: PerspectiveCameraOptions | OrthographicCameraOptions) => Camera
+  updateCamera: () => void
+  pushCamera: (camera: Camera) => void
+  clearCameras: () => void
+}
+
+const VERTICAL_FIELD_OF_VIEW = 45
+let camera: Camera
+
+/**
+ * Create and update cameras
+ *
+ * ```ts
+ * import { useCamera } from '@tresjs/core'
+ * const { createCamera, updateCamera } = useCamera()
+ * const camera = createCamera(new PerspectiveCamera(45, 1, 0.1, 1000))
+ * updateCamera()
+ * ```
+ *
+ * @export
+ * @return {*}  {UseCameraReturn}
+ */
+export function useCamera(): UseCameraReturn {
+  const { state, setState } = useTres()
+  const aspectRatio = inject('aspect-ratio')
+  /**
+   * Create camera and push to Tres `state.cameras` array
+   *
+   * ```ts
+   * import { useCamera } from '@tresjs/core'
+   * const { createCamera } = useCamera()
+   * const camera = createCamera(new PerspectiveCamera(45, 1, 0.1, 1000))
+   * ```
+   *
+   * @param {*} [cameraType=CameraType.Perspective]
+   * @param {(PerspectiveCameraOptions | OrthographicCameraOptions)} [options]
+   * @return {*}
+   */
+  function createCamera(
+    cameraType = CameraType.Perspective,
+    options?: PerspectiveCameraOptions | OrthographicCameraOptions,
+  ) {
+    if (cameraType === CameraType.Perspective) {
+      const { near, far, fov } = (options as PerspectiveCameraOptions) || {
+        near: 0.1,
+        far: 1000,
+        fov: VERTICAL_FIELD_OF_VIEW,
+      }
+      camera = new PerspectiveCamera(fov, state.aspectRatio?.value || window.innerWidth / window.innerHeight, near, far)
+      state.cameras?.push(camera as PerspectiveCamera)
+    } else {
+      const { left, right, top, bottom, near, far } = (options as OrthographicCameraOptions) || {
+        left: -100,
+        right: 100,
+        top: 100,
+        bottom: -100,
+        near: 0.1,
+        far: 1000,
+      }
+      camera = new OrthographicCamera(left, right, top, bottom, near, far)
+      state.cameras?.push(camera as OrthographicCamera)
+    }
+    state.camera = camera
+
+    setState('camera', state.camera)
+
+    return camera
+  }
+
+  /**
+   * Update camera aspect ratio and projection matrix
+   *
+   */
+  function updateCamera() {
+    if (state.camera instanceof PerspectiveCamera && state.aspectRatio) {
+      state.camera.aspect = state.aspectRatio.value
+    }
+    state.camera?.updateProjectionMatrix()
+  }
+
+  /**
+   * Push camera to cameras array and update aspect ratio if camera is PerspectiveCamera
+   *
+   * @param {Camera} camera
+   */
+  function pushCamera(camera: Camera): void {
+    state.cameras?.push(camera)
+    if (camera instanceof PerspectiveCamera && state.aspectRatio) {
+      camera.aspect = state.aspectRatio.value
+    }
+    camera.updateProjectionMatrix()
+    setState('camera', camera)
+  }
+
+  /**
+   * Clear cameras array
+   *
+   */
+  function clearCameras() {
+    state.cameras = []
+  }
+  if (aspectRatio) {
+    watch(aspectRatio, updateCamera)
+  }
+
+  return {
+    activeCamera: toRef(state, 'camera'),
+    createCamera,
+    updateCamera,
+    pushCamera,
+    clearCameras,
+  }
+}

+ 51 - 0
packages/tres/src/composables/useRenderLoop.ts

@@ -0,0 +1,51 @@
+import { createEventHook, EventHookOn, Fn, useRafFn } from '@vueuse/core'
+import { Ref } from 'vue'
+import { Clock } from 'three'
+
+export interface RenderLoop {
+  delta: number
+  elapsed: number
+  clock: Clock
+}
+
+export interface UseRenderLoopReturn {
+  onBeforeLoop: EventHookOn<RenderLoop>
+  onLoop: EventHookOn<RenderLoop>
+  onAfterLoop: EventHookOn<RenderLoop>
+  pause: Fn
+  resume: Fn
+  isActive: Ref<boolean>
+}
+
+const onBeforeLoop = createEventHook<RenderLoop>()
+const onLoop = createEventHook<RenderLoop>()
+const onAfterLoop = createEventHook<RenderLoop>()
+
+const clock = new Clock()
+let delta = 0
+let elapsed = 0
+
+const { pause, resume, isActive } = useRafFn(
+  () => {
+    onBeforeLoop.trigger({ delta, elapsed, clock })
+    onLoop.trigger({ delta, elapsed, clock })
+    onAfterLoop.trigger({ delta, elapsed, clock })
+  },
+  { immediate: false },
+)
+
+onAfterLoop.on(() => {
+  delta = clock.getDelta()
+  elapsed = clock.getElapsedTime()
+})
+
+export function useRenderLoop(): UseRenderLoopReturn {
+  return {
+    onBeforeLoop: onBeforeLoop.on,
+    onLoop: onLoop.on,
+    onAfterLoop: onAfterLoop.on,
+    pause,
+    resume,
+    isActive,
+  }
+}

+ 280 - 0
packages/tres/src/composables/useRenderer.ts

@@ -0,0 +1,280 @@
+/* eslint-disable max-len */
+import { watch, ref, shallowRef, computed, toRefs } from 'vue'
+import {
+  MaybeComputedRef,
+  MaybeElementRef,
+  resolveUnref,
+  unrefElement,
+  useDevicePixelRatio,
+  useElementSize,
+  useWindowSize,
+} from '@vueuse/core'
+import {
+  WebGLRendererParameters,
+  NoToneMapping,
+  LinearEncoding,
+  WebGLRenderer,
+  ShadowMapType,
+  PCFShadowMap,
+  Clock,
+} from 'three'
+import type { TextureEncoding, ToneMapping } from 'three'
+
+import { normalizeColor } from '/@/utils/normalize'
+import { TresColor } from '/@/types'
+import { merge } from '/@/utils'
+import { useRenderLoop, useTres } from '/@/composables'
+import { useLogger } from '/@/composables/useLogger'
+
+import { ACESFilmicToneMapping, PCFSoftShadowMap, sRGBEncoding } from 'three'
+
+export const rendererPresets = {
+  realistic: {
+    outputEncoding: sRGBEncoding,
+    toneMapping: ACESFilmicToneMapping,
+    toneMappingExposure: 3,
+    shadowMap: {
+      enabled: true,
+      type: PCFSoftShadowMap,
+    },
+  },
+}
+
+export type RendererPresetsType = keyof typeof rendererPresets
+
+export interface UseRendererOptions extends WebGLRendererParameters {
+  /**
+   * Enable shadows in the Renderer
+   *
+   * @default false
+   */
+  shadows?: MaybeComputedRef<boolean>
+
+  /**
+   * Set the shadow map type
+   * Can be PCFShadowMap, PCFSoftShadowMap, BasicShadowMap, VSMShadowMap
+   * [see](https://threejs.org/docs/?q=we#api/en/constants/Renderer)
+   *
+   * @default PCFSoftShadowMap
+   */
+  shadowMapType?: MaybeComputedRef<ShadowMapType>
+
+  /**
+   * Whether to use physically correct lighting mode.
+   * See the [lights / physical example](https://threejs.org/examples/#webgl_lights_physical).
+   *
+   * @default false
+   * @deprecated Use {@link WebGLRenderer.useLegacyLights useLegacyLights} instead.
+   */
+  physicallyCorrectLights?: MaybeComputedRef<boolean>
+  /**
+   * Whether to use legacy lighting mode.
+   *
+   * @type {MaybeComputedRef<boolean>}
+   * @memberof UseRendererOptions
+   */
+  useLegacyLights?: MaybeComputedRef<boolean>
+  /**
+   * Defines the output encoding of the renderer.
+   * Can be LinearEncoding, sRGBEncoding
+   *
+   * @default LinearEncoding
+   */
+  outputEncoding?: MaybeComputedRef<TextureEncoding>
+
+  /**
+   * Defines the tone mapping used by the renderer.
+   * Can be NoToneMapping, LinearToneMapping, ReinhardToneMapping, Uncharted2ToneMapping, CineonToneMapping, ACESFilmicToneMapping, CustomToneMapping
+   *
+   * @default NoToneMapping
+   */
+  toneMapping?: MaybeComputedRef<ToneMapping>
+
+  /**
+   * Defines the tone mapping exposure used by the renderer.
+   *
+   * @default 1
+   */
+  toneMappingExposure?: MaybeComputedRef<number>
+
+  /**
+   * The context used by the renderer.
+   *
+   * @default undefined
+   */
+  context?: WebGLRenderingContext | undefined
+
+  /**
+   * Provides a hint to the user agent indicating what configuration of GPU is suitable for this WebGL context.
+   * Can be "high-performance", "low-power" or "default".
+   *
+   * @default "default"
+   */
+  powerPreference?: 'high-performance' | 'low-power' | 'default'
+
+  /**
+   * Whether to preserve the buffers until manually cleared or overwritten.
+   *
+   * @default false
+   */
+  preserveDrawingBuffer?: boolean
+
+  /**
+   * The color value to use when clearing the canvas.
+   *
+   * @default 0x000000
+   */
+  clearColor?: MaybeComputedRef<TresColor>
+  windowSize?: MaybeComputedRef<boolean>
+  preset?: RendererPresetsType
+}
+
+const renderer = shallowRef<WebGLRenderer>()
+const isReady = ref(false)
+
+/**
+ * Reactive Three.js WebGLRenderer instance
+ *
+ * @param canvas
+ * @param container
+ * @param {UseRendererOptions} [options]
+ */
+export function useRenderer(canvas: MaybeElementRef, container: MaybeElementRef, options: UseRendererOptions) {
+  // Defaults
+  const {
+    alpha = true,
+    antialias = true,
+    depth,
+    logarithmicDepthBuffer,
+    failIfMajorPerformanceCaveat,
+    precision,
+    premultipliedAlpha,
+    stencil,
+    shadows = false,
+    shadowMapType = PCFShadowMap,
+    physicallyCorrectLights = false,
+    useLegacyLights = false,
+    outputEncoding = LinearEncoding,
+    toneMapping = NoToneMapping,
+    toneMappingExposure = 1,
+    context = undefined,
+    powerPreference = 'default',
+    preserveDrawingBuffer = false,
+    clearColor,
+    windowSize = false,
+    preset = undefined,
+  } = toRefs(options)
+
+  const { setState } = useTres()
+
+  const { width, height } = resolveUnref(windowSize) ? useWindowSize() : useElementSize(container)
+  const { logError } = useLogger()
+  const { pixelRatio } = useDevicePixelRatio()
+  const { pause, resume } = useRenderLoop()
+  const aspectRatio = computed(() => width.value / height.value)
+
+  const updateRendererSize = () => {
+    if (!renderer.value) {
+      return
+    }
+
+    renderer.value.setSize(width.value, height.value)
+    renderer.value.setPixelRatio(Math.min(pixelRatio.value, 2))
+  }
+
+  const updateRendererOptions = () => {
+    if (!renderer.value) {
+      return
+    }
+
+    const rendererPreset = resolveUnref(preset)
+
+    if (rendererPreset) {
+      if (!(rendererPreset in rendererPresets))
+        logError('Renderer Preset must be one of these: ' + Object.keys(rendererPresets).join(', '))
+      merge(renderer.value, rendererPresets[rendererPreset])
+
+      return
+    }
+
+    renderer.value.shadowMap.enabled = resolveUnref(shadows) as boolean
+    renderer.value.shadowMap.type = resolveUnref(shadowMapType) as ShadowMapType
+    renderer.value.toneMapping = (resolveUnref(toneMapping) as ToneMapping) || NoToneMapping
+    renderer.value.toneMappingExposure = resolveUnref(toneMappingExposure) as number
+    renderer.value.outputEncoding = (resolveUnref(outputEncoding) as TextureEncoding) || LinearEncoding
+    if (clearColor?.value) renderer.value.setClearColor(normalizeColor(resolveUnref(clearColor) as TresColor))
+
+    /*    renderer.value.physicallyCorrectLights = resolveUnref(physicallyCorrectLights) as boolean */
+    renderer.value.useLegacyLights = resolveUnref(useLegacyLights) as boolean
+  }
+
+  const init = () => {
+    const _canvas = unrefElement(canvas)
+
+    if (renderer.value || !_canvas) {
+      return
+    }
+
+    renderer.value = new WebGLRenderer({
+      canvas: _canvas,
+      alpha: resolveUnref(alpha),
+      antialias: resolveUnref(antialias),
+      context: resolveUnref(context),
+      depth: resolveUnref(depth),
+      failIfMajorPerformanceCaveat: resolveUnref(failIfMajorPerformanceCaveat),
+      logarithmicDepthBuffer: resolveUnref(logarithmicDepthBuffer),
+      powerPreference: resolveUnref(powerPreference),
+      precision: resolveUnref(precision),
+      stencil: resolveUnref(stencil),
+      preserveDrawingBuffer: resolveUnref(preserveDrawingBuffer),
+      premultipliedAlpha: resolveUnref(premultipliedAlpha),
+    })
+
+    setState('renderer', renderer.value)
+    setState('clock', new Clock())
+    setState('aspectRatio', aspectRatio)
+    updateRendererOptions()
+    updateRendererSize()
+    resume()
+
+    isReady.value = true
+  }
+
+  const dispose = () => {
+    if (!renderer.value) {
+      return
+    }
+
+    renderer.value.dispose()
+    renderer.value = undefined
+
+    isReady.value = false
+    pause()
+  }
+
+  watch([aspectRatio, pixelRatio], updateRendererSize)
+
+  watch(
+    [shadows, shadowMapType, outputEncoding, useLegacyLights, toneMapping, toneMappingExposure, clearColor],
+    updateRendererOptions,
+  )
+
+  watch(
+    () => [canvas, container],
+    () => {
+      if (unrefElement(canvas) && unrefElement(container)) {
+        init()
+      }
+    },
+    { immediate: true, deep: true },
+  )
+
+  return {
+    renderer,
+    isReady,
+    dispose,
+    aspectRatio,
+  }
+}
+
+export type UseRendererReturn = ReturnType<typeof useRenderer>

+ 135 - 0
packages/tres/src/composables/useTres.ts

@@ -0,0 +1,135 @@
+import { Clock, EventDispatcher, Raycaster, Scene, Vector2, WebGLRenderer } from 'three'
+import { computed, ComputedRef, shallowReactive, toRefs } from 'vue'
+import { Camera } from '/@/core'
+
+export interface TresState {
+  /**
+   * The active camera used for rendering the scene.
+   *
+   * @see https://threejs.org/docs/index.html?q=camera#api/en/cameras/Camera
+   *
+   * @type {Camera}
+   * @memberof TresState
+   */
+  camera?: Camera
+  /**
+   * All cameras available in the scene.
+   *
+   * @see https://threejs.org/docs/index.html?q=camera#api/en/cameras/Camera
+   *
+   * @type {Camera[]}
+   * @memberof TresState
+   */
+  cameras?: Camera[]
+  /**
+   * The aspect ratio of the scene.
+   *
+   * @type {ComputedRef<number>}
+   * @memberof TresState
+   */
+  aspectRatio?: ComputedRef<number>
+  /**
+   * The WebGLRenderer used to display the scene using WebGL.
+   *
+   * @see https://threejs.org/docs/index.html?q=webglren#api/en/renderers/WebGLRenderer
+   *
+   * @type {WebGLRenderer}
+   * @memberof TresState
+   */
+  renderer?: WebGLRenderer
+  /**
+   * The scene. This is the place where you place objects, lights and cameras.
+   *
+   * @see https://threejs.org/docs/index.html?q=scene#api/en/scenes/Scene
+   *
+   * @type {Scene}
+   * @memberof TresState
+   */
+  scene?: Scene
+  /**
+   * The raycaster.
+   *
+   * @see https://threejs.org/docs/index.html?q=raycas#api/en/core/Raycaster
+   *
+   * @type {Raycaster}
+   * @memberof TresState
+   */
+  raycaster?: Raycaster
+
+  /**
+   * Object for keeping track of time. This uses `performance.now` if it is available,
+   * otherwise it reverts to the less accurate `Date.now`.
+   *
+   * @see https://threejs.org/docs/index.html?q=clock#api/en/core/Clock
+   *
+   * @type {Clock}
+   * @memberof TresState
+   */
+  clock?: Clock
+  /**
+   * The current mouse position.
+   *
+   * @type {Vector2}
+   * @memberof TresState
+   */
+  pointer?: Vector2
+  /**
+   * The current instance of the component.
+   *
+   * @type {*}
+   * @memberof TresState
+   */
+  currentInstance?: any
+  /**
+   *  The current active scene control
+   *
+   * @type {((EventDispatcher & { enabled: boolean }) | null)}
+   * @memberof TresState
+   */
+  controls?: (EventDispatcher & { enabled: boolean }) | null
+  [key: string]: any
+}
+
+const state: TresState = shallowReactive({
+  camera: undefined,
+  cameras: [],
+  aspectRatio: computed(() => window.innerWidth / window.innerHeight),
+})
+
+/**
+ * The Tres state.
+ *
+ * @see https://threejs.org/docs/index.html?q=scene#api/en/scenes/Scene
+ *
+ * @export
+ * @return {*} {TresState, getState, setState}
+ */
+export function useTres() {
+  /**
+   * Get a state value.
+   *
+   *
+   * @param {string} key
+   * @return {*}
+   */
+  function getState(key: string) {
+    return state[key]
+  }
+
+  /**
+   * Set a state value.
+   *
+   * @param {string} key
+   * @param {*} value
+   */
+  function setState(key: string, value: any) {
+    state[key] = value
+  }
+
+  return {
+    state,
+    ...toRefs(state),
+    getState,
+    setState,
+  }
+}

+ 196 - 0
packages/tres/src/core/useCamera/useCamera.ts

@@ -0,0 +1,196 @@
+import { useTres } from '/@/core/'
+import { PerspectiveCamera, OrthographicCamera } from 'three'
+
+import { toRef, watch, Ref, inject } from 'vue'
+
+export enum CameraType {
+  Perspective = 'Perspective',
+  Orthographic = 'Orthographic',
+}
+
+export type Camera = PerspectiveCamera | OrthographicCamera
+
+export interface PerspectiveCameraOptions {
+  /**
+   * Camera frustum vertical field of view, from bottom to top of view, in degrees.
+   *
+   * @type {number}
+   * @memberof PerspectiveCameraOptions
+   */
+  fov?: number
+  /**
+   * Camera frustum near plane.
+   *
+   * @type {number}
+   * @memberof PerspectiveCameraOptions
+   */
+  near?: number
+  /**
+   * Camera frustum far plane.
+   *
+   * @type {number}
+   * @memberof PerspectiveCameraOptions
+   */
+  far?: number
+}
+
+export interface OrthographicCameraOptions {
+  /**
+   * Camera frustum left plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  left?: number
+  /**
+   * Camera frustum right plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  right?: number
+  /**
+   * Camera frustum top plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  top?: number
+  /**
+   * Camera frustum bottom plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  bottom?: number
+  /**
+   * Camera frustum near plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  near?: number
+  /**
+   * Camera frustum far plane.
+   *
+   * @type {number}
+   * @memberof OrthographicCameraOptions
+   */
+  far?: number
+}
+
+interface UseCameraReturn {
+  activeCamera: Ref<Camera | undefined>
+  createCamera: (cameraType?: CameraType, options?: PerspectiveCameraOptions | OrthographicCameraOptions) => Camera
+  updateCamera: () => void
+  pushCamera: (camera: Camera) => void
+  clearCameras: () => void
+}
+
+const VERTICAL_FIELD_OF_VIEW = 45
+let camera: Camera
+
+/**
+ * Create and update cameras
+ *
+ * ```ts
+ * import { useCamera } from '@tresjs/core'
+ * const { createCamera, updateCamera } = useCamera()
+ * const camera = createCamera(new PerspectiveCamera(45, 1, 0.1, 1000))
+ * updateCamera()
+ * ```
+ *
+ * @export
+ * @return {*}  {UseCameraReturn}
+ */
+export function useCamera(): UseCameraReturn {
+  const { state, setState } = useTres()
+  const aspectRatio = inject('aspect-ratio')
+  /**
+   * Create camera and push to Tres `state.cameras` array
+   *
+   * ```ts
+   * import { useCamera } from '@tresjs/core'
+   * const { createCamera } = useCamera()
+   * const camera = createCamera(new PerspectiveCamera(45, 1, 0.1, 1000))
+   * ```
+   *
+   * @param {*} [cameraType=CameraType.Perspective]
+   * @param {(PerspectiveCameraOptions | OrthographicCameraOptions)} [options]
+   * @return {*}
+   */
+  function createCamera(
+    cameraType = CameraType.Perspective,
+    options?: PerspectiveCameraOptions | OrthographicCameraOptions,
+  ) {
+    if (cameraType === CameraType.Perspective) {
+      const { near, far, fov } = (options as PerspectiveCameraOptions) || {
+        near: 0.1,
+        far: 1000,
+        fov: VERTICAL_FIELD_OF_VIEW,
+      }
+      camera = new PerspectiveCamera(fov, state.aspectRatio?.value || window.innerWidth / window.innerHeight, near, far)
+      state.cameras?.push(camera as PerspectiveCamera)
+    } else {
+      const { left, right, top, bottom, near, far } = (options as OrthographicCameraOptions) || {
+        left: -100,
+        right: 100,
+        top: 100,
+        bottom: -100,
+        near: 0.1,
+        far: 1000,
+      }
+      camera = new OrthographicCamera(left, right, top, bottom, near, far)
+      state.cameras?.push(camera as OrthographicCamera)
+    }
+    state.camera = camera
+
+    setState('camera', state.camera)
+
+    return camera
+  }
+
+  /**
+   * Update camera aspect ratio and projection matrix
+   *
+   */
+  function updateCamera() {
+    if (state.camera instanceof PerspectiveCamera && state.aspectRatio) {
+      state.camera.aspect = state.aspectRatio.value
+    }
+    state.camera?.updateProjectionMatrix()
+  }
+
+  /**
+   * Push camera to cameras array and update aspect ratio if camera is PerspectiveCamera
+   *
+   * @param {Camera} camera
+   */
+  function pushCamera(camera: Camera): void {
+    state.cameras?.push(camera)
+    if (camera instanceof PerspectiveCamera && state.aspectRatio) {
+      camera.aspect = state.aspectRatio.value
+    }
+    camera.updateProjectionMatrix()
+    setState('camera', camera)
+  }
+
+  /**
+   * Clear cameras array
+   *
+   */
+  function clearCameras() {
+    state.cameras = []
+  }
+  if (aspectRatio) {
+    watch(aspectRatio, updateCamera)
+  }
+
+  return {
+    activeCamera: toRef(state, 'camera'),
+    createCamera,
+    updateCamera,
+    pushCamera,
+    clearCameras,
+  }
+}

+ 1 - 1
packages/tres/src/core/useCatalogue/index.ts

@@ -1,7 +1,7 @@
 import { App, ref, Component, Ref } from 'vue'
 import * as THREE from 'three'
 import { useInstanceCreator } from '/@/core'
-import { useLogger } from '/@/composables'
+import { useLogger } from '/@/composables/useLogger'
 import { TresCatalogue } from '/@/types'
 
 const catalogue: Ref<TresCatalogue> = ref({ ...THREE, uuid: THREE.MathUtils.generateUUID() })

+ 1 - 1
packages/tres/src/core/useInstanceCreator/index.ts

@@ -7,7 +7,7 @@ import { useEventListener } from '@vueuse/core'
 import { isArray, isDefined, isFunction } from '@alvarosabu/utils'
 import { normalizeVectorFlexibleParam } from '/@/utils/normalize'
 import { useCamera, useRenderLoop, useTres } from '/@/core/'
-import { useLogger } from '/@/composables'
+import { useLogger } from '/@/composables/useLogger'
 import { TresAttributes, TresCatalogue, TresInstance, TresVNode, TresVNodeType, TresEvent } from '/@/types'
 
 const VECTOR3_PROPS = ['rotation', 'scale', 'position']

+ 1 - 1
packages/tres/src/core/useLoader/index.ts

@@ -1,6 +1,6 @@
 import { isArray } from '@alvarosabu/utils'
 import { Object3D } from 'three'
-import { useLogger } from '/@/composables'
+import { useLogger } from '/@/composables/useLogger'
 
 export interface TresLoader<T> extends THREE.Loader {
   load(

+ 51 - 0
packages/tres/src/core/useRenderLoop/useRenderLoop.ts

@@ -0,0 +1,51 @@
+import { createEventHook, EventHookOn, Fn, useRafFn } from '@vueuse/core'
+import { Ref } from 'vue'
+import { Clock } from 'three'
+
+export interface RenderLoop {
+  delta: number
+  elapsed: number
+  clock: Clock
+}
+
+export interface UseRenderLoopReturn {
+  onBeforeLoop: EventHookOn<RenderLoop>
+  onLoop: EventHookOn<RenderLoop>
+  onAfterLoop: EventHookOn<RenderLoop>
+  pause: Fn
+  resume: Fn
+  isActive: Ref<boolean>
+}
+
+const onBeforeLoop = createEventHook<RenderLoop>()
+const onLoop = createEventHook<RenderLoop>()
+const onAfterLoop = createEventHook<RenderLoop>()
+
+const clock = new Clock()
+let delta = 0
+let elapsed = 0
+
+const { pause, resume, isActive } = useRafFn(
+  () => {
+    onBeforeLoop.trigger({ delta, elapsed, clock })
+    onLoop.trigger({ delta, elapsed, clock })
+    onAfterLoop.trigger({ delta, elapsed, clock })
+  },
+  { immediate: false },
+)
+
+onAfterLoop.on(() => {
+  delta = clock.getDelta()
+  elapsed = clock.getElapsedTime()
+})
+
+export function useRenderLoop(): UseRenderLoopReturn {
+  return {
+    onBeforeLoop: onBeforeLoop.on,
+    onLoop: onLoop.on,
+    onAfterLoop: onAfterLoop.on,
+    pause,
+    resume,
+    isActive,
+  }
+}

+ 1 - 1
packages/tres/src/core/useRenderer/component.ts

@@ -2,7 +2,7 @@ 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 { useLogger } from '/@/composables/useLogger'
 import { TresVNodeType } from '/@/types'
 
 /**

+ 1 - 1
packages/tres/src/core/useRenderer/index.ts

@@ -24,7 +24,7 @@ import { normalizeColor } from '/@/utils/normalize'
 import { TresColor } from '/@/types'
 import { rendererPresets, RendererPresetsType } from './const'
 import { merge } from '/@/utils'
-import { useLogger } from '/@/composables'
+import { useLogger } from '/@/composables/useLogger'
 
 export interface UseRendererOptions extends WebGLRendererParameters {
   /**

+ 3 - 39
packages/tres/src/index.ts

@@ -1,42 +1,6 @@
-import { App, Component } from 'vue'
-import { TresCanvas } from '/@/core/useRenderer/component'
-import { Scene } from '/@/core/useScene/component'
-import { useCatalogue, useInstanceCreator, useTres } from '/@/core'
-export * from '/@/core'
+export * from './components'
+export * from './composables'
 export * from './keys'
 export * from './types'
 
-export interface TresOptions {
-  prefix?: string
-  extends?: Record<string, unknown>
-}
-export interface TresPlugin {
-  [key: string]: any
-  install: (app: App, options?: TresOptions) => void
-}
-
-const plugin: TresPlugin = {
-  install(app: App, options) {
-    const prefix = options?.prefix || 'Tres'
-
-    // Register core components
-    app.component(`${prefix}Canvas`, TresCanvas)
-    app.component(`${prefix}Scene`, Scene)
-
-    // Initialize catalogue
-    const { catalogue, extend } = useCatalogue(app, prefix)
-    app.provide('catalogue', catalogue)
-    app.provide('extend', extend)
-    app.provide('useTres', useTres())
-
-    // Create components from catalogue
-    const { createComponentInstances } = useInstanceCreator(prefix)
-    const components = createComponentInstances(catalogue)
-
-    components.forEach(([key, cmp]) => {
-      app.component(key as string, cmp as Component)
-    })
-  },
-}
-
-export default plugin
+export * from '../.tres/components'

+ 3 - 3
packages/tres/src/main.ts

@@ -1,9 +1,9 @@
 import { createApp } from 'vue'
 import App from './App.vue'
-import plugin from '.'
+/* import plugin from '.' */
 import './style.css'
 
 export const app = createApp(App)
-
-app.use(plugin)
+/* 
+app.use(plugin) */
 app.mount('#app')

+ 42 - 0
packages/tres/src/plugin.ts

@@ -0,0 +1,42 @@
+import { App, Component } from 'vue'
+import { TresCanvas } from '/@/core/useRenderer/component'
+import { Scene } from '/@/core/useScene/component'
+import { useCatalogue, useInstanceCreator, useTres } from '/@/core'
+export * from '/@/core'
+export * from './keys'
+export * from './types'
+
+export interface TresOptions {
+  prefix?: string
+  extends?: Record<string, unknown>
+}
+export interface TresPlugin {
+  [key: string]: any
+  install: (app: App, options?: TresOptions) => void
+}
+
+const plugin: TresPlugin = {
+  install(app: App, options) {
+    const prefix = options?.prefix || 'Tres'
+
+    // Register core components
+    app.component(`${prefix}Canvas`, TresCanvas)
+    app.component(`${prefix}Scene`, Scene)
+
+    // Initialize catalogue
+    const { catalogue, extend } = useCatalogue(app, prefix)
+    app.provide('catalogue', catalogue)
+    app.provide('extend', extend)
+    app.provide('useTres', useTres())
+
+    // Create components from catalogue
+    const { createComponentInstances } = useInstanceCreator(prefix)
+    const components = createComponentInstances(catalogue)
+
+    components.forEach(([key, cmp]) => {
+      app.component(key as string, cmp as Component)
+    })
+  },
+}
+
+export default plugin

+ 3 - 1
packages/tres/tsconfig.json

@@ -9,6 +9,7 @@
     "sourceMap": true,
     "resolveJsonModule": true,
     "esModuleInterop": true,
+    "noImplicitAny": false,
     "lib": ["esnext", "dom"],
     "types": ["vite/client", "node", "vitest/globals"],
     "incremental": false,
@@ -18,7 +19,8 @@
     "forceConsistentCasingInFileNames": true,
     "declaration": true,
     "paths": {
-      "/@/*": ["src/*"]
+      "/@/*": ["src/*"],
+      "#tres/*": [".tres/"]
     }
   },
   "include": [

+ 2 - 1
packages/tres/tsconfig.node.json

@@ -6,7 +6,8 @@
     "moduleResolution": "Node",
     "allowSyntheticDefaultImports": true,
     "paths": {
-      "/@/*": ["src/*"]
+      "/@/*": ["src/*"],
+      "#tres/*": [".tres/"]
     },
     "types": ["vitest/globals"]
   },

+ 2 - 0
packages/tres/vite.config.ts

@@ -28,6 +28,7 @@ export default defineConfig({
   resolve: {
     alias: {
       '/@': resolve(__dirname, './src'),
+      '#tres': resolve(__dirname, '.tres'),
     },
     dedupe: ['@tresjs/cientos'],
   },
@@ -38,6 +39,7 @@ export default defineConfig({
     ViteTresPlugin(),
     dts({
       insertTypesEntry: true,
+      include: ['.tres/components/**/*.ts'],
     }),
     banner({
       content: `/**\n * name: ${pkg.name}\n * version: v${

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 342 - 0
packages/tres/vite.config.ts.timestamp-1678272846346.mjs


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio