diff --git a/README.md b/README.md index d6ae74ed..4ce23356 100644 --- a/README.md +++ b/README.md @@ -14,27 +14,27 @@ Build performant 3D user interfaces for Three.js using @react-three/fiber and yo TODO Release +- fix: changing font weight with hot reload (test if its the same for normal react state change) +- fix: conditionally render children (see Discord) +- feat: ref.current.setStyle({ ... }) - feat: nesting inside non root/container components (e.g. image) -- feat: support more characters for different languages -- fix: always loading normal font - fix: scrollbar border radius to high (happens with very long panels) - feat: drag/click threshold - feat: cli for kits - feat: add apfel components -- feat: Content "measureContent" flag => allow disabling content measuring and scaling -- feat: support for visibility="hidden" - feat: input - fix: decrease clipping rect when scrollbar present -TODO Later +Roadmap - on demand rendering to save battery for UI only apps / rendering to render targets - upgrade to yoga2.0 - virtual lists (support thousands of elements in a list by using fixed sizes and not using yoga) - option to render to seperate render targets depending on element type (e.g. render text to high quality quad layer for WebXR) - scrollIntoView -- tailwind colors (with option to support dark mode) - Instancing for icons +- Support more characters for different languages +- Support for visibility="hidden" & display="none" Limitations @@ -48,4 +48,4 @@ Limitations `pnpm -r generate` `pnpm -r build` -go to `examples/dashboard` and run `pnpm dev` to view the example dashboard +go to `examples/market` and run `pnpm dev` to view the example dashboard diff --git a/examples/card/src/App.tsx b/examples/card/src/App.tsx index 9e67e41c..d36d6a12 100644 --- a/examples/card/src/App.tsx +++ b/examples/card/src/App.tsx @@ -11,6 +11,7 @@ import { Switch } from '@/switch' import { useMemo, useRef } from 'react' import { signal } from '@preact/signals-core' import { damp } from 'three/src/math/MathUtils.js' +import { MeshPhongMaterial } from 'three' setPreferredColorScheme('light') @@ -18,10 +19,13 @@ export default function App() { return ( + + @@ -67,7 +71,7 @@ export function CardPage() { openRef.current = !openRef.current }} cursor="pointer" - zIndexOffset={1} + zIndexOffset={10} transformTranslateZ={translateZ} > @@ -80,6 +84,7 @@ export function CardPage() { alignItems="center" justifyContent="space-between" borderRadiusBottom={20} + castShadow > @@ -98,6 +103,8 @@ export function CardPage() { , 'hover'> + containerHoverProps?: ComponentPropsWithoutRef['hover'] + } +} = { default: { defaultProps: { color: colors.primaryForeground, }, containerProps: { backgroundColor: colors.primary, - hover: { - backgroundOpacity: 0.8, - }, + }, + containerHoverProps: { + backgroundOpacity: 0.8, }, }, secondary: { @@ -20,9 +26,9 @@ const badgeVariants = { }, containerProps: { backgroundColor: colors.secondary, - hover: { - backgroundOpacity: 0.8, - }, + }, + containerHoverProps: { + backgroundOpacity: 0.8, }, }, destructive: { @@ -31,30 +37,31 @@ const badgeVariants = { }, containerProps: { backgroundColor: colors.destructive, - hover: { - backgroundOpacity: 0.8, - }, + }, + containerHoverProps: { + backgroundOpacity: 0.8, }, }, - outline: { - defaultProps: {}, - containerProps: {}, - }, -} satisfies { - [Key in string]: { - defaultProps: AllOptionalProperties - containerProps: ComponentPropsWithoutRef - } + outline: {}, } export function Badge({ children, variant = 'default', + hover, ...props }: ComponentPropsWithoutRef & { variant?: keyof typeof badgeVariants }) { - const { containerProps, defaultProps } = badgeVariants[variant] + const { containerProps, defaultProps, containerHoverProps } = badgeVariants[variant] return ( - + {children} diff --git a/packages/kits/default/button.tsx b/packages/kits/default/button.tsx index 852ef0f4..02291114 100644 --- a/packages/kits/default/button.tsx +++ b/packages/kits/default/button.tsx @@ -2,60 +2,61 @@ import { AllOptionalProperties, Container, DefaultProperties } from '@react-thre import { ComponentPropsWithoutRef } from 'react' import { colors } from './theme.js' -const buttonVariants = { +const buttonVariants: { + [Key in string]: { + containerHoverProps?: ComponentPropsWithoutRef['hover'] + containerProps?: Omit, 'hover'> + defaultProps?: AllOptionalProperties + } +} = { default: { + containerHoverProps: { + backgroundOpacity: 0.9, + }, containerProps: { backgroundColor: colors.primary, - hover: { - backgroundOpacity: 0.9, - }, }, defaultProps: { color: colors.primaryForeground, }, }, destructive: { + containerHoverProps: { + backgroundOpacity: 0.9, + }, containerProps: { backgroundColor: colors.destructive, - hover: { - backgroundOpacity: 0.9, - }, }, defaultProps: { color: colors.destructiveForeground, }, }, outline: { + containerHoverProps: { + backgroundColor: colors.accent, + }, containerProps: { border: 1, borderColor: colors.input, backgroundColor: colors.background, - hover: { - backgroundColor: colors.accent, - }, }, - defaultProps: {}, }, //TODO: hover:text-accent-foreground", secondary: { + containerHoverProps: { + backgroundOpacity: 0.8, + }, containerProps: { backgroundColor: colors.secondary, - hover: { - backgroundOpacity: 0.8, - }, }, defaultProps: { color: colors.secondaryForeground, }, }, ghost: { - containerProps: { - hover: { - backgroundColor: colors.accent, - }, - }, - defaultProps: { - hover: {}, + containerHoverProps: { + backgroundColor: colors.accent, }, + defaultProps: {}, }, // TODO: hover:text-accent-foreground", link: { containerProps: {}, @@ -63,11 +64,6 @@ const buttonVariants = { color: colors.primary, }, }, //TODO: underline-offset-4 hover:underline", -} satisfies { - [Key in string]: { - containerProps: ComponentPropsWithoutRef - defaultProps: AllOptionalProperties - } } const buttonSizes = { @@ -82,13 +78,14 @@ export function Button({ variant = 'default', size = 'default', disabled = false, + hover, ...props }: ComponentPropsWithoutRef & { variant?: keyof typeof buttonVariants size?: keyof typeof buttonSizes disabled?: boolean }) { - const { containerProps, defaultProps } = buttonVariants[variant] + const { containerProps, defaultProps, containerHoverProps } = buttonVariants[variant] const sizeProps = buttonSizes[size] return ( @@ -102,6 +99,10 @@ export function Button({ backgroundOpacity={disabled ? 0.5 : undefined} cursor={disabled ? undefined : 'pointer'} flexDirection="row" + hover={{ + ...containerHoverProps, + ...hover, + }} {...props} > ) -} +}*/ diff --git a/packages/kits/default/carousel.tsx b/packages/kits/default/carousel.tsx index b6f5918b..550e702f 100644 --- a/packages/kits/default/carousel.tsx +++ b/packages/kits/default/carousel.tsx @@ -1,6 +1,4 @@ -'use client' - -import * as React from 'react' +/*import * as React from 'react' import useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react' import { ArrowLeft, ArrowRight } from 'lucide-react' @@ -224,3 +222,4 @@ const CarouselNext = React.forwardRef { ) -}*/ - - +} export function Command() { export function diff --git a/packages/kits/default/drawer.tsx b/packages/kits/default/drawer.tsx index 1d1ff5be..f0813b9f 100644 --- a/packages/kits/default/drawer.tsx +++ b/packages/kits/default/drawer.tsx @@ -1,6 +1,4 @@ -'use client' - -import * as React from 'react' +/*import * as React from 'react' import { Drawer as DrawerPrimitive } from 'vaul' import { cn } from '@/lib/utils' @@ -86,4 +84,4 @@ export { DrawerFooter, DrawerTitle, DrawerDescription, -} +}*/ diff --git a/packages/kits/default/dropdown-menu.tsx b/packages/kits/default/dropdown-menu.tsx index 4f266f12..b6e20482 100644 --- a/packages/kits/default/dropdown-menu.tsx +++ b/packages/kits/default/dropdown-menu.tsx @@ -1,6 +1,4 @@ -'use client' - -import * as React from 'react' +/*import * as React from 'react' import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' import { Check, ChevronRight, Circle } from 'lucide-react' @@ -178,4 +176,4 @@ export { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, -} +}*/ diff --git a/packages/kits/default/hover-card.tsx b/packages/kits/default/hover-card.tsx index 7b9302ac..f5f49293 100644 --- a/packages/kits/default/hover-card.tsx +++ b/packages/kits/default/hover-card.tsx @@ -1,6 +1,4 @@ -'use client' - -import * as React from 'react' +/*import * as React from 'react' import * as HoverCardPrimitive from '@radix-ui/react-hover-card' import { cn } from '@/lib/utils' @@ -26,4 +24,4 @@ const HoverCardContent = React.forwardRef< )) HoverCardContent.displayName = HoverCardPrimitive.Content.displayName -export { HoverCard, HoverCardTrigger, HoverCardContent } +export { HoverCard, HoverCardTrigger, HoverCardContent }*/ diff --git a/packages/kits/default/navigation-menu.tsx b/packages/kits/default/navigation-menu.tsx index e2ca5b19..23aa1003 100644 --- a/packages/kits/default/navigation-menu.tsx +++ b/packages/kits/default/navigation-menu.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +/*import * as React from 'react' import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu' import { cva } from 'class-variance-authority' import { ChevronDown } from 'lucide-react' @@ -117,4 +117,4 @@ export { NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, -} +}*/ diff --git a/packages/kits/default/pagination.tsx b/packages/kits/default/pagination.tsx index 32827a2f..4647be49 100644 --- a/packages/kits/default/pagination.tsx +++ b/packages/kits/default/pagination.tsx @@ -13,22 +13,27 @@ export function PaginationContent(props: ComponentPropsWithoutRef, 'hover'> + containerHoverProps?: ComponentPropsWithoutRef['hover'] + } +} = { outline: { - border: 1, - borderColor: colors.input, - backgroundColor: colors.background, - hover: { + containerProps: { + border: 1, + borderColor: colors.input, + backgroundColor: colors.background, + }, + containerHoverProps: { backgroundColor: colors.accent, }, }, //TODO: hover:text-accent-foreground", ghost: { - hover: { + containerHoverProps: { backgroundColor: colors.accent, }, }, // TODO: hover:text-accent-foreground", -} satisfies { - [Key in string]: ComponentPropsWithoutRef } const paginationSizes = { @@ -41,12 +46,13 @@ const paginationSizes = { export function PaginationLink({ isActive = false, size = 'icon', + hover, ...props }: ComponentPropsWithoutRef & { size?: keyof typeof paginationSizes isActive?: boolean }) { - const containerProps = paginationVariants[isActive ? 'outline' : 'ghost'] + const { containerProps, containerHoverProps } = paginationVariants[isActive ? 'outline' : 'ghost'] const sizeProps = paginationSizes[size] return ( ) -} +}*/ diff --git a/packages/kits/default/toggle-group.tsx b/packages/kits/default/toggle-group.tsx index cc122bee..a509980f 100644 --- a/packages/kits/default/toggle-group.tsx +++ b/packages/kits/default/toggle-group.tsx @@ -2,11 +2,13 @@ import { Container, DefaultProperties } from '@react-three/uikit' import { ComponentPropsWithoutRef, createContext, useContext, useState } from 'react' import { colors } from './theme.js' -const toggleVariants = { - default: { - containerProps: {}, - containerHoverProps: {}, - }, +const toggleVariants: { + [Key in string]: { + containerProps?: ComponentPropsWithoutRef + containerHoverProps?: ComponentPropsWithoutRef['hover'] + } +} = { + default: {}, outline: { containerProps: { border: 1, @@ -17,11 +19,6 @@ const toggleVariants = { }, }, //TODO: hover:text-accent-foreground -} satisfies { - [Key in string]: { - containerProps: ComponentPropsWithoutRef - containerHoverProps: ComponentPropsWithoutRef['hover'] - } } const toggleSizes = { default: { height: 40, paddingX: 12 }, @@ -56,6 +53,7 @@ export function ToggleGroupItem({ checked: providedChecked, disabled = false, onCheckedChange, + hover, ...props }: ComponentPropsWithoutRef & { defaultChecked?: boolean @@ -85,7 +83,9 @@ export function ToggleGroupItem({ backgroundOpacity={disabled ? 0.5 : undefined} borderOpacity={disabled ? 0.5 : undefined} backgroundColor={checked ? colors.accent : undefined} - hover={disabled ? undefined : { backgroundColor: colors.muted, ...toggleVariants[variant].containerHoverProps }} + hover={ + disabled ? hover : { backgroundColor: colors.muted, ...toggleVariants[variant].containerHoverProps, ...hover } + } {...toggleVariants[variant].containerProps} {...toggleSizes[size]} {...props} diff --git a/packages/kits/default/toggle.tsx b/packages/kits/default/toggle.tsx index 98a0c94c..eb906371 100644 --- a/packages/kits/default/toggle.tsx +++ b/packages/kits/default/toggle.tsx @@ -2,11 +2,13 @@ import { Container, DefaultProperties } from '@react-three/uikit' import { ComponentPropsWithoutRef, useState } from 'react' import { colors } from './theme.js' -const toggleVariants = { - default: { - containerProps: {}, - containerHoverProps: {}, - }, +const toggleVariants: { + [Key in string]: { + containerProps?: ComponentPropsWithoutRef + containerHoverProps?: ComponentPropsWithoutRef['hover'] + } +} = { + default: {}, outline: { containerProps: { border: 1, @@ -17,11 +19,6 @@ const toggleVariants = { }, }, //TODO: hover:text-accent-foreground -} satisfies { - [Key in string]: { - containerProps: ComponentPropsWithoutRef - containerHoverProps: ComponentPropsWithoutRef['hover'] - } } const toggleSizes = { default: { height: 40, paddingX: 12 }, @@ -37,6 +34,7 @@ export function Toggle({ checked: providedChecked, disabled = false, onCheckedChange, + hover, ...props }: ComponentPropsWithoutRef & { defaultChecked?: boolean @@ -67,7 +65,9 @@ export function Toggle({ backgroundOpacity={disabled ? 0.5 : undefined} borderOpacity={disabled ? 0.5 : undefined} backgroundColor={checked ? colors.accent : undefined} - hover={disabled ? undefined : { backgroundColor: colors.muted, ...toggleVariants[variant].containerHoverProps }} + hover={ + disabled ? hover : { backgroundColor: colors.muted, ...toggleVariants[variant].containerHoverProps, ...hover } + } {...toggleVariants[variant].containerProps} {...toggleSizes[size]} {...props} diff --git a/packages/kits/default/use-toast.tsx b/packages/kits/default/use-toast.tsx index 81989388..a95d5f46 100644 --- a/packages/kits/default/use-toast.tsx +++ b/packages/kits/default/use-toast.tsx @@ -1,5 +1,5 @@ // Inspired by react-hot-toast library -import * as React from 'react' +/*import * as React from 'react' import type { ToastActionElement, ToastProps } from '@/registry/default/ui/toast' @@ -184,4 +184,4 @@ function useToast() { } } -export { useToast, toast } +export { useToast, toast }*/ diff --git a/packages/uikit/src/components/container.tsx b/packages/uikit/src/components/container.tsx index 813bab97..4e03ba28 100644 --- a/packages/uikit/src/components/container.tsx +++ b/packages/uikit/src/components/container.tsx @@ -1,7 +1,14 @@ import { ReactNode, forwardRef, useRef } from 'react' import { useFlexNode, FlexProvider } from '../flex/react.js' import { WithReactive, createCollection, finalizeCollection } from '../properties/utils.js' -import { InteractionGroup, MaterialClass, useInstancedPanel, useInteractionPanel } from '../panel/react.js' +import { + InteractionGroup, + MaterialClass, + ShadowProperties, + useInstancedPanel, + useInteractionPanel, + usePanelGroupDependencies, +} from '../panel/react.js' import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events.js' import { YogaProperties } from '../flex/node.js' import { useApplyHoverProperties } from '../hover.js' @@ -57,7 +64,8 @@ export const Container = forwardRef< EventHandlers & LayoutListeners & ViewportListeners & - ScrollListeners + ScrollListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const groupRef = useRef(null) @@ -67,7 +75,8 @@ export const Container = forwardRef< const parentClippingRect = useParentClippingRect() const globalMatrix = useGlobalMatrix(transformMatrix) const isClipped = useIsClipped(parentClippingRect, globalMatrix, node.size, node) - const orderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset) + const groupDeps = usePanelGroupDependencies(properties.backgroundMaterialClass, properties) + const orderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset, groupDeps) useInstancedPanel( collection, globalMatrix, @@ -77,7 +86,7 @@ export const Container = forwardRef< isClipped, orderInfo, parentClippingRect, - properties.backgroundMaterialClass, + groupDeps, panelAliasPropertyTransformation, ) diff --git a/packages/uikit/src/components/content.tsx b/packages/uikit/src/components/content.tsx index 90359b4b..e7bbe846 100644 --- a/packages/uikit/src/components/content.tsx +++ b/packages/uikit/src/components/content.tsx @@ -2,7 +2,14 @@ import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/eve import { ReactNode, RefObject, forwardRef, useEffect, useMemo, useRef } from 'react' import { YogaProperties } from '../flex/node.js' import { FlexProvider, useFlexNode } from '../flex/react.js' -import { InteractionGroup, MaterialClass, useInstancedPanel, useInteractionPanel } from '../panel/react.js' +import { + InteractionGroup, + MaterialClass, + ShadowProperties, + useInstancedPanel, + useInteractionPanel, + usePanelGroupDependencies, +} from '../panel/react.js' import { ManagerCollection, WithReactive, @@ -11,7 +18,7 @@ import { useGetBatchedProperties, writeCollection, } from '../properties/utils.js' -import { alignmentZMap, fitNormalizedContentInside, useRootGroupRef, useSignalEffect } from '../utils.js' +import { alignmentZMap, useRootGroupRef } from '../utils.js' import { Box3, Group, Mesh, Vector3 } from 'three' import { computed, effect, Signal, signal } from '@preact/signals-core' import { useApplyHoverProperties } from '../hover.js' @@ -60,7 +67,8 @@ export const Content = forwardRef< } & ContentProperties & EventHandlers & LayoutListeners & - ViewportListeners + ViewportListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const groupRef = useRef(null) @@ -72,7 +80,8 @@ export const Content = forwardRef< const isClipped = useIsClipped(parentClippingRect, globalMatrix, node.size, node) useLayoutListeners(properties, node.size) useViewportListeners(properties, isClipped) - const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset) + const groupDeps = usePanelGroupDependencies(properties.backgroundMaterialClass, properties) + const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset, groupDeps) useInstancedPanel( collection, globalMatrix, @@ -82,12 +91,12 @@ export const Content = forwardRef< isClipped, backgroundOrderInfo, parentClippingRect, - properties.backgroundMaterialClass, + groupDeps, panelAliasPropertyTransformation, ) const innerGroupRef = useRef(null) const rootGroupRef = useRootGroupRef() - const orderInfo = useOrderInfo(ElementType.Object, undefined, backgroundOrderInfo) + const orderInfo = useOrderInfo(ElementType.Object, undefined, undefined, backgroundOrderInfo) const size = useNormalizedContent( collection, innerGroupRef, diff --git a/packages/uikit/src/components/custom.tsx b/packages/uikit/src/components/custom.tsx index 974f828e..c0e5d930 100644 --- a/packages/uikit/src/components/custom.tsx +++ b/packages/uikit/src/components/custom.tsx @@ -1,12 +1,12 @@ import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events.js' -import { forwardRef, ReactNode, useEffect, useMemo, useRef } from 'react' +import { forwardRef, ReactNode, useEffect, useRef } from 'react' import { YogaProperties } from '../flex/node.js' import { useFlexNode, FlexProvider } from '../flex/react.js' import { useApplyHoverProperties } from '../hover.js' -import { InteractionGroup } from '../panel/react.js' +import { InteractionGroup, ShadowProperties } from '../panel/react.js' import { createCollection, finalizeCollection, WithReactive } from '../properties/utils.js' -import { useRootGroupRef, useSignalEffect } from '../utils.js' -import { Group, Material, Mesh } from 'three' +import { useRootGroupRef } from '../utils.js' +import { FrontSide, Group, Material, Mesh } from 'three' import { ComponentInternals, LayoutListeners, @@ -38,10 +38,13 @@ export const CustomContainer = forwardRef< { children?: ReactNode zIndexOffset?: ZIndexOffset + customDepthMaterial?: Material + customDistanceMaterial?: Material } & CustomContainerProperties & EventHandlers & LayoutListeners & - ViewportListeners + ViewportListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const groupRef = useRef(null) @@ -53,7 +56,7 @@ export const CustomContainer = forwardRef< const rootGroupRef = useRootGroupRef() const clippingPlanes = useGlobalClippingPlanes(parentClippingRect, rootGroupRef) - const orderInfo = useOrderInfo(ElementType.Custom, properties.zIndexOffset) + const orderInfo = useOrderInfo(ElementType.Custom, properties.zIndexOffset, undefined) const meshRef = useRef(null) @@ -72,6 +75,7 @@ export const CustomContainer = forwardRef< if (mesh.material instanceof Material) { mesh.material.clippingPlanes = clippingPlanes mesh.material.needsUpdate = true + mesh.material.shadowSide = FrontSide } const unsubscribeScale = effect(() => { @@ -98,11 +102,19 @@ export const CustomContainer = forwardRef< useLayoutListeners(properties, node.size) useViewportListeners(properties, isClipped) - //useComponentInternals(ref, node, meshRef) + useComponentInternals(ref, node, meshRef) return ( - + {properties.children} diff --git a/packages/uikit/src/components/icon.tsx b/packages/uikit/src/components/icon.tsx index 4956fd81..198c75fa 100644 --- a/packages/uikit/src/components/icon.tsx +++ b/packages/uikit/src/components/icon.tsx @@ -1,7 +1,14 @@ import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events.js' import { ReactNode, forwardRef, useMemo, useRef } from 'react' import { useFlexNode } from '../flex/react.js' -import { InteractionGroup, MaterialClass, useInstancedPanel, useInteractionPanel } from '../panel/react.js' +import { + InteractionGroup, + MaterialClass, + ShadowProperties, + useInstancedPanel, + useInteractionPanel, + usePanelGroupDependencies, +} from '../panel/react.js' import { createCollection, finalizeCollection, useGetBatchedProperties, writeCollection } from '../properties/utils.js' import { useSignalEffect, fitNormalizedContentInside, useRootGroupRef } from '../utils.js' import { Color, Group, Mesh, MeshBasicMaterial, ShapeGeometry } from 'three' @@ -46,7 +53,8 @@ export const SvgIconFromText = forwardRef< } & SvgProperties & EventHandlers & LayoutListeners & - ViewportListeners + ViewportListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const groupRef = useRef(null) @@ -56,7 +64,9 @@ export const SvgIconFromText = forwardRef< const globalMatrix = useGlobalMatrix(transformMatrix) const parentClippingRect = useParentClippingRect() const isClipped = useIsClipped(parentClippingRect, globalMatrix, node.size, node) - const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset) + + const groupDeps = usePanelGroupDependencies(properties.backgroundMaterialClass, properties) + const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset, groupDeps) useInstancedPanel( collection, globalMatrix, @@ -66,14 +76,14 @@ export const SvgIconFromText = forwardRef< isClipped, backgroundOrderInfo, parentClippingRect, - properties.backgroundMaterialClass, + groupDeps, panelAliasPropertyTransformation, ) const rootGroupRef = useRootGroupRef() const clippingPlanes = useGlobalClippingPlanes(parentClippingRect, rootGroupRef) - const orderInfo = useOrderInfo(ElementType.Svg, undefined, backgroundOrderInfo) + const orderInfo = useOrderInfo(ElementType.Svg, undefined, undefined, backgroundOrderInfo) const svgGroup = useMemo(() => { const group = new Group() group.matrixAutoUpdate = false @@ -118,11 +128,13 @@ export const SvgIconFromText = forwardRef< if (!(object instanceof Mesh)) { return } + object.receiveShadow = properties.receiveShadow ?? false + object.castShadow = properties.castShadow ?? false const material: MeshBasicMaterial = object.material material.color.copy(color ?? object.userData.color) material.opacity = opacity ?? 1 }) - }, [svgGroup, properties.color]) + }, [svgGroup, properties.color, properties.receiveShadow, properties.castShadow]) //apply all properties writeCollection(collection, 'width', properties.svgWidth) diff --git a/packages/uikit/src/components/image.tsx b/packages/uikit/src/components/image.tsx index 5368f8ad..9697c391 100644 --- a/packages/uikit/src/components/image.tsx +++ b/packages/uikit/src/components/image.tsx @@ -4,7 +4,7 @@ import { useResourceWithParams, useRootGroupRef, useSignalEffect } from '../util import { Signal, computed } from '@preact/signals-core' import { Inset, YogaProperties } from '../flex/node.js' import { panelGeometry } from '../panel/utils.js' -import { InteractionGroup, MaterialClass, usePanelMaterial } from '../panel/react.js' +import { InteractionGroup, MaterialClass, ShadowProperties, usePanelMaterials } from '../panel/react.js' import { useFlexNode } from '../flex/react.js' import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/events.js' import { useApplyHoverProperties } from '../hover.js' @@ -79,7 +79,8 @@ export const Image = forwardRef< zIndexOffset?: ZIndexOffset } & EventHandlers & LayoutListeners & - ViewportListeners + ViewportListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const texture = useResourceWithParams(loadTexture, properties.src) @@ -105,7 +106,7 @@ export const Image = forwardRef< const clippingPlanes = useGlobalClippingPlanes(parentClippingRect, rootGroupRef) const globalMatrix = useGlobalMatrix(transformMatrix) const isClipped = useIsClipped(parentClippingRect, globalMatrix, node.size, node) - const material = usePanelMaterial( + const materials = usePanelMaterials( collection, node.size, node.borderInset, @@ -114,14 +115,19 @@ export const Image = forwardRef< clippingPlanes, materialPropertyTransformation, ) - const orderInfo = useOrderInfo(ElementType.Image, properties.zIndexOffset) + const orderInfo = useOrderInfo(ElementType.Image, properties.zIndexOffset, undefined) const mesh = useMemo(() => { + const [material, depthMaterial, distanceMaterial] = materials const result = new Mesh(panelGeometry, material) result.matrixAutoUpdate = false + result.castShadow = properties.castShadow ?? false + result.receiveShadow = properties.receiveShadow ?? false + result.customDepthMaterial = depthMaterial + result.customDistanceMaterial = distanceMaterial result.raycast = makeClippedRaycast(result, makePanelRaycast(result), rootGroupRef, parentClippingRect, orderInfo) setupRenderOrder(result, node.cameraDistance, orderInfo) return result - }, [node, material, rootGroupRef, parentClippingRect, orderInfo]) + }, [node, materials, rootGroupRef, parentClippingRect, orderInfo, properties.receiveShadow, properties.castShadow]) //apply all properties useApplyProperties(collection, properties) diff --git a/packages/uikit/src/components/root.tsx b/packages/uikit/src/components/root.tsx index d28af45b..7ba72c27 100644 --- a/packages/uikit/src/components/root.tsx +++ b/packages/uikit/src/components/root.tsx @@ -6,9 +6,11 @@ import { InstancedPanelProvider, InteractionGroup, MaterialClass, + ShadowProperties, useGetInstancedPanelGroup, useInstancedPanel, useInteractionPanel, + usePanelGroupDependencies, } from '../panel/react.js' import { WithReactive, createCollection, finalizeCollection, writeCollection } from '../properties/utils.js' import { FlexProvider, useDeferredRequestLayoutCalculation } from '../flex/react.js' @@ -83,7 +85,8 @@ export const Root = forwardRef< }> & EventHandlers & LayoutListeners & - ScrollListeners + ScrollListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const renderer = useThree((state) => state.gl) @@ -117,7 +120,8 @@ export const Root = forwardRef< const getPanelGroup = useGetInstancedPanelGroup(pixelSize, node.cameraDistance, groupsContainer) const getGylphGroup = useGetInstancedGlyphGroup(pixelSize, node.cameraDistance, groupsContainer) - const orderInfo = useOrderInfo(ElementType.Panel, undefined) + const groupDeps = usePanelGroupDependencies(properties.backgroundMaterialClass, properties) + const orderInfo = useOrderInfo(ElementType.Panel, undefined, groupDeps) const rootMatrix = useRootMatrix(transformMatrix, node.size, pixelSize, properties) const scrollPosition = useScrollPosition() @@ -143,7 +147,7 @@ export const Root = forwardRef< undefined, orderInfo, undefined, - properties.backgroundMaterialClass, + groupDeps, panelAliasPropertyTransformation, getPanelGroup, ) diff --git a/packages/uikit/src/components/svg.tsx b/packages/uikit/src/components/svg.tsx index ec114468..c777da09 100644 --- a/packages/uikit/src/components/svg.tsx +++ b/packages/uikit/src/components/svg.tsx @@ -2,7 +2,14 @@ import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/eve import { ReactNode, RefObject, forwardRef, useMemo, useRef } from 'react' import { YogaProperties } from '../flex/node.js' import { useFlexNode } from '../flex/react.js' -import { InteractionGroup, MaterialClass, useInstancedPanel, useInteractionPanel } from '../panel/react.js' +import { + InteractionGroup, + MaterialClass, + ShadowProperties, + useInstancedPanel, + useInteractionPanel, + usePanelGroupDependencies, +} from '../panel/react.js' import { WithReactive, createCollection, @@ -119,7 +126,8 @@ export const Svg = forwardRef< } & SvgProperties & EventHandlers & LayoutListeners & - ViewportListeners + ViewportListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const groupRef = useRef(null) @@ -129,7 +137,8 @@ export const Svg = forwardRef< const globalMatrix = useGlobalMatrix(transformMatrix) const parentClippingRect = useParentClippingRect() const isClipped = useIsClipped(parentClippingRect, globalMatrix, node.size, node) - const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset) + const groupDeps = usePanelGroupDependencies(properties.backgroundMaterialClass, properties) + const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset, groupDeps) useInstancedPanel( collection, globalMatrix, @@ -139,13 +148,13 @@ export const Svg = forwardRef< isClipped, backgroundOrderInfo, parentClippingRect, - properties.backgroundMaterialClass, + groupDeps, panelAliasPropertyTransformation, ) const rootGroupRef = useRootGroupRef() const clippingPlanes = useGlobalClippingPlanes(parentClippingRect, rootGroupRef) - const orderInfo = useOrderInfo(ElementType.Svg, undefined, backgroundOrderInfo) + const orderInfo = useOrderInfo(ElementType.Svg, undefined, undefined, backgroundOrderInfo) const svgObject = useResourceWithParams( loadSvg, properties.src, @@ -171,11 +180,13 @@ export const Svg = forwardRef< if (!(object instanceof Mesh)) { return } + object.receiveShadow = properties.receiveShadow ?? false + object.castShadow = properties.castShadow ?? false const material: MeshBasicMaterial = object.material material.color.copy(color ?? object.userData.color) material.opacity = opacity ?? 1 }) - }, [svgObject, properties.color]) + }, [svgObject, properties.color, properties.receiveShadow, properties.castShadow]) const aspectRatio = useMemo(() => computed(() => svgObject.value?.aspectRatio), [svgObject]) //apply all properties diff --git a/packages/uikit/src/components/text.tsx b/packages/uikit/src/components/text.tsx index a92b96a5..7515355c 100644 --- a/packages/uikit/src/components/text.tsx +++ b/packages/uikit/src/components/text.tsx @@ -2,7 +2,14 @@ import { EventHandlers } from '@react-three/fiber/dist/declarations/src/core/eve import { YogaProperties } from '../flex/node.js' import { useApplyHoverProperties } from '../hover.js' import { PanelProperties } from '../panel/instanced-panel.js' -import { InteractionGroup, MaterialClass, useInstancedPanel, useInteractionPanel } from '../panel/react.js' +import { + InteractionGroup, + MaterialClass, + ShadowProperties, + useInstancedPanel, + useInteractionPanel, + usePanelGroupDependencies, +} from '../panel/react.js' import { WithAllAliases, flexAliasPropertyTransformation, @@ -50,7 +57,8 @@ export const Text = forwardRef< EventHandlers & LayoutListeners & ViewportListeners & - ScrollListeners + ScrollListeners & + ShadowProperties >((properties, ref) => { const collection = createCollection() const groupRef = useRef(null) @@ -63,7 +71,8 @@ export const Text = forwardRef< const isClipped = useIsClipped(parentClippingRect, globalMatrix, node.size, node) useLayoutListeners(properties, node.size) useViewportListeners(properties, isClipped) - const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset) + const groupDeps = usePanelGroupDependencies(properties.backgroundMaterialClass, properties) + const backgroundOrderInfo = useOrderInfo(ElementType.Panel, properties.zIndexOffset, groupDeps) useInstancedPanel( collection, globalMatrix, @@ -73,10 +82,10 @@ export const Text = forwardRef< isClipped, backgroundOrderInfo, parentClippingRect, - properties.backgroundMaterialClass, + groupDeps, panelAliasPropertyTransformation, ) - const orderInfo = useOrderInfo(ElementType.Text, undefined, backgroundOrderInfo) + const orderInfo = useOrderInfo(ElementType.Text, undefined, undefined, backgroundOrderInfo) const measureFunc = useInstancedText( collection, properties.children, diff --git a/packages/uikit/src/order.ts b/packages/uikit/src/order.ts index 0cf680fe..e51171b5 100644 --- a/packages/uikit/src/order.ts +++ b/packages/uikit/src/order.ts @@ -45,6 +45,7 @@ export type OrderInfo = { majorIndex: number elementType: ElementType minorIndex: number + instancedGroupDependencies?: Record | undefined } function compareOrderInfo(o1: OrderInfo, o2: OrderInfo): number { @@ -68,6 +69,7 @@ export type ZIndexOffset = { major?: number; minor?: number } | number export function useOrderInfo( type: ElementType, offset: ZIndexOffset | undefined, + instancedGroupDependencies: Record | undefined, providedParentOrderInfo?: OrderInfo, ): OrderInfo { // eslint-disable-next-line react-hooks/rules-of-hooks @@ -84,12 +86,15 @@ export function useOrderInfo( } else if (type > parentOrderInfo.elementType) { majorIndex = parentOrderInfo.majorIndex minorIndex = 0 - } else if (type === parentOrderInfo.elementType) { - majorIndex = parentOrderInfo.majorIndex - minorIndex = parentOrderInfo.minorIndex + 1 - } else { + } else if ( + type != parentOrderInfo.elementType || + !shallowEqualRecord(instancedGroupDependencies, parentOrderInfo.instancedGroupDependencies) + ) { majorIndex = parentOrderInfo.majorIndex + 1 minorIndex = 0 + } else { + majorIndex = parentOrderInfo.majorIndex + minorIndex = parentOrderInfo.minorIndex + 1 } if (majorOffset > 0) { @@ -104,7 +109,25 @@ export function useOrderInfo( majorIndex, minorIndex, } - }, [majorOffset, minorOffset, parentOrderInfo, type]) + }, [majorOffset, minorOffset, parentOrderInfo, type, instancedGroupDependencies]) +} + +function shallowEqualRecord(r1: Record | undefined, r2: Record | undefined): boolean { + if (r1 === r2) { + return true + } + if (r1 == null || r2 == null) { + return false + } + //i counts the number of keys in r1 + let i = 0 + for (const key in r1) { + if (r1[key] != r2[key]) { + return false + } + ++i + } + return i === Object.keys(r2).length } export function setupRenderOrder(result: T, rootCameraDistance: CameraDistanceRef, orderInfo: OrderInfo): T { diff --git a/packages/uikit/src/panel/instanced-panel-group.ts b/packages/uikit/src/panel/instanced-panel-group.ts index 39bfea51..0c634458 100644 --- a/packages/uikit/src/panel/instanced-panel-group.ts +++ b/packages/uikit/src/panel/instanced-panel-group.ts @@ -53,6 +53,8 @@ export class InstancedPanelGroup extends Group { public readonly pixelSize: number, private readonly cameraDistance: CameraDistanceRef, private readonly orderInfo: OrderInfo, + private readonly meshReceiveShadow: boolean, + private readonly meshCastShadow: boolean, ) { super() } @@ -158,6 +160,8 @@ export class InstancedPanelGroup extends Group { this.mesh = new InstancedPanelMesh(this.instanceMatrix, this.instanceData, this.instanceClipping) setupRenderOrder(this.mesh, this.cameraDistance, this.orderInfo) this.mesh.material = this.material + this.mesh.receiveShadow = this.meshReceiveShadow + this.mesh.castShadow = this.meshCastShadow this.add(this.mesh) } diff --git a/packages/uikit/src/panel/instanced-panel-mesh.ts b/packages/uikit/src/panel/instanced-panel-mesh.ts index e797ecb5..8ae034d2 100644 --- a/packages/uikit/src/panel/instanced-panel-mesh.ts +++ b/packages/uikit/src/panel/instanced-panel-mesh.ts @@ -1,5 +1,6 @@ import { Box3, InstancedBufferAttribute, Mesh, Object3DEventMap, Sphere } from 'three' import { createPanelGeometry } from './utils.js' +import { instancedPanelDepthMaterial, instancedPanelDistanceMaterial } from './panel-material.js' export class InstancedPanelMesh extends Mesh { public count = 0 @@ -19,6 +20,8 @@ export class InstancedPanelMesh extends Mesh { this.frustumCulled = false panelGeometry.attributes.aData = instanceData panelGeometry.attributes.aClipping = instanceClipping + this.customDepthMaterial = instancedPanelDepthMaterial + this.customDistanceMaterial = instancedPanelDistanceMaterial } dispose() { diff --git a/packages/uikit/src/panel/panel-material.ts b/packages/uikit/src/panel/panel-material.ts index fccb7f1d..9bd97f5e 100644 --- a/packages/uikit/src/panel/panel-material.ts +++ b/packages/uikit/src/panel/panel-material.ts @@ -1,10 +1,18 @@ -import { Color, Material, TypedArray, Vector2Tuple, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three' -import { Constructor, isPanelVisible, setBorderRadius, setComponentInFloat } from './utils.js' -import { Signal, computed, effect, signal } from '@preact/signals-core' +import { + Color, + FrontSide, + Material, + MeshDepthMaterial, + MeshDistanceMaterial, + RGBADepthPacking, + Vector2Tuple, + WebGLProgramParametersWithUniforms, + WebGLRenderer, +} from 'three' +import { Constructor, isPanelVisible, setBorderRadius } from './utils.js' +import { Signal, effect, signal } from '@preact/signals-core' import { Inset } from '../flex/node.js' -import { clamp } from 'three/src/math/MathUtils.js' import { PanelProperties } from './instanced-panel.js' -import { Properties, readReactiveProperty } from '../properties/utils.js' import { WithImmediateProperties } from '../properties/immediate.js' import { WithBatchedProperties } from '../properties/batched.js' @@ -16,7 +24,7 @@ export const panelDefaultColor = new Color(-1, -1, -1) const panelMaterialSetters: { [Key in keyof PanelProperties]-?: ( - material: InstanceOf>, + data: Float32Array, value: PanelProperties[Key], size: Signal, ) => void @@ -24,28 +32,27 @@ const panelMaterialSetters: { //0-3 = borderSizes //4-6 = background color - backgroundColor: (m, p) => - (Array.isArray(p) ? colorHelper.setRGB(...p) : colorHelper.set(p ?? panelDefaultColor)).toArray(m.data, 4), + backgroundColor: (d, p) => + (Array.isArray(p) ? colorHelper.setRGB(...p) : colorHelper.set(p ?? panelDefaultColor)).toArray(d, 4), //7 = border radiuses - borderBottomLeftRadius: (m, p, size) => setBorderRadius(m.data, 7, 0, p, size.value[1]), - borderBottomRightRadius: (m, p, size) => setBorderRadius(m.data, 7, 1, p, size.value[1]), - borderTopRightRadius: (m, p, size) => setBorderRadius(m.data, 7, 2, p, size.value[1]), - borderTopLeftRadius: (m, p, size) => setBorderRadius(m.data, 7, 3, p, size.value[1]), + borderBottomLeftRadius: (d, p, size) => setBorderRadius(d, 7, 0, p, size.value[1]), + borderBottomRightRadius: (d, p, size) => setBorderRadius(d, 7, 1, p, size.value[1]), + borderTopRightRadius: (d, p, size) => setBorderRadius(d, 7, 2, p, size.value[1]), + borderTopLeftRadius: (d, p, size) => setBorderRadius(d, 7, 3, p, size.value[1]), //8 - 10 = border color - borderColor: (m, p) => - (Array.isArray(p) ? colorHelper.setRGB(...p) : colorHelper.set(p ?? 0xffffff)).toArray(m.data, 8), + borderColor: (d, p) => (Array.isArray(p) ? colorHelper.setRGB(...p) : colorHelper.set(p ?? 0xffffff)).toArray(d, 8), //11 - borderBend: (m, p) => (m.data[11] = p ?? 0), + borderBend: (d, p) => (d[11] = p ?? 0), //12 - borderOpacity: (m, p) => (m.data[12] = p ?? 1), + borderOpacity: (d, p) => (d[12] = p ?? 1), //13 = width //14 = height //15 - backgroundOpacity: (m, p) => (m.data[15] = p ?? -1), + backgroundOpacity: (d, p) => (d[15] = p ?? -1), } export type PanelSetter = (typeof panelMaterialSetters)[keyof typeof panelMaterialSetters] @@ -75,119 +82,184 @@ const batchedProperties = ['borderOpacity', 'backgroundColor', 'backgroundOpacit type BatchedProperties = Pick type BatchedPropertiesKey = keyof BatchedProperties -export function createPanelMaterial>(MaterialClass: T) { - return class extends MaterialClass implements WithImmediateProperties, WithBatchedProperties { - //data layout: vec4 borderSize = data[0]; vec4 borderRadius = data[1]; vec3 borderColor = data[2].xyz; float borderBend = data[2].w; float borderOpacity = data[3].x; float width = data[3].y; float height = data[3].z; float backgroundOpacity = data[3].w; - readonly data = new Float32Array(16) +export class MaterialSetter implements WithBatchedProperties, WithImmediateProperties { + //data layout: vec4 borderSize = data[0]; vec4 borderRadius = data[1]; vec3 borderColor = data[2].xyz; float borderBend = data[2].w; float borderOpacity = data[3].x; float width = data[3].y; float height = data[3].z; float backgroundOpacity = data[3].w; + public readonly data = new Float32Array(16) + + private unsubscribeList: Array<() => void> = [] + private unsubscribe: () => void + private visible = false + private materials: Array = [] + + active = signal(false) + + constructor( + private size: Signal, + borderInset: Signal, + isClipped: Signal, + ) { + this.size = size + this.unsubscribe = effect(() => { + const get = this.getProperty.value + const isVisible = isPanelVisible( + borderInset, + size, + isClipped, + get('borderOpacity'), + get('backgroundOpacity'), + get('backgroundColor'), + ) + this.active.value = isVisible + if (!isVisible) { + this.deactivate() + return + } + this.activate(size, borderInset) + }) + } - unsubscribeList: Array<() => void> = [] - unsubscribe!: () => void - active = signal(false) + addMaterial(material: Material) { + material.visible = this.visible + this.materials.push(material) + } - size!: Signal + hasBatchedProperty(key: BatchedPropertiesKey): boolean { + return batchedProperties.includes(key) + } - constructor(...args: Array) { - super({ transparent: true, toneMapped: false, depthWrite: false }, ...args.slice(1)) - if (this.defines == null) { - this.defines = {} - } - this.defines.USE_UV = '' - this.defines.USE_TANGENT = '' - this.visible = false - } + getProperty: Signal<(key: K) => BatchedProperties[K]> = signal(() => undefined) - hasBatchedProperty(key: BatchedPropertiesKey): boolean { - return batchedProperties.includes(key) - } + hasImmediateProperty(key: string): boolean { + return key in panelMaterialSetters + } - getProperty: Signal<(key: K) => BatchedProperties[K]> = signal(() => undefined) - - setup(size: Signal, borderInset: Signal, isClipped: Signal) { - this.size = size - this.unsubscribe = effect(() => { - const get = this.getProperty.value - const isVisible = isPanelVisible( - borderInset, - size, - isClipped, - get('borderOpacity'), - get('backgroundOpacity'), - get('backgroundColor'), - ) - this.active.value = isVisible - if (!isVisible) { - this.deactivate() - return - } - this.activate(size, borderInset) - }) + setProperty(key: string, value: unknown): void { + const setter = panelMaterialSetters[key as keyof typeof panelMaterialSetters] + setter(this.data, value as any, this.size) + } + + private activate(size: Signal, borderInset: Signal): void { + if (this.visible) { + return } - hasImmediateProperty(key: string): boolean { - return key in panelMaterialSetters + this.visible = true + this.syncVisible() + + this.data.set(panelMaterialDefaultData) + this.unsubscribeList.push( + effect(() => this.data.set(size.value, 13)), + effect(() => this.data.set(borderInset.value, 0)), + ) + } + + private deactivate(): void { + if (!this.visible) { + return } - setProperty(key: string, value: unknown): void { - panelMaterialSetters[key as keyof typeof panelMaterialSetters](this, value as any, this.size) + this.visible = false + this.syncVisible() + + const unsubscribeListLength = this.unsubscribeList.length + for (let i = 0; i < unsubscribeListLength; i++) { + this.unsubscribeList[i]() } + this.unsubscribeList.length = 0 + } - activate(size: Signal, borderInset: Signal): void { - if (this.visible) { - return - } - this.visible = true - this.data.set(panelMaterialDefaultData) - this.unsubscribeList.push( - effect(() => this.data.set(size.value, 13)), - effect(() => this.data.set(borderInset.value, 0)), - ) + destroy(): void { + this.deactivate() + this.unsubscribe() + } + + private syncVisible() { + const materialsLength = this.materials.length + for (let i = 0; i < materialsLength; i++) { + this.materials[i].visible = this.visible } + } +} - deactivate(): void { - if (!this.visible) { - return - } +export type PanelMaterialInfo = { type: 'instanced' } | { type: 'normal'; data: Float32Array } - this.visible = false - const unsubscribeListLength = this.unsubscribeList.length - for (let i = 0; i < unsubscribeListLength; i++) { - this.unsubscribeList[i]() - } - this.unsubscribeList.length = 0 +export function createPanelMaterial>(MaterialClass: T, info: PanelMaterialInfo) { + const material = new MaterialClass() + if (material.defines == null) { + material.defines = {} + } + material.side = FrontSide + material.clipShadows = true + material.transparent = true + material.toneMapped = false + material.depthWrite = false + material.shadowSide = FrontSide + material.defines.USE_UV = '' + material.defines.USE_TANGENT = '' + + const superOnBeforeCompile = material.onBeforeCompile + material.onBeforeCompile = (parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => { + superOnBeforeCompile.call(material, parameters, renderer) + if (info.type === 'normal') { + parameters.uniforms.data = { value: info.data } } + compilePanelMaterial(parameters, info.type === 'instanced') + } + return material +} - destroy(): void { - this.deactivate() - this.unsubscribe() +export class PanelDistanceMaterial extends MeshDistanceMaterial { + constructor(private info: PanelMaterialInfo) { + super() + if (this.defines == null) { + this.defines = {} } + this.defines.USE_UV = '' + this.clipShadows = true + } - onBeforeCompile(parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void { - super.onBeforeCompile(parameters, renderer) - parameters.uniforms.data = { value: this.data } - compilePanelMaterial(parameters, false) + onBeforeCompile(parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void { + super.onBeforeCompile(parameters, renderer) + if (this.info.type === 'normal') { + parameters.uniforms.data = { value: this.info.data } } + compilePanelDepthMaterial(parameters, this.info.type === 'instanced') } } -export function createInstancedPanelMaterial>(MaterialClass: T) { - return class extends MaterialClass { - constructor(...args: Array) { - super({ transparent: true, depthWrite: false, toneMapped: false, ...args[0] }, ...args.slice(1)) - if (this.defines == null) { - this.defines = {} - } - this.defines.USE_UV = '' - this.defines.USE_TANGENT = '' +export class PanelDepthMaterial extends MeshDepthMaterial { + constructor(private info: PanelMaterialInfo) { + super({ depthPacking: RGBADepthPacking }) + if (this.defines == null) { + this.defines = {} } + this.defines.USE_UV = '' + this.clipShadows = true + } - onBeforeCompile(parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void { - super.onBeforeCompile(parameters, renderer) - compilePanelMaterial(parameters, true) + onBeforeCompile(parameters: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void { + super.onBeforeCompile(parameters, renderer) + if (this.info.type === 'normal') { + parameters.uniforms.data = { value: this.info.data } } + compilePanelDepthMaterial(parameters, this.info.type === 'instanced') } } -export function compilePanelMaterial(parameters: WebGLProgramParametersWithUniforms, instanced: boolean) { +export const instancedPanelDepthMaterial = new PanelDepthMaterial({ type: 'instanced' }) +export const instancedPanelDistanceMaterial = new PanelDistanceMaterial({ type: 'instanced' }) + +function compilePanelDepthMaterial(parameters: WebGLProgramParametersWithUniforms, instanced: boolean) { + compilePanelClippingMaterial(parameters, instanced) + parameters.fragmentShader = parameters.fragmentShader.replace( + '#include ', + `#include + ${getFargmentOpacityCode(instanced, undefined)} + `, + ) +} + +function compilePanelClippingMaterial(parameters: WebGLProgramParametersWithUniforms, instanced: boolean) { if (instanced) { parameters.vertexShader = parameters.vertexShader.replace( '#include ', @@ -207,6 +279,7 @@ export function compilePanelMaterial(parameters: WebGLProgramParametersWithUnifo localPosition = (instanceMatrix * vec4(position, 1.0)).xyz;`, ) } + parameters.fragmentShader = `${instanced ? 'in' : 'uniform'} mat4 data; ${ @@ -235,104 +308,146 @@ export function compilePanelMaterial(parameters: WebGLProgramParametersWithUnifo ); } ` + parameters.fragmentShader - parameters.fragmentShader = parameters.fragmentShader.replace( '#include ', ` ${ instanced ? ` - vec4 plane; - float distanceToPlane, distanceGradient; - float clipOpacity = 1.0; - for(int i = 0; i < 4; i++) { - plane = clipping[ i ]; - distanceToPlane = - dot( -localPosition, plane.xyz ) + plane.w; - distanceGradient = fwidth( distanceToPlane ) / 2.0; - clipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane ); - - if ( clipOpacity == 0.0 ) discard; - } - ` + vec4 plane; + float distanceToPlane, distanceGradient; + float clipOpacity = 1.0; + + for(int i = 0; i < 4; i++) { + plane = clipping[ i ]; + distanceToPlane = - dot( -localPosition, plane.xyz ) + plane.w; + distanceGradient = fwidth( distanceToPlane ) / 2.0; + clipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane ); + + if ( clipOpacity < 0.01 ) discard; + } + ` : '' } - vec4 absoluteBorderSize = data[0]; - vec3 backgroundColor = data[1].xyz; - int packedBorderRadius = int(data[1].w); - vec4 borderRadius = vec4(packedBorderRadius / 50 / 50 / 50 % 50, packedBorderRadius / 50 / 50 % 50, packedBorderRadius / 50 % 50, packedBorderRadius % 50) * vec4(0.5 / 50.0); - vec3 borderColor = data[2].xyz; - float borderBend = data[2].w; - float borderOpacity = data[3].x; - float width = data[3].y; - float height = data[3].z; - float backgroundOpacity = data[3].w; - float ratio = width / height; - vec4 relative = vec4(height, height, height, height); - vec4 borderSize = absoluteBorderSize / relative; - vec4 v_outsideDistance = vec4(1.0 - vUv.y, (1.0 - vUv.x) * ratio, vUv.y, vUv.x * ratio); - vec4 v_borderDistance = v_outsideDistance - borderSize; - - vec2 dist = vec2(min4(v_outsideDistance), min4(v_borderDistance)); - vec4 negateBorderDistance = vec4(1.0) - v_borderDistance; - float maxWeight = max4(negateBorderDistance); - vec4 borderWeight = step(maxWeight, negateBorderDistance); - - vec4 insideBorder; - - if(all(lessThan(v_outsideDistance.xw, borderRadius.xx))) { - dist = radiusDistance(borderRadius.x, v_outsideDistance.xw, v_borderDistance.xw, borderSize.xw); - - float tmp = borderRadius.x - borderSize.w; - vec2 xIntersection = vec2(tmp, tmp / ratio); - tmp = borderRadius.x - borderSize.x; - vec2 yIntersection = vec2(tmp * ratio, tmp); - vec2 lineIntersection = min(xIntersection, yIntersection); - - insideBorder.yz = vec2(0.0); - insideBorder.xw = max(vec2(0.0), lineIntersection - v_borderDistance.xw); - - } else if(all(lessThan(v_outsideDistance.xy, borderRadius.yy))) { - dist = radiusDistance(borderRadius.y, v_outsideDistance.xy, v_borderDistance.xy, borderSize.xy); - - float tmp = borderRadius.y - borderSize.y; - vec2 xIntersection = vec2(tmp, tmp / ratio); - tmp = borderRadius.y - borderSize.x; - vec2 yIntersection = vec2(tmp * ratio, tmp); - vec2 lineIntersection = min(xIntersection, yIntersection); - - insideBorder.zw = vec2(0.0); - insideBorder.xy = max(vec2(0.0), lineIntersection - v_borderDistance.xy); - - } else if(all(lessThan(v_outsideDistance.zy, borderRadius.zz))) { - dist = radiusDistance(borderRadius.z, v_outsideDistance.zy, v_borderDistance.zy, borderSize.zy); - - float tmp = borderRadius.z - borderSize.y; - vec2 xIntersection = vec2(tmp, tmp / ratio); - tmp = borderRadius.z - borderSize.z; - vec2 yIntersection = vec2(tmp * ratio, tmp); - vec2 lineIntersection = min(xIntersection, yIntersection); - - insideBorder.xw = vec2(0.0); - insideBorder.zy =max(vec2(0.0), lineIntersection - v_borderDistance.zy); - - } else if(all(lessThan(v_outsideDistance.zw, borderRadius.ww))) { - dist = radiusDistance(borderRadius.w, v_outsideDistance.zw, v_borderDistance.zw, borderSize.zw); - - float tmp = borderRadius.w - borderSize.w; - vec2 xIntersection = vec2(tmp, tmp / ratio); - tmp = borderRadius.w - borderSize.z; - vec2 yIntersection = vec2(tmp * ratio, tmp); - vec2 lineIntersection = min(xIntersection, yIntersection); - - insideBorder.xy = vec2(0.0); - insideBorder.zw = max(vec2(0.0), lineIntersection - v_borderDistance.zw); + vec4 absoluteBorderSize = data[0]; + vec3 backgroundColor = data[1].xyz; + int packedBorderRadius = int(data[1].w); + vec4 borderRadius = vec4(packedBorderRadius / 50 / 50 / 50 % 50, packedBorderRadius / 50 / 50 % 50, packedBorderRadius / 50 % 50, packedBorderRadius % 50) * vec4(0.5 / 50.0); + vec3 borderColor = data[2].xyz; + float borderBend = data[2].w; + float borderOpacity = data[3].x; + float width = data[3].y; + float height = data[3].z; + float backgroundOpacity = data[3].w; + float ratio = width / height; + vec4 relative = vec4(height, height, height, height); + vec4 borderSize = absoluteBorderSize / relative; + vec4 v_outsideDistance = vec4(1.0 - vUv.y, (1.0 - vUv.x) * ratio, vUv.y, vUv.x * ratio); + vec4 v_borderDistance = v_outsideDistance - borderSize; + + vec2 distance = vec2(min4(v_outsideDistance), min4(v_borderDistance)); + vec4 negateBorderDistance = vec4(1.0) - v_borderDistance; + float maxWeight = max4(negateBorderDistance); + vec4 borderWeight = step(maxWeight, negateBorderDistance); + + vec4 insideBorder; + + if(all(lessThan(v_outsideDistance.xw, borderRadius.xx))) { + distance = radiusDistance(borderRadius.x, v_outsideDistance.xw, v_borderDistance.xw, borderSize.xw); + + float tmp = borderRadius.x - borderSize.w; + vec2 xIntersection = vec2(tmp, tmp / ratio); + tmp = borderRadius.x - borderSize.x; + vec2 yIntersection = vec2(tmp * ratio, tmp); + vec2 lineIntersection = min(xIntersection, yIntersection); + + insideBorder.yz = vec2(0.0); + insideBorder.xw = max(vec2(0.0), lineIntersection - v_borderDistance.xw); + + } else if(all(lessThan(v_outsideDistance.xy, borderRadius.yy))) { + distance = radiusDistance(borderRadius.y, v_outsideDistance.xy, v_borderDistance.xy, borderSize.xy); + + float tmp = borderRadius.y - borderSize.y; + vec2 xIntersection = vec2(tmp, tmp / ratio); + tmp = borderRadius.y - borderSize.x; + vec2 yIntersection = vec2(tmp * ratio, tmp); + vec2 lineIntersection = min(xIntersection, yIntersection); + + insideBorder.zw = vec2(0.0); + insideBorder.xy = max(vec2(0.0), lineIntersection - v_borderDistance.xy); + + } else if(all(lessThan(v_outsideDistance.zy, borderRadius.zz))) { + distance = radiusDistance(borderRadius.z, v_outsideDistance.zy, v_borderDistance.zy, borderSize.zy); + + float tmp = borderRadius.z - borderSize.y; + vec2 xIntersection = vec2(tmp, tmp / ratio); + tmp = borderRadius.z - borderSize.z; + vec2 yIntersection = vec2(tmp * ratio, tmp); + vec2 lineIntersection = min(xIntersection, yIntersection); + + insideBorder.xw = vec2(0.0); + insideBorder.zy =max(vec2(0.0), lineIntersection - v_borderDistance.zy); + + } else if(all(lessThan(v_outsideDistance.zw, borderRadius.ww))) { + distance = radiusDistance(borderRadius.w, v_outsideDistance.zw, v_borderDistance.zw, borderSize.zw); + + float tmp = borderRadius.w - borderSize.w; + vec2 xIntersection = vec2(tmp, tmp / ratio); + tmp = borderRadius.w - borderSize.z; + vec2 yIntersection = vec2(tmp * ratio, tmp); + vec2 lineIntersection = min(xIntersection, yIntersection); + + insideBorder.xy = vec2(0.0); + insideBorder.zw = max(vec2(0.0), lineIntersection - v_borderDistance.zw); + + } + + if(insideBorder.x + insideBorder.y + insideBorder.z + insideBorder.w > 0.0) { + borderWeight = normalize(insideBorder); + } + + #include `, + ) +} - } +function getFargmentOpacityCode(instanced: boolean, existingOpacity: string | undefined) { + return `float ddx = fwidth(distance.x); + float outer = smoothstep(-ddx, ddx, distance.x); - if(insideBorder.x + insideBorder.y + insideBorder.z + insideBorder.w > 0.0) { - borderWeight = normalize(insideBorder); - } + float ddy = fwidth(distance.y); + float inner = smoothstep(-ddy, ddy, distance.y); + + float transition = 1.0 - step(0.1, outer - inner) * (1.0 - inner); + + if(backgroundColor.r < 0.0 && backgroundOpacity >= 0.0) { + backgroundColor = vec3(1.0); + } + if(backgroundOpacity < 0.0) { + backgroundOpacity = backgroundColor.r >= 0.0 ? 1.0 : 0.0; + } - #include `, + if(backgroundOpacity < 0.0) { + backgroundOpacity = 0.0; + } + + float outOpacity = ${ + instanced ? 'clipOpacity * ' : '' + } outer * mix(borderOpacity, ${existingOpacity == null ? '' : `${existingOpacity} *`} backgroundOpacity, transition); + + if(outOpacity < 0.01) { + discard; + }` +} + +export function compilePanelMaterial(parameters: WebGLProgramParametersWithUniforms, instanced: boolean) { + compilePanelClippingMaterial(parameters, instanced) + + parameters.fragmentShader = parameters.fragmentShader.replace( + '#include ', + ` #include + ${getFargmentOpacityCode(instanced, 'diffuseColor.a')} + diffuseColor.rgb = mix(borderColor, diffuseColor.rgb * backgroundColor, transition); + diffuseColor.a = outOpacity; + `, ) parameters.fragmentShader = parameters.fragmentShader.replace( '#include ', @@ -340,41 +455,10 @@ export function compilePanelMaterial(parameters: WebGLProgramParametersWithUnifo vec3 b = normalize(vBitangent); vec3 t = normalize(vTangent); mat4 directions = mat4(vec4(b, 1.0), vec4(t, 1.0), vec4(-b, 1.0), vec4(-t, 1.0)); - float currentBorderSize = dist.x - dist.y; - float outsideNormalWeight = currentBorderSize < 0.00001 ? 0.0 : max(0.0, -dist.y / currentBorderSize) * borderBend; + float currentBorderSize = distance.x - distance.y; + float outsideNormalWeight = currentBorderSize < 0.00001 ? 0.0 : max(0.0, -distance.y / currentBorderSize) * borderBend; vec3 outsideNormal = (borderWeight * transpose(directions)).xyz; normal = normalize(outsideNormalWeight * outsideNormal + (1.0 - outsideNormalWeight) * normal); `, ) - parameters.fragmentShader = parameters.fragmentShader.replace( - '#include ', - ` #include - - float ddx = fwidth(dist.x); - float outer = smoothstep(-ddx, ddx, dist.x); - - float ddy = fwidth(dist.y); - float inner = smoothstep(-ddy, ddy, dist.y); - - float transition = 1.0 - step(0.1, outer - inner) * (1.0 - inner); - - if(backgroundColor.r < 0.0 && backgroundOpacity >= 0.0) { - backgroundColor = vec3(1.0); - } - if(backgroundOpacity < 0.0) { - backgroundOpacity = backgroundColor.r >= 0.0 ? 1.0 : 0.0; - } - - if(backgroundOpacity < 0.0) { - backgroundOpacity = 0.0; - } - - diffuseColor.rgb = mix(borderColor, diffuseColor.rgb * backgroundColor, transition); - - diffuseColor.a = ${ - instanced ? 'clipOpacity * ' : '' - } outer * mix(borderOpacity, diffuseColor.a * backgroundOpacity, transition); - - `, - ) } diff --git a/packages/uikit/src/panel/react.tsx b/packages/uikit/src/panel/react.tsx index 31a2685b..2a0b1955 100644 --- a/packages/uikit/src/panel/react.tsx +++ b/packages/uikit/src/panel/react.tsx @@ -10,7 +10,7 @@ import { makeClippedRaycast, makePanelRaycast } from './interaction-panel-mesh.j import { HoverEventHandlers } from '../hover.js' import { InstancedPanelGroup } from './instanced-panel-group.js' import { InstancedPanel } from './instanced-panel.js' -import { createInstancedPanelMaterial, createPanelMaterial } from './panel-material.js' +import { MaterialSetter, PanelDepthMaterial, PanelDistanceMaterial, createPanelMaterial } from './panel-material.js' import { useImmediateProperties } from '../properties/immediate.js' import { ManagerCollection, PropertyTransformation } from '../properties/utils.js' import { useBatchedProperties } from '../properties/batched.js' @@ -103,13 +103,16 @@ export function useInteractionPanel( return panel } -export type MaterialClass = { new (): Material } +export type MaterialClass = { new (...args: Array): Material } -export type GetInstancedPanelGroup = (majorIndex: number, materialClass: MaterialClass) => InstancedPanelGroup +export type GetInstancedPanelGroup = ( + majorIndex: number, + panelGroupDependencies: PanelGroupDependencies, +) => InstancedPanelGroup const InstancedPanelContext = createContext(null as any) -export function usePanelMaterial( +export function usePanelMaterials( collection: ManagerCollection, size: Signal, borderInset: Signal, @@ -117,18 +120,44 @@ export function usePanelMaterial( materialClass: MaterialClass | undefined, clippingPlanes: Array, propertyTransformation: PropertyTransformation, -) { - const material = useMemo(() => { - const MaterialClass = createPanelMaterial(materialClass ?? MeshBasicMaterial) - const result = new MaterialClass() - result.clippingPlanes = clippingPlanes - result.setup(size, borderInset, isClipped) - return result +): readonly [Material, Material, Material] { + const { materials, setter } = useMemo(() => { + const setter = new MaterialSetter(size, borderInset, isClipped) + const info = { data: setter.data, type: 'normal' } as const + const material = createPanelMaterial(materialClass ?? MeshBasicMaterial, info) + const depthMaterial = new PanelDepthMaterial(info) + const distanceMaterial = new PanelDistanceMaterial(info) + material.clippingPlanes = clippingPlanes + depthMaterial.clippingPlanes = clippingPlanes + distanceMaterial.clippingPlanes = clippingPlanes + return { materials: [material, depthMaterial, distanceMaterial], setter } as const }, [size, borderInset, isClipped, materialClass, clippingPlanes]) - useImmediateProperties(collection, material, propertyTransformation) - useBatchedProperties(collection, material, propertyTransformation) - useEffect(() => () => material.destroy(), [material]) - return material + useImmediateProperties(collection, setter, propertyTransformation) + useBatchedProperties(collection, setter, propertyTransformation) + useEffect(() => () => setter.destroy(), [setter]) + return materials +} + +export type PanelGroupDependencies = { + materialClass: MaterialClass + receiveShadow: boolean + castShadow: boolean +} & ShadowProperties + +export type ShadowProperties = { receiveShadow?: boolean; castShadow?: boolean } + +export function usePanelGroupDependencies( + materialClass: MaterialClass = MeshBasicMaterial, + { castShadow = false, receiveShadow = false }: ShadowProperties, +): PanelGroupDependencies { + return useMemo( + () => ({ + materialClass, + castShadow, + receiveShadow, + }), + [materialClass, castShadow, receiveShadow], + ) } /** @@ -143,7 +172,7 @@ export function useInstancedPanel( isHidden: Signal | undefined, orderInfo: OrderInfo, parentClippingRect: Signal | undefined, - materialClass: MaterialClass = MeshBasicMaterial, + panelGroupDependencies: PanelGroupDependencies, propertyTransformation: PropertyTransformation, providedGetGroup?: GetInstancedPanelGroup, ): void { @@ -152,7 +181,7 @@ export function useInstancedPanel( const panel = useMemo( () => new InstancedPanel( - getGroup(orderInfo.majorIndex, materialClass), + getGroup(orderInfo.majorIndex, panelGroupDependencies), matrix, size, offset, @@ -161,7 +190,7 @@ export function useInstancedPanel( isHidden, orderInfo.minorIndex, ), - [getGroup, materialClass, matrix, size, borderInset, parentClippingRect, isHidden, orderInfo, offset], + [getGroup, matrix, size, borderInset, parentClippingRect, isHidden, orderInfo, offset, panelGroupDependencies], ) useEffect(() => () => panel.destroy(), [panel]) useImmediateProperties(collection, panel, propertyTransformation) @@ -175,21 +204,29 @@ export function useGetInstancedPanelGroup( ) { const map = useMemo(() => new Map>(), []) const getGroup = useCallback( - (majorIndex, materialClass) => { + (majorIndex, { materialClass, receiveShadow, castShadow }) => { let groups = map.get(materialClass) if (groups == null) { map.set(materialClass, (groups = new Map())) } - let group = groups.get(majorIndex) + const key = (majorIndex << 2) + ((receiveShadow ? 1 : 0) << 1) + (castShadow ? 1 : 0) + let group = groups.get(key) if (group == null) { - const InstancedMaterialClass = createInstancedPanelMaterial(materialClass) + const material = createPanelMaterial(materialClass, { type: 'instanced' }) groups.set( - majorIndex, - (group = new InstancedPanelGroup(new InstancedMaterialClass(), pixelSize, cameraDistance, { - elementType: ElementType.Panel, - majorIndex, - minorIndex: 0, - })), + key, + (group = new InstancedPanelGroup( + material, + pixelSize, + cameraDistance, + { + elementType: ElementType.Panel, + majorIndex, + minorIndex: 0, + }, + receiveShadow, + castShadow, + )), ) groupsContainer.add(group) } diff --git a/packages/uikit/src/scroll.tsx b/packages/uikit/src/scroll.tsx index b2dd22ea..0fc9f1cd 100644 --- a/packages/uikit/src/scroll.tsx +++ b/packages/uikit/src/scroll.tsx @@ -1,11 +1,11 @@ -import { Signal, computed, effect, signal } from '@preact/signals-core' +import { ReadonlySignal, Signal, computed, effect, signal } from '@preact/signals-core' import { EventHandlers, ThreeEvent } from '@react-three/fiber/dist/declarations/src/core/events.js' import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Group, Matrix4, Vector2, Vector2Tuple, Vector3, Vector4Tuple } from 'three' +import { Group, Matrix4, MeshBasicMaterial, Vector2, Vector2Tuple, Vector3, Vector4Tuple } from 'three' import { FlexNode, Inset } from './flex/node.js' import { Color as ColorRepresentation, useFrame } from '@react-three/fiber' import { useSignalEffect } from './utils.js' -import { GetInstancedPanelGroup, MaterialClass, useInstancedPanel } from './panel/react.js' +import { GetInstancedPanelGroup, MaterialClass, PanelGroupDependencies, useInstancedPanel } from './panel/react.js' import { ClippingRect } from './clipping.js' import { clamp } from 'three/src/math/MathUtils.js' import { PanelProperties } from './panel/instanced-panel.js' @@ -337,7 +337,31 @@ export function useScrollbars( orderInfo: OrderInfo, providedGetGroup?: GetInstancedPanelGroup, ): void { - const scrollbarOrderInfo = useOrderInfo(ElementType.Panel, undefined, orderInfo) + const scrollbarOrderInfo = useOrderInfo(ElementType.Panel, undefined, materialClass, orderInfo) + + const getScrollbarWidthSignal = useGetBatchedProperties<{ scrollbarWidth?: number }>(collection, propertyKeys) + const getBorderSignal = useGetBatchedProperties<{ + scrollbarBorderLeft?: number + scrollbarBorderRight?: number + scrollbarBorderBottom?: number + scrollbarBorderTop?: number + }>(collection, borderPropertyKeys, scrollbarBorderPropertyTransformation) + const borderSize = useMemo( + () => + computed(() => { + const get = getBorderSignal.value + return [ + get('scrollbarBorderTop') ?? 0, + get('scrollbarBorderRight') ?? 0, + get('scrollbarBorderBottom') ?? 0, + get('scrollbarBorderLeft') ?? 0, + ] + }), + [getBorderSignal], + ) + + const startIndex = collection.length + useScrollbar( collection, 0, @@ -349,6 +373,8 @@ export function useScrollbars( parentClippingRect, scrollbarOrderInfo, providedGetGroup, + getScrollbarWidthSignal, + borderSize, ) useScrollbar( collection, @@ -361,7 +387,16 @@ export function useScrollbars( parentClippingRect, scrollbarOrderInfo, providedGetGroup, + getScrollbarWidthSignal, + borderSize, ) + + //setting the scrollbar color and opacity default for all property managers of the instanced panel + const collectionLength = collection.length + for (let i = startIndex; i < collectionLength; i++) { + collection[i].add('scrollbarColor', 0xffffff) + collection[i].add('scrollbarOpacity', 1) + } } const propertyKeys = ['scrollbarWidth'] as const @@ -382,14 +417,15 @@ function useScrollbar( materialClass: MaterialClass | undefined, parentClippingRect: Signal | undefined, orderInfo: OrderInfo, - providedGetGroup?: GetInstancedPanelGroup, + providedGetGroup: GetInstancedPanelGroup | undefined, + getScrollbarWidthSignal: Signal<(key: 'scrollbarWidth') => number | undefined>, + borderSize: ReadonlySignal, ) { - const getPropertySignal = useGetBatchedProperties<{ scrollbarWidth?: number }>(collection, propertyKeys) const [scrollbarPosition, scrollbarSize] = useMemo(() => { const scrollbarTransformation = computed(() => computeScrollbarTransformation( mainIndex, - getPropertySignal.value('scrollbarWidth') ?? 10, + getScrollbarWidthSignal.value('scrollbarWidth') ?? 10, node.size.value, node.maxScrollPosition.value, node.borderInset.value, @@ -400,27 +436,12 @@ function useScrollbar( computed(() => scrollbarTransformation.value.slice(0, 2) as Vector2Tuple), computed(() => scrollbarTransformation.value.slice(2, 4) as Vector2Tuple), ] - }, [mainIndex, node, scrollPosition, getPropertySignal]) - const getBorderPropertySignal = useGetBatchedProperties<{ - scrollbarBorderLeft?: number - scrollbarBorderRight?: number - scrollbarBorderBottom?: number - scrollbarBorderTop?: number - }>(collection, borderPropertyKeys, scrollbarBorderPropertyTransformation) - const borderSize = useMemo( - () => - computed(() => { - const get = getBorderPropertySignal.value - return [ - get('scrollbarBorderTop') ?? 0, - get('scrollbarBorderRight') ?? 0, - get('scrollbarBorderBottom') ?? 0, - get('scrollbarBorderLeft') ?? 0, - ] - }), - [getBorderPropertySignal], + }, [mainIndex, node, scrollPosition, getScrollbarWidthSignal]) + + const groupDeps = useMemo( + () => ({ materialClass: materialClass ?? MeshBasicMaterial, receiveShadow: false, castShadow: false }), + [materialClass], ) - const startIndex = collection.length useInstancedPanel( collection, globalMatrix, @@ -430,16 +451,10 @@ function useScrollbar( isClipped, orderInfo, parentClippingRect, - materialClass, + groupDeps, scrollbarPanelPropertyTransformation, providedGetGroup, ) - //setting the scrollbar color and opacity default for all property managers of the instanced panel - const collectionLength = collection.length - for (let i = startIndex; i < collectionLength; i++) { - collection[i].add('scrollbarColor', 0xffffff) - collection[i].add('scrollbarOpacity', 1) - } } function computeScrollbarTransformation( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef27d84c..06cb401b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,8 +84,50 @@ importers: specifier: ^5.0.12 version: 5.0.12(@types/node@20.11.0) + examples/card: + dependencies: + '@preact/signals-core': + specifier: ^1.5.1 + version: 1.5.1 + '@react-three/drei': + specifier: ^9.96.1 + version: 9.96.1(@react-three/fiber@8.15.13)(@types/react@18.2.47)(@types/three@0.160.0)(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + '@react-three/fiber': + specifier: ^8.15.13 + version: 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + '@react-three/postprocessing': + specifier: ^2.16.0 + version: 2.16.0(@react-three/fiber@8.15.13)(@types/three@0.160.0)(react@18.2.0)(three@0.160.0) + '@react-three/uikit': + specifier: workspace:^ + version: link:../../packages/uikit + '@react-three/uikit-lucide': + specifier: workspace:^ + version: link:../../packages/icons/lucide + '@types/three': + specifier: ^0.160.0 + version: 0.160.0 + r3f-perf: + specifier: ^7.1.2 + version: 7.1.2(@react-three/fiber@8.15.13)(@types/react@18.2.47)(@types/three@0.160.0)(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + three: + specifier: ^0.160.0 + version: 0.160.0 + examples/dashboard: dependencies: + '@coconut-xr/koestlich': + specifier: ^0.3.12 + version: 0.3.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) + '@coconut-xr/natuerlich': + specifier: ^0.0.49 + version: 0.0.49(@coconut-xr/koestlich@0.3.12)(@react-three/fiber@8.15.13)(@types/react@18.2.47)(react@18.2.0)(three@0.160.0) '@react-three/drei': specifier: ^9.96.1 version: 9.96.1(@react-three/fiber@8.15.13)(@types/react@18.2.47)(@types/three@0.160.0)(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) @@ -116,6 +158,12 @@ importers: three: specifier: ^0.160.0 version: 0.160.0 + vite-plugin-mkcert: + specifier: ^1.17.4 + version: 1.17.4(vite@5.0.12) + zustand: + specifier: '4' + version: 4.4.7(@types/react@18.2.47)(react@18.2.0) examples/default: dependencies: @@ -576,6 +624,64 @@ packages: to-fast-properties: 2.0.0 dev: true + /@coconut-xr/flex@3.0.4: + resolution: {integrity: sha512-F8OVP+0oBSvX3aQohHAV+XmhvbU66FtOKXJZ70Z0uolaecPpc13XsJU+U1ryEZ+0ywCP7Nd1UWmCRK+UKqrGZA==} + dependencies: + base64-js: 1.5.1 + yoga-wasm-web: 0.3.3 + dev: false + + /@coconut-xr/glyph@0.0.6(three@0.160.0): + resolution: {integrity: sha512-+2kwmNpZzKM17mrWpAlSODziK+XXp1OL4Cb/u8smolCy5mcELcTvaergWJzipzZoDa6SBIWGX5HjjHSlfHoepw==} + peerDependencies: + three: '>=0.140.0' + dependencies: + three: 0.160.0 + dev: false + + /@coconut-xr/koestlich@0.3.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0): + resolution: {integrity: sha512-Dex78txRqFIHSsNeIV3cWnQtwpcKM/n8RgBYx860lhGpD5vKTK3kf6N+6DWIpyvi7GVZ+vW/5/ea3Y1ReRvfnQ==} + peerDependencies: + '@react-three/fiber': '*' + react: '*' + three: '>=0.140.0' + dependencies: + '@coconut-xr/flex': 3.0.4 + '@coconut-xr/glyph': 0.0.6(three@0.160.0) + '@coconut-xr/xmaterials': 0.0.3(three@0.160.0) + '@react-three/fiber': 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + react: 18.2.0 + suspend-react: 0.1.3(react@18.2.0) + three: 0.160.0 + yoga-wasm-web: 0.3.3 + dev: false + + /@coconut-xr/natuerlich@0.0.49(@coconut-xr/koestlich@0.3.12)(@react-three/fiber@8.15.13)(@types/react@18.2.47)(react@18.2.0)(three@0.160.0): + resolution: {integrity: sha512-DBoIV9qt0KQWii+Ko01MtrlVoXHGonBWQa87baVmJ6kjnGDIwSL2UkSstXl5vXkzZW0T+dY9jnPOlHZxNFpNcQ==} + peerDependencies: + '@coconut-xr/koestlich': '*' + '@react-three/fiber': '*' + react: '*' + three: '*' + peerDependenciesMeta: + '@coconut-xr/koestlich': + optional: true + dependencies: + '@coconut-xr/koestlich': 0.3.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) + '@coconut-xr/xinteraction': 0.1.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0) + '@coconut-xr/xmaterials': 0.0.3(three@0.160.0) + '@react-three/fiber': 8.15.13(react-dom@18.2.0)(react@18.2.0)(three@0.160.0) + '@webxr-input-profiles/motion-controllers': 1.0.0 + meshline: 3.1.6(three@0.160.0) + react: 18.2.0 + suspend-react: 0.0.9(react@18.2.0) + three: 0.160.0 + zustand: 4.4.7(@types/react@18.2.47)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /@coconut-xr/xinteraction@0.1.12(@react-three/fiber@8.15.13)(react@18.2.0)(three@0.160.0): resolution: {integrity: sha512-nuKWTDz9qx1HRw7MASvqhxO7i676FVWdh+vITFhQYpQem7QLyJqSaU/gw3l7Qh7QYZENmvvte4X83D1oih/htA==} peerDependencies: @@ -593,6 +699,14 @@ packages: three: 0.160.0 dev: false + /@coconut-xr/xmaterials@0.0.3(three@0.160.0): + resolution: {integrity: sha512-lqchBqGz5cbKzIAZhVHR/BvHH9ReS6rtZo+FMU/Ra4B/htJGA1UgzjTor6ybUuRLHgB9o1MWcW9GJ2KX/qukNg==} + peerDependencies: + three: '>=0.140.0' + dependencies: + three: 0.160.0 + dev: false + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -606,7 +720,6 @@ packages: cpu: [ppc64] os: [aix] requiresBuild: true - dev: true optional: true /@esbuild/android-arm64@0.19.11: @@ -615,7 +728,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-arm@0.19.11: @@ -624,7 +736,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/android-x64@0.19.11: @@ -633,7 +744,6 @@ packages: cpu: [x64] os: [android] requiresBuild: true - dev: true optional: true /@esbuild/darwin-arm64@0.19.11: @@ -642,7 +752,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/darwin-x64@0.19.11: @@ -651,7 +760,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-arm64@0.19.11: @@ -660,7 +768,6 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/freebsd-x64@0.19.11: @@ -669,7 +776,6 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm64@0.19.11: @@ -678,7 +784,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-arm@0.19.11: @@ -687,7 +792,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ia32@0.19.11: @@ -696,7 +800,6 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-loong64@0.19.11: @@ -705,7 +808,6 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-mips64el@0.19.11: @@ -714,7 +816,6 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-ppc64@0.19.11: @@ -723,7 +824,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-riscv64@0.19.11: @@ -732,7 +832,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-s390x@0.19.11: @@ -741,7 +840,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/linux-x64@0.19.11: @@ -750,7 +848,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@esbuild/netbsd-x64@0.19.11: @@ -759,7 +856,6 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true - dev: true optional: true /@esbuild/openbsd-x64@0.19.11: @@ -768,7 +864,6 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true - dev: true optional: true /@esbuild/sunos-x64@0.19.11: @@ -777,7 +872,6 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true - dev: true optional: true /@esbuild/win32-arm64@0.19.11: @@ -786,7 +880,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-ia32@0.19.11: @@ -795,7 +888,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@esbuild/win32-x64@0.19.11: @@ -804,7 +896,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): @@ -1617,6 +1708,109 @@ packages: fastq: 1.17.1 dev: true + /@octokit/auth-token@4.0.0: + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/core@5.1.0: + resolution: {integrity: sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.0.2 + '@octokit/request': 8.2.0 + '@octokit/request-error': 5.0.1 + '@octokit/types': 12.6.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/endpoint@9.0.4: + resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 12.6.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/graphql@7.0.2: + resolution: {integrity: sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request': 8.2.0 + '@octokit/types': 12.6.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/openapi-types@20.0.0: + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + dev: false + + /@octokit/plugin-paginate-rest@9.2.0(@octokit/core@5.1.0): + resolution: {integrity: sha512-NKi0bJEZqOSbBLMv9kdAcuocpe05Q2xAXNLTGi0HN2GSMFJHNZuSoPNa0tcQFTOFCKe+ZaYBZ3lpXh1yxgUDCA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.1.0 + '@octokit/types': 12.6.0 + dev: false + + /@octokit/plugin-request-log@4.0.0(@octokit/core@5.1.0): + resolution: {integrity: sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.1.0 + dev: false + + /@octokit/plugin-rest-endpoint-methods@10.4.0(@octokit/core@5.1.0): + resolution: {integrity: sha512-INw5rGXWlbv/p/VvQL63dhlXr38qYTHkQ5bANi9xofrF9OraqmjHsIGyenmjmul1JVRHpUlw5heFOj1UZLEolA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.1.0 + '@octokit/types': 12.6.0 + dev: false + + /@octokit/request-error@5.0.1: + resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 12.6.0 + deprecation: 2.3.1 + once: 1.4.0 + dev: false + + /@octokit/request@8.2.0: + resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/endpoint': 9.0.4 + '@octokit/request-error': 5.0.1 + '@octokit/types': 12.6.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/rest@20.0.2: + resolution: {integrity: sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/core': 5.1.0 + '@octokit/plugin-paginate-rest': 9.2.0(@octokit/core@5.1.0) + '@octokit/plugin-request-log': 4.0.0(@octokit/core@5.1.0) + '@octokit/plugin-rest-endpoint-methods': 10.4.0(@octokit/core@5.1.0) + dev: false + + /@octokit/types@12.6.0: + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + dependencies: + '@octokit/openapi-types': 20.0.0 + dev: false + /@pkgr/core@0.1.1: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1927,7 +2121,6 @@ packages: cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-android-arm64@4.9.5: @@ -1935,7 +2128,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-arm64@4.9.5: @@ -1943,7 +2135,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-x64@4.9.5: @@ -1951,7 +2142,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.9.5: @@ -1959,7 +2149,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.9.5: @@ -1967,7 +2156,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.9.5: @@ -1975,7 +2163,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-riscv64-gnu@4.9.5: @@ -1983,7 +2170,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.9.5: @@ -1991,7 +2177,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-musl@4.9.5: @@ -1999,7 +2184,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.9.5: @@ -2007,7 +2191,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.9.5: @@ -2015,7 +2198,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.9.5: @@ -2023,7 +2205,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@sindresorhus/is@0.14.0: @@ -2110,7 +2291,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2440,6 +2620,10 @@ packages: - supports-color dev: true + /@webxr-input-profiles/motion-controllers@1.0.0: + resolution: {integrity: sha512-Ppxde+G1/QZbU8ShCQg+eq5VtlcL/FPkerF1dkDOLlIml0LJD1tFqnCZYR0SrHzYleIQ2siRnOx7xbFLaCpExQ==} + dev: false + /acorn-jsx@5.3.2(acorn@8.11.3): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2634,6 +2818,10 @@ packages: has-symbols: 1.0.3 dev: true + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -2641,6 +2829,16 @@ packages: possible-typed-array-names: 1.0.0 dev: true + /axios@1.6.7(debug@4.3.4): + resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} + dependencies: + follow-redirects: 1.15.5(debug@4.3.4) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} dev: false @@ -2682,6 +2880,10 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + /before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + dev: false + /bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} dependencies: @@ -2977,6 +3179,13 @@ packages: color-string: 1.9.1 dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: false @@ -3059,7 +3268,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 8.1.1 - dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -3136,6 +3344,15 @@ packages: object-keys: 1.1.1 dev: true + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dev: false + /detect-gpu@5.0.37: resolution: {integrity: sha512-EraWs84faI4iskB4qvE39bevMIazEvd1RpoyGLOBesRLbiz6eMeJqqRPHjEFClfRByYZzi9IzU35rBXIO76oDw==} dependencies: @@ -3354,7 +3571,6 @@ packages: '@esbuild/win32-arm64': 0.19.11 '@esbuild/win32-ia32': 0.19.11 '@esbuild/win32-x64': 0.19.11 - dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -3768,12 +3984,33 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true + /follow-redirects@1.15.5(debug@4.3.4): + resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: + debug: 4.3.4(supports-color@8.1.1) + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 dev: true + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: false @@ -3786,7 +4023,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true /function-bind@1.1.2: @@ -4688,6 +4924,14 @@ packages: engines: {node: '>= 8'} dev: true + /meshline@3.1.6(three@0.160.0): + resolution: {integrity: sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==} + peerDependencies: + three: '>=0.137' + dependencies: + three: 0.160.0 + dev: false + /meshline@3.1.7(three@0.160.0): resolution: {integrity: sha512-uf9fPI9wy0Ie0kZjvKuIkf2n7gi3ih0wdTeb/kmSvmzpPyEL5d9lFohg9+JV9VC4sQUBOZDgxu6fnjn57goSHg==} peerDependencies: @@ -4706,6 +4950,18 @@ packages: picomatch: 2.3.1 dev: true + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -4815,7 +5071,6 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4858,7 +5113,6 @@ packages: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: true /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} @@ -5189,7 +5443,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -5224,7 +5477,6 @@ packages: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - dev: true /postprocessing@6.34.3(three@0.160.0): resolution: {integrity: sha512-AjsxAGWeHMKlCTuWLeahNnPyFwR0c/pEmveq5mBz767lFBc1+HHYzk0aRGBCE9PZIjiA27oRL7lATd+fVOscRA==} @@ -5291,6 +5543,10 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -5603,7 +5859,6 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.9.5 '@rollup/rollup-win32-x64-msvc': 4.9.5 fsevents: 2.3.3 - dev: true /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -5782,7 +6037,6 @@ packages: /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} - dev: true /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -5936,13 +6190,20 @@ packages: engines: {node: '>=10'} dependencies: has-flag: 4.0.0 - dev: true /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: true + /suspend-react@0.0.9(react@18.2.0): + resolution: {integrity: sha512-668Pxy4z54fhjpnPqw6olj3vvpV03VywFNXazyNSYSTuHaRw3NJJjO5l8R49AUk5fMTkJXGVGaaVSgdaO01S3w==} + peerDependencies: + react: '>=17.0' + dependencies: + react: 18.2.0 + dev: false + /suspend-react@0.1.3(react@18.2.0): resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} peerDependencies: @@ -6264,6 +6525,10 @@ packages: crypto-random-string: 2.0.0 dev: false + /universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + dev: false + /update-browserslist-db@1.0.13(browserslist@4.23.0): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -6347,6 +6612,21 @@ packages: engines: {node: '>= 0.10'} dev: true + /vite-plugin-mkcert@1.17.4(vite@5.0.12): + resolution: {integrity: sha512-P/B/tXhsUdpFO4Go1f2obu8WL3xxA3lbsbz4YzflbWfqWl15uwFiXaiCCBztJu1f4WnilrdXYihGrGT9jU743A==} + engines: {node: '>=v16.7.0'} + peerDependencies: + vite: '>=3' + dependencies: + '@octokit/rest': 20.0.2 + axios: 1.6.7(debug@4.3.4) + debug: 4.3.4(supports-color@8.1.1) + picocolors: 1.0.0 + vite: 5.0.12(@types/node@20.11.0) + transitivePeerDependencies: + - supports-color + dev: false + /vite@5.0.12(@types/node@20.11.0): resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} engines: {node: ^18.0.0 || >=20.0.0} @@ -6381,7 +6661,6 @@ packages: rollup: 4.9.5 optionalDependencies: fsevents: 2.3.3 - dev: true /wasmwrap@1.0.0: resolution: {integrity: sha512-USSwELYC5NunOCD+GnBuiWfGB4mp9Pluhl5ulgcoYRBLuMk3mfBwWvE5bW3LPEljlbksjjJHjHk+hX6azFreBQ==}