diff --git a/app/src/assets/shaders/edge/edge-fade.frag.glsl b/app/src/assets/shaders/edge/edge-fade.frag.glsl new file mode 100644 index 0000000..3e1a004 --- /dev/null +++ b/app/src/assets/shaders/edge/edge-fade.frag.glsl @@ -0,0 +1,32 @@ +precision highp float; + +varying vec3 vWorldPosition; + +uniform vec3 uColor; +uniform float uFadeDistance; // max distance inward from edge +uniform vec2 uEdges[64]; // polygon edges packed as pairs (start, end) +uniform int uEdgeCount; + +float pointToSegmentDist(vec2 p, vec2 a, vec2 b) { + vec2 pa = p - a; + vec2 ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h); +} + +void main() { + vec2 fragXZ = vWorldPosition.xz; + + float minDist = 1e6; + for (int i = 0; i < 64; i++) { + if (i >= uEdgeCount) break; + vec2 a = uEdges[i*2 + 0]; + vec2 b = uEdges[i*2 + 1]; + float d = pointToSegmentDist(fragXZ, a, b); + minDist = min(minDist, d); + } + + float alpha = smoothstep(uFadeDistance, 0.0, minDist); + + gl_FragColor = vec4(uColor, alpha); +} diff --git a/app/src/assets/shaders/edge/edge-fade.vert.glsl b/app/src/assets/shaders/edge/edge-fade.vert.glsl new file mode 100644 index 0000000..0b8dc6d --- /dev/null +++ b/app/src/assets/shaders/edge/edge-fade.vert.glsl @@ -0,0 +1,7 @@ +varying vec3 vWorldPosition; + +void main() { + vec4 worldPos = modelMatrix * vec4(position, 1.0); + vWorldPosition = worldPos.xyz; + gl_Position = projectionMatrix * viewMatrix * worldPos; +} diff --git a/app/src/modules/builder/materials/polygonEdgeMaterial.tsx b/app/src/modules/builder/materials/polygonEdgeMaterial.tsx new file mode 100644 index 0000000..0eebb01 --- /dev/null +++ b/app/src/modules/builder/materials/polygonEdgeMaterial.tsx @@ -0,0 +1,75 @@ +import { useMemo } from "react"; +import { Color, DoubleSide, ShaderMaterial, Shape, ShapeGeometry, Vector3 } from "three"; +import useShaderReader from "../../../utils/scene/useShaderReader"; + +import vertexShaderUrl from "../../../assets/shaders/edge/edge-fade.vert.glsl"; +import fragmentShaderUrl from "../../../assets/shaders/edge/edge-fade.frag.glsl"; + +type PolygonEdgeMaterialProps = { + points: { position: [number, number, number] }[]; + positionY: number; + color: string; +}; + +function PolygonEdgeMaterial({ points, positionY, color }: Readonly) { + const vertexShader = useShaderReader(vertexShaderUrl); + const fragmentShader = useShaderReader(fragmentShaderUrl); + + const geometry = useMemo(() => { + if (points.length < 3) return null; + + const shape = new Shape(); + points.forEach((p, i) => { + const v = new Vector3(...p.position); + if (i === 0) shape.moveTo(v.x, v.z); + else shape.lineTo(v.x, v.z); + }); + shape.closePath(); + + return new ShapeGeometry(shape); + }, [points]); + + const edges = useMemo(() => { + return points.map((p, i) => { + const next = points[(i + 1) % points.length]; + return [new Vector3(...p.position), new Vector3(...next.position)]; + }); + }, [points]); + + const material = useMemo(() => { + if (!vertexShader || !fragmentShader) return null; + + const edgeArray: number[] = []; + edges.forEach(([a, b]) => { + edgeArray.push(a.x, a.z, b.x, b.z); + }); + + return new ShaderMaterial({ + side: DoubleSide, + vertexShader, + fragmentShader, + uniforms: { + uColor: { value: new Color(color) }, + uFadeDistance: { value: 2.0 }, + uEdges: { value: edgeArray }, + uEdgeCount: { value: edges.length }, + }, + transparent: true, + depthWrite: false, + depthTest: true, + polygonOffset: true, + polygonOffsetFactor: -10, + polygonOffsetUnits: -10, + }); + }, [color, vertexShader, fragmentShader, edges]); + + if (!geometry || !material) return null; + + return ( + + + + ); +} + +export default PolygonEdgeMaterial; diff --git a/app/src/modules/builder/zone/Instances/Instance/zoneInstance.tsx b/app/src/modules/builder/zone/Instances/Instance/zoneInstance.tsx index c6a0503..b0a2d40 100644 --- a/app/src/modules/builder/zone/Instances/Instance/zoneInstance.tsx +++ b/app/src/modules/builder/zone/Instances/Instance/zoneInstance.tsx @@ -3,17 +3,24 @@ import { Color, DoubleSide, ShaderMaterial, Vector3 } from "three"; import { useFrame } from "@react-three/fiber"; import { useSceneStore } from "../../../../../store/scene/useSceneStore"; import useShaderReader from "../../../../../utils/scene/useShaderReader"; +import CornerReference from "./cornerReference"; +import PolygonEdgeMaterial from "../../../materials/polygonEdgeMaterial"; import vertexShaderUrl from "../../../../../assets/shaders/zone/zone1.vert.glsl"; import fragmentShaderUrl from "../../../../../assets/shaders/zone/zone1.frag.glsl"; -import CornerReference from "./cornerReference"; + +// import vertexShaderUrl from "../../../../../assets/shaders/zone/zone2.vert.glsl"; +// import fragmentShaderUrl from "../../../../../assets/shaders/zone/zone2.frag.glsl"; + +// import vertexShaderUrl from "../../../../../assets/shaders/zone/zone2.vert.glsl"; +// import fragmentShaderUrl from "../../../../../assets/shaders/zone/zone2.frag.glsl"; function ZoneInstance({ zone }: { readonly zone: Zone }) { const vertexShader = useShaderReader(vertexShaderUrl); const fragmentShader = useShaderReader(fragmentShaderUrl); const zoneLayer = zone.points[0].layer; const { limitFps } = useSceneStore(); - const [time, setTime] = useState(); + const [time, setTime] = useState(0); const zoneMaterial = useMemo(() => { if (!vertexShader || !fragmentShader) return null; @@ -40,7 +47,9 @@ function ZoneInstance({ zone }: { readonly zone: Zone }) { if (!zoneMaterial) return null; return ( - + + + {zone.points.map((point, index: number) => { const nextPoint = zone.points[(index + 1) % zone.points.length]; const prevPoint = zone.points[(index - 1 + zone.points.length) % zone.points.length]; @@ -65,7 +74,7 @@ function ZoneInstance({ zone }: { readonly zone: Zone }) { ); })} - + ); }