فهرست منبع

fix: manually iterate all writable properties and asign transform withcopy

alvarosabu 11 ماه پیش
والد
کامیت
2788afe762

+ 342 - 0
playground/src/pages/advanced/cientos/ContactShadowsLocal.vue

@@ -0,0 +1,342 @@
+<script setup lang="ts">
+// The author of the original code is @mrdoob https://twitter.com/mrdoob
+// https://threejs.org/examples/?q=con#webgl_shadow_contact
+
+// As well, basically the same implementation as in pmndrs drei but with Vue Composition API
+// https://github.com/pmndrs/drei/blob/master/src/core/ContactShadows.tsx#L113
+
+import type { TresColor } from '@tresjs/core'
+import { useLoop, useRenderLoop, useTresContext } from '@tresjs/core'
+import type {
+  ColorRepresentation,
+  Material,
+  Texture,
+} from 'three'
+import {
+  Color,
+  Mesh,
+  MeshDepthMaterial,
+  OrthographicCamera,
+  PlaneGeometry,
+  ShaderMaterial,
+  WebGLRenderTarget,
+} from 'three'
+import { computed, shallowRef, watchEffect } from 'vue'
+import { HorizontalBlurShader, VerticalBlurShader } from 'three-stdlib'
+import { useThrottleFn } from '@vueuse/core'
+
+export interface ContactShadowsProps {
+  /**
+   *
+   * The opacity of the shadows.
+   *
+   * @default 1
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  opacity?: number
+  /**
+   * The width of the shadows.
+   *
+   * @default 1
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  width?: number
+  /**
+   * The height of the shadows.
+   *
+   * @default 1
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  height?: number
+  /**
+   * The blur of the shadows.
+   *
+   * @default 1
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  blur?: number
+  /**
+   * How far the OrthographicCamera should be to capture the shadows.
+   *
+   * @default 10
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  far?: number
+  /**
+   * Whether the shadows should be smooth or not.
+   *
+   * @default true
+   * @type {boolean}
+   * @memberof ContactShadowsProps
+   *
+   */
+  smooth?: boolean
+  /**
+   * The resolution of the shadows.
+   *
+   * @default 512
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  resolution?: number
+  /**
+   * The number of frames to render the shadows.
+   *
+   * @default Infinity
+   * @type {number}
+   * @memberof ContactShadowsProps
+   *
+   */
+  frames?: number
+  /**
+   * The scale of the shadows.
+   *
+   * @default 10
+   * @type {(number | [x: number, y: number])}
+   * @memberof ContactShadowsProps
+   *
+   */
+  scale?: number | [x: number, y: number]
+  /**
+   * The color of the shadows.
+   *
+   * @default '#000000'
+   * @type {TresColor}
+   * @memberof ContactShadowsProps
+   *
+   */
+  color?: TresColor
+  /**
+   * Whether the shadows should write to the depth buffer or not.
+   *
+   * @default false
+   * @type {boolean}
+   * @memberof ContactShadowsProps
+   *
+   */
+  depthWrite?: boolean
+  /**
+   * Whether the OrthographicCamera helper should be visible or not.
+   *
+   * @default false
+   * @type {boolean}
+   * @memberof ContactShadowsProps
+   *
+   */
+  helper?: boolean
+}
+
+const props = withDefaults(defineProps<ContactShadowsProps>(), {
+  scale: 10,
+  frames: Number.POSITIVE_INFINITY,
+  opacity: 1,
+  width: 1,
+  height: 1,
+  blur: 1,
+  far: 10,
+  resolution: 512,
+  smooth: true,
+  color: '#000000',
+  depthWrite: false,
+  helper: false,
+})
+
+const groupRef = shallowRef()
+const shadowCamera = shallowRef<OrthographicCamera>()
+
+/* defineExpose(groupRef) */
+
+let renderTarget: WebGLRenderTarget, renderTargetBlur: WebGLRenderTarget
+let planeGeometry: PlaneGeometry, blurPlane: Mesh
+let depthMaterial: MeshDepthMaterial
+
+const { renderer, scene } = useTresContext()
+
+const cameraW = computed(() => props.width * (Array.isArray(props.scale) ? props.scale[0] : props.scale || 1))
+const cameraH = computed(() => props.height * (Array.isArray(props.scale) ? props.scale[1] : props.scale || 1))
+
+watchEffect(() => {
+  // the render target that will show the shadows in the plane texture and
+  // the target that we will use to blur the first render target
+  if (renderTarget) { renderTarget.dispose() }
+  if (renderTargetBlur) { renderTargetBlur.dispose() }
+  if (planeGeometry) { planeGeometry.dispose() }
+  if (blurPlane) { blurPlane.geometry.dispose() }
+  renderTarget = new WebGLRenderTarget(props.resolution, props.resolution)
+  renderTargetBlur = new WebGLRenderTarget(props.resolution, props.resolution)
+  renderTargetBlur.texture.generateMipmaps = renderTarget.texture.generateMipmaps = false
+
+  shadowCamera.value = new OrthographicCamera(
+    -cameraW.value / 2,
+    cameraW.value / 2,
+    cameraH.value / 2,
+    -cameraH.value / 2,
+    0,
+    props.far,
+  )
+
+  planeGeometry = new PlaneGeometry(cameraW.value, cameraH.value).rotateX(Math.PI / 2)
+  blurPlane = new Mesh(planeGeometry)
+  blurPlane.visible = false
+  console.log('shadowCamera', {
+    left: shadowCamera.value.left,
+    right: shadowCamera.value.right,
+    top: shadowCamera.value.top,
+    bottom: shadowCamera.value.bottom,
+    near: shadowCamera.value.near,
+    far: shadowCamera.value.far,
+    cameraH: cameraH.value,
+    cameraW: cameraW.value,
+  })
+})
+
+watchEffect(() => {
+  if (props.color) {
+    if (depthMaterial) { depthMaterial.dispose() }
+    depthMaterial = new MeshDepthMaterial()
+    depthMaterial.depthTest = depthMaterial.depthWrite = false
+
+    // Overwrite depthMaterial sahders
+    depthMaterial.onBeforeCompile = (shader) => {
+      shader.uniforms = {
+        ...shader.uniforms,
+        ucolor: { value: props.color ? new Color(props.color as ColorRepresentation) : new Color() },
+      }
+      shader.fragmentShader = shader.fragmentShader.replace(
+        'void main() {', //
+        `uniform vec3 ucolor;
+             void main() {
+            `,
+      )
+      shader.fragmentShader = shader.fragmentShader.replace(
+        'vec4( vec3( 1.0 - fragCoordZ ), opacity );',
+        // Colorize the shadow, multiply by the falloff so that the center can remain darker
+        'vec4( ucolor * fragCoordZ * 2.0, ( 1.0 - fragCoordZ ) * 1.0 );',
+      )
+    }
+  }
+})
+
+// make a plane and make it face up
+
+// Initialize the blur shaders
+const horizontalBlurMaterial = new ShaderMaterial(HorizontalBlurShader)
+const verticalBlurMaterial = new ShaderMaterial(VerticalBlurShader)
+verticalBlurMaterial.depthTest = horizontalBlurMaterial.depthTest = false
+
+// Blur the shadow
+
+function blurShadows(blur: number) {
+  if (!renderer.value || !shadowCamera.value) { return }
+
+  blurPlane.visible = true
+  blurPlane.material = horizontalBlurMaterial
+  horizontalBlurMaterial.uniforms.tDiffuse.value = renderTarget.texture
+  horizontalBlurMaterial.uniforms.h.value = blur / 256
+
+  renderer.value.setRenderTarget(renderTargetBlur)
+  renderer.value.render(blurPlane, unref(shadowCamera.value))
+
+  blurPlane.material = verticalBlurMaterial
+  verticalBlurMaterial.uniforms.tDiffuse.value = renderTargetBlur.texture
+  verticalBlurMaterial.uniforms.v.value = blur / 256
+
+  renderer.value.setRenderTarget(renderTarget)
+  renderer.value.render(blurPlane, unref(shadowCamera.value))
+
+  blurPlane.visible = false
+}
+
+let count = 0
+let initialBackground: Color | Texture | null
+let initialOverrideMaterial: Material | null
+
+const { onBeforeRender } = useLoop()
+const log = useThrottleFn(state => console.log('updating sphere', state), 3000)
+
+onBeforeRender(() => {
+  if (!shadowCamera.value || scene.value === undefined || renderer.value === undefined) { return }
+
+  log({
+    left: shadowCamera.value.left,
+    right: shadowCamera.value.right,
+    top: shadowCamera.value.top,
+    bottom: shadowCamera.value.bottom,
+    near: shadowCamera.value.near,
+    far: shadowCamera.value.far,
+    cameraH: cameraH.value,
+    cameraW: cameraW.value,
+  })
+
+  if (props.frames === Number.POSITIVE_INFINITY || count < props.frames) {
+    count++
+
+    // Save the initial background and override material
+    initialBackground = scene.value.background
+    initialOverrideMaterial = scene.value.overrideMaterial
+
+    // Render the shadows
+    groupRef.value.visible = false
+    scene.value.background = null
+    scene.value.overrideMaterial = depthMaterial
+
+    renderer.value.setRenderTarget(renderTarget)
+    renderer.value.render(scene.value, unref(shadowCamera.value))
+
+    // Blur the shadows
+    blurShadows(props.blur)
+
+    if (props.smooth) {
+      blurShadows(props.blur * 0.4)
+    }
+
+    // Restore the initial background and override material
+    renderer.value.setRenderTarget(null)
+
+    groupRef.value.visible = true
+    scene.value.background = initialBackground
+    scene.value.overrideMaterial = initialOverrideMaterial
+  }
+}, -1)
+</script>
+
+<template>
+  <TresGroup
+    ref="groupRef"
+    v-bind="$attrs"
+  >
+    <TresMesh
+      :scale="[1, -1, 1]"
+      :geometry="planeGeometry"
+    >
+      <TresMeshBasicMaterial
+        :map="renderTarget.texture"
+        :opacity="opacity"
+        :depth-write="depthWrite"
+        :transparent="true"
+      />
+    </TresMesh>
+    <primitive :object="blurPlane" />
+
+    <!--  <TresCameraHelper
+      v-if="shadowCamera && helper"
+      :args="[shadowCamera]"
+    /> -->
+    <TresOrthographicCamera
+      ref="shadowCamera"
+      :position="[0, 0, 0]"
+      :rotation="[Math.PI / 2, 0, 0]"
+    />
+  </TresGroup>
+</template>

+ 96 - 0
playground/src/pages/advanced/cientos/DemoContactShadows.vue

@@ -0,0 +1,96 @@
+<script setup lang="ts">
+import { useControls } from '@tresjs/leches'
+
+import { Box, Icosahedron, Plane } from '@tresjs/cientos'
+import { useLoop } from '@tresjs/core'
+import ContactShadows from './ContactShadowsLocal.vue'
+
+const boxRef = shallowRef()
+const icoRef = shallowRef()
+
+const state = reactive({
+  blur: 3.5,
+  opacity: 1,
+  resolution: 512,
+  color: '#0000ff',
+  helper: true,
+})
+
+const { blur, opacity, resolution, color, helper } = useControls({
+  blur: {
+    value: state.blur,
+    step: 0.1,
+    min: 0,
+    max: 10,
+  },
+  opacity: {
+    value: state.opacity,
+    step: 0.1,
+    min: 0,
+    max: 1,
+  },
+  resolution: {
+    value: state.resolution,
+    step: 1,
+    min: 0,
+    max: 1024,
+  },
+  color: {
+    type: 'color',
+    value: state.color,
+  },
+  helper: state.helper,
+})
+
+/* watch([blur, opacity, resolution, color, helper], () => {
+  state.blur = blur
+  state.opacity = opacity
+  state.resolution = resolution
+  state.color = color
+  state.helper = helper
+}) */
+
+const { onBeforeRender } = useLoop()
+
+onBeforeRender(() => {
+  if (boxRef.value) {
+    boxRef.value.value.rotation.y += 0.02
+    boxRef.value.value.rotation.x += 0.01
+  }
+  if (icoRef.value) {
+    icoRef.value.value.rotation.y += 0.02
+    icoRef.value.value.rotation.x += 0.01
+  }
+})
+</script>
+
+<template>
+  <Box
+    ref="boxRef"
+    :args="[0.4, 0.4, 0.4]"
+    :position="[0, 1, 0]"
+  >
+    <TresMeshNormalMaterial />
+  </Box>
+  <Icosahedron
+    ref="icoRef"
+    :args="[0.3]"
+    :position="[1, 1, 1]"
+  >
+    <TresMeshNormalMaterial />
+  </Icosahedron>
+
+  <Plane
+    :args="[10, 10, 10]"
+    :position="[0, -0.02, 0]"
+  >
+    <TresMeshBasicMaterial color="#ffffff" />
+  </Plane>
+  <ContactShadows
+    :blur="state.blur"
+    :resolution="state.resolution"
+    :opacity="state.opacity"
+    :color="state.color"
+    :helper="state.helper"
+  />
+</template>

+ 29 - 0
playground/src/pages/advanced/cientos/index.vue

@@ -0,0 +1,29 @@
+<script setup lang="ts">
+import { TresCanvas } from '@tresjs/core'
+import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
+import { TresLeches, useControls } from '@tresjs/leches'
+import '@tresjs/leches/styles'
+import { OrbitControls } from '@tresjs/cientos'
+import DemoContactShadows from './DemoContactShadows.vue'
+
+const gl = {
+  clearColor: '#fff',
+  shadows: true,
+  alpha: false,
+  shadowMapType: BasicShadowMap,
+  outputColorSpace: SRGBColorSpace,
+  toneMapping: NoToneMapping,
+}
+
+useControls('fpsgraph')
+</script>
+
+<template>
+  <TresLeches />
+  <TresCanvas v-bind="gl">
+    <TresPerspectiveCamera :position="[3, 3, 3]" />
+    <OrbitControls />
+    <DemoContactShadows />
+    <TresAmbientLight :intensity="1" />
+  </TresCanvas>
+</template>

+ 5 - 0
playground/src/router/routes/advanced.ts

@@ -19,4 +19,9 @@ export const advancedRoutes = [
     name: 'FBO',
     component: () => import('../../pages/advanced/FBO.vue'),
   },
+  {
+    path: '/advanced/cientos',
+    name: 'Cientos',
+    component: () => import('../../pages/advanced/cientos/index.vue'),
+  },
 ]

+ 17 - 4
src/core/nodeOps.ts

@@ -13,6 +13,11 @@ function noop(fn: string): any {
   fn
 }
 
+function isReadOnly(obj, prop) {
+  const descriptor = Object.getOwnPropertyDescriptor(obj, prop)
+  return descriptor && descriptor.writable === false
+}
+
 const { logError } = useLogger()
 
 const supportedPointerEvents = [
@@ -238,10 +243,18 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
           && prevArgs.length
           && !deepArrayEqual(prevArgs, args)
         ) {
-          root = Object.assign(
-            prevNode,
-            new catalogue.value[instanceName](...nextValue),
-          )
+          const newInstance = new catalogue.value[instanceName](...nextValue)
+
+          // Manually copy properties that are not read-only
+          for (const key in prevNode) {
+            if (Object.prototype.hasOwnProperty.call(prevNode, key) && !isReadOnly(prevNode, key)) {
+              newInstance[key] = prevNode[key]
+            }
+          }
+          newInstance.position.copy(prevNode.position)
+          newInstance.rotation.copy(prevNode.rotation)
+          newInstance.scale.copy(prevNode.scale)
+          root = newInstance
         }
         return
       }