first commit

This commit is contained in:
nemutas
2022-05-17 19:30:09 +09:00
commit d4c857b88e
32 changed files with 5315 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+4483
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "r3f-water-drops",
"homepage": "https://nemutas.github.io/r3f-water-drops/",
"private": true,
"version": "0.0.0",
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"deploy": "npm run build && gh-pages -d dist"
},
"dependencies": {
"@emotion/css": "^11.9.0",
"@react-three/cannon": "^6.3.0",
"@react-three/drei": "^9.11.0",
"@react-three/fiber": "^8.0.17",
"lil-gui": "^0.16.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"three": "^0.140.2"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/three": "^0.140.0",
"@vitejs/plugin-react": "^1.3.0",
"gh-pages": "^4.0.0",
"typescript": "^4.6.3",
"vite": "^2.9.9",
"vite-plugin-glsl": "^0.1.2"
}
}
+3
View File
@@ -0,0 +1,3 @@
<svg width="165" height="165" viewBox="0 0 165 165" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.5 0C36.9188 0 0 36.9188 0 82.5C0 119.006 23.6156 149.841 56.4094 160.772C60.5344 161.494 62.0812 159.019 62.0812 156.853C62.0812 154.894 61.9781 148.397 61.9781 141.488C41.25 145.303 35.8875 136.434 34.2375 131.794C33.3094 129.422 29.2875 122.1 25.7812 120.141C22.8937 118.594 18.7687 114.778 25.6781 114.675C32.175 114.572 36.8156 120.656 38.3625 123.131C45.7875 135.609 57.6469 132.103 62.3906 129.938C63.1125 124.575 65.2781 120.966 67.65 118.903C49.2938 116.841 30.1125 109.725 30.1125 78.1688C30.1125 69.1969 33.3094 61.7719 38.5688 55.9969C37.7438 53.9344 34.8563 45.4781 39.3937 34.1344C39.3937 34.1344 46.3031 31.9688 62.0812 42.5906C68.6813 40.7344 75.6937 39.8063 82.7062 39.8063C89.7188 39.8063 96.7312 40.7344 103.331 42.5906C119.109 31.8656 126.019 34.1344 126.019 34.1344C130.556 45.4781 127.669 53.9344 126.844 55.9969C132.103 61.7719 135.3 69.0938 135.3 78.1688C135.3 109.828 116.016 116.841 97.6594 118.903C100.65 121.481 103.228 126.431 103.228 134.166C103.228 145.2 103.125 154.069 103.125 156.853C103.125 159.019 104.672 161.597 108.797 160.772C125.174 155.242 139.406 144.717 149.488 130.676C159.57 116.635 164.995 99.7857 165 82.5C165 36.9188 128.081 0 82.5 0Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

+25
View File
@@ -0,0 +1,25 @@
import React, { FC } from 'react';
import { css } from '@emotion/css';
import { publicPath } from '../modules/utils';
import { LinkIconButton } from './LinkIconButton';
import { TCanvas } from './three/TCanvas';
export const App: FC = () => {
return (
<div className={styles.container}>
<TCanvas />
<LinkIconButton
imagePath={publicPath('/assets/icons/github.svg')}
linkPath="https://github.com/nemutas/r3f-water-drops"
/>
</div>
)
}
const styles = {
container: css`
position: relative;
width: 100vw;
height: 100vh;
`
}
+104
View File
@@ -0,0 +1,104 @@
import React, { FC, useState } from 'react';
type LinkIconButtonProps = {
/**
* Resource path directly under the public folder.
* @example '/assets/icons/github.svg'
*/
imagePath: string
/**
* @example 'https://github.com'
*/
linkPath: string
/**
* @default 'bottom-right'
*/
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
/**
* @default [50, 50] - width:50px, height:50px
*/
size?: [number, number]
}
export const LinkIconButton: FC<LinkIconButtonProps> = props => {
const { imagePath, linkPath, position = 'bottom-right', size = [50, 50] } = props
const [hover, setHover] = useState(false)
const publicImagePath = imagePath
let positionStyle
switch (position) {
case 'top-left':
positionStyle = styles.topLeft
break
case 'top-right':
positionStyle = styles.topRight
break
case 'bottom-left':
positionStyle = styles.bottomLeft
break
default:
positionStyle = styles.bottomRight
}
return (
<a
style={positionStyle}
href={linkPath}
target="_blank"
rel="noreferrer noopener"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}>
<img style={hover ? hoverStyles.img : styles.img} src={publicImagePath} alt="" width={size[0]} height={size[1]} />
</a>
)
}
// ========================================================
// styles
type Styles = { [key in string]: React.CSSProperties }
const temp: Styles = {
container: {
position: 'absolute',
fontSize: '0'
}
}
const styles: Styles = {
topLeft: {
...temp.container,
top: '10px',
left: '10px'
},
topRight: {
...temp.container,
top: '10px',
right: '10px'
},
bottomLeft: {
...temp.container,
bottom: '10px',
left: '10px'
},
bottomRight: {
...temp.container,
bottom: '10px',
right: '10px'
},
img: {
objectFit: 'cover',
opacity: '0.5',
transform: 'rotate(0deg)',
transition: 'all 0.3s'
}
}
const hoverStyles: Styles = {
img: {
...styles.img,
opacity: '1',
transform: 'rotate(360deg)'
}
}
+49
View File
@@ -0,0 +1,49 @@
import React, { FC } from 'react';
import * as THREE from 'three';
import { Plane, useTexture } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber';
import fragmentShader from '../../modules/glsl/raymarchingFrag.glsl';
import vertexShader from '../../modules/glsl/raymarchingVert.glsl';
import { AMOUNT, datas } from '../../modules/store';
import { publicPath } from '../../modules/utils';
export const ScreenPlane: FC = () => {
const { viewport } = useThree()
const texture = useTexture(publicPath('/assets/images/wlop.jpg'))
texture.encoding = THREE.sRGBEncoding
texture.wrapS = THREE.MirroredRepeatWrapping
texture.wrapT = THREE.MirroredRepeatWrapping
// Calculate screen size and texture aspect ratio
const textureAspect = texture.image.width / texture.image.height
const aspect = viewport.aspect
const ratio = aspect / textureAspect
const [x, y] = aspect < textureAspect ? [ratio, 1] : [1, 1 / ratio]
// Replace embedded text
const fragment = fragmentShader.replaceAll('AMOUNT', AMOUNT.toString())
const shader: THREE.Shader = {
uniforms: {
u_aspect: { value: viewport.aspect },
u_datas: { value: datas },
u_texture: { value: texture },
u_uvScale: { value: new THREE.Vector2(x, y) }
},
vertexShader,
fragmentShader: fragment
}
useFrame(() => {
datas.forEach((data, i) => {
shader.uniforms.u_datas.value[i].position.copy(data.position)
})
})
return (
<Plane args={[1, 1]} scale={[viewport.width, viewport.height, 1]}>
<shaderMaterial args={[shader]} />
</Plane>
)
}
+82
View File
@@ -0,0 +1,82 @@
import React, { FC, useEffect } from 'react';
import * as THREE from 'three';
import { Physics, usePlane, useSphere } from '@react-three/cannon';
import { useFrame } from '@react-three/fiber';
import { datas } from '../../modules/store';
import { Data } from '../../types/types';
export const Simulator: FC = () => {
return (
<Physics gravity={[0, 0, 0]} iterations={10} broadphase="SAP">
{/* debug mode */}
{/* <Debug color="#fff" scale={1.05}>
<Collisions />
{datas.map((data, i) => (
<PhysicalSphere key={i} data={data} />
))}
</Debug> */}
{/* product mode */}
<Collisions />
{datas.map((data, i) => (
<PhysicalSphere key={i} data={data} />
))}
</Physics>
)
}
// ========================================================
const Collisions: FC = () => {
// back
usePlane(() => ({ position: [0, 0, -1], rotation: [0, 0, 0] }))
// front
usePlane(() => ({ position: [0, 0, 5], rotation: [0, -Math.PI, 0] }))
// // bottom
// usePlane(() => ({ position: [0, -4, 0], rotation: [-Math.PI / 2, 0, 0] }))
// // top
// usePlane(() => ({ position: [0, 4, 0], rotation: [Math.PI / 2, 0, 0] }))
const [, api] = useSphere(() => ({ type: 'Kinematic', args: [3] }))
useFrame(({ mouse, viewport }) => {
const x = (mouse.x * viewport.width) / 2
const y = (mouse.y * viewport.height) / 2
api.position.set(x, y, 1.5)
})
return null
}
// ========================================================
const PhysicalSphere: FC<{ data: Data }> = ({ data }) => {
const scale = data.scale
const [ref, api] = useSphere(() => ({
mass: 1,
angularDamping: 0.1,
linearDamping: 0.95,
fixedRotation: true,
args: [scale],
position: data.position.toArray()
}))
useEffect(() => {
const vec = new THREE.Vector3()
const unsubscribe = api.position.subscribe(p => {
data.position.set(p[0], p[1], p[2])
vec
.set(p[0], p[1], p[2])
.normalize()
.multiplyScalar(-scale * 30)
return api.applyForce(vec.toArray(), [0, 0, 0])
})
return () => {
unsubscribe()
}
}, [api])
return null
}
+26
View File
@@ -0,0 +1,26 @@
import React, { FC, Suspense } from 'react';
import { Canvas } from '@react-three/fiber';
import { PostProcessing } from './postprocessing/PostProcessing';
import { ScreenPlane } from './ScreenPlane';
import { Simulator } from './Simulator';
export const TCanvas: FC = () => {
return (
<Canvas
camera={{
position: [0, 0, 15],
fov: 50,
aspect: window.innerWidth / window.innerHeight,
near: 0.1,
far: 2000
}}
dpr={window.devicePixelRatio}>
<color attach="background" args={['#000']} />
<Suspense fallback={null}>
<Simulator />
<ScreenPlane />
<PostProcessing />
</Suspense>
</Canvas>
)
}
@@ -0,0 +1,18 @@
import React, { FC } from 'react';
import { Effects } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import { FXAA } from './pass/FXAA';
export const PostProcessing: FC = () => {
const fxaa = new FXAA()
useFrame(({ size }) => {
fxaa.update(size)
})
return (
<Effects>
<shaderPass ref={fxaa.ref} args={[fxaa.shader]} />
</Effects>
)
}
@@ -0,0 +1,44 @@
import { createRef } from 'react';
import * as THREE from 'three';
import { Pass, ShaderPass } from 'three-stdlib';
export abstract class BasePass<T extends Pass> {
public ref
constructor() {
this.ref = createRef<T>()
}
protected abstract _initController: () => void
abstract update: (...args: any) => void
protected get pass() {
return this.ref.current
}
protected validatedPass = (enabled = true) => {
if (!this.pass) return null
this.pass.enabled = enabled
return enabled ? this.pass : null
}
}
export abstract class BaseShader extends BasePass<ShaderPass> {
public shader: THREE.Shader
constructor(shader: THREE.Shader) {
super()
this.shader = shader
}
}
export abstract class BaseCustomShader extends BaseShader {
constructor(uniforms: { [uniform: string]: THREE.IUniform<any> }, vertexShader: string, fragmentShader: string) {
const shader = {
uniforms: THREE.UniformsUtils.merge([{ tDiffuse: { value: null } }, uniforms]),
vertexShader,
fragmentShader
}
super(shader)
}
}
@@ -0,0 +1,20 @@
import { FXAAShader } from 'three-stdlib';
import { Size } from '@react-three/fiber';
import { BaseShader } from './Base';
export class FXAA extends BaseShader {
constructor() {
super(FXAAShader)
}
protected _initController = () => {}
update = (size: Size) => {
// validate pass
const pass = this.validatedPass()
if (!pass) return
// update uniforms
pass.uniforms.resolution.value.set(1 / size.width, 1 / size.height)
}
}
+15
View File
@@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+18
View File
@@ -0,0 +1,18 @@
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 62.5%;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
* {
box-sizing: border-box;
overflow: hidden;
}
+10
View File
@@ -0,0 +1,10 @@
import './index.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './components/App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
+3
View File
@@ -0,0 +1,3 @@
float fresnel(vec3 eye, vec3 normal) {
return pow(1.0 + dot(eye, normal), 3.0);
}
@@ -0,0 +1,25 @@
//---------------------------------------------------------
// This function is based on the Inigo Quilez article.
// https://iquilezles.org/articles/distfunctions/
//---------------------------------------------------------
float opUnion( float d1, float d2 ) { return min(d1,d2); }
float opSubtraction( float d1, float d2 ) { return max(-d1,d2); }
float opIntersection( float d1, float d2 ) { return max(d1,d2); }
float opSmoothUnion( float d1, float d2, float k ) {
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) - k*h*(1.0-h);
}
float opSmoothSubtraction( float d1, float d2, float k ) {
float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
return mix( d2, -d1, h ) + k*h*(1.0-h);
}
float opSmoothIntersection( float d1, float d2, float k ) {
float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) + k*h*(1.0-h);
}
+14
View File
@@ -0,0 +1,14 @@
//---------------------------------------------------------
// This function is based on the Inigo Quilez article.
// https://iquilezles.org/articles/normalsSDF/
//---------------------------------------------------------
// this function must be define after sdf.
vec3 calcNormal(in vec3 p) {
const float h = 0.0001;
const vec2 k = vec2(1, -1) * h;
return normalize( k.xyy * sdf( p + k.xyy ) +
k.yyx * sdf( p + k.yyx ) +
k.yxy * sdf( p + k.yxy ) +
k.xxx * sdf( p + k.xxx ) );
}
@@ -0,0 +1,13 @@
//---------------------------------------------------------
// This function is based on the Inigo Quilez article.
// https://iquilezles.org/articles/distfunctions/
//---------------------------------------------------------
float sdSphere( vec3 p, float s ) {
return length(p)-s;
}
float sdBox( vec3 p, vec3 b ) {
vec3 q = abs(p) - b;
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}
+71
View File
@@ -0,0 +1,71 @@
// AMOUNT replaces at call position
struct Data {
vec3 position;
float scale;
};
uniform float u_aspect;
uniform Data u_datas[AMOUNT];
uniform sampler2D u_texture;
uniform vec2 u_uvScale;
varying vec2 v_uv;
const int COUNT = AMOUNT;
#include './fresnel.glsl'
#include './raymarching/primitives.glsl'
#include './raymarching/combinations.glsl'
float sdf(vec3 p) {
vec3 correct = vec3(u_aspect, 1.0, 1.0) * vec3(0.08, 0.15, 0.2);
vec3 pos = p + -u_datas[0].position * correct;
float final = sdSphere(pos, 0.2 * u_datas[0].scale);
for(int i = 1; i < COUNT; i++) {
pos = p + -u_datas[i].position * correct;
float sphere = sdSphere(pos, 0.2 * u_datas[i].scale);
final = opSmoothUnion(final, sphere, 0.4);
}
return final;
}
#include './raymarching/normal.glsl'
void main() {
vec2 centeredUV = (v_uv - 0.5) * vec2(u_aspect, 1.0);
vec3 ray = normalize(vec3(centeredUV, -1.0));
vec3 camPos = vec3(0.0, 0.0, 2.3);
vec3 rayPos = camPos;
float totalDist = 0.0;
float tMax = 5.0;
for(int i = 0; i < 256; i++) {
float dist = sdf(rayPos);
if (dist < 0.0001 || tMax < totalDist) break;
totalDist += dist;
rayPos = camPos + totalDist * ray;
}
vec2 uv = (v_uv - 0.5) * u_uvScale + 0.5;
vec4 tex = texture2D(u_texture, uv);
tex = vec4(vec3((tex.r + tex.g + tex.b) / 3.0), tex.a);
vec4 outgoing = tex;
if(totalDist < tMax) {
vec3 normal = calcNormal(rayPos);
float f = fresnel(ray, normal);
float len = pow(length(normal.xy), 3.0);
uv += normal.xy * len * 0.1;
tex = texture2D(u_texture, uv);
tex += f * 0.3;
outgoing = tex;
}
gl_FragColor = outgoing;
}
+6
View File
@@ -0,0 +1,6 @@
varying vec2 v_uv;
void main() {
v_uv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
+156
View File
@@ -0,0 +1,156 @@
import GUI from 'lil-gui';
export class GUIController {
private static _instance: GUIController | null
private _gui
private _currentFolderName: string | undefined
private constructor() {
this._gui = new GUI()
}
static get instance() {
if (!this._instance) {
this._instance = new GUIController()
}
this._instance._currentFolderName = undefined
return this._instance
}
private _getGui = (folderName: string | undefined) => {
let gui = this._gui
if (folderName) {
gui = this._folder(folderName)
} else if (this._currentFolderName) {
gui = this._folder(this._currentFolderName)
}
return gui
}
private _folder = (title: string) => {
let folder = this._gui.folders.find(f => f._title === title)
if (!folder) folder = this._gui.addFolder(title)
return folder
}
private _controller = (gui: GUI, name: string) => {
return gui.controllers.find(c => c._name === name)
}
setFolder = (name: string) => {
this._currentFolderName = name
return this
}
open = (open: boolean) => {
this._getGui(this._currentFolderName).open(open)
return this
}
/**
* add color controls
* @reference https://lil-gui.georgealways.com/#Guide#Colors
*/
addColor = (
obj: object,
propertyName: string,
rgbScale?: number | undefined,
displayName?: string | undefined,
folderName?: string | undefined
) => {
const controllerName = displayName ? displayName : propertyName
const gui = this._getGui(folderName)
let controller = this._controller(gui, controllerName)
if (!controller) {
controller = gui.addColor(obj, propertyName, rgbScale).name(controllerName)
}
return controller
}
/**
* add numeric slider controls
* @reference https://lil-gui.georgealways.com/#Guide#Numbers-and-Sliders
*/
addNumericSlider = (
obj: object,
propertyName: string,
min: number,
max: number,
step: number,
displayName?: string | undefined,
folderName?: string | undefined
) => {
const controllerName = displayName ? displayName : propertyName
const gui = this._getGui(folderName)
let controller = this._controller(gui, controllerName)
if (!controller) {
controller = gui.add(obj, propertyName, min, max, step).name(controllerName)
}
return controller
}
/**
* add dropdown controls
* @reference https://lil-gui.georgealways.com/#Guide#Dropdowns
*/
addDropdown = (
obj: object,
propertyName: string,
list: string[] | { [key: string]: number },
displayName?: string | undefined,
folderName?: string | undefined
) => {
const controllerName = displayName ? displayName : propertyName
const gui = this._getGui(folderName)
let controller = this._controller(gui, controllerName)
if (!controller) {
controller = gui.add(obj, propertyName, list).name(controllerName)
}
return controller
}
/**
* add Button controls
* @description property given by its property name is a callback method.
* @reference https://lil-gui.georgealways.com/#Guide#Saving
*/
addButton = (
obj: object,
propertyName: string,
displayName?: string | undefined,
folderName?: string | undefined
) => {
const controllerName = displayName ? displayName : propertyName
const gui = this._getGui(folderName)
let controller = this._controller(gui, controllerName)
if (!controller) {
controller = gui.add(obj, propertyName).name(controllerName)
}
return controller
}
/**
* add CheckBox controls
* @description property given by its property name is type of boolean.
* @reference https://lil-gui.georgealways.com/#Guide#Adding-Controllers
*/
addCheckBox = (
obj: object,
propertyName: string,
displayName?: string | undefined,
folderName?: string | undefined
) => {
const controllerName = displayName ? displayName : propertyName
const gui = this._getGui(folderName)
let controller = this._controller(gui, controllerName)
if (!controller) {
controller = gui.add(obj, propertyName).name(controllerName)
}
return controller
}
}
+10
View File
@@ -0,0 +1,10 @@
import * as THREE from 'three';
import { Data } from '../types/types';
export const AMOUNT = 20
export const datas: Data[] = [...Array(AMOUNT)].map(() => {
const position = new THREE.Vector3(THREE.MathUtils.randFloat(-5, 5), THREE.MathUtils.randFloat(-5, 5), 0)
const scale = THREE.MathUtils.randFloat(0.5, 1.5)
return { position, scale }
})
+4
View File
@@ -0,0 +1,4 @@
export const publicPath = (path: string) => {
path.startsWith('/') && (path = path.substring(1))
return import.meta.env.BASE_URL + path
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.glsl' {
const value: string
export default value
}
+4
View File
@@ -0,0 +1,4 @@
export type Data = {
position: THREE.Vector3
scale: number
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'vite';
import glsl from 'vite-plugin-glsl';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), glsl()],
base: '/r3f-water-drops/'
})