import Roact from "@rbxts/roact" import { useEffect, useCallback, useMemo, useMutable } from "@rbxts/roact-hooked" import { acrylicInstance } from "./acrylicInstance" import { Lighting, Workspace } from "@rbxts/services" import Make from "@rbxts/make" const cylinderAngleOffset = CFrame.Angles(0, math.rad(90), 0) function viewportPointToWorld(location: Vector2, distance: number): Vector3 { const unitRay = Workspace.CurrentCamera!.ScreenPointToRay(location.X, location.Y) return unitRay.Origin.add(unitRay.Direction.mul(distance)) } function map(n: number, min0: number, max0: number, min1: number, max1: number): number { return min1 + ((n - min0) * (max1 - min1)) / (max0 - min0) } function getOffset(): number { return map(Workspace.CurrentCamera!.ViewportSize.Y, 0, 2560, 8, 56) } interface acrylicProps extends Roact.JsxInstanceProperties { radius: number distance: number Event?: Roact.JsxInstanceEvents Change?: Roact.JsxInstanceChangeEvents } Make("DepthOfFieldEffect", { FarIntensity: 0, InFocusRadius: 0.1, NearIntensity: 1, Parent: Lighting }) function acrylic(props: acrylicProps): Roact.Element { const { radius, distance } = props const spreadableProps = { ...props } as Partial delete spreadableProps.radius delete spreadableProps.distance const frameInfo = useMutable({ topleft2d: new Vector2(), topright2d: new Vector2(), bottomright2d: new Vector2(), topleftradius2d: new Vector2() }) const acrylic = useMemo(() => { const clone = acrylicInstance.Clone() clone.Parent = Workspace return clone }, []) useEffect(() => { return () => acrylic.Destroy() }, []) const updateFrameInfo = useCallback( (size: Vector2, position: Vector2) => { const topleftRaw = position.sub(size.div(2)) const info = frameInfo.current info.topleft2d = new Vector2(math.ceil(topleftRaw.X), math.ceil(topleftRaw.Y)) info.topright2d = info.topleft2d.add(new Vector2(size.X, 0)) info.bottomright2d = info.topleft2d.add(size) info.topleftradius2d = info.topleft2d.add(new Vector2(radius, 0)) }, [distance, radius] ) const updateInstance = useCallback(() => { const { topleft2d, topright2d, bottomright2d, topleftradius2d } = frameInfo.current const topleft = viewportPointToWorld(topleft2d, distance) const topright = viewportPointToWorld(topright2d, distance) const bottomright = viewportPointToWorld(bottomright2d, distance) const topleftradius = viewportPointToWorld(topleftradius2d, distance) const cornerRadius = topleftradius.sub(topleft).Magnitude const width = topright.sub(topleft).Magnitude const height = topright.sub(bottomright).Magnitude const center = CFrame.fromMatrix( topleft.add(bottomright).div(2), Workspace.CurrentCamera!.CFrame.XVector, Workspace.CurrentCamera!.CFrame.YVector, Workspace.CurrentCamera!.CFrame.ZVector ) if (radius !== undefined && radius > 0) { acrylic.Horizontal.CFrame = center acrylic.Horizontal.Mesh.Scale = new Vector3(width - cornerRadius * 2, height, 0) acrylic.Vertical.CFrame = center acrylic.Vertical.Mesh.Scale = new Vector3(width, height - cornerRadius * 2, 0) } else { acrylic.Horizontal.CFrame = center acrylic.Horizontal.Mesh.Scale = new Vector3(width, height, 0) } if (radius !== undefined && radius > 0) { acrylic.TopLeft.CFrame = center .mul(new CFrame(-width / 2 + cornerRadius, height / 2 - cornerRadius, 0)) .mul(cylinderAngleOffset) acrylic.TopLeft.Mesh.Scale = new Vector3(0, cornerRadius * 2, cornerRadius * 2) acrylic.TopRight.CFrame = center .mul(new CFrame(width / 2 - cornerRadius, height / 2 - cornerRadius, 0)) .mul(cylinderAngleOffset) acrylic.TopRight.Mesh.Scale = new Vector3(0, cornerRadius * 2, cornerRadius * 2) acrylic.BottomLeft.CFrame = center .mul(new CFrame(-width / 2 + cornerRadius, -height / 2 + cornerRadius, 0)) .mul(cylinderAngleOffset) acrylic.BottomLeft.Mesh.Scale = new Vector3(0, cornerRadius * 2, cornerRadius * 2) acrylic.BottomRight.CFrame = center .mul(new CFrame(width / 2 - cornerRadius, -height / 2 + cornerRadius, 0)) .mul(cylinderAngleOffset) acrylic.BottomRight.Mesh.Scale = new Vector3(0, cornerRadius * 2, cornerRadius * 2) } }, [radius, distance]) useEffect(() => { updateInstance() const posHandle = Workspace.CurrentCamera!.GetPropertyChangedSignal("CFrame").Connect(updateInstance) const fovHandle = Workspace.CurrentCamera!.GetPropertyChangedSignal("FieldOfView").Connect(updateInstance) const viewportHandle = Workspace.CurrentCamera!.GetPropertyChangedSignal("ViewportSize").Connect(updateInstance) return () => { posHandle.Disconnect() fovHandle.Disconnect() viewportHandle.Disconnect() } }, [updateInstance]) return ( { const blurOffset = getOffset() const size = rbx.AbsoluteSize.sub(new Vector2(blurOffset, blurOffset)) const position = rbx.AbsolutePosition.add(rbx.AbsoluteSize.div(2)) updateFrameInfo(size, position) task.spawn(updateInstance) }, AbsolutePosition: (rbx): void => { const blurOffset = getOffset() const size = rbx.AbsoluteSize.sub(new Vector2(blurOffset, blurOffset)) const position = rbx.AbsolutePosition.add(rbx.AbsoluteSize.div(2)) updateFrameInfo(size, position) task.spawn(updateInstance) } }} Size={new UDim2(1, 0, 1, 0)} BackgroundTransparency={1} /> ) } export default acrylic