├── .changeset
├── README.md
└── config.json
├── .eslintignore
├── .eslintrc.js
├── .github
├── FUNDING.yml
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .pnpm-debug.log
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build
├── babel.config.js
├── gulp.config.js
└── ts.config.js
├── components
├── cloudy
│ ├── index.tsx
│ └── use-cloud.ts
├── context
│ ├── context.tsx
│ └── index.ts
├── fog
│ └── index.tsx
├── haze
│ ├── index.tsx
│ └── use-haze.ts
├── hooks
│ ├── use-theme.ts
│ └── use-transition.ts
├── index.tsx
├── interface
│ └── index.ts
├── meteors
│ ├── index.tsx
│ └── use-meteors.ts
├── partly-cloudy
│ ├── index.tsx
│ └── use-partly-cloud.ts
├── rain-ring
│ ├── index.tsx
│ └── use-rain-ring.tsx
├── rain
│ ├── index.tsx
│ └── use-rain.ts
├── snow
│ ├── index.tsx
│ └── use-snowflake.ts
├── star-rings
│ ├── index.tsx
│ └── use-starrings.ts
├── sun
│ ├── index.tsx
│ └── use-sun.ts
├── theme.ts
├── typing.d.ts
└── utils
│ ├── angle.ts
│ ├── constants.ts
│ ├── element.ts
│ ├── point.ts
│ ├── random.ts
│ ├── scene.ts
│ └── vertority.ts
├── docs
├── README.md
├── cloudy.md
├── fog.md
├── haze.md
├── meteors.md
├── rain-rings.md
├── rain.md
├── snow.md
├── star-rings.md
└── sun.md
├── example
├── .babelrc
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── build
│ ├── config.js
│ ├── webpack.common.config.js
│ ├── webpack.dev.config.js
│ └── webpack.prod.config.js
├── mock
│ └── fake.js
├── package.json
├── postcss.config.js
├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── index.html
│ └── site.webmanifest
├── src
│ ├── App.tsx
│ ├── api
│ │ ├── fake.ts
│ │ ├── index.ts
│ │ └── utils.ts
│ ├── components
│ │ ├── WeatherSwitcher.tsx
│ │ └── WeatherText.tsx
│ ├── constants.ts
│ ├── index.tsx
│ ├── pages
│ │ └── prod
│ │ │ ├── cloudy.tsx
│ │ │ ├── fog.tsx
│ │ │ ├── haze.tsx
│ │ │ ├── meteors.tsx
│ │ │ ├── partly-cloudy.tsx
│ │ │ ├── rain.tsx
│ │ │ ├── snow.tsx
│ │ │ ├── star-ring.tsx
│ │ │ └── sun.tsx
│ ├── routes
│ │ └── index.tsx
│ ├── typings
│ │ ├── component.d.ts
│ │ ├── rematch.d.ts
│ │ └── type.d.ts
│ └── utils
│ │ └── weather.ts
├── static
│ └── font.ttf
├── test
│ ├── jest.config.js
│ └── jest.setup.js
└── tsconfig.json
├── global.d.ts
├── gulpfile.js
├── jest.config.js
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── test
└── setupTests.ts
├── tsconfig.json
└── typings
└── style.d.ts
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "linked": [],
6 | "access": "public",
7 | "baseBranch": "master",
8 | "updateInternalDependencies": "patch",
9 | "ignore": []
10 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | es
2 | lib
3 | gulpfile.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@aiou'],
3 | rules: {
4 | '@typescript-eslint/no-redeclare': 'off',
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: JiangWeixian # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: jiangweixian
5 | open_collective: qidanta
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://afdian.net/@jiangweixian
13 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### What
2 |
3 | ### Warning
4 |
5 | - [ ] - Any warnings?
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # common
2 | node_modules
3 |
4 | # editor
5 | .DS_Store
6 | .vscode
7 |
8 | # test
9 | converage
10 | cache
11 | jest
12 |
13 | # build
14 | dist
15 | lib
16 | .lib
17 |
18 | # log
19 | lerna-debug.log
20 |
21 | # types
22 | **/*.styl.d.ts
23 |
24 | # bin-template
25 | lib
26 | .lib
27 |
28 | # react-components-lib-tempate
29 | es
30 |
--------------------------------------------------------------------------------
/.pnpm-debug.log:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.0.0
4 |
5 | ### Major Changes
6 |
7 | - f2d2313: - enable load custom buidlings
8 | - enable fog with phase
9 | - 381a5fa: enable transition config and fix camer switch
10 | - e71ca73: animation
11 | - cae159e: support night and day theme
12 | - ea560bb: - provide much friendly api like `useTheme`
13 | - extends meshline inside
14 |
15 | # v0.11.0
16 |
17 | - ✨ support `haze` weather type
18 |
19 | # v0.10.0
20 |
21 | - ✨ support `fog` weather type
22 |
23 | # ~ v0.9.0
24 |
25 | - ✨ weather type `cloudy`, `meteors`, `partly-cloudy`, `rain`, `rain-ring`, `snow`, `star-rings`, `sun`
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 JW
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
@threejs-weather
5 |
6 | [](https://www.npmjs.org/package/threejs-weather) [](https://www.npmjs.org/package/threejs-weather) [](https://www.npmjs.org/package/threejs-weather)
7 |
8 | [📝](/docs/README.md) / [✨](https://threejs-weather.now.sh)
9 |
10 |
11 |
12 | [](https://chrome.google.com/webstore/detail/%E5%B0%8F%E5%B7%9D/gckdnedgcldldbdajllnmbmfhacalini)
13 |
14 | ## install
15 |
16 | `npm install threejs-weather three @react-three/fiber`
17 |
18 | ## demos and documents
19 |
20 | docs can be found [here](/docs/README.md)
21 |
22 |
30 |
31 | ## todo
32 |
33 | - [ ] 萤火虫
34 | - [ ] 水波纹
35 | - [x] 雾霾
36 | - [x] online-demo
37 | - [x] 光环
38 | - [x] 云
39 | - [x] 流星
40 |
41 | ## develop
42 |
43 | ```console
44 | pnpm i
45 | ```
46 |
47 | 1. cd `example`
48 | 2. `pnpm link ../`
49 | 3. `pnpm dev`
50 |
--------------------------------------------------------------------------------
/build/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (modules) {
2 | const plugins = [
3 | [
4 | require.resolve('@babel/plugin-transform-typescript'),
5 | {
6 | isTSX: true,
7 | },
8 | ],
9 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }, 'ant'],
10 | ]
11 | return {
12 | presets: [
13 | require.resolve('@babel/preset-react'),
14 | [
15 | require.resolve('@babel/preset-env'),
16 | {
17 | modules,
18 | targets: {
19 | browsers: [
20 | 'last 2 versions',
21 | 'Firefox ESR',
22 | '> 1%',
23 | 'ie >= 9',
24 | 'iOS >= 8',
25 | 'Android >= 4',
26 | ],
27 | },
28 | },
29 | ],
30 | ],
31 | plugins,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/build/gulp.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const tsConfig = require('./ts.config')
3 | const babelConfig = require('./babel.config')
4 |
5 | const configs = {
6 | dirs: {
7 | components: path.resolve(__dirname, '../components'),
8 | lib: path.resolve(__dirname, '../lib'),
9 | es: path.resolve(__dirname, '../es'),
10 | devLib: path.resolve(__dirname, '../example/src/components/lib'),
11 | devEs: path.resolve(__dirname, '../example/src/components/es'),
12 | },
13 | tsConfig: tsConfig(),
14 | getBabelConfig: babelConfig,
15 | }
16 |
17 | module.exports = configs
18 |
--------------------------------------------------------------------------------
/build/ts.config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const lodash = require('lodash')
4 |
5 | const tsConfigPath = path.resolve(__dirname, '../tsconfig.json')
6 |
7 | module.exports = function () {
8 | let my = {}
9 | if (fs.existsSync(tsConfigPath)) {
10 | my = require(tsConfigPath)
11 | }
12 | return lodash.assign(
13 | {
14 | noUnusedParameters: true,
15 | noUnusedLocals: true,
16 | strictNullChecks: true,
17 | target: 'es6',
18 | jsx: 'preserve',
19 | moduleResolution: 'node',
20 | declaration: true,
21 | // isolatedModules: true,
22 | allowSyntheticDefaultImports: true,
23 | },
24 | my.compilerOptions,
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/components/cloudy/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { a } from '@react-spring/three'
3 | import { Mesh } from 'three'
4 |
5 | import { useClouds, UseCloudsProps, Cloud, useCloud } from './use-cloud'
6 | import { Style } from '../interface'
7 |
8 | type CloudyProps = UseCloudsProps & {
9 | style?: Style
10 | }
11 |
12 | type DarkCloudProps = {
13 | value: Cloud
14 | style?: Style
15 | }
16 |
17 | export const DarkCloud = ({ value, style }: DarkCloudProps) => {
18 | const cloud = useRef()
19 | useCloud(cloud)
20 | return (
21 | x * value.opacity)}
24 | position={value.startpoint}
25 | >
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | const Cloudy = (props: CloudyProps) => {
33 | const { clouds } = useClouds(props)
34 | const pY = props.style?.opacity.to([0, 1], [2, 0])
35 | return (
36 |
37 | {clouds.map((cloud, index) => {
38 | return
39 | })}
40 |
41 | )
42 | }
43 |
44 | export default Cloudy
45 |
--------------------------------------------------------------------------------
/components/cloudy/use-cloud.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react'
2 | import { Vector3, Mesh } from 'three'
3 | import { useFrame } from '@react-three/fiber'
4 |
5 | import { DIRS } from '../utils/constants'
6 | import random from '../utils/random'
7 | import { getCoord } from '../utils/scene'
8 | import vertority from '../utils/vertority'
9 |
10 | const DARK_CLOUD_COLORS = ['#21373d', '#535657']
11 |
12 | export const useCloud = (cloud: React.MutableRefObject) => {
13 | const dir = useRef(1)
14 | const speed = useRef(Math.random() * 0.001 * random.inRange(DIRS))
15 | const distance = useRef(0)
16 | useFrame(() => {
17 | if (!cloud.current) {
18 | return
19 | }
20 | distance.current += 0.001 * dir.current
21 | cloud.current.position.x += speed.current * dir.current
22 | cloud.current.position.y -= speed.current * dir.current
23 | if (distance.current <= 0) {
24 | dir.current = -dir.current
25 | speed.current = Math.random() * 0.001 * random.inRange(DIRS)
26 | } else if (distance.current >= 0.1) {
27 | dir.current = -dir.current
28 | speed.current = Math.random() * 0.001 * random.inRange(DIRS)
29 | }
30 | })
31 | }
32 |
33 | export type UseCloudsProps = {
34 | count?: number
35 | colors?: string[]
36 | }
37 |
38 | export type Cloud = {
39 | radius: number
40 | startpoint: Vector3
41 | opacity: number
42 | color: string
43 | }
44 |
45 | export const useClouds = (
46 | { count = 10, colors = DARK_CLOUD_COLORS }: UseCloudsProps = {
47 | count: 10,
48 | colors: DARK_CLOUD_COLORS,
49 | },
50 | ) => {
51 | const coord = useRef(getCoord()).current
52 | const clouds = useMemo(() => {
53 | return new Array(count).fill(0).map(() => {
54 | return {
55 | radius: (Math.random() * coord[0]) / 2,
56 | startpoint: vertority.fromAxis('fromTop').add(new Vector3(0, coord[1], 0)),
57 | opacity: Math.random(),
58 | color: random.inRange(colors),
59 | } as Cloud
60 | })
61 | }, [colors, coord, count])
62 | return {
63 | clouds,
64 | startpoint: new Vector3(0, coord[1], 0),
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/components/context/context.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState, useCallback, useContext, useEffect } from 'react'
2 | import { Canvas, useFrame, useThree } from '@react-three/fiber'
3 | import { Color } from 'three'
4 | import { useSpring, SpringValue } from '@react-spring/three'
5 | import { PerspectiveCamera } from '@react-three/drei'
6 |
7 | import { useTheme, UseThemeProps } from '../hooks/use-theme'
8 | import { FogCamera } from '../fog'
9 |
10 | type Config = {
11 | type?: UseThemeProps['type']
12 | mode?: UseThemeProps['mode']
13 | handleChangeType?: (type: UseThemeProps['type']) => void
14 | handleChangeMode?: (mode?: UseThemeProps['mode']) => void
15 | }
16 |
17 | const WeatherContext = createContext({})
18 |
19 | type WeatherProps = {
20 | children?: React.ReactNode
21 | defaultType?: UseThemeProps['type']
22 | defaultMode?: UseThemeProps['mode']
23 | type?: UseThemeProps['type']
24 | mode?: UseThemeProps['mode']
25 | extra?: React.ReactNode
26 | }
27 |
28 | const Container = (props: {
29 | children?: React.ReactNode
30 | style: {
31 | backgroundColor?: SpringValue
32 | opacity?: SpringValue
33 | }
34 | type: UseThemeProps['type']
35 | }) => {
36 | const { scene, camera } = useThree()
37 | useFrame(() => {
38 | scene.background = new Color(props.style.backgroundColor?.get())
39 | if (props.type !== 'fog' && props.style.backgroundColor?.idle) {
40 | camera.lookAt(0, 0, 0)
41 | camera.position.z = 5
42 | }
43 | })
44 | return <>{props.children}>
45 | }
46 |
47 | export const WeatherProvider = ({
48 | defaultMode = 'day',
49 | defaultType = 'sun',
50 | ...props
51 | }: WeatherProps) => {
52 | const [type, setType] = useState(defaultType)
53 | const [mode, setMode] = useState(defaultMode)
54 | const controlType = props.type || type
55 | const controlMode = props.mode || mode
56 | const { bind } = useTheme({ type: controlType, mode: controlMode })
57 | const { style: _style, ...config } = bind()
58 | const [style, api] = useSpring(() => ({
59 | backgroundColor: _style.backgroundColor,
60 | }))
61 | useEffect(() => {
62 | api.start({
63 | backgroundColor: _style.backgroundColor,
64 | })
65 | }, [_style.backgroundColor, api])
66 | const handleChangeType = useCallback((type: UseThemeProps['type']) => {
67 | setType(type)
68 | }, [])
69 | const handleChangeMode = useCallback((mode: UseThemeProps['mode']) => {
70 | setMode(mode)
71 | }, [])
72 | return (
73 |
76 | {props.extra}
77 |
87 |
88 | )
89 | }
90 |
91 | export const useWeather = () => useContext(WeatherContext)
92 | export const WeatherConsumer = WeatherContext.Consumer
93 |
--------------------------------------------------------------------------------
/components/context/index.ts:
--------------------------------------------------------------------------------
1 | export { WeatherProvider, useWeather, WeatherConsumer } from './context'
2 |
--------------------------------------------------------------------------------
/components/fog/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from 'react'
2 | import { useThree, useFrame } from '@react-three/fiber'
3 | import { PerspectiveCamera } from '@react-three/drei'
4 | import { Object3D, MeshPhysicalMaterial, Vector3, Fog as _Fog, Euler } from 'three'
5 | import { a, TransitionState } from '@react-spring/three'
6 | import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
7 |
8 | import { Style } from '../interface'
9 |
10 | const url = 'https://raw.githubusercontent.com/iondrimba/images/master/buildings.obj'
11 | const loader = new OBJLoader()
12 |
13 | type FogProps = {
14 | style?: Style
15 | p?: TransitionState
16 | url?: string
17 | }
18 |
19 | const color = '#353c3c'
20 |
21 | const CityFog = (props: FogProps) => {
22 | const [group, setGroup] = useState()
23 | const buildingRef = useRef([])
24 | const { scene } = useThree()
25 | useEffect(() => {
26 | loader.load(props.url || url, (obj) => {
27 | obj.castShadow = true
28 | obj.receiveShadow = true
29 | const models = [...obj.children].map((model) => {
30 | const scale = 0.01
31 |
32 | model.scale.set(scale, scale, scale)
33 | model.receiveShadow = true
34 | model.castShadow = true
35 |
36 | return model
37 | })
38 |
39 | const boxSize = 3
40 | const meshParams = {
41 | metalness: 0,
42 | roughness: 0.77,
43 | }
44 | const max = 0.009
45 | const min = 0.001
46 | const material = new MeshPhysicalMaterial({ ...meshParams, transparent: true })
47 |
48 | const buildings = new Object3D()
49 | const gridSize = 40
50 |
51 | for (let i = 0; i < gridSize; i++) {
52 | for (let j = 0; j < gridSize; j++) {
53 | const model = models[Math.floor(Math.random() * Math.floor(models.length))].clone()
54 | ;(model as any).material = material
55 | model.scale.y = Math.random() * (max - min + 0.01)
56 | model.position.x = i * boxSize
57 | model.position.z = j * boxSize
58 |
59 | buildingRef.current.push(model)
60 | buildings.add(model)
61 | }
62 | }
63 | buildings.castShadow = true
64 | buildings.receiveShadow = true
65 | setGroup(buildings)
66 | })
67 | }, [])
68 | useFrame(() => {
69 | const near = 1
70 | const far = 208 * (props.style?.opacity.get() ?? 0)
71 | scene.fog = new _Fog(color, near, far)
72 | if (buildingRef.current) {
73 | buildingRef.current.forEach((building) => {
74 | building.material.opacity = props.p?.phase === 'leave' ? 0 : props.style?.opacity.get() ?? 1
75 | })
76 | }
77 | })
78 | const scale = props.style?.scale.to([0, 1], [0.8, 1])
79 | return (
80 |
81 |
82 | {group ? (
83 |
84 | ) : null}
85 |
86 |
87 | )
88 | }
89 |
90 | const rotation = [-0.4239391588266323, 0.7010640463834621, 0.2832774959276831]
91 | const position = [127.45293777867074, 62.11080512264083, 137.6247069251716]
92 |
93 | export const FogCamera = () => {
94 | return (
95 |
101 | )
102 | }
103 |
104 | const Fog = (props: FogProps) => {
105 | return (
106 |
107 |
108 |
109 |
110 | {/* sky */}
111 |
116 |
117 |
118 |
119 | {/* land */}
120 |
127 |
128 |
129 |
130 |
131 | )
132 | }
133 |
134 | export default Fog
135 |
--------------------------------------------------------------------------------
/components/haze/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { extend } from '@react-three/fiber'
3 | import { Mesh } from 'three'
4 | import * as meshline from 'threejs-meshline'
5 | import { a } from '@react-spring/three'
6 |
7 | import { useHazeDrop, UseHazeDropProps, UseHazeProps, useHaze } from './use-haze'
8 | import { Style } from '../interface'
9 |
10 | extend(meshline)
11 |
12 | export const Haze = (
13 | props: UseHazeDropProps & {
14 | style?: Style
15 | },
16 | ) => {
17 | const haze = useRef()
18 | const mat = useRef()
19 | useHazeDrop(haze, mat, { value: props.value })
20 | return (
21 | x * props.value.opacity)}>
22 |
23 |
34 |
35 | )
36 | }
37 |
38 | type HazeProps = UseHazeProps & {
39 | style?: Style
40 | }
41 |
42 | const Hazes = (props: HazeProps) => {
43 | const { lines } = useHaze(props)
44 | return (
45 |
46 | {lines.map((haze, index) => {
47 | return
48 | })}
49 |
50 | )
51 | }
52 |
53 | export default Hazes
54 |
--------------------------------------------------------------------------------
/components/haze/use-haze.ts:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { useFrame } from '@react-three/fiber'
3 |
4 | import vertority from '../utils/vertority'
5 | import { computeBoundingbox } from '../utils/element'
6 | import random from '../utils/random'
7 | import { getCoord } from '../utils/scene'
8 | import { DIRS } from '../utils/constants'
9 | import { useRain } from '../rain/use-rain'
10 | import { angle2dir } from '../utils/angle'
11 |
12 | export type Haze = {
13 | vertices: THREE.Vector3[] // rain-drop geom
14 | orientation: 'fromRight' | 'fromTop' | 'fromLeft' // rain-drop direction
15 | leg: number
16 | angle: number // unit=deg
17 | color: string
18 | dashArray: number
19 | opacity: number
20 | }
21 |
22 | const HAZE_COLORS = ['#fff', '#0F203B']
23 |
24 | export type UseHazeProps = {
25 | count?: number
26 | angle?: number
27 | }
28 |
29 | export const DEFAULT_RAINPROPS = {
30 | count: 100,
31 | angle: -45,
32 | }
33 |
34 | export const useHaze = ({ angle = -45, count = 100 }: UseHazeProps = DEFAULT_RAINPROPS) => {
35 | const { lines } = useRain({
36 | angle,
37 | count,
38 | colors: HAZE_COLORS,
39 | })
40 | return {
41 | lines: lines.map((line) => ({
42 | ...line,
43 | opacity: Math.random() + 0.5,
44 | dashArray: Math.random() * 0.3,
45 | })),
46 | }
47 | }
48 |
49 | export type UseHazeDropProps = {
50 | value: Haze
51 | }
52 |
53 | export const useHazeDrop = (
54 | hazedrop: React.MutableRefObject,
55 | mat: React.MutableRefObject,
56 | props?: UseHazeDropProps,
57 | ) => {
58 | const friction = useRef(
59 | random.inRange(DIRS) === 1 ? 0 : 1 * 0.01 * Math.round(Math.random() * 10),
60 | ).current
61 | const coord = useRef(getCoord()).current
62 | const vy0 = useRef(0.001 + friction)
63 | // vy0 / vx0 = tan(angle)
64 | const vx0 = useRef(0.001 + friction)
65 | const a = useRef(0.002)
66 | const { offsetTop } = computeBoundingbox(props?.value.vertices[0])
67 | useFrame(() => {
68 | if (hazedrop.current?.position.y === undefined || !props?.value || !mat.current) {
69 | return
70 | }
71 | // hazedrop加速下落
72 | hazedrop.current.position.y -= vy0.current
73 | // left hazedrop从左到右移动, right hazedrop从右到左移动
74 | hazedrop.current.position.x -= vx0.current * angle2dir(props.value.angle)
75 | // 从无到有比较真实
76 | vy0.current += a.current
77 | vx0.current += a.current
78 | // 判断hazedrop是否出了边界
79 | if (offsetTop + Math.abs(hazedrop.current.position.y) > coord[1] * 2 + props.value.leg) {
80 | const vertor = vertority.fromPlacement(props.value.orientation)
81 | // 随机hazedrop初始位置, 避免loop重复
82 | hazedrop.current.position.set(vertor.x, vertor.y, vertor.z)
83 | vy0.current = 0.0001
84 | vx0.current = 0.0001
85 | }
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/components/hooks/use-theme.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react'
2 |
3 | import { Weather } from '../interface'
4 | import { dayTheme, nightTheme } from '../theme'
5 |
6 | export type UseThemeProps = {
7 | type: Weather
8 | mode: 'day' | 'night'
9 | }
10 |
11 | const theme: Record = {
12 | day: dayTheme,
13 | night: nightTheme,
14 | }
15 |
16 | export const useTheme = ({ mode = 'day', type }: UseThemeProps) => {
17 | const config = useMemo(() => {
18 | return theme[mode][type]
19 | }, [mode, type])
20 | const bind = useCallback(() => {
21 | return {
22 | ...config,
23 | dpr: window.devicePixelRatio,
24 | }
25 | }, [config])
26 | return {
27 | bind,
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/components/hooks/use-transition.ts:
--------------------------------------------------------------------------------
1 | import { useTransition as useSpringTransition } from '@react-spring/core'
2 |
3 | type TransitionProps = {
4 | location: any
5 | delay?: number
6 | }
7 |
8 | export const useTransition = (props: TransitionProps) => {
9 | const transition = useSpringTransition(props.location, {
10 | from: { opacity: 0, scale: [0, 0, 0] },
11 | enter: { opacity: 1, scale: [1, 1, 1] },
12 | leave: { opacity: 0, scale: [0, 0, 0] },
13 | delay: props.delay,
14 | })
15 | return {
16 | transition,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/components/index.tsx:
--------------------------------------------------------------------------------
1 | import Rain from './rain'
2 | import Sun from './sun'
3 | import StarRings from './star-rings'
4 | import Snow from './snow'
5 | import Meteors from './meteors'
6 | import Cloudy from './cloudy'
7 | import RainRing from './rain-ring'
8 | import PartlyCloudy from './partly-cloudy'
9 | import Fog from './fog'
10 | import Haze from './haze'
11 | import { useTheme } from './hooks/use-theme'
12 | import { useTransition } from './hooks/use-transition'
13 | import { WeatherProvider, useWeather, WeatherConsumer } from './context'
14 | export * as types from './interface'
15 |
16 | export {
17 | Rain,
18 | Sun,
19 | StarRings,
20 | Snow,
21 | Meteors,
22 | Cloudy,
23 | RainRing,
24 | PartlyCloudy,
25 | Fog,
26 | Haze,
27 | useWeather,
28 | useTheme,
29 | useTransition,
30 | WeatherProvider,
31 | WeatherConsumer,
32 | }
33 |
--------------------------------------------------------------------------------
/components/interface/index.ts:
--------------------------------------------------------------------------------
1 | import type { SpringValue } from '@react-spring/core'
2 |
3 | export type Orientation = 'fromTop' | 'fromRight' | 'fromLeft' | 'fromBottom'
4 | export type Weather =
5 | | 'cloudy'
6 | | 'fog'
7 | | 'haze'
8 | | 'meteors'
9 | | 'partly-cloudy'
10 | | 'rain'
11 | | 'snow'
12 | | 'sun'
13 | | 'star-rings'
14 |
15 | export type Style = {
16 | opacity: SpringValue
17 | scale: SpringValue
18 | }
19 |
--------------------------------------------------------------------------------
/components/meteors/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { Mesh } from 'three'
3 | import { extend } from '@react-three/fiber'
4 | import { a } from '@react-spring/three'
5 | import * as meshline from 'threejs-meshline'
6 |
7 | import { useMeteors, Meteor, useMeteor, UseMeteorsProps } from './use-meteors'
8 | import { Style } from '../interface'
9 | extend(meshline)
10 |
11 | type MeteorProps = {
12 | value: Meteor
13 | style?: Style
14 | }
15 |
16 | const Meteor = ({ value, style }: MeteorProps) => {
17 | const meteor = useRef()
18 | const mat = useRef()
19 | useMeteor(meteor, mat, { value })
20 | return (
21 | x * 0.75)}>
22 |
23 |
34 |
35 | )
36 | }
37 |
38 | type MeteorsProps = UseMeteorsProps & {
39 | style?: Style
40 | }
41 |
42 | const Meteors = (props: MeteorsProps) => {
43 | const { meteors } = useMeteors(props)
44 | return (
45 | <>
46 | {meteors.map((meteor, index) => {
47 | return
48 | })}
49 | >
50 | )
51 | }
52 |
53 | export default Meteors
54 |
--------------------------------------------------------------------------------
/components/meteors/use-meteors.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react'
2 | import { Vector3, Mesh } from 'three'
3 | import { useFrame } from '@react-three/fiber'
4 |
5 | import vertority from '../utils/vertority'
6 | import { computeBoundingbox } from '../utils/element'
7 | import { deg2rad } from '../utils/angle'
8 | import { getCoord } from '../utils/scene'
9 |
10 | export type UseMeteorsProps = {
11 | count?: number
12 | angle?: number
13 | }
14 |
15 | export type Meteor = {
16 | angle: number
17 | leg: number
18 | vertices: Vector3[]
19 | hypotenuse: number
20 | color: string
21 | }
22 |
23 | export const useMeteors = (
24 | { count = 5, angle = 30 }: UseMeteorsProps = { count: 10, angle: 30 },
25 | ) => {
26 | const meteors = useMemo(() => {
27 | const _angle = deg2rad(angle)
28 | const coord = getCoord()
29 | return new Array(count).fill(0).map(() => {
30 | const leg = Math.random() * coord[1]
31 | // noise vertor for startpoint
32 | const vertor = vertority.fromPlacement('fromRight')
33 | return {
34 | vertices: [
35 | // endpoint
36 | new Vector3()
37 | .copy(vertor)
38 | // distance vertor
39 | .add(new Vector3(leg / Math.tan(_angle), leg, 0)),
40 | // startpoint
41 | new Vector3().copy(vertor),
42 | ],
43 | angle: _angle,
44 | leg,
45 | hypotenuse: leg / Math.sin(_angle),
46 | color: 'white',
47 | } as Meteor
48 | })
49 | }, [count, angle])
50 | return {
51 | meteors,
52 | }
53 | }
54 |
55 | export type UseMeteorProps = {
56 | value: Meteor
57 | }
58 |
59 | export const useMeteor = (
60 | meteor: React.MutableRefObject,
61 | mat: React.MutableRefObject,
62 | { value }: UseMeteorProps,
63 | ) => {
64 | const vopacity = useRef(0.01)
65 | const { offsetTop } = computeBoundingbox(value.vertices[0])
66 | const threshold = Math.random() * 8
67 | useFrame(() => {
68 | if (meteor.current?.position.y === undefined || !mat.current) {
69 | return
70 | }
71 | // 判断meteor是否出了边界
72 | if (offsetTop + Math.abs(meteor.current.position.y) > threshold) {
73 | mat.current.uniforms.dashOffset.value -= Math.abs(vopacity.current)
74 | mat.current.opacity -= vopacity.current
75 | } else {
76 | // meteor加速下落
77 | meteor.current.position.y -= Math.sin(value.angle) * Math.abs(vopacity.current) * 10
78 | meteor.current.position.x -= Math.cos(value.angle) * Math.abs(vopacity.current) * 10
79 | }
80 | if (Math.abs(mat.current.uniforms.dashOffset.value) >= 1.1) {
81 | mat.current.opacity = 1
82 | mat.current.uniforms.dashOffset.value = 0
83 | const vertor = vertority.fromAxis('fromRight')
84 | // 随机meteor初始位置, 避免loop重复
85 | meteor.current.position.set(vertor.x, vertor.y, vertor.z)
86 | }
87 | })
88 | }
89 |
--------------------------------------------------------------------------------
/components/partly-cloudy/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { Mesh } from 'three'
3 | import { a } from '@react-spring/three'
4 |
5 | import { usePartlyClouds, UsePartlyCloudProps, Cloud, usePartlyCloud } from './use-partly-cloud'
6 | import { Style } from '../interface'
7 |
8 | type CloudyProps = UsePartlyCloudProps
9 | type WhiteCloudProps = {
10 | value: Cloud
11 | style?: Style
12 | }
13 |
14 | export const WhiteCloud = ({ value, style }: WhiteCloudProps) => {
15 | const cloud = useRef()
16 | usePartlyCloud(cloud)
17 | return (
18 | x * value.opacity)}
20 | ref={cloud}
21 | position={value.startpoint}
22 | >
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | const PartlyCloudy = (props: CloudyProps & Pick) => {
30 | const { clouds } = usePartlyClouds(props)
31 | const pY = props.style?.opacity.to([0, 1], [2, 0])
32 | return (
33 |
34 | {clouds.map((cloud, index) => {
35 | return
36 | })}
37 |
38 | )
39 | }
40 |
41 | export default PartlyCloudy
42 |
--------------------------------------------------------------------------------
/components/partly-cloudy/use-partly-cloud.ts:
--------------------------------------------------------------------------------
1 | import { useClouds, UseCloudsProps, useCloud } from '../cloudy/use-cloud'
2 | export { Cloud } from '../cloudy/use-cloud'
3 |
4 | const WHITE_CLOUD_COLORS = ['#fff']
5 |
6 | export const usePartlyCloud = useCloud
7 | export type UsePartlyCloudProps = UseCloudsProps
8 |
9 | export const usePartlyClouds = (
10 | { count = 10, colors = WHITE_CLOUD_COLORS }: UsePartlyCloudProps = {
11 | count: 10,
12 | colors: WHITE_CLOUD_COLORS,
13 | },
14 | ) => {
15 | return useClouds({
16 | count,
17 | colors,
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/components/rain-ring/index.tsx:
--------------------------------------------------------------------------------
1 | import { Mesh } from 'three'
2 | import React, { useRef } from 'react'
3 |
4 | import { useRainRing, useRainRings, UseRainRingsProps, Ring } from './use-rain-ring'
5 | import { Style } from '../interface'
6 |
7 | type RainRingProps = {
8 | value: Ring
9 | style?: Style
10 | }
11 |
12 | export const RainRing = ({ value, style }: RainRingProps) => {
13 | const mesh = useRef()
14 | useRainRing(mesh, { style })
15 | return (
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | type RainRingsProps = UseRainRingsProps & {
24 | style?: Style
25 | }
26 |
27 | const RainRings = (props: RainRingsProps) => {
28 | const { rings } = useRainRings(props)
29 | return (
30 | <>
31 | {rings.map((ring, index) => {
32 | return
33 | })}
34 | >
35 | )
36 | }
37 |
38 | export default RainRings
39 |
--------------------------------------------------------------------------------
/components/rain-ring/use-rain-ring.tsx:
--------------------------------------------------------------------------------
1 | import { Mesh, Material, Vector3 } from 'three'
2 | import { useFrame, useThree } from '@react-three/fiber'
3 | import React, { useMemo } from 'react'
4 |
5 | import vertority from '../utils/vertority'
6 | import { Style } from '../interface'
7 |
8 | export type Ring = {
9 | radius: number
10 | startpoint: Vector3
11 | }
12 |
13 | type UseRainRingProps = {
14 | style?: Style
15 | }
16 |
17 | export const useRainRing = (
18 | rainring: React.MutableRefObject,
19 | props: UseRainRingProps,
20 | ) => {
21 | const { camera } = useThree()
22 | useFrame(() => {
23 | if (!rainring.current || !rainring.current.material) {
24 | return
25 | }
26 | const mat: Material = rainring.current.material as Material
27 | if (camera.rotation.z >= 0 || camera.rotation.y <= 0) {
28 | mat.opacity = 0
29 | return
30 | }
31 | mat.opacity -= 0.01
32 | mat.opacity *= props.style?.opacity.get() ?? 1
33 | rainring.current.scale.x += 0.1
34 | rainring.current.scale.y += 0.1
35 | if ((rainring.current.material as Material).opacity <= 0) {
36 | const vertor = vertority.random()
37 | rainring.current.position.set(vertor.x, vertor.y, vertor.z)
38 | mat.opacity = 0.2
39 | rainring.current.scale.x = 0
40 | rainring.current.scale.y = 0
41 | }
42 | rainring.current.rotation.x = camera.rotation.x
43 | rainring.current.rotation.y = camera.rotation.y
44 | rainring.current.rotation.z = camera.rotation.z
45 | })
46 | }
47 |
48 | export type UseRainRingsProps = {
49 | count?: number
50 | }
51 |
52 | export const useRainRings = ({ count = 10 }: UseRainRingsProps = { count: 10 }) => {
53 | const rings = useMemo(() => {
54 | return new Array(count).fill(0).map(() => {
55 | return {
56 | radius: Math.random() * 0.1,
57 | startpoint: vertority.random(),
58 | } as Ring
59 | })
60 | }, [count])
61 | return {
62 | rings,
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/components/rain/index.tsx:
--------------------------------------------------------------------------------
1 | import { useRain, useRaindrop, UseRaindropProps, UseRainProps } from './use-rain'
2 | import { Style } from '../interface'
3 |
4 | import React, { useRef } from 'react'
5 | import { a } from '@react-spring/three'
6 | import { Mesh } from 'three'
7 | import { extend } from '@react-three/fiber'
8 | import * as meshline from 'threejs-meshline'
9 | extend(meshline)
10 |
11 | export const Raindrop = (props: UseRaindropProps) => {
12 | const raindrop = useRef()
13 | const mat = useRef()
14 | useRaindrop(raindrop, mat, { value: props.value, style: props.style })
15 | return (
16 |
17 |
18 |
28 |
29 | )
30 | }
31 |
32 | type RainProps = UseRainProps & {
33 | style?: Style
34 | }
35 |
36 | const Rain = (props: RainProps) => {
37 | const { lines } = useRain(props)
38 | return (
39 |
40 | {lines.map((raindrop, index) => {
41 | return
42 | })}
43 |
44 | )
45 | }
46 |
47 | export default Rain
48 |
--------------------------------------------------------------------------------
/components/rain/use-rain.ts:
--------------------------------------------------------------------------------
1 | import { useFrame } from '@react-three/fiber'
2 | import * as THREE from 'three'
3 | import React, { useRef, useMemo } from 'react'
4 | import { Vector3 } from 'three'
5 |
6 | import { computeBoundingbox } from '../utils/element'
7 | import random from '../utils/random'
8 | import vertority from '../utils/vertority'
9 | import point from '../utils/point'
10 | import { deg2rad, angle2dir } from '../utils/angle'
11 | import { DIRS } from '../utils/constants'
12 | import { getCoord } from '../utils/scene'
13 | import { Orientation, Style } from '../interface'
14 |
15 | export type Raindrop = {
16 | vertices: THREE.Vector3[] // rain-drop geom
17 | orientation: 'fromRight' | 'fromTop' | 'fromLeft' // rain-drop direction
18 | leg: number
19 | angle: number // unit=deg
20 | color: string
21 | }
22 |
23 | const RAIN_COLORS = ['#cdd1d3', '#fcd337']
24 |
25 | const angle2placement = (angle?: number) => {
26 | if (!angle || angle === 0) {
27 | return 'fromTop'
28 | }
29 | return angle < 0 ? 'fromRight' : 'fromLeft'
30 | }
31 |
32 | export type UseRainProps = {
33 | count?: number
34 | angle?: number
35 | colors?: string[]
36 | }
37 |
38 | export const DEFAULT_RAINPROPS = {
39 | count: 100,
40 | angle: -45,
41 | }
42 |
43 | export const useRain = ({
44 | angle = -45,
45 | count = 100,
46 | colors = RAIN_COLORS,
47 | }: UseRainProps = DEFAULT_RAINPROPS) => {
48 | // raindrop start position data
49 | const _angle = deg2rad(angle)
50 | const startpoints = useRef<{ [key: string]: THREE.Vector3 }>({
51 | fromTop: new THREE.Vector3().fromArray(point.axisxy.top),
52 | fromRight: new THREE.Vector3().fromArray(point.axisxy.right),
53 | fromLeft: new THREE.Vector3().fromArray(point.axisxy.left),
54 | }).current
55 | // radindrop come from which orientation
56 | const comefrom = useRef<{ [key: string]: Orientation[] }>({
57 | fromLeft: ['fromTop', 'fromRight'],
58 | fromRight: ['fromTop', 'fromLeft'],
59 | fromTop: ['fromTop'],
60 | }).current
61 | const lines = useMemo(() => {
62 | return Array(count)
63 | .fill(0)
64 | .map(() => {
65 | // h / deltax = tan(ang)
66 | // 直角边
67 | const leg = Math.random() * 2
68 | // FIXME: should modify from angle
69 | const orientation = random.inRange(comefrom[angle2placement(_angle)])
70 | // noise vertor for startpoint
71 | const vertor = vertority
72 | .fromPlacement(orientation)
73 | .add(new Vector3(0, 0, point.fromAxisZ()))
74 | return {
75 | vertices: [
76 | // endpoint
77 | new THREE.Vector3()
78 | .copy(startpoints[orientation])
79 | .add(vertor)
80 | // distance vertor
81 | .add(new THREE.Vector3(leg / Math.tan(_angle), leg, 0)),
82 | // startpoint
83 | new THREE.Vector3().copy(startpoints[orientation]).add(vertor),
84 | ],
85 | angle: _angle,
86 | leg,
87 | orientation,
88 | color: random.inRange(colors),
89 | } as Raindrop
90 | })
91 | }, [_angle, comefrom, count, startpoints, colors])
92 | return {
93 | lines,
94 | }
95 | }
96 |
97 | export type UseRaindropProps = {
98 | value: Raindrop
99 | style?: Style
100 | }
101 |
102 | export const useRaindrop = (
103 | raindrop: React.MutableRefObject,
104 | mat: React.MutableRefObject,
105 | props?: UseRaindropProps,
106 | ) => {
107 | // 摩擦系数, raindrop从负数开始运动, 更加随机的效果
108 | const friction = useRef(
109 | random.inRange(DIRS) === 1 ? 0 : -1 * 0.01 * Math.round(Math.random() * 10),
110 | ).current
111 | const coord = useRef(getCoord()).current
112 | const vy0 = useRef(0.0001 + friction)
113 | // vy0 / vx0 = tan(angle)
114 | const vx0 = useRef(0.0001 + friction)
115 | const a = useRef(0.001)
116 | const { offsetTop } = computeBoundingbox(props?.value.vertices[0])
117 | useFrame(() => {
118 | if (raindrop.current?.position.y === undefined || !props?.value || !mat.current) {
119 | return
120 | }
121 | // raindrop加速下落
122 | raindrop.current.position.y -= vy0.current
123 | // left raindrop从左到右移动, right raindrop从右到左移动
124 | raindrop.current.position.x -= vx0.current * angle2dir(props.value.angle)
125 | // 从无到有比较真实
126 | mat.current.opacity += 0.01
127 | mat.current.opacity *= props.style?.opacity.get() ?? 1
128 | vy0.current += a.current
129 | vx0.current += a.current
130 | // 判断raindrop是否出了边界
131 | if (offsetTop + Math.abs(raindrop.current.position.y) > coord[1] * 2 + props.value.leg) {
132 | const vertor = vertority.fromPlacement(props.value.orientation)
133 | // 随机raindrop初始位置, 避免loop重复
134 | raindrop.current.position.set(vertor.x, vertor.y, vertor.z)
135 | mat.current.opacity = 0
136 | vy0.current = 0.0001
137 | vx0.current = 0.0001
138 | }
139 | })
140 | }
141 |
--------------------------------------------------------------------------------
/components/snow/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { Mesh } from 'three'
3 | import { Style } from '../interface'
4 | import { a } from '@react-spring/three'
5 |
6 | import { useSnowflakes, useSnowflake, Snowflake, UseSnowflakesProps } from './use-snowflake'
7 |
8 | type SnowFlakeProps = {
9 | value: Snowflake
10 | style?: Style
11 | }
12 |
13 | const SnowFlake = ({ value, style }: SnowFlakeProps) => {
14 | const flake = useRef()
15 | useSnowflake(flake, { value })
16 | return (
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | type SnowProps = UseSnowflakesProps & {
25 | style?: Style
26 | }
27 |
28 | const Snow = (props: SnowProps) => {
29 | const { snowflakes } = useSnowflakes(props)
30 | return (
31 |
32 | {snowflakes.map((snowflake, index) => {
33 | return
34 | })}
35 |
36 | )
37 | }
38 |
39 | export default Snow
40 |
--------------------------------------------------------------------------------
/components/snow/use-snowflake.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react'
2 | import { Vector3, Mesh } from 'three'
3 | import { useFrame } from '@react-three/fiber'
4 |
5 | import { DIRS } from '../utils/constants'
6 | import random from '../utils/random'
7 | import { computeBoundingbox } from '../utils/element'
8 | import { getCoord } from '../utils/scene'
9 | import vertority from '../utils/vertority'
10 |
11 | export type Snowflake = {
12 | startpoint: Vector3
13 | radius: number
14 | }
15 |
16 | export type UseSnowflakesProps = {
17 | count?: number
18 | }
19 |
20 | export const useSnowflakes = ({ count = 100 }: UseSnowflakesProps = { count: 100 }) => {
21 | const snowflakes = useMemo(() => {
22 | const coord = getCoord()
23 | return new Array(4).fill(0).reduce((prev, _cur, i) => {
24 | return prev.concat(
25 | new Array(Math.round(count / 4)).fill(0).map(() => {
26 | const vertor = vertority.fromPlacement('fromTop')
27 | return {
28 | startpoint: new Vector3().copy(new Vector3(0, coord[1] + i * 2, 0)).add(vertor),
29 | radius: Math.random() * 0.1,
30 | }
31 | }),
32 | )
33 | }, [])
34 | }, [count])
35 | return {
36 | snowflakes: snowflakes as Snowflake[],
37 | }
38 | }
39 |
40 | type UseSnowflakeProps = {
41 | value: Snowflake
42 | }
43 |
44 | export const useSnowflake = (
45 | flake: React.MutableRefObject,
46 | { value }: UseSnowflakeProps,
47 | ) => {
48 | const vy0 = useRef(0.01)
49 | const coord = useRef(getCoord()).current
50 | // vy0 / vx0 = tan(angle)
51 | const vx0 = useRef(0.001 * Math.random() * random.inRange(DIRS))
52 | // const a = useRef(0.00001)
53 | const { offsetTop } = computeBoundingbox(value.startpoint)
54 | useFrame(() => {
55 | if (!flake.current) {
56 | return
57 | }
58 | // 雪花加速下落
59 | flake.current.position.y -= vy0.current
60 | flake.current.position.x -= vx0.current
61 | // 判断是否出了边界
62 | if (offsetTop + Math.abs(value.startpoint.y - flake.current.position.y) > coord[1] * 2) {
63 | const vertor = vertority.fromAxis('fromTop')
64 | // 随机raindrop初始位置, 避免loop重复
65 | flake.current.position.set(vertor.x, vertor.y + coord[1], vertor.z)
66 | vy0.current = 0.01
67 | vx0.current = 0.001 * Math.random() * random.inRange(DIRS)
68 | }
69 | })
70 | }
71 |
--------------------------------------------------------------------------------
/components/star-rings/index.tsx:
--------------------------------------------------------------------------------
1 | import { useStarRings, Ring, useRing, UseStarRingsProps } from './use-starrings'
2 | import React, { useRef } from 'react'
3 | import { extend } from '@react-three/fiber'
4 | import { a } from '@react-spring/three'
5 |
6 | import * as meshline from 'threejs-meshline'
7 | import { Style } from '../interface'
8 |
9 | extend(meshline)
10 |
11 | type RingProps = {
12 | value: Ring
13 | style?: Style
14 | }
15 |
16 | const Ring = ({ value, style }: RingProps) => {
17 | const ring = useRef()
18 | useRing(ring)
19 | return (
20 | x * value.opacity)}>
21 |
22 |
33 |
34 | )
35 | }
36 |
37 | type StarRingsProps = UseStarRingsProps & {
38 | style?: Style
39 | }
40 |
41 | const StarRings = (props: StarRingsProps) => {
42 | const { rings, startpoint } = useStarRings(props)
43 | return (
44 |
45 | {rings.map((ring, index) => {
46 | return
47 | })}
48 |
49 | )
50 | }
51 |
52 | export default StarRings
53 |
--------------------------------------------------------------------------------
/components/star-rings/use-starrings.ts:
--------------------------------------------------------------------------------
1 | import { useMemo, useRef } from 'react'
2 | import * as THREE from 'three'
3 |
4 | import random from '../utils/random'
5 | import { DIRS } from '../utils/constants'
6 | import { useFrame } from '@react-three/fiber'
7 | import { getCoord } from '../utils/scene'
8 |
9 | export type UseStarRingsProps = {
10 | count?: number
11 | }
12 |
13 | export type Ring = {
14 | radius: number
15 | vertices: THREE.Vector3[]
16 | dashArray: number
17 | opacity: number
18 | color: string
19 | lineWidth: number
20 | }
21 |
22 | const RING_COLORS = ['#cdd1d3', '#fcd337', '#1677b3']
23 |
24 | export const useStarRings = ({ count = 50 }: UseStarRingsProps = { count: 50 }) => {
25 | const coord = useRef(getCoord()).current
26 | const startpoint = useMemo(() => {
27 | return new THREE.Vector3(-coord[0], coord[1], 0)
28 | }, [coord])
29 | const rings = useMemo(() => {
30 | return new Array(count).fill(0).map(() => {
31 | const radius = Math.random() * 10
32 | const vertices = new Array(180).fill(0).map((_v, i) => {
33 | return new THREE.Vector3(
34 | Math.cos((i * 2 * Math.PI) / 180) * radius,
35 | Math.sin((i * 2 * Math.PI) / 180) * radius,
36 | 0,
37 | )
38 | })
39 | return {
40 | radius,
41 | vertices,
42 | dashArray: Math.random() + 0.1,
43 | opacity: Math.random() * 0.8,
44 | color: random.inRange(RING_COLORS),
45 | lineWidth: Math.random() * 0.05,
46 | } as Ring
47 | })
48 | }, [count])
49 | return {
50 | rings,
51 | startpoint,
52 | }
53 | }
54 |
55 | export const useRing = (ring: React.MutableRefObject) => {
56 | const dir = random.inRange(DIRS)
57 | const speed = useRef(Math.random() * 2 * 0.0001)
58 | useFrame(() => {
59 | if (!ring.current) {
60 | return
61 | }
62 | ring.current.uniforms.dashOffset.value -= speed.current * dir
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/components/sun/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { Group, Mesh } from 'three'
3 | import { useFrame } from '@react-three/fiber'
4 | import { a } from '@react-spring/three'
5 |
6 | import { useSun, UseSunProps, Halo } from './use-sun'
7 | import { inRange } from '../utils/random'
8 | import { DIRS } from '../utils/constants'
9 | import { Style } from '../interface'
10 |
11 | type SunProps = UseSunProps & {
12 | style?: Style
13 | }
14 |
15 | type HaloProps = {
16 | value: Halo
17 | style?: Style
18 | }
19 |
20 | const SunHalo = ({ value, style }: HaloProps) => {
21 | const dir = useRef(1)
22 | const speed = useRef(Math.random() * 0.0001 * inRange(DIRS))
23 | const halo = useRef()
24 | useFrame(() => {
25 | if (!halo.current) {
26 | return
27 | }
28 | if (halo.current.scale.x <= 0.9) {
29 | dir.current = -dir.current
30 | } else if (halo.current.scale.x >= 1.1) {
31 | dir.current = -dir.current
32 | }
33 | halo.current.scale.x += speed.current * dir.current
34 | halo.current.scale.y += speed.current * dir.current
35 | })
36 | return (
37 |
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | const Sun = (props: SunProps) => {
45 | const sun = useRef()
46 | const { halos, startpoint } = useSun(props)
47 | return (
48 |
49 | {halos.map((halo, index) => {
50 | return
51 | })}
52 |
53 | )
54 | }
55 |
56 | export default Sun
57 |
--------------------------------------------------------------------------------
/components/sun/use-sun.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useMemo } from 'react'
2 | import { useFrame } from '@react-three/fiber'
3 | import { Group, Vector3 } from 'three'
4 |
5 | import { getCoord } from '../utils/scene'
6 |
7 | const R = 2
8 | const HALO_COLORS = ['#faf3d2', '#fbebb3', '#fae09c', '#f9d67c', '#f6c451', '#c34e35']
9 |
10 | type Sunshine = {
11 | vertices: Vector3[]
12 | angle: number
13 | }
14 |
15 | export type Halo = {
16 | radius: number
17 | color: string
18 | startpoint: Vector3
19 | }
20 |
21 | export type UseSunProps = {
22 | percentX?: number
23 | count?: number
24 | }
25 |
26 | export const useSun = (
27 | { percentX = 1, count = HALO_COLORS.length }: UseSunProps = {
28 | percentX: 1,
29 | count: HALO_COLORS.length,
30 | },
31 | ) => {
32 | const angle = useRef(-(Math.random() * 90 + 90))
33 | const coord = useRef(getCoord()).current
34 | const startpoint = useMemo(() => {
35 | return new Vector3(coord[0] * percentX, coord[1], 0)
36 | }, [coord, percentX])
37 |
38 | const sunshines = useMemo(() => {
39 | return Array(3)
40 | .fill(0)
41 | .map((_v, i) => {
42 | angle.current += i * 20
43 | const startpoint = Math.random() * coord[0] + coord[0] * percentX
44 | const length = Math.random() * 2
45 | return {
46 | vertices: [
47 | new Vector3(
48 | Math.cos((angle.current * Math.PI) / 180) * startpoint,
49 | Math.sin((angle.current * Math.PI) / 180) * startpoint,
50 | 0,
51 | ),
52 | new Vector3(
53 | Math.cos((angle.current * Math.PI) / 180) * (startpoint + length),
54 | Math.sin((angle.current * Math.PI) / 180) * (startpoint + length),
55 | 0,
56 | ),
57 | ],
58 | angle: angle.current,
59 | } as Sunshine
60 | })
61 | }, [coord, percentX])
62 | const halos = useMemo(() => {
63 | return Array(count)
64 | .fill(0)
65 | .map((_v, i) => {
66 | const startpoint = new Vector3(0.5 - Math.random() * 1, 0.5 - Math.random() * 1, 0)
67 | return {
68 | radius: R + 5 - i,
69 | color: HALO_COLORS[i],
70 | startpoint,
71 | } as Halo
72 | })
73 | }, [count])
74 | return {
75 | sunshines,
76 | halos,
77 | startpoint,
78 | }
79 | }
80 |
81 | export const useSunshine = (sunshine: React.MutableRefObject) => {
82 | useFrame(() => {
83 | if (!sunshine.current) {
84 | return
85 | }
86 | sunshine.current.rotation.z -= 0.001
87 | if (Math.abs(sunshine.current.rotation.z) > 2) {
88 | sunshine.current.rotation.z = 0
89 | }
90 | })
91 | }
92 |
--------------------------------------------------------------------------------
/components/theme.ts:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from 'react'
2 | import { PCFSoftShadowMap } from 'three'
3 |
4 | import { Weather } from './interface'
5 |
6 | export const dayTheme: Record<
7 | Weather,
8 | {
9 | style: CSSProperties
10 | shadows?: any
11 | }
12 | > = {
13 | cloudy: {
14 | style: {
15 | backgroundColor: '#3C4245',
16 | },
17 | },
18 | fog: {
19 | style: {
20 | backgroundColor: '#0F203B',
21 | },
22 | shadows: { enabled: true, type: PCFSoftShadowMap },
23 | },
24 | haze: {
25 | style: {
26 | backgroundColor: '#A2915E',
27 | },
28 | shadows: true,
29 | },
30 | meteors: {
31 | style: {
32 | backgroundColor: '#0F203B',
33 | },
34 | },
35 | 'partly-cloudy': {
36 | style: {
37 | backgroundColor: '#1677b3',
38 | },
39 | },
40 | rain: {
41 | style: {
42 | backgroundColor: '#1677b3',
43 | },
44 | },
45 | snow: {
46 | style: {
47 | backgroundColor: '#1677b3',
48 | },
49 | },
50 | 'star-rings': {
51 | style: {
52 | backgroundColor: '#0F203B',
53 | },
54 | },
55 | sun: {
56 | style: {
57 | backgroundColor: '#faf4e8',
58 | },
59 | },
60 | }
61 |
62 | export const nightTheme = {
63 | ...dayTheme,
64 | rain: {
65 | ...dayTheme.rain,
66 | style: {
67 | backgroundColor: '#0F203B',
68 | },
69 | },
70 | cloudy: {
71 | ...dayTheme.cloudy,
72 | style: {
73 | backgroundColor: '#0F203B',
74 | },
75 | },
76 | 'partly-cloudy': {
77 | ...dayTheme['partly-cloudy'],
78 | style: {
79 | backgroundColor: '#0F203B',
80 | },
81 | },
82 | snow: {
83 | ...dayTheme.snow,
84 | style: {
85 | backgroundColor: '#0F203B',
86 | },
87 | },
88 | }
89 |
--------------------------------------------------------------------------------
/components/typing.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'three.meshline'
2 | declare module 'threejs-meshline'
3 |
--------------------------------------------------------------------------------
/components/utils/angle.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * convert deg to rad
3 | * @param {number} deg
4 | */
5 | export const deg2rad = (deg = 0) => {
6 | return (deg * Math.PI) / 180
7 | }
8 |
9 | /**
10 | * 角度正负数值
11 | * @param {number | undefined} angle
12 | */
13 | export const angle2dir = (angle?: number) => {
14 | if (!angle || angle === 0) {
15 | return 0
16 | }
17 | return angle / Math.abs(angle)
18 | }
19 |
--------------------------------------------------------------------------------
/components/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const DIRS = [1, -1]
2 |
--------------------------------------------------------------------------------
/components/utils/element.ts:
--------------------------------------------------------------------------------
1 | import { Vector3 } from 'three'
2 | import { getCoord } from './scene'
3 |
4 | /**
5 | * box offset to four edge
6 | * @param pos
7 | */
8 | export const computeBoundingbox = (pos?: Vector3) => {
9 | if (!pos) {
10 | return {
11 | offsetLeft: 0,
12 | offsetRight: 0,
13 | offsetTop: 0,
14 | offsetBottom: 0,
15 | }
16 | }
17 | const [coordx, coordy] = getCoord()
18 | const { x, y } = pos
19 | return {
20 | offsetLeft: x > 0 ? coordx + x : coordx - Math.abs(x),
21 | offsetRight: x > 0 ? coordx - x : coordx + Math.abs(x),
22 | offsetTop: y > 0 ? coordy - y : coordy + Math.abs(y),
23 | offsetBottom: y > 0 ? coordy + y : coordy - Math.abs(y),
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/components/utils/point.ts:
--------------------------------------------------------------------------------
1 | import { getCoord } from './scene'
2 | import { atPoint } from './random'
3 |
4 | const [axisx, axisy, axisz] = getCoord()
5 | export const axisxy = {
6 | top: [0, axisy, 0],
7 | bottom: [0, -axisy, 0],
8 | left: [-axisx, 0, 0],
9 | right: [axisx, 0, 0],
10 | }
11 |
12 | export const fromAxisX = () => {
13 | return atPoint() * axisx
14 | }
15 | export const fromAxisY = () => {
16 | return atPoint() * axisy
17 | }
18 | export const fromAxisZ = () => {
19 | return atPoint() * axisz
20 | }
21 | export const fromAxis = (axis: 'x' | 'y' | 'z') => {
22 | switch (axis) {
23 | case 'x':
24 | return fromAxisX()
25 | case 'y':
26 | return fromAxisY()
27 | case 'z':
28 | return fromAxisZ()
29 | default:
30 | return fromAxisX()
31 | }
32 | }
33 |
34 | export default {
35 | fromAxisX,
36 | fromAxisY,
37 | fromAxisZ,
38 | fromAxis,
39 | axisxy,
40 | }
41 |
--------------------------------------------------------------------------------
/components/utils/random.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 从range数组中随机一个元素
3 | * @param range any[]
4 | */
5 | export const inRange = (range: T[]) => {
6 | return range[Math.round(Math.random() * (range.length - 1))]
7 | }
8 |
9 | /**
10 | * scene内部随机点
11 | */
12 | export const atPoint = () => {
13 | return 1 - Math.random() * 2
14 | }
15 |
16 | export default {
17 | atPoint,
18 | inRange,
19 | }
20 |
--------------------------------------------------------------------------------
/components/utils/scene.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * get Coordinate System
3 | */
4 | export const getCoord = () => {
5 | const w = window.innerWidth
6 | const h = window.innerHeight
7 | let unit = 1
8 | if (w < h) {
9 | unit = w / 4
10 | return [4, h / unit, 4]
11 | }
12 | unit = h / 4
13 | return [w / unit, 4, 4]
14 | }
15 |
--------------------------------------------------------------------------------
/components/utils/vertority.ts:
--------------------------------------------------------------------------------
1 | import point from './point'
2 | import { Orientation } from '../interface'
3 |
4 | import { Vector3 } from 'three'
5 |
6 | const PLACEMENT_TO_AXIS: { [key: string]: 'x' | 'y' | 'z' } = {
7 | fromTop: 'x',
8 | fromRight: 'y',
9 | fromLeft: 'y',
10 | fromBottom: 'x',
11 | }
12 | export const PLACEMENT_TO_DIR: { [key in Orientation]: 1 | -1 } = {
13 | fromTop: 1,
14 | fromRight: 1,
15 | fromLeft: -1,
16 | fromBottom: -1,
17 | }
18 |
19 | export const PLACEMENTS = Object.keys(PLACEMENT_TO_DIR) as Orientation[]
20 | /**
21 | * 得到一个随机的噪声向量, 和orientation有关。
22 | * - placement = top -> [a, b, 0](fromPlacement) or [0, b, 0](fromAxis)
23 | * - placement = left -> [a, b, 0] or [a, 0, 0]
24 | * - placement = right -> [a, b, 0] or [a, 0, 0]
25 | * @param {Orientation} placement
26 | * @param props
27 | */
28 | const fromAxis = (placement: Orientation) => {
29 | const endpoint = { x: 0, y: 0, z: 0 }
30 | endpoint[PLACEMENT_TO_AXIS[placement]] = point.fromAxis(PLACEMENT_TO_AXIS[placement])
31 | return new Vector3(endpoint.x, endpoint.z, endpoint.z)
32 | }
33 |
34 | const fromPlacement = (placement: Orientation) => {
35 | const endpoint = { x: 0, y: 0, z: 0 }
36 | endpoint[PLACEMENT_TO_AXIS[placement]] = point.fromAxis(PLACEMENT_TO_AXIS[placement])
37 | if (PLACEMENT_TO_AXIS[placement] === 'x') {
38 | endpoint.y = Math.abs(point.fromAxisY() * 0.5) * PLACEMENT_TO_DIR[placement]
39 | }
40 | if (PLACEMENT_TO_AXIS[placement] === 'y') {
41 | endpoint.x = Math.abs(point.fromAxisY() * 0.5) * PLACEMENT_TO_DIR[placement]
42 | }
43 | return new Vector3(endpoint.x, endpoint.y, endpoint.z)
44 | }
45 |
46 | const random = () => {
47 | return new Vector3(point.fromAxisX(), point.fromAxisY(), point.fromAxisZ())
48 | }
49 |
50 | export default {
51 | fromAxis,
52 | fromPlacement,
53 | random,
54 | }
55 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # docs
2 |
3 | - [rain](/docs/rain.md)
4 | - [meteors](/docs/meteors.md)
5 | - [sun](/docs/sun.md)
6 | - [snow](/docs/snow.md)
7 | - [star-rings](/docs/star-rings.md)
8 | - [cloudy](/docs/cloudy.md)
9 | - [rain-ring](/docs/rain-rings.md)
10 |
--------------------------------------------------------------------------------
/docs/cloudy.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Cloudy, useTheme } from 'threejs-weather'
7 | import { Canvas } from '@react-three/fiber'
8 |
9 | const CloudyPage = () => {
10 | const { bind } = useTheme({ type: 'cloudy', mode: 'day' })
11 | return (
12 |
15 | )
16 | }
17 | ```
18 |
19 | ## props
20 |
21 | | name | description | type | default |
22 | | :---: | :---------------------------: | :----: | :-----: |
23 | | count | num of clouds(云的数目) | number | 10 |
--------------------------------------------------------------------------------
/docs/fog.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Fog, useTheme, FogCamera } from 'threejs-weather'
7 | import { Canvas } from '@react-three/fiber'
8 |
9 | const Page = () => {
10 | const { bind } = useTheme({ type: 'fog', mode: 'day' })
11 | return (
12 |
18 | )
19 | }
20 | ```
21 |
22 | ## props
23 |
24 | | name | description | type | default |
25 | | :---: | :---------------------------: | :----: | :-----: |
26 | | url | custom building model url | string | |
--------------------------------------------------------------------------------
/docs/haze.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Haze, useTheme } from 'threejs-weather'
7 | import { Canvas } from '@react-three/fiber'
8 |
9 | const Page = () => {
10 | const { bind } = useTheme({ type: 'haze', mode: 'day' })
11 | return (
12 |
17 | )
18 | }
19 | ```
20 |
21 | ## props
22 |
23 | | name | description | type | default |
24 | | :---: | :---------------------------: | :----: | :-----: |
25 | | count | num of line | number | 100 |
26 | | angle | angle of line | deg | -45 |
--------------------------------------------------------------------------------
/docs/meteors.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Meteors, useTheme } from 'threejs-weather'
7 | import { Canvas } from 'react-three/fiber'
8 |
9 | const Page = () => {
10 | const { bind } = useTheme({ type: 'meteors', mode: 'day' })
11 | return (
12 |
17 | )
18 | }
19 | ```
20 |
21 | ## props
22 |
23 | | name | description | type | default |
24 | | :---: | :----------------------: | :----: | :-----: |
25 | | count | num of meteors(流星数目) | number | 30 |
--------------------------------------------------------------------------------
/docs/rain-rings.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 | > 只会在特定角度出现
5 |
6 | ```tsx
7 | import { Rain, RainRings, useTheme } from 'threejs-weather'
8 | import { Canvas } from 'react-three/fiber'
9 |
10 | const RainPage = () => {
11 | const { bind } = useTheme({ type: 'rain', mode: 'day' })
12 | return (
13 |
19 | )
20 | }
21 | ```
22 |
23 | 可配合[rain-ring](/docs/rain-rings.md)使用
24 |
25 | ## props
26 |
27 | | name | description | type | default |
28 | | :---: | :---------------------------: | :----: | :-----: |
29 | | count | num of rainring(雨斑的数目) | number | 10 |
--------------------------------------------------------------------------------
/docs/rain.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Rain, useTheme } from 'threejs-weather'
7 | import { Canvas } from 'react-three/fiber'
8 |
9 | const RainPage = () => {
10 | const { bind } = useTheme({ type: 'rain', mode: 'day' })
11 | return (
12 |
17 | )
18 | }
19 | ```
20 |
21 | ## props
22 |
23 | | name | description | type | default |
24 | | :---: | :---------------------------: | :----: | :-----: |
25 | | count | num of raindrop(雨点的数目) | number | 100 |
26 | | angle | angle of raindrop(下雨的角度) | deg | -45 |
--------------------------------------------------------------------------------
/docs/snow.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Snow, useTheme } from 'threejs-weather'
7 | import { Canvas } from 'react-three/fiber'
8 |
9 | const CloudyPage = () => {
10 | const { bind } = useTheme({ type: 'snow', mode: 'day' })
11 | return (
12 |
15 | )
16 | }
17 | ```
18 |
19 | ## props
20 |
21 | | name | description | type | default |
22 | | :---: | :---------------------------: | :----: | :-----: |
23 | | count | num of snowflakes(雪花的数目) | number | 100 |
--------------------------------------------------------------------------------
/docs/star-rings.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { StarRings, useTheme } from 'threejs-weather'
7 | import { Canvas } from 'react-three/fiber'
8 |
9 | const RainPage = () => {
10 | const { bind } = useTheme({ type: 'snow', mode: 'day' })
11 | return (
12 |
17 | )
18 | }
19 | ```
20 |
21 | ## props
22 |
23 | | name | description | type | default |
24 | | :---: | :--------------------: | :----: | :-----: |
25 | | count | num of rings(星环数目) | number | 50 |
--------------------------------------------------------------------------------
/docs/sun.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## usage
4 |
5 | ```tsx
6 | import { Sun, useTheme } from 'threejs-weather'
7 | import { Canvas } from 'react-three/fiber'
8 |
9 | const RainPage = () => {
10 | const { bind } = useTheme({ type: 'sun', mode: 'day' })
11 | return (
12 |
17 | )
18 | }
19 | ```
20 |
21 | ## props
22 |
23 | | name | description | type | default |
24 | | :------: | :------------------------------------: | :---------: | :-----: |
25 | | percentX | startpoint offset(太阳初始位置百分比)) | number(0-1) | 1 |
26 | | count | sum of halo(光晕数目)|number | 6 |
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "browsers": [
8 | "> 1%"
9 | ]
10 | },
11 | "useBuiltIns": "usage",
12 | "corejs": 3,
13 | "modules": "commonjs"
14 | }
15 | ],
16 | "@babel/preset-react"
17 | ],
18 | "plugins": [
19 | "@babel/plugin-syntax-dynamic-import",
20 | "react-hot-loader/babel"
21 | ]
22 | }
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # common
2 | node_modules
3 |
4 | # editor
5 | .DS_Store
6 | .vscode
7 |
8 | # test
9 | converage
10 | cache
11 | jest
12 |
13 | # build
14 | dist
15 | lib
16 | .lib
17 |
18 | # log
19 | lerna-debug.log
20 |
21 | # types
22 | **/*.styl.d.ts
23 |
24 | # bin-template
25 | lib
26 | .lib
27 |
28 | # react-components-lib-tempate
29 | es
30 |
--------------------------------------------------------------------------------
/example/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # example-for-threejs-weather
2 |
3 | ## 0.0.1
4 | ### Patch Changes
5 |
6 | - Updated dependencies [f2d2313]
7 | - Updated dependencies [381a5fa]
8 | - Updated dependencies [e71ca73]
9 | - Updated dependencies [cae159e]
10 | - Updated dependencies [ea560bb]
11 | - threejs-weather@1.0.0
12 |
--------------------------------------------------------------------------------
/example/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 JW
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/build/config.js:
--------------------------------------------------------------------------------
1 | // NOTE: dont move this file
2 | const path = require('path')
3 | const Rupture = require('rupture')
4 |
5 | // path
6 | const context = path.resolve(__dirname, '../')
7 | const assets = path.resolve(context, 'src/assets')
8 | const project = path.resolve(context, 'src')
9 | const static = path.resolve(context, 'static')
10 | const output = path.resolve(context, 'dist')
11 | const public = path.resolve(context, 'public')
12 |
13 | const common = {
14 | path: {
15 | static,
16 | assets,
17 | project,
18 | output,
19 | context,
20 | public,
21 | tsconfig: path.resolve(context, 'tsconfig.json'),
22 | },
23 | stylus: {
24 | plugins: [Rupture()],
25 | },
26 | gzip: false,
27 | analyzer: false,
28 | workerpool: {
29 | workers: require('os').cpus().length - 1,
30 | poolTimeout: process.env.NODE_ENV === 'development' ? Infinity : 2000,
31 | },
32 | }
33 |
34 | module.exports = common
35 |
--------------------------------------------------------------------------------
/example/build/webpack.common.config.js:
--------------------------------------------------------------------------------
1 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
2 | const CopyWebpakcPlugin = require('copy-webpack-plugin')
3 |
4 | const configs = require('./config')
5 |
6 | /**
7 | * @type import('webpack').Configuration
8 | */
9 | const common = {
10 | context: configs.path.context,
11 | entry: ['react-hot-loader/patch', './src/index.tsx'],
12 | output: {
13 | path: configs.path.output,
14 | filename: '[name].js',
15 | },
16 | resolve: {
17 | extensions: ['.ts', '.tsx', '.js', 'jsx'],
18 | alias: {
19 | '@': configs.path.project,
20 | assets: configs.path.assets,
21 | static: configs.path.static,
22 | },
23 | },
24 | module: {
25 | rules: [
26 | {
27 | test: /\.tsx?$/,
28 | exclude: /node_modules/,
29 | use: [
30 | { loader: 'cache-loader' },
31 | {
32 | loader: 'thread-loader',
33 | options: configs.workerpool,
34 | },
35 | { loader: 'babel-loader' },
36 | {
37 | loader: 'ts-loader',
38 | options: {
39 | // IMPORTANT! use happyPackMode mode to speed-up compilation and reduce errors reported to webpack
40 | transpileOnly: true,
41 | happyPackMode: true,
42 | },
43 | },
44 | ],
45 | },
46 | {
47 | test: /\.(png|jpg|gif)$/i,
48 | use: [
49 | {
50 | loader: 'url-loader',
51 | options: {
52 | name: '[path][name].[ext]',
53 | },
54 | },
55 | ],
56 | },
57 | ],
58 | },
59 | plugins: [
60 | new CopyWebpakcPlugin([
61 | {
62 | from: configs.path.static,
63 | to: 'static',
64 | },
65 | {
66 | from: configs.path.public,
67 | to: '',
68 | },
69 | ]),
70 | new ForkTsCheckerWebpackPlugin({
71 | tsconfig: configs.path.tsconfig,
72 | checkSyntacticErrors: true,
73 | }),
74 | ],
75 | }
76 |
77 | module.exports = common
78 |
--------------------------------------------------------------------------------
/example/build/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin')
2 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
3 | const MergeWebpack = require('webpack-merge')
4 | const ThreadLoader = require('thread-loader')
5 | const webpack = require('webpack')
6 |
7 | const configs = require('./config')
8 | const common = require('./webpack.common.config')
9 | const port = 8080
10 |
11 | ThreadLoader.warmup(configs.workerpool, ['ts-loader', 'babel-loader'])
12 |
13 | /**
14 | * @type import('webpack').Configuration
15 | */
16 | const dev = {
17 | devtool: 'cheap-module-eval-source-map',
18 | mode: 'development',
19 | output: {
20 | path: configs.path.output,
21 | filename: '[name].js',
22 | publicPath: '/',
23 | },
24 | resolve: {
25 | alias: {
26 | react: require.resolve('../../node_modules/react'),
27 | '@react-three/fiber': require.resolve('../../node_modules/@react-three/fiber'),
28 | },
29 | },
30 | devServer: {
31 | port,
32 | watchContentBase: true,
33 | contentBase: configs.path.public,
34 | hot: true,
35 | inline: true,
36 | overlay: true,
37 | compress: true,
38 | clientLogLevel: 'none',
39 | quiet: true,
40 | public: `http://localhost:${port}`,
41 | proxy: {
42 | '/proxy': {
43 | target: 'http://localhost:3000/',
44 | changeOrigin: true,
45 | },
46 | },
47 | },
48 | module: {
49 | rules: [
50 | {
51 | test: /\.css$/,
52 | use: [
53 | { loader: 'style-loader', options: { sourceMap: true } },
54 | {
55 | loader: 'typings-for-css-modules-loader',
56 | options: {
57 | sourceMap: true,
58 | modules: true,
59 | localIdentName: '[name]_[local]___[hash:base64:5]',
60 | namedExport: true,
61 | silent: true,
62 | },
63 | },
64 | ],
65 | },
66 | {
67 | test: /(\.styl$|\.stylus$)/,
68 | use: [
69 | { loader: 'style-loader', options: { sourceMap: true } },
70 | {
71 | loader: 'typings-for-css-modules-loader',
72 | options: {
73 | sourceMap: true,
74 | modules: true,
75 | localIdentName: '[name]_[local]___[hash:base64:5]',
76 | namedExport: true,
77 | silent: true,
78 | },
79 | },
80 | { loader: 'postcss-loader', options: { sourceMap: true } },
81 | {
82 | loader: 'stylus-loader',
83 | options: {
84 | sourceMap: true,
85 | use: configs.stylus.plugins,
86 | },
87 | },
88 | ],
89 | },
90 | ],
91 | },
92 | plugins: [
93 | new HtmlWebpackPlugin({
94 | filename: 'index.html',
95 | template: 'public/index.html',
96 | inject: true,
97 | }),
98 | new webpack.HotModuleReplacementPlugin(),
99 | new webpack.NamedModulesPlugin(),
100 | new FriendlyErrorsPlugin({
101 | compilationSuccessInfo: {
102 | messages: [`Running here http://localhost:${port}`],
103 | notes: ['Happy coding'],
104 | },
105 | onErrors(_severity, _errors) {
106 | // You can listen to errors transformed and prioritized by the plugin
107 | // severity can be 'error' or 'warning'
108 | },
109 | }),
110 | ],
111 | }
112 |
113 | module.exports = MergeWebpack(common, dev)
114 |
--------------------------------------------------------------------------------
/example/build/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
3 | const TerserPlugin = require('terser-webpack-plugin')
4 | const HtmlWebpackPlugin = require('html-webpack-plugin')
5 | const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
6 | const CleanWebpackPlugin = require('clean-webpack-plugin')
7 | const MergeWebpack = require('webpack-merge')
8 | const PreloadWebpackPlugin = require('preload-webpack-plugin')
9 | const webpack = require('webpack')
10 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
11 | const CompressionPlugin = require('compression-webpack-plugin')
12 |
13 | const configs = require('./config')
14 | const common = require('./webpack.common.config')
15 |
16 | /**
17 | * @type import('webpack').Configuration
18 | */
19 | const prod = {
20 | devtool: 'source-map',
21 | mode: 'production',
22 | output: {
23 | path: configs.path.output,
24 | filename: path.posix.join('static', 'js/[name].[chunkhash].js'),
25 | chunkFilename: path.posix.join('static', 'js/[name].[chunkhash].async.js'),
26 | publicPath: './',
27 | },
28 | optimization: {
29 | splitChunks: {
30 | cacheGroups: {
31 | vendors: {
32 | test(module) {
33 | return module.resource && /react/.test(module.resource)
34 | },
35 | name: 'vendors',
36 | chunks: 'all',
37 | priority: -10,
38 | },
39 | commons: {
40 | chunks: 'async',
41 | name: 'async',
42 | minChunks: 2,
43 | minSize: 0,
44 | },
45 | },
46 | },
47 | minimizer: [
48 | new TerserPlugin({
49 | parallel: true,
50 | extractComments: false,
51 | terserOptions: {
52 | warnings: false,
53 | compress: {
54 | drop_console: true,
55 | },
56 | },
57 | }),
58 | new OptimizeCssAssetsPlugin({
59 | cssProcessorOptions: {
60 | safe: true,
61 | autoprefixer: { disable: true },
62 | discardComments: { removeAll: true },
63 | },
64 | }),
65 | ],
66 | },
67 | module: {
68 | rules: [
69 | {
70 | test: /\.css$/,
71 | exclude: /node_modules/,
72 | use: [
73 | { loader: MiniCSSExtractPlugin.loader, options: { sourceMap: true } },
74 | { loader: 'css-loader', options: { sourceMap: true } },
75 | { loader: 'postcss-loader', options: { sourceMap: true } },
76 | ],
77 | },
78 | {
79 | test: /(\.styl$|\.stylus$)/,
80 | use: [
81 | { loader: MiniCSSExtractPlugin.loader, options: { sourceMap: true } },
82 | {
83 | loader: 'css-loader',
84 | options: {
85 | sourceMap: true,
86 | modules: true,
87 | localIdentName: '[name]_[local]___[hash:base64:5]',
88 | },
89 | },
90 | { loader: 'postcss-loader', options: { sourceMap: true } },
91 | {
92 | loader: 'stylus-loader',
93 | options: {
94 | sourceMap: true,
95 | use: configs.stylus.plugins,
96 | },
97 | },
98 | ],
99 | },
100 | ],
101 | },
102 | plugins: [
103 | new CleanWebpackPlugin(),
104 | new webpack.HashedModuleIdsPlugin(),
105 | new HtmlWebpackPlugin({
106 | filename: 'index.html',
107 | template: 'public/index.html',
108 | inject: true,
109 | minify: {
110 | collapseWhitespace: true,
111 | removeComments: true,
112 | removeEmptyAttributes: true,
113 | },
114 | }),
115 | new PreloadWebpackPlugin({
116 | rel: 'preload',
117 | include: ['vendors', 'main'],
118 | }),
119 | new MiniCSSExtractPlugin({
120 | filename: path.posix.join('static', 'css/[name].[contenthash].css'),
121 | chunkFilename: path.posix.join('static', 'css/[name].[contenthash].async.css'),
122 | }),
123 | ]
124 | .concat(
125 | configs.analyzer
126 | ? [
127 | new BundleAnalyzerPlugin({
128 | openAnalyzer: false,
129 | }),
130 | ]
131 | : [],
132 | )
133 | .concat(configs.gzip ? [new CompressionPlugin()] : []),
134 | }
135 |
136 | module.exports = MergeWebpack(common, prod)
137 |
--------------------------------------------------------------------------------
/example/mock/fake.js:
--------------------------------------------------------------------------------
1 | const proxy = {
2 | 'GET /proxy/fake': (req, res) => {
3 | res.json(
4 | Array(10)
5 | .fill(0)
6 | .map((_v, i) => i),
7 | )
8 | },
9 | }
10 |
11 | export default proxy
12 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-for-threejs-weather",
3 | "version": "0.0.1",
4 | "description": "example-for-threejs-weather",
5 | "license": "MIT",
6 | "homepage": "https://github.com/JiangWeixian/templates#readme",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/JiangWeixian/templates.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/JiangWeixian/templates/issues"
13 | },
14 | "author": "jiangwei",
15 | "main": "src/index.tsx",
16 | "scripts": {
17 | "test": "jest --config ./test/jest.config.js",
18 | "dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.config.js --host 0.0.0.0",
19 | "build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.config.js --progress",
20 | "update": "npm update && npm update --save-dev",
21 | "check": "npm outdated && npm outdated --save-dev",
22 | "mock": "PORT=3000 umi-serve",
23 | "prepublishOnly": "np --no-cleanup --yolo --no-publish --any-branch"
24 | },
25 | "dependencies": {
26 | "@react-spring/web": "^9.2.4",
27 | "@react-three/drei": "^7.6.1",
28 | "@react-three/fiber": "^7.0.6",
29 | "axios": "0.21.1",
30 | "classnames": "2.3.1",
31 | "core-js": "3.16.2",
32 | "history": "5.0.1",
33 | "leva": "^0.9.13",
34 | "react": "17.0.2",
35 | "react-dom": "17.0.2",
36 | "react-router": "5.2.0",
37 | "react-router-dom": "5.2.0",
38 | "react-transition-group": "4.4.2",
39 | "require-context": "1.1.0",
40 | "styled-components": "5.3.0",
41 | "swr": "0.5.6",
42 | "three": "0.125.2",
43 | "threejs-meshline": "2.0.12",
44 | "threejs-weather": "^1.0.0",
45 | "wouter": "^2.7.4"
46 | },
47 | "devDependencies": {
48 | "@babel/core": "7.15.0",
49 | "@babel/plugin-syntax-dynamic-import": "7.8.3",
50 | "@babel/preset-env": "7.15.0",
51 | "@babel/preset-react": "7.14.5",
52 | "@babel/preset-typescript": "7.15.0",
53 | "@types/chai": "4.1.7",
54 | "@types/classnames": "2.2.7",
55 | "@types/copy-webpack-plugin": "5.0.0",
56 | "@types/enzyme": "3.9.1",
57 | "@types/html-webpack-plugin": "3.2.0",
58 | "@types/jest": "24.0.12",
59 | "@types/lodash": "4.14.172",
60 | "@types/mini-css-extract-plugin": "0.2.0",
61 | "@types/optimize-css-assets-webpack-plugin": "1.3.4",
62 | "@types/react-dom": "17.0.9",
63 | "@types/react-hot-loader": "4.1.0",
64 | "@types/react-loadable": "5.5.6",
65 | "@types/react-router-dom": "5.1.8",
66 | "@types/styled-components": "5.1.12",
67 | "@types/uglifyjs-webpack-plugin": "1.1.0",
68 | "@types/webpack": "4.4.31",
69 | "@types/webpack-bundle-analyzer": "2.13.1",
70 | "@types/webpack-dev-server": "3.9.0",
71 | "@types/webpack-env": "1.13.9",
72 | "@types/webpack-merge": "4.1.5",
73 | "babel-core": "7.0.0-bridge.0",
74 | "babel-jest": "24.8.0",
75 | "babel-loader": "8.2.2",
76 | "babel-plugin-import": "1.13.3",
77 | "cache-loader": "3.0.0",
78 | "chai": "4.2.0",
79 | "clean-webpack-plugin": "2.0.2",
80 | "compression-webpack-plugin": "3.0.1",
81 | "copy-webpack-plugin": "5.0.3",
82 | "cross-env": "7.0.3",
83 | "css-loader": "1.0.1",
84 | "cssnano": "4.1.10",
85 | "cssnano-preset-advanced": "4.0.7",
86 | "enzyme": "3.9.0",
87 | "enzyme-adapter-react-16": "1.13.0",
88 | "fork-ts-checker-webpack-plugin": "1.3.1",
89 | "friendly-errors-webpack-plugin": "1.7.0",
90 | "html-webpack-plugin": "3.2.0",
91 | "jest": "24.8.0",
92 | "jest-css-modules": "2.0.0",
93 | "jest-localstorage-mock": "2.4.0",
94 | "mini-css-extract-plugin": "0.6.0",
95 | "np": "7.5.0",
96 | "optimize-css-assets-webpack-plugin": "5.0.1",
97 | "postcss-cssnext": "3.1.0",
98 | "postcss-import": "14.0.2",
99 | "postcss-js": "3.0.3",
100 | "postcss-loader": "3.0.0",
101 | "postcss-preset-env": "6.7.0",
102 | "postcss-url": "10.1.3",
103 | "preload-webpack-plugin": "3.0.0-beta.4",
104 | "prettier": "2.3.2",
105 | "prettier-quick": "0.0.5",
106 | "pretty-quick": "2.0.1",
107 | "react-hot-loader": "4.13.0",
108 | "react-loadable": "5.5.0",
109 | "rucksack-css": "1.0.2",
110 | "rupture": "0.7.1",
111 | "style-loader": "0.23.1",
112 | "stylus": "0.54.5",
113 | "stylus-loader": "3.0.2",
114 | "stylus-supremacy": "2.12.7",
115 | "terser-webpack-plugin": "3.0.2",
116 | "thread-loader": "2.1.3",
117 | "ts-import-plugin": "1.5.5",
118 | "ts-jest": "24.0.2",
119 | "ts-loader": "6.0.0",
120 | "tslib": "2.3.1",
121 | "typescript": "4.3.5",
122 | "typings-for-css-modules-loader": "1.7.0",
123 | "umi-serve": "1.9.4",
124 | "url-loader": "1.1.2",
125 | "webpack": "4.23.1",
126 | "webpack-bundle-analyzer": "3.3.2",
127 | "webpack-cli": "3.3.2",
128 | "webpack-dev-server": "3.3.1",
129 | "webpack-merge": "4.2.1"
130 | },
131 | "browserslits": [
132 | "> 1%"
133 | ]
134 | }
135 |
--------------------------------------------------------------------------------
/example/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-preset-env': {},
4 | 'rucksack-css': {},
5 | 'postcss-import': {},
6 | 'postcss-url': {},
7 | 'postcss-cssnext': {
8 | browsers: ['> 1%'],
9 | },
10 | cssnano: {
11 | preset: 'advanced',
12 | autoprefixer: false,
13 | },
14 | },
15 | }
16 |
--------------------------------------------------------------------------------
/example/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/example/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/example/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/example/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/favicon-16x16.png
--------------------------------------------------------------------------------
/example/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/favicon-32x32.png
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @threejs-weather
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/example/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
6 | { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
7 | ],
8 | "theme_color": "#ffffff",
9 | "background_color": "#ffffff",
10 | "display": "standalone"
11 | }
12 |
--------------------------------------------------------------------------------
/example/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { hot } from 'react-hot-loader/root'
2 | import React from 'react'
3 | import RouterViewer from '@/routes'
4 | import { createGlobalStyle } from 'styled-components'
5 |
6 | const GlobalStyle = createGlobalStyle`
7 | body {
8 | padding: 0px;
9 | margin: 0px;
10 | }
11 | #app {
12 | width: 100vw;
13 | height: 100vh;
14 | }
15 | @font-face {
16 | font-family: font;
17 | src: url("/static/font.ttf") format("truetype")
18 | }
19 | `
20 |
21 | const App = () => {
22 | return (
23 | <>
24 |
25 |
26 | >
27 | )
28 | }
29 |
30 | export default hot(App)
31 |
--------------------------------------------------------------------------------
/example/src/api/fake.ts:
--------------------------------------------------------------------------------
1 | import { get } from './utils'
2 |
3 | export namespace Fake {
4 | export type Response = number
5 | }
6 |
7 | export const fake = {
8 | async list(skip?: number, limit?: number): Promise {
9 | return get('/fake', { skip, limit })
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/example/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import { fake, Fake } from './fake'
2 |
3 | const api = {
4 | fake,
5 | }
6 |
7 | export { Fake }
8 |
9 | export default api
10 |
--------------------------------------------------------------------------------
/example/src/api/utils.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | axios.defaults.withCredentials = true
4 | axios.defaults.baseURL = '/proxy'
5 |
6 | export const get = async (path: string, params: Q): Promise => {
7 | return axios
8 | .get(path, {
9 | params,
10 | })
11 | .then((res) => res.data)
12 | }
13 |
14 | export const post = async (path: string, params: Q): Promise => {
15 | return axios.post(path, params).then((res) => res.data)
16 | }
17 |
--------------------------------------------------------------------------------
/example/src/components/WeatherSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import styled from 'styled-components'
3 | import { useLocation } from 'wouter'
4 | import { useWeather, types } from 'threejs-weather'
5 | import { useSprings, animated, useSpring } from '@react-spring/web'
6 |
7 | import { PATHS } from '@/constants'
8 |
9 | const StyledWeatherSwitcher = styled(animated.ul)`
10 | display: flex;
11 | justify-content: center;
12 | position: absolute;
13 | z-index: 1000;
14 | padding: 0px;
15 | left: 0px;
16 | right: 0px;
17 | bottom: 0px;
18 | padding: 16px;
19 | text-align: center;
20 | font-family: font;
21 | font-size: 24px;
22 |
23 | li {
24 | list-style-type: none;
25 | padding: 0px 4px;
26 | cursor: pointer;
27 | }
28 | `
29 |
30 | export const WeatherSwitcher = () => {
31 | const [location, setLocation] = useLocation()
32 | const values = Object.values(PATHS)
33 | const index = values.findIndex((v) => v.path === location)
34 | const [activeIndex, setActiveIndex] = useState(index < 0 ? 0 : index)
35 | const [springs, set] = useSprings(values.length, () => ({
36 | opacity: 0.2,
37 | }))
38 | const color = useSpring({
39 | color: PATHS[location.replace('/prod/', '')]?.mode === 'light' ? '#000' : '#fff',
40 | })
41 | const { handleChangeType } = useWeather()
42 | useEffect(() => {
43 | set(((index: number) => {
44 | if (index !== activeIndex) {
45 | return { opacity: 0.2 }
46 | }
47 | return { opacity: 1 }
48 | }) as any)
49 | }, [activeIndex, set])
50 | return (
51 |
52 | {springs.map((props, i) => {
53 | return (
54 | {
58 | setActiveIndex(i)
59 | handleChangeType?.(values[i].path.replace('/prod/', '') as types.Weather)
60 | setLocation(values[i].path)
61 | }}
62 | onMouseEnter={() => {
63 | set(((index: number) => {
64 | if (index !== i && index !== activeIndex) {
65 | return { opacity: 0.2 }
66 | }
67 | return { opacity: 1 }
68 | }) as any)
69 | }}
70 | onMouseLeave={() => {
71 | set(((index: number) => {
72 | if (index === i && index !== activeIndex) {
73 | return { opacity: 0.2 }
74 | }
75 | }) as any)
76 | }}
77 | >
78 | {values[i].name}
79 |
80 | )
81 | })}
82 |
83 | )
84 | }
85 |
--------------------------------------------------------------------------------
/example/src/components/WeatherText.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { useFrame, useThree } from '@react-three/fiber'
3 | import { Mesh, Vector3, Color } from 'three'
4 | import { Text } from '@react-three/drei'
5 |
6 | type TextProps = {
7 | children: string
8 | color?: string
9 | position?: Vector3
10 | style?: any
11 | }
12 |
13 | const InnerText = Text as any
14 |
15 | export const WeatherText = ({ color = '#310f1b', ...props }: TextProps) => {
16 | const text = useRef()
17 | const { camera } = useThree()
18 | useFrame(() => {
19 | if (!text.current || !camera) {
20 | return
21 | }
22 | const rotation = camera.rotation
23 | text.current.rotation.x = rotation.x
24 | text.current.rotation.y = rotation.y
25 | text.current.rotation.z = rotation.z
26 | if (Array.isArray(text.current.material)) {
27 | text.current.material.forEach((v) => {
28 | v.opacity = props.style?.opacity.get() ?? 1
29 | ;(v as any).color = new Color(color)
30 | })
31 | } else {
32 | text.current.material.opacity = props.style?.opacity.get() ?? 1
33 | ;(text.current.material as any).color = new Color(color)
34 | }
35 | })
36 | return (
37 |
45 |
46 | {props.children}
47 |
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/example/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const PATHS = {
2 | rain: {
3 | name: '雨',
4 | path: '/prod/rain',
5 | count: 100,
6 | },
7 | cloudy: {
8 | name: '多云',
9 | path: '/prod/cloudy',
10 | count: 10,
11 | },
12 | partlyCloudy: {
13 | name: '阴天',
14 | path: '/prod/partly-cloudy',
15 | count: 10,
16 | },
17 | sun: {
18 | name: '晴',
19 | path: '/prod/sun',
20 | mode: 'light',
21 | count: 6,
22 | },
23 | snow: {
24 | name: '雪',
25 | path: '/prod/snow',
26 | count: 10,
27 | },
28 | meteors: {
29 | name: '流星',
30 | path: '/prod/meteors',
31 | count: 10,
32 | },
33 | starRings: {
34 | name: '星环',
35 | path: '/prod/star-rings',
36 | count: 100,
37 | },
38 | fog: {
39 | name: '雾',
40 | path: '/prod/fog',
41 | count: 10,
42 | },
43 | haze: {
44 | name: '霾',
45 | path: '/prod/haze',
46 | count: 10,
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/example/src/index.tsx:
--------------------------------------------------------------------------------
1 | import App from './App'
2 | import React from 'react'
3 | import { render } from 'react-dom'
4 |
5 | const $ROOT = document.querySelector('#app')
6 |
7 | const renderApp = (Component: any) => {
8 | render(, $ROOT)
9 | }
10 |
11 | document.addEventListener('DOMContentLoaded', () => {
12 | renderApp(App)
13 | })
14 |
--------------------------------------------------------------------------------
/example/src/pages/prod/cloudy.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 |
4 | import { Cloudy } from 'threejs-weather'
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const CloudyPage = (props) => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | {PATHS.cloudy.name}
17 |
18 |
19 | >
20 | )
21 | }
22 |
23 | export default CloudyPage
24 |
--------------------------------------------------------------------------------
/example/src/pages/prod/fog.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 | import { Vector3 } from 'three'
4 |
5 | import { Fog } from 'threejs-weather'
6 | import { WeatherText } from '@/components/WeatherText'
7 | import { PATHS } from '@/constants'
8 |
9 | const position = [127.45293777867074, 62.11080512264083, 137.6247069251716]
10 |
11 | const FogPage = (props) => {
12 | return (
13 | <>
14 |
15 |
20 | {PATHS.fog.name}
21 |
22 |
23 |
24 |
25 | >
26 | )
27 | }
28 |
29 | export default FogPage
30 |
--------------------------------------------------------------------------------
/example/src/pages/prod/haze.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats, OrbitControls } from '@react-three/drei'
3 | import { Haze } from 'threejs-weather'
4 |
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const OC: any = OrbitControls
9 |
10 | const HazePage = (props) => {
11 | return (
12 | <>
13 |
14 |
15 |
16 | {PATHS.haze.name}
17 |
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export default HazePage
26 |
--------------------------------------------------------------------------------
/example/src/pages/prod/meteors.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 |
4 | import { Meteors } from 'threejs-weather'
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const MeteorsPage = (props) => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | {PATHS.meteors.name}
16 |
17 |
18 | >
19 | )
20 | }
21 |
22 | export default MeteorsPage
23 |
--------------------------------------------------------------------------------
/example/src/pages/prod/partly-cloudy.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 | import { PartlyCloudy } from 'threejs-weather'
4 |
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const PartlyCloudyPage = (props) => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | {PATHS.partlyCloudy.name}
16 |
17 |
18 | {/* */}
19 | >
20 | )
21 | }
22 |
23 | export default PartlyCloudyPage
24 |
--------------------------------------------------------------------------------
/example/src/pages/prod/rain.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats, OrbitControls } from '@react-three/drei'
3 |
4 | import { Rain, RainRing } from 'threejs-weather'
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const OC: any = OrbitControls
9 |
10 | const RainPage = (props) => {
11 | return (
12 | <>
13 |
14 |
15 |
16 |
17 |
18 |
19 | {PATHS.rain.name}
20 |
21 |
22 | >
23 | )
24 | }
25 |
26 | export default RainPage
27 |
--------------------------------------------------------------------------------
/example/src/pages/prod/snow.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 | import { Snow } from 'threejs-weather'
4 |
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const SnowPage = (props) => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | {PATHS.snow.name}
16 |
17 |
18 | >
19 | )
20 | }
21 |
22 | export default SnowPage
23 |
--------------------------------------------------------------------------------
/example/src/pages/prod/star-ring.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 |
4 | import { StarRings } from 'threejs-weather'
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const StarRingsPage = (props) => {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 | {PATHS.starRings.name}
16 |
17 |
18 | >
19 | )
20 | }
21 |
22 | export default StarRingsPage
23 |
--------------------------------------------------------------------------------
/example/src/pages/prod/sun.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react'
2 | import { Stats } from '@react-three/drei'
3 |
4 | import { Sun } from 'threejs-weather'
5 | import { WeatherText } from '@/components/WeatherText'
6 | import { PATHS } from '@/constants'
7 |
8 | const SunPage = (props) => {
9 | return (
10 | <>
11 |
12 |
13 |
14 | {PATHS.sun.name}
15 |
16 | >
17 | )
18 | }
19 |
20 | export default SunPage
21 |
--------------------------------------------------------------------------------
/example/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useEffect, useState, useCallback } from 'react'
2 | import { useLocation, Switch, Route, Router } from 'wouter'
3 | import { useTransition, WeatherProvider, types } from 'threejs-weather'
4 | import { a } from '@react-spring/three'
5 | import { Leva, useControls } from 'leva'
6 | import Sun from '@/pages/prod/sun'
7 | import Cloudy from '@/pages/prod/cloudy'
8 | import StarRing from '@/pages/prod/star-ring'
9 | import Snow from '@/pages/prod/snow'
10 | import Rain from '@/pages/prod/rain'
11 | import Meteors from '@/pages/prod/meteors'
12 | import Haze from '@/pages/prod/haze'
13 | import Fog from '@/pages/prod/fog'
14 | import PartlyCloudy from '@/pages/prod/partly-cloudy'
15 |
16 | import { PATHS } from '@/constants'
17 | import { WeatherSwitcher } from '@/components/WeatherSwitcher'
18 | import { getWeatherType } from '@/utils/weather'
19 |
20 | const Transition = () => {
21 | const [location] = useLocation()
22 | const defaultCount = useMemo(() => {
23 | const path = Object.values(PATHS).find((item) => item.path === location)
24 | return path?.count || 6
25 | }, [location])
26 | const [{ count }, set] = useControls(() => ({
27 | count: {
28 | value: defaultCount,
29 | max: 100,
30 | min: 1,
31 | },
32 | }))
33 | useEffect(() => {
34 | set({ count: defaultCount })
35 | }, [defaultCount, set])
36 | console.log(location)
37 | const { transition } = useTransition({ location })
38 | const type = location === '/' ? 'rain' : getWeatherType(location) || 'rain'
39 | return (
40 | }>
41 | {transition((style, _location, p) => {
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | )
75 | })}
76 |
77 | )
78 | }
79 |
80 | const currentLoc = () => window.location.hash.replace('#', '') || '/'
81 |
82 | const useHashLocation = () => {
83 | const [loc, setLoc] = useState(currentLoc())
84 |
85 | useEffect(() => {
86 | const handler = () => setLoc(currentLoc())
87 |
88 | // subscribe on hash changes
89 | window.addEventListener('hashchange', handler)
90 | return () => window.removeEventListener('hashchange', handler)
91 | }, [])
92 |
93 | const navigate = useCallback((to) => (window.location.hash = to), [])
94 |
95 | useEffect(() => {
96 | if (loc === '/') {
97 | navigate(PATHS.rain.path)
98 | }
99 | }, [loc, navigate])
100 | return [loc, navigate]
101 | }
102 |
103 | const HashRouter = (props) => {
104 | return {props.children}
105 | }
106 |
107 | const RouterViewer = () => {
108 | return (
109 |
110 |
111 |
112 |
113 | )
114 | }
115 |
116 | export default RouterViewer
117 |
--------------------------------------------------------------------------------
/example/src/typings/component.d.ts:
--------------------------------------------------------------------------------
1 | export type Props =
2 | | (JSX.IntrinsicAttributes &
3 | React.PropsWithoutRef &
4 | // tslint:disable-next-line
5 | React.RefAttributes>)
6 | | (JSX.IntrinsicAttributes & React.PropsWithRef>)
7 |
--------------------------------------------------------------------------------
/example/src/typings/rematch.d.ts:
--------------------------------------------------------------------------------
1 | import models from '@/store/models'
2 | import { RematchStore, RematchDispatch, RematchRootState } from '@rematch/core'
3 | import { ReactThreeFiber } from '@react-three/fiber'
4 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
5 |
6 | type Models = typeof models
7 |
8 | export type Store = RematchStore
9 | export type Dispatch = RematchDispatch
10 | export type RootState = RematchRootState
11 |
12 | declare global {
13 | namespace JSX {
14 | interface IntrinsicElements {
15 | orbitControls: ReactThreeFiber.Object3DNode
16 | meshLine: any
17 | meshLineMaterial: any
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/example/src/typings/type.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.styl'
2 |
--------------------------------------------------------------------------------
/example/src/utils/weather.ts:
--------------------------------------------------------------------------------
1 | export const getWeatherType = (loc: string) => {
2 | return loc.replace('/prod/', '')
3 | }
4 |
--------------------------------------------------------------------------------
/example/static/font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JiangWeixian/threejs-weather/2383ba077dbc2e2eee57cbee9b0441ecd53eb9b9/example/static/font.ttf
--------------------------------------------------------------------------------
/example/test/jest.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | verbose: true,
5 | collectCoverage: true,
6 | globals: {
7 | NODE_ENV: 'test',
8 | },
9 | testEnvironment: 'jsdom',
10 | rootDir: path.resolve(__dirname, '../'),
11 | moduleFileExtensions: ['tsx', 'jsx', 'js', 'ts'],
12 | moduleNameMapper: {
13 | '^@/(.*)$': '/src/$1',
14 | },
15 | transform: {
16 | '^.+\\.(js|jsx)$': '/node_modules/babel-jest',
17 | '.*\\.(ts|tsx)$': '/node_modules/ts-jest/preprocessor.js',
18 | '^.+\\.(css)$': '/node_modules/jest-css-modules',
19 | },
20 | setupFiles: ['jest-localstorage-mock', '/test/jest.setup.js'],
21 | }
22 |
--------------------------------------------------------------------------------
/example/test/jest.setup.js:
--------------------------------------------------------------------------------
1 | const enzyme = require('enzyme')
2 | const Adapter = require('enzyme-adapter-react-16')
3 |
4 | enzyme.configure({ adapter: new Adapter() })
5 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "react",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": ".",
11 | "strict": true,
12 | "resolveJsonModule": true,
13 | "noImplicitAny": false,
14 | "skipLibCheck": true,
15 | "paths": {
16 | "@/*": ["src/*"]
17 | },
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace JSX {
2 | interface IntrinsicElements {
3 | orbitControls: ReactThreeFiber.Object3DNode
4 | meshLine: any
5 | meshLineMaterial: any
6 | }
7 | }
8 |
9 | declare global {
10 | namespace JSX {
11 | interface IntrinsicElements {
12 | orbitControls: ReactThreeFiber.Object3DNode
13 | meshLine: any
14 | meshLineMaterial: any
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const through2 = require('through2')
3 | const ts = require('gulp-typescript')
4 | const babel = require('gulp-babel')
5 | const rimraf = require('rimraf')
6 | const merge2 = require('merge2')
7 | const gulp = require('gulp')
8 | const sourcemaps = require('gulp-sourcemaps')
9 | const tsDefaultReporter = ts.reporter.defaultReporter()
10 |
11 | // config
12 | const config = require('./build/gulp.config')
13 | const source = ['components/**/*.tsx', 'components/**/*.ts', 'typings/**/*.d.ts']
14 | const tsProject = ts.createProject('./tsconfig.json')
15 | const lib = process.env.NODE_ENV === 'development' ? config.dirs.devLib : config.dirs.lib
16 | const es = process.env.NODE_ENV === 'development' ? config.dirs.devEs : config.dirs.es
17 |
18 | function babelify(js, modules) {
19 | const babelConfig = config.getBabelConfig(modules)
20 | delete babelConfig.cacheDirectory
21 | const stream = js
22 | .pipe(sourcemaps.init())
23 | .pipe(babel(babelConfig))
24 | .pipe(
25 | through2
26 | .obj(function z(file, encoding, next) {
27 | this.push(file.clone())
28 | next()
29 | })
30 | .pipe(sourcemaps.write('.')),
31 | )
32 | return stream.pipe(gulp.dest(modules === false ? es : lib))
33 | }
34 |
35 | function compile(modules) {
36 | rimraf.sync(modules !== false ? lib : es)
37 | const assets = gulp
38 | .src(['./components/**/*.@(png|svg)'])
39 | .pipe(gulp.dest(modules === false ? es : lib))
40 | let error = 0
41 | // allow jsx file in src/xxx/
42 | if (config.tsConfig.allowJs) {
43 | source.unshift('components/**/*.jsx')
44 | }
45 | const tsResult = tsProject.src().pipe(
46 | ts(config.tsConfig, {
47 | finish: tsDefaultReporter.finish,
48 | }).on('error', () => {}),
49 | )
50 |
51 | const tsFilesStream = babelify(tsResult.js, modules)
52 | const tsd = tsResult.dts.pipe(gulp.dest(modules === false ? es : lib))
53 | return merge2([tsFilesStream, tsd, assets])
54 | }
55 |
56 | gulp.task('compile-with-es', (done) => {
57 | console.log('[Parallel] Compile to es...')
58 | compile(false).on('finish', done)
59 | })
60 |
61 | gulp.task('compile-with-lib', (done) => {
62 | console.log('[Parallel] Compile to lib...')
63 | compile().on('finish', done)
64 | })
65 |
66 | gulp.task('compile', gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib')))
67 | gulp.task('watch', () => {
68 | gulp.watch(source, gulp.series(gulp.parallel('compile-with-es', 'compile-with-lib')))
69 | })
70 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | cacheDirectory: './jest/cache',
3 | collectCoverage: true,
4 | collectCoverageFrom: ['src/**/*'],
5 | coverageDirectory: './jest/coverage',
6 | preset: 'ts-jest',
7 | resetMocks: true,
8 | resetModules: true,
9 | restoreMocks: true,
10 | globals: {
11 | 'ts-jest': {
12 | diagnostics: false,
13 | },
14 | },
15 | moduleNameMapper: {
16 | '@/(.*)': '/src/$1',
17 | },
18 | roots: [''],
19 | moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
20 | testRegex: '/__test__/.+\\.test\\.tsx?$',
21 | verbose: false,
22 | setupFiles: ['/test/setupTests.ts'],
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "threejs-weather",
3 | "version": "1.0.0",
4 | "description": "threejs-weather",
5 | "keywords": [
6 | "react",
7 | "tree",
8 | "weather"
9 | ],
10 | "license": "MIT",
11 | "repository": {
12 | "url": "https://github.com/JiangWeixian/threejs-weather",
13 | "type": "git"
14 | },
15 | "author": "JW",
16 | "files": [
17 | "es",
18 | "lib"
19 | ],
20 | "main": "lib/index.js",
21 | "module": "es/index.js",
22 | "typings": "es/index.d.ts",
23 | "scripts": {
24 | "test": "cross-env NODE_ENV=test jest",
25 | "clean": "rimraf es && rimraf lib",
26 | "dev": "pnpm run dev --prefix example",
27 | "lint:fix": "eslint . --fix",
28 | "release": "pnpm run build && pnpm changeset publish",
29 | "build": "pnpm run clean && cross-env NODE_ENV=production gulp compile",
30 | "build:dev": "pnpm run clean && cross-env NODE_ENV=development gulp compile"
31 | },
32 | "husky": {
33 | "hooks": {
34 | "pre-commit": "lint-staged"
35 | }
36 | },
37 | "lint-staged": {
38 | "**/**/*.{js,ts,tsx,vue,json}": [
39 | "eslint --fix"
40 | ]
41 | },
42 | "peerDependencies": {
43 | "@react-three/fiber": ">=7.5.0",
44 | "three": ">=0.125.2"
45 | },
46 | "dependencies": {
47 | "@react-spring/core": "^9.2.4",
48 | "@react-spring/three": "^9.2.4",
49 | "three.meshline": "^1.3.0",
50 | "threejs-meshline": "2.0.12"
51 | },
52 | "devDependencies": {
53 | "@aiou/eslint-config": "^0.2.1",
54 | "@babel/core": "7.15.0",
55 | "@babel/plugin-transform-typescript": "7.15.0",
56 | "@babel/preset-env": "7.15.0",
57 | "@babel/preset-react": "7.14.5",
58 | "@changesets/cli": "^2.16.0",
59 | "@react-three/drei": "^7.6.1",
60 | "@react-three/fiber": "^7.0.6",
61 | "@testing-library/react-hooks": "3.2.1",
62 | "@types/classnames": "2.2.9",
63 | "@types/enzyme-adapter-react-16": "1.0.5",
64 | "@types/lodash.isnull": "3.0.6",
65 | "@types/react": "17.0.19",
66 | "@types/react-dom": "17.0.9",
67 | "autoprefixer": "10.3.2",
68 | "babel-plugin-import": "1.13.3",
69 | "core-js": "3.16.2",
70 | "cross-env": "7.0.3",
71 | "cz-emoji": "^1.3.1",
72 | "debug": "4.3.2",
73 | "enzyme": "3.10.0",
74 | "enzyme-adapter-react-16": "1.15.1",
75 | "eslint": "^7.32.0",
76 | "fs-extra": "10.0.0",
77 | "gulp": "4.0.2",
78 | "gulp-babel": "8.0.0",
79 | "gulp-replace": "1.1.3",
80 | "gulp-sourcemaps": "3.0.0",
81 | "gulp-typescript": "5.0.1",
82 | "husky": "~3.1.0",
83 | "jest": "24.9.0",
84 | "lint-staged": "^11.1.2",
85 | "lodash.assign": "4.2.0",
86 | "merge2": "1.4.1",
87 | "np": "5.0.3",
88 | "postcss-modules": "4.2.2",
89 | "poststylus": "1.0.1",
90 | "prettier": "2.3.2",
91 | "pretty-quick": "2.0.1",
92 | "react": "17.0.2",
93 | "react-dom": "17.0.2",
94 | "rimraf": "3.0.2",
95 | "three": "^0.125.2",
96 | "through2": "4.0.2",
97 | "ts-jest": "24.2.0",
98 | "ts-loader": "9.2.5",
99 | "tslib": "2.3.1",
100 | "typescript": "4.3.5"
101 | },
102 | "config": {
103 | "commitizen": {
104 | "path": "cz-emoji"
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'example'
3 | - './'
4 |
--------------------------------------------------------------------------------
/test/setupTests.ts:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme'
2 | import Adapter from 'enzyme-adapter-react-16'
3 |
4 | Enzyme.configure({ adapter: new Adapter() })
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strictNullChecks": true,
4 | "moduleResolution": "node",
5 | "esModuleInterop": true,
6 | "declaration": true,
7 | "experimentalDecorators": true,
8 | "jsx": "react",
9 | "noUnusedParameters": true,
10 | "noUnusedLocals": true,
11 | "noImplicitAny": true,
12 | "target": "ES2015",
13 | "skipLibCheck": true,
14 | "lib": ["dom", "es2017"],
15 | "resolveJsonModule": true,
16 | "rootDir": "./components",
17 | "outDir": "es"
18 | },
19 | "include": ["components", "global.d.ts"],
20 | "exclude": ["node_modules", "lib", "es", "example"]
21 | }
22 |
--------------------------------------------------------------------------------
/typings/style.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.styl'
2 |
3 | declare module '*.css'
4 |
5 | declare module '*.json'
6 |
--------------------------------------------------------------------------------