├── .gitattributes
├── .github
└── workflows
│ └── deno.yml
├── .gitignore
├── README.md
├── package.json
├── public
├── assets
│ ├── env
│ │ └── env.hdr
│ ├── loading
│ │ └── loading-bg.jpg
│ └── models
│ │ ├── lamp.glb
│ │ ├── mat.glb
│ │ ├── pot-1.glb
│ │ ├── pot-2.glb
│ │ ├── pot-3.glb
│ │ ├── sofa-1.glb
│ │ ├── sofa.glb
│ │ ├── table-1.glb
│ │ ├── table-3.glb
│ │ └── table-4.glb
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── Assets
│ └── Images
│ │ ├── grass.jpg
│ │ ├── marble.jpg
│ │ ├── metal.jpg
│ │ ├── preview.png
│ │ ├── roof panel.jpg
│ │ ├── roof ridge.jpg
│ │ ├── stone wall.jpg
│ │ ├── surface.jpg
│ │ ├── wood-1.jpg
│ │ └── wood-2.jpg
├── Component
│ ├── Building
│ │ ├── Pergola
│ │ │ ├── Metal
│ │ │ │ ├── Accessories
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── CommonModel
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Pillar
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Roof
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Wall
│ │ │ │ │ └── index.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── Stone
│ │ │ │ ├── Accessories
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── CommonModel
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Fence
│ │ │ │ │ ├── CommonModel
│ │ │ │ │ │ └── index.jsx
│ │ │ │ │ ├── Pillar
│ │ │ │ │ │ └── index.jsx
│ │ │ │ │ ├── Wall
│ │ │ │ │ │ └── index.jsx
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Pillar
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Roof
│ │ │ │ │ └── index.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── Wood
│ │ │ │ ├── Accessories
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── CommonModel
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Pillar
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── Roof
│ │ │ │ │ └── index.jsx
│ │ │ │ └── index.jsx
│ │ │ └── index.jsx
│ │ ├── Surface
│ │ │ └── index.jsx
│ │ └── index.jsx
│ ├── ControlPanel
│ │ └── index.jsx
│ ├── Env
│ │ └── index.jsx
│ ├── LoadingProgress
│ │ └── index.jsx
│ ├── Style
│ │ └── controller.scss
│ └── index.jsx
├── Redux
│ ├── Features
│ │ ├── BuildingCtrl
│ │ │ └── buildingCtrlSlice.jsx
│ │ ├── GLBModel
│ │ │ └── glbModelSlice.jsx
│ │ └── Texture
│ │ │ └── textureSlice.jsx
│ └── Store
│ │ └── index.jsx
├── Utils
│ ├── Constants.jsx
│ ├── Function.jsx
│ ├── SvgSource.jsx
│ └── TextureSource.jsx
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
├── tailwind.config.js
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/.gitattributes
--------------------------------------------------------------------------------
/.github/workflows/deno.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | # This workflow will install Deno then run `deno lint` and `deno test`.
7 | # For more information see: https://github.com/denoland/setup-deno
8 |
9 | name: Deno
10 |
11 | on:
12 | push:
13 | branches: ["main"]
14 | pull_request:
15 | branches: ["main"]
16 |
17 | permissions:
18 | contents: read
19 |
20 | jobs:
21 | test:
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - name: Setup repo
26 | uses: actions/checkout@v4
27 |
28 | - name: Setup Deno
29 | # uses: denoland/setup-deno@v1
30 | uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2
31 | with:
32 | deno-version: v1.x
33 |
34 | # Uncomment this step to verify the use of 'deno fmt' on each commit.
35 | # - name: Verify formatting
36 | # run: deno fmt --check
37 |
38 | - name: Run linter
39 | run: deno lint
40 |
41 | - name: Run tests
42 | run: deno test -A
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Getting Started with Create React App
3 |
4 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
5 |
6 | ## Available Scripts
7 |
8 | In the project directory, you can run:
9 |
10 | ### `npm start`
11 |
12 | Runs the app in the development mode.\
13 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
14 |
15 | The page will reload when you make changes.\
16 | You may also see any lint errors in the console.
17 |
18 | ### `npm test`
19 |
20 | Launches the test runner in the interactive watch mode.\
21 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
22 |
23 | ### `npm run build`
24 |
25 | Builds the app for production to the `build` folder.\
26 | It correctly bundles React in production mode and optimizes the build for the best performance.
27 |
28 | The build is minified and the filenames include the hashes.\
29 | Your app is ready to be deployed!
30 |
31 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
32 |
33 |
34 | ### `npm run eject`
35 |
36 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
37 |
38 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
39 |
40 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
41 |
42 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
43 |
44 | ## Learn More
45 |
46 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
47 |
48 | To learn React, check out the [React documentation](https://reactjs.org/).
49 |
50 | ### Code Splitting
51 |
52 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
53 |
54 | ### Analyzing the Bundle Size
55 |
56 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
57 |
58 | ### Making a Progressive Web App
59 |
60 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
61 |
62 | ### Advanced Configuration
63 |
64 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
65 |
66 | ### Deployment
67 |
68 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
69 |
70 | ### `npm run build` fails to minify
71 |
72 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "3d_pergola_design",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@react-three/csg": "^3.3.0",
7 | "@react-three/drei": "^9.122.0",
8 | "@react-three/fiber": "^8.18.0",
9 | "@react-three/postprocessing": "^3.0.4",
10 | "@reduxjs/toolkit": "^2.8.0",
11 | "@testing-library/jest-dom": "^5.17.0",
12 | "@testing-library/react": "^13.4.0",
13 | "@testing-library/user-event": "^13.5.0",
14 | "bootstrap-icons": "^1.12.1",
15 | "react": "^18.3.1",
16 | "react-bootstrap": "^2.10.9",
17 | "react-dom": "^18.3.1",
18 | "react-redux": "^9.2.0",
19 | "react-scripts": "5.0.1",
20 | "react-tweakpane": "^0.8.1",
21 | "sass": "^1.87.0",
22 | "three": "^0.170.0",
23 | "tweakpane": "^4.0.5",
24 | "web-vitals": "^2.1.4"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "@tweakpane/core": "^2.0.5",
52 | "tailwindcss": "^3.4.17"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/assets/env/env.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/env/env.hdr
--------------------------------------------------------------------------------
/public/assets/loading/loading-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/loading/loading-bg.jpg
--------------------------------------------------------------------------------
/public/assets/models/lamp.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/lamp.glb
--------------------------------------------------------------------------------
/public/assets/models/mat.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/mat.glb
--------------------------------------------------------------------------------
/public/assets/models/pot-1.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/pot-1.glb
--------------------------------------------------------------------------------
/public/assets/models/pot-2.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/pot-2.glb
--------------------------------------------------------------------------------
/public/assets/models/pot-3.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/pot-3.glb
--------------------------------------------------------------------------------
/public/assets/models/sofa-1.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/sofa-1.glb
--------------------------------------------------------------------------------
/public/assets/models/sofa.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/sofa.glb
--------------------------------------------------------------------------------
/public/assets/models/table-1.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/table-1.glb
--------------------------------------------------------------------------------
/public/assets/models/table-3.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/table-3.glb
--------------------------------------------------------------------------------
/public/assets/models/table-4.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/assets/models/table-4.glb
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
19 |
20 |
29 | Pergola Design
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import Component from './Component';
3 | import { InitiallyAssetsLoad } from './Utils/Function';
4 |
5 | function App() {
6 | InitiallyAssetsLoad();
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/Assets/Images/grass.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/grass.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/marble.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/marble.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/metal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/metal.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/preview.png
--------------------------------------------------------------------------------
/src/Assets/Images/roof panel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/roof panel.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/roof ridge.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/roof ridge.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/stone wall.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/stone wall.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/surface.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/surface.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/wood-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/wood-1.jpg
--------------------------------------------------------------------------------
/src/Assets/Images/wood-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcdrm/webgl_pegola_building_configuration/328dc167d1d8cd982cfe97e1c99c0ecd90247659/src/Assets/Images/wood-2.jpg
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Metal/Accessories/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 |
4 | const Accessories = () => {
5 | const { width, length } = useSelector(state => state.buildingCtrl)
6 | const lampModel = useSelector(state => state.glbModel.lampModel)
7 | const sofaModel_1 = useSelector(state => state.glbModel.sofaModel_1)
8 | const tableModel_1 = useSelector(state => state.glbModel.tableModel_1)
9 |
10 | return (
11 | <>
12 |
13 |
14 |
15 | >
16 | )
17 | }
18 |
19 | export default Accessories
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Metal/CommonModel/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { ConstMetalPergolaProps, ConstProps } from "../../../../../Utils/Constants";
4 | import { extrudeSettings } from '../../../../../Utils/Function';
5 |
6 | const { length, pitch } = ConstProps
7 | const { pillarSize, pillarGapSize } = ConstMetalPergolaProps;
8 |
9 | export const PillarModel = ({ modelLength, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], map=null, bumpScale=0, roughness=0.2, metalness=0.1 }) => {
10 | const model = new THREE.Shape();
11 | model.moveTo(-(pillarSize / 2 - pillarGapSize), -pillarSize / 2);
12 | model.lineTo((pillarSize / 2 - pillarGapSize), -pillarSize / 2);
13 | model.lineTo((pillarSize / 2 - pillarGapSize), -(pillarSize / 2 - pillarGapSize));
14 | model.lineTo((pillarSize / 2), -(pillarSize / 2 - pillarGapSize));
15 | model.lineTo((pillarSize / 2), (pillarSize / 2 - pillarGapSize));
16 | model.lineTo((pillarSize / 2 - pillarGapSize), (pillarSize / 2 - pillarGapSize));
17 | model.lineTo((pillarSize / 2 - pillarGapSize), (pillarSize / 2));
18 | model.lineTo(-(pillarSize / 2 - pillarGapSize), (pillarSize / 2));
19 | model.lineTo(-(pillarSize / 2 - pillarGapSize), (pillarSize / 2 - pillarGapSize));
20 | model.lineTo(-(pillarSize / 2), (pillarSize / 2 - pillarGapSize));
21 | model.lineTo(-(pillarSize / 2), -(pillarSize / 2 - pillarGapSize));
22 | model.closePath()
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export const SideBowTrussModel = ({ modelHeight, modelThickness, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], color='white', map=null, bumpScale=0, roughness=0.2, metalness=0.1, opacity=1, transparent=false }) => {
35 | const modelWidth = length - pillarSize * 2;
36 |
37 | const model = new THREE.Shape();
38 | model.moveTo(0, 0);
39 | model.lineTo(0, -modelHeight);
40 | model.lineTo(modelWidth, length * pitch / 12 - modelHeight);
41 | model.lineTo(modelWidth, length * pitch / 12);
42 | model.closePath();
43 |
44 | return (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
56 | export const RectModel = ({ modelSize, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], color='white', map=null, bumpScale=0, roughness=0.2, metalness=0.1, opacity=1, transparent=false }) => {
57 | return (
58 |
59 |
60 |
61 |
62 |
63 |
64 | )
65 | }
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Metal/Pillar/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { useThree } from '@react-three/fiber';
4 |
5 | import { textureAnisotropy } from '../../../../../Utils/Function';
6 | import { ConstMetalPergolaProps, ConstProps } from '../../../../../Utils/Constants';
7 |
8 | import { PillarModel, RectModel } from '../CommonModel';
9 |
10 | const { width, length, height, pitch } = ConstProps
11 | const { pillarSize, pillarBaseSize } = ConstMetalPergolaProps;
12 |
13 | const Pillar = () => {
14 | const { gl } = useThree();
15 | // const { width, length, height, pitch } = useSelector(state => state.buildingCtrl)
16 | const { metalTexture } = useSelector(state => state.texture.textureProps)
17 |
18 | const metalPillarTexture = metalTexture?.clone();
19 | textureAnisotropy(gl, metalPillarTexture, 0.01, 5, Math.PI / 2);
20 | if (metalPillarTexture) metalPillarTexture.offset.y = 0.3
21 |
22 | const PillarModelInfoArr = useMemo(() => {
23 | let data = [];
24 | let pillarHeight = 0;
25 |
26 | new Array(2).fill("").forEach((_, index_i) => {
27 | new Array(2).fill("").forEach((_, index_j) => {
28 | if (index_j === 0) pillarHeight = height;
29 | else pillarHeight = height + length * pitch / 12;
30 | data.push({
31 | length: pillarHeight,
32 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index_i),
33 | pos_y: 0,
34 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, index_j),
35 | rotation_1: [-Math.PI / 2, 0, 0]
36 | })
37 | })
38 | })
39 |
40 | return data;
41 | }, [])
42 |
43 | const PillarBaseModelInfoArr = useMemo(() => {
44 | let data = [];
45 | const modelHeight = 0.07;
46 |
47 | new Array(2).fill("").forEach((_, index_i) => {
48 | new Array(2).fill("").forEach((_, index_j) => {
49 | data.push({
50 | width: pillarBaseSize,
51 | length: pillarBaseSize,
52 | height: modelHeight,
53 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index_i),
54 | pos_y: modelHeight / 2,
55 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, index_j),
56 | })
57 | })
58 | })
59 |
60 | return data;
61 | }, [])
62 |
63 | return (
64 | <>
65 | {PillarModelInfoArr.map((item, index) => )}
66 | {PillarBaseModelInfoArr.map((item, index) => )}
67 | >
68 | )
69 | }
70 |
71 | export default Pillar
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Metal/Roof/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { useThree } from '@react-three/fiber';
4 |
5 | import { ConstMetalPergolaProps, ConstProps } from '../../../../../Utils/Constants';
6 | import { getDistanceAndCount, textureAnisotropy } from '../../../../../Utils/Function';
7 |
8 | import { RectModel, SideBowTrussModel } from '../CommonModel';
9 |
10 | const { width, length, height, pitch, roofAlpha } = ConstProps;
11 | const { pillarSize, pillarGapSize, endRoofBowHeight, sideRoofBowHeight } = ConstMetalPergolaProps;
12 |
13 | const Roof = () => {
14 | const { gl } = useThree();
15 | const { metalTexture } = useSelector(state => state.texture.textureProps)
16 |
17 | const roofCoverModelTexture = metalTexture?.clone();
18 | textureAnisotropy(gl, roofCoverModelTexture, 0.01, 1, 0.15);
19 |
20 | const endBowTrussTexture = metalTexture?.clone();
21 | textureAnisotropy(gl, endBowTrussTexture, 0.01, 1, 0);
22 | if (endBowTrussTexture) endBowTrussTexture.offset.y = 0.3;
23 |
24 | const sideBowTrussTexture = metalTexture?.clone();
25 | textureAnisotropy(gl, sideBowTrussTexture, 0.01, 1, roofAlpha);
26 | if (sideBowTrussTexture) sideBowTrussTexture.offset.y = 0.5;
27 |
28 | const frontGlassTrimTexture = metalTexture?.clone();
29 | textureAnisotropy(gl, frontGlassTrimTexture, 0.01, 1, 0);
30 | if (frontGlassTrimTexture) frontGlassTrimTexture.offset.y = 0.1;
31 |
32 | const RoofCoverModelInfoArr = useMemo(() => {
33 | const initDistance = 0.3
34 | const modelWidth = initDistance / 5 * 4;
35 | const modellength = length - pillarSize * 2;
36 | const modelHeight = 0.05;
37 | const { distance, count } = getDistanceAndCount(initDistance, width - pillarSize * 2);
38 |
39 | let data = [];
40 | new Array(count).fill("").forEach((_, index) => {
41 | data.push({
42 | width: modelWidth,
43 | length: modelHeight,
44 | height: modellength,
45 | pos_x: width/ 2 - distance * index - initDistance - 0.05,
46 | pos_y: height + length / 2 * Math.tan(roofAlpha) - modelHeight * 2,
47 | pos_z: 0,
48 | rotation_1: [roofAlpha, 0, 0],
49 | rotation_2: [0, 0, Math.PI / 4],
50 | })
51 | })
52 |
53 | return data
54 | }, [])
55 |
56 | const EndBowModelInfoArr = useMemo(() => {
57 | let data = []
58 |
59 | new Array(2).fill("").forEach((_, index) => {
60 | data.push({
61 | width: width - pillarSize * 2 + pillarGapSize * 2,
62 | length: endRoofBowHeight,
63 | height: pillarSize,
64 |
65 | pos_x: 0,
66 | pos_y: height + length * pitch / 12 * index - endRoofBowHeight / 2,
67 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, index),
68 | })
69 | })
70 |
71 | return data;
72 | }, [])
73 |
74 | const SideBowModelInfoArr = useMemo(() => {
75 | let data = []
76 |
77 | new Array(2).fill("").forEach((_, index) => {
78 | data.push({
79 | modelHeight: sideRoofBowHeight,
80 | modelThickness: pillarSize / 3 * 2,
81 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index),
82 | pos_y: height,
83 | pos_z: length / 2 - pillarSize,
84 | rotation_1: [0, Math.PI / 2, 0]
85 | })
86 | })
87 |
88 | return data;
89 | }, [])
90 |
91 | const SideGlassTrimModelInfoArr = useMemo(() => {
92 | let data = []
93 |
94 | new Array(2).fill("").forEach((_, index) => {
95 | data.push({
96 | modelHeight: 0.05,
97 | modelThickness: pillarSize - pillarGapSize * 2,
98 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index),
99 | pos_y: height - 0.6 - 0.4 * index,
100 | pos_z: length / 2 - pillarSize,
101 | rotation_1: [0, Math.PI / 2, 0]
102 | })
103 | })
104 |
105 | return data;
106 | }, [])
107 |
108 | const SideGlassModelInfoArr = useMemo(() => {
109 | let data = []
110 |
111 | new Array(2).fill("").forEach((_, index) => {
112 | data.push({
113 | modelHeight: 0.6 + 0.4 * index - sideRoofBowHeight,
114 | modelThickness: pillarSize - pillarGapSize * 3,
115 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index),
116 | pos_y: height - sideRoofBowHeight,
117 | pos_z: length / 2 - pillarSize,
118 | rotation_1: [0, Math.PI / 2, 0],
119 | })
120 | })
121 |
122 | return data;
123 | }, [])
124 |
125 | const frontGlassTrimHeight = 0.15;
126 | const frontGlassTrimDst = 0.45;
127 | const FrontGlassTrimModelInfoArr = useMemo(() => {
128 | let data = []
129 |
130 |
131 | new Array(2).fill("").forEach((_, index) => {
132 | data.push({
133 | width: width - pillarSize * 2,
134 | length: frontGlassTrimHeight - frontGlassTrimHeight / 2 * index,
135 | height: pillarSize - pillarGapSize * 2,
136 |
137 | pos_x: 0,
138 | pos_y: height + length * pitch / 12 - endRoofBowHeight - frontGlassTrimHeight / 2 - frontGlassTrimDst * index,
139 | pos_z: -(length / 2 - pillarSize / 2),
140 | })
141 | })
142 |
143 | return data;
144 | }, [])
145 |
146 | const FrontGlassModelInfoArr = useMemo(() => {
147 | let data = []
148 |
149 | const glassHeight = frontGlassTrimDst - frontGlassTrimHeight / 2 - frontGlassTrimHeight / 4
150 | data.push({
151 | width: width - pillarSize * 2,
152 | length: glassHeight,
153 | height: pillarSize - pillarGapSize * 2,
154 |
155 | pos_x: 0,
156 | pos_y: height + length * pitch / 12 - endRoofBowHeight - frontGlassTrimHeight - glassHeight / 2,
157 | pos_z: -(length / 2 - pillarSize / 2),
158 | })
159 |
160 | return data;
161 | }, [])
162 |
163 | return (
164 | <>
165 | {RoofCoverModelInfoArr.map((item, index) => )}
166 | {EndBowModelInfoArr.map((item, index) => )}
167 | {SideBowModelInfoArr.map((item, index) => )}
168 | {SideGlassTrimModelInfoArr.map((item, index) => )}
169 | {SideGlassModelInfoArr.map((item, index) => )}
170 | {FrontGlassTrimModelInfoArr.map((item, index) => )}
171 | {FrontGlassModelInfoArr.map((item, index) => )}
172 | >
173 | )
174 | }
175 |
176 | export default Roof
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Metal/Wall/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { useThree } from '@react-three/fiber';
4 |
5 | import { getDistanceAndCount, textureAnisotropy } from '../../../../../Utils/Function';
6 | import { ConstMetalPergolaProps, ConstProps } from '../../../../../Utils/Constants';
7 |
8 | import { RectModel } from '../CommonModel';
9 |
10 | const { width, length, height } = ConstProps;
11 | const { pillarSize, endRoofBowHeight } = ConstMetalPergolaProps;
12 |
13 | const Wall = () => {
14 | const { gl } = useThree();
15 | const { metalTexture } = useSelector(state => state.texture.textureProps)
16 |
17 | const horRailModelTexture = metalTexture?.clone();
18 | textureAnisotropy(gl, horRailModelTexture, 0.01, 1, 0);
19 | const verRailModelTexture = metalTexture?.clone();
20 | textureAnisotropy(gl, verRailModelTexture, 0.01, 1, Math.PI / 2);
21 |
22 | const HorRailModelInfoArr = useMemo(() => {
23 | const initDistance = 0.2;
24 | const modelWidth = (width - pillarSize * 2) / 3;
25 | const modelHeight = initDistance / 4 * 3;
26 | const modelThickness = 0.05;
27 | const { distance, count } = getDistanceAndCount(initDistance, height - endRoofBowHeight - (initDistance - modelHeight));
28 |
29 | let data = [];
30 | new Array(2).fill("").forEach((_, index_i) => {
31 | new Array(count).fill("").forEach((_, index_j) => {
32 | data.push({
33 | width: modelWidth,
34 | length: modelThickness,
35 | height: modelHeight,
36 | pos_x: (width / 2 - pillarSize - modelWidth / 2) * (index_i === 0 ? 1 : -1),
37 | pos_y: height - endRoofBowHeight - modelHeight / 2 - distance * index_j - modelThickness / 2,
38 | pos_z: length / 2 - endRoofBowHeight / 2,
39 | angle: [Math.PI / 2, 0, 0]
40 | })
41 | })
42 | })
43 | data.push({
44 | width: width - pillarSize * 2,
45 | length: modelThickness * 1.5,
46 | height: modelThickness * 2,
47 | pos_x: 0,
48 | pos_y: modelThickness * 2,
49 | pos_z: length / 2 - endRoofBowHeight / 2,
50 | angle: [Math.PI / 2, 0, 0]
51 | })
52 |
53 | return data
54 | }, [])
55 |
56 | const VerRailModelInfoArr = useMemo(() => {
57 | let data = [];
58 | const modelWidth = 0.05;
59 | const modelThickness = modelWidth * 1.5;
60 | const modelHeight = height - endRoofBowHeight - 0.1;
61 |
62 | new Array(2).fill("").forEach((_, index_i) => {
63 | data.push({
64 | width: modelWidth,
65 | length: modelHeight,
66 | height: modelThickness,
67 | pos_x: (width / 6 - modelWidth / 2 - 0.05) * (index_i === 0 ? 1 : -1),
68 | pos_y: modelHeight / 2 + 0.15,
69 | pos_z: length / 2 - endRoofBowHeight / 2,
70 | angle: [0, 0, 0]
71 | })
72 | })
73 |
74 | return data;
75 | }, [])
76 |
77 | return (
78 | <>
79 | {HorRailModelInfoArr.map((item, index) => )}
80 | {VerRailModelInfoArr.map((item, index) => )}
81 | >
82 | )
83 | }
84 |
85 | export default Wall
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Metal/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Pillar from './Pillar'
4 | import Roof from './Roof'
5 | import Wall from './Wall'
6 | import Accessories from './Accessories'
7 |
8 | const MetalPergola = () => {
9 |
10 | return (
11 | <>
12 |
13 |
14 |
15 |
16 | >
17 | )
18 | }
19 |
20 | export default MetalPergola
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Accessories/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 |
4 | const Accessories = () => {
5 | const sofaModel = useSelector(state => state.glbModel.sofaModel)
6 | const matModel = useSelector(state => state.glbModel.matModel)
7 | const tableModel_4 = useSelector(state => state.glbModel.tableModel_4)
8 | const potModel_2 = useSelector(state => state.glbModel.potModel_2)
9 | const potModel_3 = useSelector(state => state.glbModel.potModel_3)
10 |
11 | return sofaModel && (
12 | <>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | >
21 | )
22 | }
23 |
24 | export default Accessories
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/CommonModel/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { Base, Geometry, Subtraction } from "@react-three/csg";
3 |
4 | import { extrudeSettings } from '../../../../../Utils/Function';
5 | import { ConstProps, ConstStonePergolaProps } from '../../../../../Utils/Constants';
6 |
7 | const { width, length, height } = ConstProps;
8 | const { stonePergolaRoofHeight, ridgeCoverTopWidth, ridgeCoverSideWidth, ridgeCoverSideThickness } = ConstStonePergolaProps
9 |
10 | export const RectModel = ({ modelSize, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], color='white', map=null, bumpScale=0.5, roughness=0.7, metalness=0.3 }) => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export const PillarCorbelModel = ({ position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], color='white', map=null, bumpScale=0.5, roughness=0.7, metalness=0.3 }) => {
22 | const modelThickness = 0.05;
23 |
24 | const dstW = 0.2;
25 | const dstH = 0.35;
26 | const patternDst = dstH / 3;
27 | const offset = 0.005;
28 |
29 | const model = new THREE.Shape();
30 | model.moveTo(0, -dstH);
31 | model.lineTo(dstW, 0);
32 | model.lineTo(dstW + patternDst, 0);
33 | model.quadraticCurveTo(dstW + patternDst + offset, -patternDst + offset, dstW + patternDst / 4, -patternDst);
34 | model.lineTo(patternDst - offset * 2, -dstH);
35 | model.quadraticCurveTo(patternDst + offset, -dstH - patternDst - offset, 0, -dstH - patternDst);
36 | model.closePath();
37 |
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export const CoverModel = ({ position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], roofMap=null, ridgeMap=null, interiorMap=null, bumpScale=2, roughness=0.7, metalness=0.3 }) => {
51 | const areaSizeW = width + 0.1;
52 | const areaSizeL = length + 0.1;
53 |
54 | const modelThickness = 0.015;
55 | const roofHeight = stonePergolaRoofHeight;
56 | const ridgeLength = areaSizeW / 3;
57 | const sideDst = (areaSizeW - ridgeLength) / 2;
58 | const angleEnd = Math.PI / 2 - Math.atan(roofHeight / (areaSizeL / 2));
59 | const angleSide = Math.PI / 2 - Math.atan(roofHeight / sideDst);
60 |
61 | const ridgeSideCoverAngle = Math.PI / 2 - Math.min(angleEnd, angleSide);
62 | const ridgeSideCoverSpaceHeight = ridgeCoverSideWidth * Math.tan(ridgeSideCoverAngle);
63 | const ridgeSideCoverLength = Math.sqrt(Math.pow(Math.sqrt(Math.pow(areaSizeL / 2, 2) + Math.pow(roofHeight, 2)), 2) + Math.pow(sideDst, 2))
64 | const roofCrossAngle = Math.asin(roofHeight / ridgeSideCoverLength)
65 |
66 | const modelEnd = new THREE.Shape();
67 | modelEnd.moveTo(-areaSizeW / 2, 0);
68 | modelEnd.lineTo(areaSizeW / 2, 0);
69 | modelEnd.lineTo(areaSizeW / 2 - sideDst, Math.sqrt(Math.pow(areaSizeL / 2, 2) + Math.pow(roofHeight, 2)));
70 | modelEnd.lineTo(-(areaSizeW / 2 - sideDst), Math.sqrt(Math.pow(areaSizeL / 2, 2) + Math.pow(roofHeight, 2)));
71 | modelEnd.closePath();
72 |
73 | const modelSide = new THREE.Shape();
74 | modelSide.moveTo(areaSizeL / 2, 0);
75 | modelSide.lineTo(-areaSizeL / 2, 0);
76 | modelSide.lineTo(0, Math.sqrt(Math.pow(sideDst, 2) + Math.pow(roofHeight, 2)));
77 | modelSide.closePath();
78 |
79 | const ridgeCoverSideModel = new THREE.Shape();
80 | ridgeCoverSideModel.moveTo(-ridgeCoverSideWidth, -ridgeSideCoverSpaceHeight);
81 | ridgeCoverSideModel.lineTo(0, 0);
82 | ridgeCoverSideModel.lineTo(ridgeCoverSideWidth, -ridgeSideCoverSpaceHeight);
83 | ridgeCoverSideModel.lineTo(ridgeCoverSideWidth, -ridgeSideCoverSpaceHeight + ridgeCoverSideThickness);
84 | ridgeCoverSideModel.lineTo(0, ridgeCoverSideThickness);
85 | ridgeCoverSideModel.lineTo(-ridgeCoverSideWidth, -ridgeSideCoverSpaceHeight + ridgeCoverSideThickness);
86 | ridgeCoverSideModel.closePath()
87 |
88 | const initAngle = Math.PI / 2 - Math.atan(areaSizeL / 2 / sideDst)
89 | const ridgeSideCoverGlobalAngle0 = new THREE.Euler(roofCrossAngle, 0, 0, 'XYZ')
90 | const ridgeSideCoverGlobalAngle1 = new THREE.Euler(0, initAngle, 0, 'XYZ')
91 | const ridgeSideCoverGlobalAngle2 = new THREE.Euler(0, initAngle + 0.008 + Math.PI / 2, 0, 'XYZ')
92 | const ridgeSideCoverGlobalAngle3 = new THREE.Euler(0, -initAngle, 0, 'XYZ')
93 | const ridgeSideCoverGlobalAngle4 = new THREE.Euler(0, -(initAngle + 0.008 + Math.PI / 2), 0, 'XYZ')
94 |
95 | const offset = 0.03;
96 | const ridgeTopCoverModel = new THREE.Shape();
97 | ridgeTopCoverModel.moveTo(-ridgeCoverTopWidth - offset, 0);
98 | ridgeTopCoverModel.lineTo(-ridgeCoverTopWidth, 0);
99 | ridgeTopCoverModel.quadraticCurveTo(-ridgeCoverTopWidth, ridgeCoverTopWidth / 2, 0, ridgeCoverTopWidth / 2);
100 | ridgeTopCoverModel.quadraticCurveTo(ridgeCoverTopWidth, ridgeCoverTopWidth / 2, ridgeCoverTopWidth, 0);
101 | ridgeTopCoverModel.lineTo(ridgeCoverTopWidth + offset, 0);
102 | ridgeTopCoverModel.quadraticCurveTo(ridgeCoverTopWidth + offset, (ridgeCoverTopWidth + offset) / 2, 0, (ridgeCoverTopWidth + offset) / 3 * 2);
103 | ridgeTopCoverModel.quadraticCurveTo(-(ridgeCoverTopWidth + offset), (ridgeCoverTopWidth + offset) / 2, -(ridgeCoverTopWidth + offset), 0);
104 | ridgeTopCoverModel.closePath();
105 | return (
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | {/*
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 | */}
261 |
262 |
263 |
264 |
265 |
266 | )
267 | }
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Fence/CommonModel/index.jsx:
--------------------------------------------------------------------------------
1 | export const RectModel = ({ modelSize, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], color='#EEEEEE', map=null, bumpScale=0.5, roughness=0.7, metalness=0.3 }) => {
2 | return (
3 |
4 |
5 |
6 |
7 |
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Fence/Pillar/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useThree } from '@react-three/fiber';
3 | import { useSelector } from 'react-redux';
4 |
5 | import { ConstFenceProps, ConstProps, ConstWoodPergolaProps } from '../../../../../../Utils/Constants';
6 | import { textureAnisotropy } from '../../../../../../Utils/Function';
7 |
8 | import { RectModel } from '../CommonModel';
9 |
10 | const { width, length, height } = ConstProps;
11 | const { pillarSize } = ConstWoodPergolaProps
12 | const { stoneFencePillarBaseSize, stoneFencePillarSize, stoneFencePillarBaseHeight, stoneFencePillarHeight } = ConstFenceProps
13 |
14 | const Pillar = () => {
15 | const { gl } = useThree();
16 | const { marbleTexture, stoneWallTexture } = useSelector(state => state.texture.textureProps)
17 |
18 | const stonePillarBaseTexture = marbleTexture?.clone();
19 | textureAnisotropy(gl, stonePillarBaseTexture, 1, 1, Math.PI);
20 | const stonePillarTexture = stoneWallTexture?.clone();
21 | textureAnisotropy(gl, stonePillarTexture, 0.35, 1, Math.PI);
22 |
23 | const offset = 0.025;
24 | const StonePillarBaseModelInfoArr = useMemo(() => {
25 | let data = [];
26 |
27 | new Array(2).fill("").forEach((_, index_i) => {
28 | new Array(2).fill("").forEach((_, index_j) => {
29 | data.push({
30 | width: stoneFencePillarBaseSize,
31 | length: stoneFencePillarBaseHeight,
32 | height: stoneFencePillarBaseSize,
33 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2 - offset) * Math.pow(-1, index_i),
34 | pos_y: height / 3 + stoneFencePillarBaseHeight / 2,
35 | pos_z: (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2 - offset) * Math.pow(-1, index_j),
36 | })
37 | })
38 | })
39 |
40 | new Array(2).fill("").forEach((_, index) => {
41 | data.push({
42 | width: stoneFencePillarBaseSize,
43 | length: stoneFencePillarBaseHeight,
44 | height: stoneFencePillarBaseSize,
45 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2 - offset) * Math.pow(-1, index),
46 | pos_y: height / 3 + stoneFencePillarBaseHeight / 2,
47 | pos_z: 0,
48 | })
49 | })
50 |
51 | return data;
52 | }, [])
53 |
54 | const StonePillarModelInfoArr = useMemo(() => {
55 | let data = [];
56 |
57 | new Array(2).fill("").forEach((_, index_i) => {
58 | new Array(2).fill("").forEach((_, index_j) => {
59 | data.push({
60 | width: stoneFencePillarSize,
61 | length: stoneFencePillarHeight,
62 | height: stoneFencePillarSize,
63 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2 - offset) * Math.pow(-1, index_i),
64 | pos_y: height / 3 - stoneFencePillarHeight / 2,
65 | pos_z: (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2 - offset) * Math.pow(-1, index_j),
66 | rotation_1: [Math.PI / 2, 0, 0],
67 | })
68 | })
69 | })
70 |
71 | new Array(2).fill("").forEach((_, index) => {
72 | data.push({
73 | width: stoneFencePillarSize,
74 | length: stoneFencePillarHeight,
75 | height: stoneFencePillarSize,
76 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2 - offset) * Math.pow(-1, index),
77 | pos_y: height / 3 - stoneFencePillarHeight / 2,
78 | pos_z: 0,
79 | rotation_1: [Math.PI / 2, 0, 0],
80 | })
81 | })
82 |
83 | return data;
84 | }, [])
85 |
86 | return (
87 | <>
88 | {StonePillarBaseModelInfoArr.map((item, index) => )}
89 | {StonePillarModelInfoArr.map((item, index) => )}
90 | >
91 | )
92 | }
93 |
94 | export default Pillar
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Fence/Wall/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useThree } from '@react-three/fiber';
3 | import { useSelector } from 'react-redux';
4 |
5 | import { ConstFenceProps, ConstProps, ConstWoodPergolaProps } from '../../../../../../Utils/Constants';
6 | import { textureAnisotropy } from '../../../../../../Utils/Function';
7 |
8 | import { RectModel } from '../CommonModel';
9 |
10 | const { width, length, height } = ConstProps;
11 | const { pillarSize } = ConstWoodPergolaProps
12 | const { stoneFencePillarSize, stoneFencePillarHeight, stoneFencePillarBaseHeight } = ConstFenceProps
13 |
14 | const Wall = () => {
15 | const { gl } = useThree();
16 | const { marbleTexture, stoneWallTexture } = useSelector(state => state.texture.textureProps)
17 |
18 | const wallTexture = stoneWallTexture?.clone();
19 | textureAnisotropy(gl, wallTexture, 5, 1.5, Math.PI);
20 | const wallTopPanelTexture = marbleTexture?.clone();
21 | textureAnisotropy(gl, wallTopPanelTexture, 0.1, 1, Math.PI);
22 |
23 | const WallModelInfoArr = useMemo(() => {
24 | let data = [];
25 | const modelWidth = stoneFencePillarSize / 7 * 3
26 | const modelLength = stoneFencePillarHeight / 3 * 2
27 |
28 | new Array(2).fill("").forEach((_, index) => {
29 | data.push({
30 | width: modelWidth,
31 | length: modelLength,
32 | height: length - stoneFencePillarSize,
33 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index) ,
34 | pos_y: height / 3 - modelLength,
35 | pos_z: 0,
36 | rotation_1: [0, 0, 0]
37 | })
38 | })
39 | data.push({
40 | width: modelWidth,
41 | length: modelLength,
42 | height: width - stoneFencePillarSize,
43 | pos_x: 0 ,
44 | pos_y: height / 3 - modelLength,
45 | pos_z: (length / 2 - pillarSize / 2),
46 | rotation_1: [0, Math.PI / 2, 0]
47 | })
48 |
49 | return data;
50 | }, [])
51 |
52 | const WallTopPanelModelInfoArr = useMemo(() => {
53 | let data = [];
54 | const modelWidth = stoneFencePillarSize / 7 * 4
55 | const modelLength = stoneFencePillarBaseHeight
56 |
57 | new Array(2).fill("").forEach((_, index) => {
58 | data.push({
59 | width: modelWidth,
60 | length: modelLength,
61 | height: length - stoneFencePillarSize,
62 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index) ,
63 | pos_y: stoneFencePillarHeight / 3 * 2,
64 | pos_z: 0,
65 | rotation_1: [0, 0, 0]
66 | })
67 | })
68 | data.push({
69 | width: modelWidth,
70 | length: modelLength,
71 | height: width - stoneFencePillarSize,
72 | pos_x: 0 ,
73 | pos_y: stoneFencePillarHeight / 3 * 2,
74 | pos_z: (length / 2 - pillarSize / 2),
75 | rotation_1: [0, Math.PI / 2, 0]
76 | })
77 |
78 | return data;
79 | }, [])
80 |
81 | return (
82 | <>
83 | {WallModelInfoArr.map((item, index) => )}
84 | {WallTopPanelModelInfoArr.map((item, index) => )}
85 | >
86 | )
87 | }
88 |
89 | export default Wall
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Fence/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Pillar from './Pillar'
3 | import Wall from './Wall'
4 |
5 | const Fence = () => {
6 | return (
7 | <>
8 |
9 |
10 | >
11 | )
12 | }
13 |
14 | export default Fence
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Pillar/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { useThree } from '@react-three/fiber';
4 |
5 | import { ConstFenceProps, ConstProps, ConstStonePergolaProps } from '../../../../../Utils/Constants';
6 | import { textureAnisotropy } from '../../../../../Utils/Function';
7 |
8 | import { PillarCorbelModel, RectModel } from '../CommonModel';
9 |
10 | const { width, length, height } = ConstProps;
11 | const { pillarSize, pillarBaseSize, pillarHeight } = ConstStonePergolaProps;
12 | const { stoneFencePillarBaseSize } = ConstFenceProps
13 |
14 | const Pillar = () => {
15 | const { gl } = useThree();
16 | const { woodTexture2 } = useSelector(state => state.texture.textureProps)
17 |
18 | const pillarTexture = woodTexture2?.clone();
19 | textureAnisotropy(gl, pillarTexture, 1, 1, Math.PI / 2);
20 |
21 | const PillarModelInfoArr = useMemo(() => {
22 | let data = [];
23 |
24 | new Array(2).fill("").forEach((_, index_i) => {
25 | new Array(2).fill("").forEach((_, index_j) => {
26 | data.push({
27 | width: pillarSize,
28 | length: pillarHeight,
29 | height: pillarSize,
30 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_i),
31 | pos_y: height / 3 + pillarHeight / 2,
32 | pos_z: (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_j),
33 | rotation_1: [Math.PI / 2, 0, 0],
34 | })
35 | })
36 | })
37 |
38 | new Array(2).fill("").forEach((_, index) => {
39 | data.push({
40 | width: pillarSize,
41 | length: pillarHeight,
42 | height: pillarSize,
43 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index),
44 | pos_y: height / 3 + pillarHeight / 2,
45 | pos_z: 0,
46 | rotation_1: [Math.PI / 2, 0, 0],
47 | })
48 | })
49 |
50 | return data;
51 | }, [])
52 |
53 | const PillarBaseModelInfoArr = useMemo(() => {
54 | let data = [];
55 |
56 | const baseThickness = 0.2;
57 | new Array(2).fill("").forEach((_, index_i) => {
58 | new Array(2).fill("").forEach((_, index_j) => {
59 | data.push({
60 | width: pillarBaseSize,
61 | length: baseThickness,
62 | height: pillarBaseSize,
63 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_i),
64 | pos_y: height / 3 + baseThickness / 2,
65 | pos_z: (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_j),
66 | rotation_1: [Math.PI / 2, 0, 0],
67 | })
68 | })
69 | })
70 |
71 | new Array(2).fill("").forEach((_, index) => {
72 | data.push({
73 | width: pillarBaseSize,
74 | length: baseThickness,
75 | height: pillarBaseSize,
76 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index),
77 | pos_y: height / 3 + baseThickness / 2,
78 | pos_z: 0,
79 | rotation_1: [Math.PI / 2, 0, 0],
80 | })
81 | })
82 |
83 | return data;
84 | }, [])
85 |
86 | const PillarCorbelModelInfoArr = useMemo(() => {
87 | let data = [];
88 |
89 | new Array(2).fill("").forEach((_, index_i) => {
90 | new Array(2).fill("").forEach((_, index_j) => {
91 | data.push({
92 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_i),
93 | pos_y: height,
94 | pos_z: -pillarSize / 2 + (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * index_j,
95 |
96 | rotation_1: [0, Math.PI / 2, 0]
97 | })
98 | })
99 | })
100 |
101 | new Array(2).fill("").forEach((_, index_i) => {
102 | new Array(2).fill("").forEach((_, index_j) => {
103 | data.push({
104 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_i),
105 | pos_y: height,
106 | pos_z: pillarSize / 2 - (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * index_j,
107 |
108 | rotation_1: [0, -Math.PI / 2, 0]
109 | })
110 | })
111 | })
112 |
113 | new Array(2).fill("").forEach((_, index_i) => {
114 | new Array(2).fill("").forEach((_, index_j) => {
115 | data.push({
116 | pos_x: (-width / 2 + (stoneFencePillarBaseSize - pillarSize) / 2 + pillarSize/ 2) * Math.pow(-1, index_i),
117 | pos_y: height,
118 | pos_z: (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * Math.pow(-1, index_j),
119 |
120 | rotation_1: [0, Math.PI * index_i, 0]
121 | })
122 | })
123 | })
124 |
125 | return data;
126 |
127 | }, [])
128 |
129 | return (
130 | <>
131 |
132 |
133 | {PillarModelInfoArr.map((item, index) => )}
134 | {PillarBaseModelInfoArr.map((item, index) => )}
135 | {PillarCorbelModelInfoArr.map((item, index) => )}
136 |
137 | >
138 | )
139 | }
140 |
141 | export default Pillar
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/Roof/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { useThree } from '@react-three/fiber';
4 |
5 | import { ConstFenceProps, ConstProps, ConstStonePergolaProps } from '../../../../../Utils/Constants';
6 | import { textureAnisotropy } from '../../../../../Utils/Function';
7 |
8 | import { CoverModel, RectModel } from '../CommonModel';
9 |
10 | const { width, length, height } = ConstProps;
11 | const { pillarSize, roofUnderBowSize2, roofUnderBowSize1, roofUpperBowSize2, roofUpperBowSize1 } = ConstStonePergolaProps;
12 | const { stoneFencePillarBaseSize } = ConstFenceProps;
13 |
14 | const Roof = () => {
15 | const { gl } = useThree();
16 | const { woodTexture2, roofPanelTileTexture, roofRidgeTileTexture } = useSelector(state => state.texture.textureProps)
17 |
18 | const underBowTexture = woodTexture2?.clone();
19 | textureAnisotropy(gl, underBowTexture, 1, 1, Math.PI / 2);
20 | const upperBowTexture = woodTexture2?.clone();
21 | textureAnisotropy(gl, upperBowTexture, 1, 1, Math.PI / 2);
22 | if (upperBowTexture) upperBowTexture.offset.y = 0.5
23 | const roofTexture = roofPanelTileTexture?.clone();
24 | textureAnisotropy(gl, roofTexture, 0.2, 0.5, 0);
25 | const ridgeTexture = roofRidgeTileTexture?.clone();
26 | textureAnisotropy(gl, ridgeTexture, 3, 3, 0);
27 | if (ridgeTexture) ridgeTexture.offset.x = 0.5
28 | const interiorTexture = woodTexture2?.clone();
29 | textureAnisotropy(gl, interiorTexture, 0.5, 0.5, Math.PI / 2);
30 |
31 | const RoofUnderBowInfoArr = useMemo(() => {
32 | let data = [];
33 | const offset = 0.05;
34 |
35 | new Array(2).fill("").forEach((_, index) => {
36 | data.push({
37 | width: roofUnderBowSize1,
38 | length: roofUnderBowSize2,
39 | height: length - roofUnderBowSize1 * 2 - offset,
40 |
41 | pos_x: (width / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * (index === 0 ? 1 : -1),
42 | pos_y: height,
43 | pos_z: 0,
44 | })
45 | })
46 | new Array(2).fill("").forEach((_, index) => {
47 | data.push({
48 | width: width - offset,
49 | length: roofUnderBowSize2,
50 | height: roofUnderBowSize1,
51 |
52 | pos_x: 0,
53 | pos_y: height,
54 | pos_z: (length / 2 - (stoneFencePillarBaseSize - pillarSize) / 2) * (index === 0 ? 1 : -1),
55 | })
56 | })
57 |
58 | return data;
59 | }, [])
60 |
61 | const RoofUpperBowInfoArr = useMemo(() => {
62 | let data = [];
63 | const offset = 0.025;
64 |
65 | new Array(2).fill("").forEach((_, index) => {
66 | data.push({
67 | width: roofUpperBowSize1,
68 | length: roofUpperBowSize2,
69 | height: length - roofUpperBowSize1 * 2 + roofUpperBowSize1 / 3,
70 |
71 | pos_x: (width / 2 - roofUpperBowSize1 / 3) * (index === 0 ? 1 : -1),
72 | pos_y: height + roofUnderBowSize2 + offset,
73 | pos_z: 0,
74 | })
75 | })
76 | new Array(2).fill("").forEach((_, index) => {
77 | data.push({
78 | width: width + roofUpperBowSize1 / 3,
79 | length: roofUpperBowSize2,
80 | height: roofUpperBowSize1,
81 |
82 | pos_x: 0,
83 | pos_y: height + roofUnderBowSize2 + offset,
84 | pos_z: (length / 2 - roofUpperBowSize1 / 3) * (index === 0 ? 1 : -1),
85 | })
86 | })
87 |
88 | return data;
89 | }, [])
90 |
91 | return (
92 | <>
93 | {RoofUnderBowInfoArr.map((item, index) => )}
94 | {RoofUpperBowInfoArr.map((item, index) => )}
95 |
96 | >
97 | )
98 | }
99 |
100 | export default Roof
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Stone/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Roof from './Roof'
4 | import Pillar from './Pillar'
5 | import Fence from './Fence'
6 | import Accessories from './Accessories'
7 |
8 |
9 | const StonePergola = () => {
10 | return (
11 | <>
12 |
13 |
14 |
15 |
16 | >
17 | )
18 | }
19 |
20 | export default StonePergola
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Wood/Accessories/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useSelector } from 'react-redux'
3 |
4 | const Accessories = () => {
5 | const { width, length } = useSelector(state => state.buildingCtrl)
6 | const potModel_1 = useSelector(state => state.glbModel.potModel_1)
7 | const tableModel = useSelector(state => state.glbModel.tableModel_3)
8 |
9 | return potModel_1 && tableModel && (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | >
17 | )
18 | }
19 |
20 | export default Accessories
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Wood/CommonModel/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { useMemo } from 'react';
3 |
4 | import { extrudeSettings } from '../../../../../Utils/Function';
5 | import { ConstProps, ConstWoodPergolaProps } from '../../../../../Utils/Constants';
6 |
7 | const { width, length, overhang, freeOverhang, woodColor } = ConstProps;
8 | const { roofBowHeight, thickness } = ConstWoodPergolaProps;
9 |
10 | export const TrussModel = ({ modelLength, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], map=null, bumpScale=0.5, roughness=0.7, metalness=0.3 }) => {
11 | const model = (size) => {
12 |
13 | const m = new THREE.Shape();
14 | m.moveTo(0, 0)
15 | m.lineTo(-(size / 2 + freeOverhang), 0)
16 | m.lineTo(-(size / 2 + freeOverhang + overhang / 2), 0)
17 | m.lineTo(-(size / 2 + freeOverhang + overhang), roofBowHeight / 3 * 2)
18 | m.lineTo(-(size / 2 + freeOverhang + overhang), roofBowHeight)
19 | m.lineTo((size / 2 + freeOverhang + overhang), roofBowHeight)
20 | m.lineTo((size / 2 + freeOverhang + overhang), roofBowHeight / 3 * 2)
21 | m.lineTo((size / 2 + freeOverhang + overhang / 2), 0)
22 | m.lineTo(size / 2 + freeOverhang, 0)
23 | m.closePath();
24 |
25 | return m;
26 | }
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | const BoltSphereModel = ({ dir }) => {
42 | const isAxisX = dir.length === 3 && dir.every((val, _) => val === 0);
43 | const boltDistance = 0.1;
44 |
45 | let initPos = width;
46 | if (isAxisX) initPos = width / 2 - 0.1;
47 | else initPos = length / 2 - 0.1;
48 | const initPosY = (roofBowHeight - boltDistance) / 2
49 |
50 | const spherePositions = isAxisX
51 | ? [
52 | // Left points along X-axis
53 | [(initPos), initPosY + boltDistance, 0],
54 | [(initPos - boltDistance), initPosY + boltDistance, 0],
55 | [(initPos), initPosY, 0],
56 | [(initPos - boltDistance), initPosY, 0],
57 | [(initPos - 1), roofBowHeight / 2, 0],
58 | [(initPos - 1 - boltDistance), roofBowHeight / 2, 0],
59 | // Right points along X-axis
60 | [-(initPos), initPosY + boltDistance, 0],
61 | [-(initPos - boltDistance), initPosY + boltDistance, 0],
62 | [-(initPos), initPosY, 0],
63 | [-(initPos - boltDistance), initPosY, 0],
64 | [-(initPos - 1), roofBowHeight / 2, 0],
65 | [-(initPos - 1 - boltDistance), roofBowHeight / 2, 0],
66 | ]
67 | : [
68 | // Left points along Z-axis
69 | [0, initPosY + boltDistance, (initPos)],
70 | [0, initPosY + boltDistance, (initPos - boltDistance)],
71 | [0, initPosY, (initPos)],
72 | [0, initPosY, (initPos - boltDistance)],
73 | [0, roofBowHeight / 2, (initPos - 0.6)],
74 | [0, roofBowHeight / 2, (initPos - 0.6 - boltDistance)],
75 | // Right points along Z-axis
76 | [0, initPosY + boltDistance, -(initPos)],
77 | [0, initPosY + boltDistance, -(initPos - boltDistance)],
78 | [0, initPosY, -(initPos)],
79 | [0, initPosY, -(initPos - boltDistance)],
80 | [0, roofBowHeight / 2, -(initPos - 0.6)],
81 | [0, roofBowHeight / 2, -(initPos - 0.6 - boltDistance)],
82 | ];
83 |
84 | return (
85 |
86 | {spherePositions.map((position, index) => (
87 |
88 |
89 |
90 |
91 | ))}
92 |
93 | );
94 | };
95 |
96 | export const PillarCorbelModel = ({ position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], map=null, bumpScale=0.5, roughness=0.7, metalness=0.3 }) => {
97 | const model = useMemo(() => {
98 | const m = new THREE.Shape();
99 | m.moveTo(0, 0);
100 | m.quadraticCurveTo(0.1, 0, 0.15, 0.2);
101 | m.quadraticCurveTo(0.2, 0.6, 0.4, 0.6);
102 | m.lineTo(0.4, 0.63)
103 | m.lineTo(0.2, 0.63)
104 | m.lineTo(0, 0.2)
105 | m.closePath();
106 |
107 | return m
108 | }, [])
109 |
110 | return (
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | )
120 | }
121 |
122 | export const RectModel = ({ modelSize, position=[0, 0, 0], rotation_1=[0, 0, 0], rotation_2=[0, 0, 0], map=null, bumpScale=0.5, roughness=0.7, metalness=0.3 }) => {
123 | return (
124 |
125 |
126 |
127 |
128 |
129 |
130 | )
131 | }
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Wood/Pillar/index.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useMemo } from 'react'
3 | import { useSelector } from 'react-redux';
4 | import { useThree } from '@react-three/fiber';
5 |
6 | import { ConstWoodPergolaProps } from '../../../../../Utils/Constants';
7 | import { textureAnisotropy } from '../../../../../Utils/Function';
8 |
9 | import { RectModel, PillarCorbelModel } from '../CommonModel';
10 |
11 | const { pillarSize, pillarBaseSize } = ConstWoodPergolaProps;
12 |
13 | const Pillar = () => {
14 | const { gl } = useThree();
15 | const { woodTexture1 } = useSelector(state => state.texture.textureProps)
16 | const { width, length, height, pitch } = useSelector(state => state.buildingCtrl)
17 |
18 | const woodPillarTexture = woodTexture1?.clone();
19 | textureAnisotropy(gl, woodPillarTexture, 1, 1, Math.PI / 2);
20 | const woodCorbelTexture = woodTexture1?.clone();
21 | textureAnisotropy(gl, woodCorbelTexture, 1, 1, Math.PI / 2);
22 |
23 | const PillarModelInfoArr = useMemo(() => {
24 | let data = [];
25 | let pillarHeight = 0;
26 |
27 | new Array(2).fill("").forEach((_, index_i) => {
28 | new Array(2).fill("").forEach((_, index_j) => {
29 | if (index_j === 0) pillarHeight = height + pillarSize * pitch / 12;
30 | else pillarHeight = height + length * pitch / 12;
31 | data.push({
32 | width: pillarSize,
33 | length: pillarHeight,
34 | height: pillarSize,
35 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index_i),
36 | pos_y: pillarHeight / 2,
37 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, index_j),
38 | })
39 | })
40 | })
41 |
42 | return data;
43 | }, [width, length, height, pitch])
44 |
45 | const PillarBaseModelInfoArr = useMemo(() => {
46 | let data = [];
47 | const modelHeight = 0.07;
48 |
49 | new Array(2).fill("").forEach((_, index_i) => {
50 | new Array(2).fill("").forEach((_, index_j) => {
51 | data.push({
52 | width: pillarBaseSize,
53 | length: pillarBaseSize,
54 | height: modelHeight,
55 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index_i),
56 | pos_y: modelHeight / 2,
57 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, index_j),
58 | })
59 | })
60 | })
61 |
62 | return data;
63 | }, [width, length, height, pitch])
64 |
65 | const PillarCorbelModelInfoArr = useMemo(() => {
66 | let data = [];
67 |
68 | new Array(2).fill("").forEach((_, index_i) => {
69 | new Array(2).fill("").forEach((_, index_j) => {
70 | data.push({
71 | pos_x: (width / 2 - pillarSize) * Math.pow(-1, index_j),
72 | pos_y: height - 0.85 + (index_i === 1 ? length * pitch / 12 : 0),
73 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, 0) * (index_i === 0 ? 1 : -1),
74 | angle: [0, (index_j === 0 ? Math.PI : 0), 0]
75 | })
76 | })
77 | })
78 |
79 | return data
80 | }, [width, length, height, pitch])
81 |
82 | return (
83 | <>
84 | {PillarModelInfoArr.map((item, index) => )}
85 | {PillarBaseModelInfoArr.map((item, index) => )}
86 | {PillarCorbelModelInfoArr.map((item, index) => )}
87 | >
88 | )
89 | }
90 |
91 | export default Pillar
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Wood/Roof/index.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useMemo } from 'react'
3 | import { useSelector } from 'react-redux';
4 | import { useThree } from '@react-three/fiber';
5 |
6 | import { getDistanceAndCount, textureAnisotropy } from '../../../../../Utils/Function';
7 | import { ConstProps, ConstWoodPergolaProps } from '../../../../../Utils/Constants';
8 |
9 | import { TrussModel, RectModel } from '../CommonModel';
10 |
11 | const { overhang, freeOverhang } = ConstProps;
12 | const { roofBowHeight, pillarSize } = ConstWoodPergolaProps;
13 |
14 | const Roof = () => {
15 | const { gl } = useThree();
16 | const { woodTexture1 } = useSelector(state => state.texture.textureProps)
17 | const { width, length, height, pitch } = useSelector(state => state.buildingCtrl)
18 |
19 | const roofAlpha = Math.atan(pitch / 12);
20 |
21 | const woodTopTexture = woodTexture1?.clone();
22 | textureAnisotropy(gl, woodTopTexture, 1, 1, 0);
23 | const woodBaseBowTexture = woodTexture1?.clone();
24 | textureAnisotropy(gl, woodBaseBowTexture, 1, 1, Math.PI / 2);
25 | const trussTexture = woodTexture1?.clone();
26 | textureAnisotropy(gl, trussTexture, 0.01, 2, 0);
27 |
28 | const RectTrussInfoArr_1_side = useMemo(() => {
29 | const { distance, count } = getDistanceAndCount(0.4, width);
30 | const modelSize = 0.05
31 |
32 | let data = [];
33 | new Array(count + 1).fill("").forEach((_, index) => {
34 | data.push({
35 | width: modelSize,
36 | length: modelSize,
37 | height: Math.sqrt(Math.pow(length, 2) + Math.pow(length * pitch / 12, 2)) + overhang + freeOverhang,
38 | pos_x: distance * index - width / 2,
39 | pos_y: roofBowHeight * 2 - modelSize + length / 2 * pitch / 12,
40 | pos_z: 0,
41 | alpha: [roofAlpha, 0, 0]
42 | })
43 | })
44 |
45 | return data
46 | }, [width, length, height, pitch])
47 |
48 | const RectTrussInfoArr_2_side = useMemo(() => {
49 | let data = [];
50 |
51 | new Array(2).fill("").forEach((_, index) => {
52 | data.push({
53 | width: pillarSize,
54 | length: roofBowHeight / 5 * 4,
55 | height: Math.sqrt(Math.pow(length, 2) + Math.pow(length * pitch / 12, 2)) + overhang + freeOverhang,
56 | pos_x: (width / 2 - pillarSize / 2) * Math.pow(-1, index),
57 | pos_y: pillarSize / 2 + length / 2 * pitch / 12 - 0.05,
58 | pos_z: 0,
59 | alpha: [roofAlpha, 0, 0]
60 | })
61 | })
62 | new Array(2).fill("").forEach((_, index) => {
63 | data.push({
64 | width: pillarSize,
65 | length: roofBowHeight / 5 * 4,
66 | height: width - pillarSize * 2,
67 | pos_x: 0,
68 | pos_y: -pillarSize / 2 + (index === 1 ? length * pitch / 12 : 0),
69 | pos_z: (length / 2 - pillarSize / 2) * Math.pow(-1, index),
70 | alpha: [0, Math.PI / 2, 0]
71 | })
72 | })
73 |
74 | return data;
75 | }, [width, length, height, pitch])
76 |
77 | const TrussInfoArr_1_end = useMemo(() => {
78 | const { distance, count } = getDistanceAndCount(0.6, length);
79 |
80 | let data = [];
81 | new Array(count + 1).fill("").forEach((_, index) => {
82 | data.push({
83 | pos_y: (length * pitch / 12) / count * (count - index) + roofBowHeight / 4 * 3,
84 | pos_z: distance * index - length / 2,
85 | })
86 | })
87 |
88 | return data
89 | }, [width, length, height, pitch])
90 |
91 | const TrussInfoArr_2_side = useMemo(() => {
92 | const distance = 0.3;
93 | let data = [];
94 |
95 | new Array(2).fill("").forEach((_, index_i) => {
96 | new Array(2).fill("").forEach((_, index_j) => {
97 | data.push({
98 | pos_x: (width / 2 - distance * index_j) * Math.pow(-1, index_i + 1),
99 | pos_y: length / 2 * Math.tan(roofAlpha),
100 | alpha_local: [roofAlpha, 0, 0],
101 | alpha_global: [0, Math.PI / 2, 0]
102 | })
103 | })
104 | })
105 |
106 | return data;
107 | }, [width, length, height, pitch])
108 |
109 | const TrussInfoArr_3_end = useMemo(() => {
110 | const distance = 0.3;
111 | let data = [];
112 |
113 | new Array(2).fill("").forEach((_, index_i) => {
114 | new Array(2).fill("").forEach((_, index_j) => {
115 | data.push({
116 | pos_y: -pillarSize + 0.05 + (index_i === 0 ? length * pitch / 12 : 0),
117 | pos_z: (length / 2 - distance * index_j) * Math.pow(-1, index_i + 1),
118 | })
119 | })
120 | })
121 |
122 | return data;
123 | }, [width, length, height, pitch])
124 |
125 | return (
126 |
127 | {RectTrussInfoArr_1_side.map((item, index) => )}
128 | {RectTrussInfoArr_2_side.map((item, index) => )}
129 | {TrussInfoArr_1_end.map((item, index) => )}
130 | {TrussInfoArr_2_side.map((item, index) => )}
131 | {TrussInfoArr_3_end.map((item, index) => )}
132 |
133 | )
134 | }
135 |
136 | export default Roof
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/Wood/index.jsx:
--------------------------------------------------------------------------------
1 | import Pillar from "./Pillar"
2 | import Roof from "./Roof"
3 | import Accessories from "./Accessories"
4 | import { useSelector } from "react-redux"
5 |
6 | const WoodPergola = () => {
7 | const isBuildingOnly = useSelector(state => state.buildingCtrl.isBuildingOnly)
8 |
9 | return (
10 | <>
11 |
12 |
13 | { !isBuildingOnly && }
14 | >
15 | )
16 | }
17 |
18 | export default WoodPergola
--------------------------------------------------------------------------------
/src/Component/Building/Pergola/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import WoodPergola from './Wood'
4 | import MetalPergola from './Metal'
5 | import StonePergola from './Stone'
6 | import { useSelector } from 'react-redux'
7 |
8 | const Pergola = () => {
9 | const buildingType = useSelector(state => state.buildingCtrl.buildingType)
10 |
11 | return (
12 | <>
13 | { buildingType === 'wood' && }
14 | { buildingType === 'metal' && }
15 | { buildingType === 'stone' && }
16 | >
17 | )
18 | }
19 |
20 | export default Pergola
--------------------------------------------------------------------------------
/src/Component/Building/Surface/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { useThree } from '@react-three/fiber';
4 |
5 | import { textureAnisotropy } from '../../../Utils/Function';
6 |
7 | const Surface = () => {
8 | const { gl } = useThree();
9 | const { surfaceTexture, grassTexture } = useSelector(state => state.texture.textureProps)
10 | const { width, length } = useSelector(state => state.buildingCtrl)
11 | console.log({ width, length});
12 | const isShowGrass = useSelector(state => state.buildingCtrl.isShowGrass)
13 |
14 | const surfaceFloorTexture = surfaceTexture?.clone();
15 | const surfaceBorderTexture = surfaceTexture?.clone();
16 | const grassGroundTexture = grassTexture?.clone();
17 |
18 | useEffect(() => {
19 | if (gl && surfaceFloorTexture && surfaceBorderTexture && grassTexture) {
20 | textureAnisotropy(gl, surfaceFloorTexture, 1, 1, 0);
21 | textureAnisotropy(gl, surfaceBorderTexture, 15, 0.3, 0);
22 | textureAnisotropy(gl, grassTexture, 500, 500, 0);
23 | }
24 | }, [gl, surfaceFloorTexture, surfaceBorderTexture, grassTexture])
25 |
26 | const overhangForPlane = 4
27 | const borderWidth = 0.15;
28 | const borderHeight = 0.1;
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | >
58 | )
59 | }
60 |
61 | export default Surface
--------------------------------------------------------------------------------
/src/Component/Building/index.jsx:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three'
2 | import { useEffect, useRef, useCallback } from "react";
3 | import { useThree } from "@react-three/fiber"
4 |
5 | import Pergola from "./Pergola"
6 | import Surface from "./Surface"
7 | import { useSelector } from "react-redux";
8 |
9 | const Building = () => {
10 | const { scene, gl } = useThree();
11 | const buildingType = useSelector(state => state.buildingCtrl.buildingType);
12 | const prevBuildingTypeRef = useRef(buildingType);
13 |
14 | // Function to reset shadow maps - memoized with useCallback
15 | const resetShadows = useCallback(() => {
16 | // Force shadow map update on all lights
17 | scene.traverse((object) => {
18 | if (object.isLight && object.shadow) {
19 | // Reset the shadow map to force recalculation
20 | object.shadow.map = null;
21 | object.shadow.needsUpdate = true;
22 |
23 | // Force light update
24 | object.position.set(
25 | object.position.x + 0.0001,
26 | object.position.y + 0.0001,
27 | object.position.z + 0.0001
28 | );
29 | }
30 | });
31 |
32 | // Force renderer to clear shadow maps
33 | if (gl.shadowMap.enabled) {
34 | gl.shadowMap.needsUpdate = true;
35 | }
36 | }, [scene, gl]);
37 |
38 | useEffect(() => {
39 | // Only apply shadows when scene changes or building type changes
40 | if (scene && (prevBuildingTypeRef.current !== buildingType)) {
41 | prevBuildingTypeRef.current = buildingType;
42 |
43 | // Reset shadow maps immediately to clear old shadows
44 | resetShadows();
45 |
46 | // Add a small delay to ensure the new building components are mounted
47 | setTimeout(() => {
48 | // Apply shadow properties to all meshes
49 | scene.traverseVisible((mesh) => {
50 | if (mesh.isMesh) {
51 | mesh.castShadow = true;
52 | mesh.receiveShadow = true;
53 | }
54 | });
55 |
56 | // Force another shadow map update after new components are mounted
57 | resetShadows();
58 | }, 100);
59 | }
60 | }, [scene, buildingType, resetShadows]);
61 |
62 | // Initial shadow setup
63 | useEffect(() => {
64 | if (scene) {
65 | scene.traverseVisible((mesh) => {
66 | if (mesh.isMesh) {
67 | mesh.castShadow = true;
68 | mesh.receiveShadow = true;
69 | }
70 | });
71 | }
72 | }, [scene]);
73 |
74 | // const offset = 0.05
75 | // const coverModel = new THREE.Shape();
76 | // coverModel.moveTo(-(length / 2 + offset), 0);
77 | // coverModel.lineTo(-(length / 2 + offset), height + length * pitch / 12);
78 | // coverModel.lineTo((length / 2 + offset), height);
79 | // coverModel.lineTo((length / 2 + offset), 0);
80 | // coverModel.closePath();
81 |
82 | return (
83 |
84 | {/*
85 |
86 |
87 | */}
88 |
89 |
90 |
91 |
92 | )
93 | }
94 |
95 | export default Building
--------------------------------------------------------------------------------
/src/Component/ControlPanel/index.jsx:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelector } from "react-redux"
2 |
3 | import { setBuildingType, setIsShowBg, setIsCamAutoRotate, setIsBuildingOnly, setIsShowGrass, setBuildingDimension, setInitBuildingSize } from "../../Redux/Features/BuildingCtrl/buildingCtrlSlice";
4 | import { SvgBuilding, SvgFrameOnly, SvgGrass, SvgImage, SvgRotate } from "../../Utils/SvgSource"
5 | import { useEffect, useState } from "react";
6 |
7 | const ControlPanel = () => {
8 | const dispatch = useDispatch();
9 |
10 | const buildingType = useSelector(state => state.buildingCtrl.buildingType)
11 | const isShowBg = useSelector(state => state.buildingCtrl.isShowBg)
12 | const isCamAutoRotate = useSelector(state => state.buildingCtrl.isCamAutoRotate)
13 | const isFrameOnly = useSelector(state => state.buildingCtrl.isFrameOnly)
14 | const isShowGrass = useSelector(state => state.buildingCtrl.isShowGrass)
15 |
16 | const [dimensions, setDimensions] = useState({
17 | width: 6,
18 | length: 4,
19 | height: 2.5,
20 | pitch: 1
21 | });
22 |
23 | const handleOptionClick = (type, value) => {
24 | if (type === 'building') {
25 | dispatch(setBuildingType(value));
26 | } else if (type === 'bgImage') {
27 | dispatch(setIsShowBg());
28 | dispatch(setIsShowGrass(false));
29 | } else if (type === 'cam-rotate') {
30 | dispatch(setIsCamAutoRotate());
31 | } else if (type === 'building-only') {
32 | dispatch(setIsBuildingOnly());
33 | } else if (type === 'grass-floor') {
34 | dispatch(setIsShowGrass());
35 | }
36 | }
37 |
38 | const handleDimensionChange = (e) => {
39 | const { name, value } = e.target;
40 | const numValue = Number(value);
41 | setDimensions(prev => ({ ...prev, [name]: numValue }));
42 | dispatch(setBuildingDimension({ [name]: numValue }));
43 | }
44 |
45 | useEffect(() => {
46 | dispatch(setInitBuildingSize(dimensions));
47 | }, [dispatch, dimensions]);
48 |
49 | const handleKeyDown = (type, value) => (e) => {
50 | if (e.key === 'Enter' || e.key === ' ') {
51 | handleOptionClick(type, value);
52 | }
53 | };
54 |
55 | return (
56 |
57 |
58 |
62 |
63 | - handleOptionClick('building', 'wood')}
65 | onKeyDown={handleKeyDown('building', 'wood')}
66 | tabIndex="0"
67 | role="menuitem"
68 | >Type 1
69 |
70 | - handleOptionClick('building', 'metal')}
72 | onKeyDown={handleKeyDown('building', 'metal')}
73 | tabIndex="0"
74 | role="menuitem"
75 | >Type 2
76 |
77 | - handleOptionClick('building', 'stone')}
79 | onKeyDown={handleKeyDown('building', 'stone')}
80 | tabIndex="0"
81 | role="menuitem"
82 | >Type 3
83 |
84 |
85 |
94 |
95 |
104 |
105 |
114 |
115 |
125 |
126 | {buildingType === "wood" &&
127 |
128 |
Dimensions
129 |
130 |
131 |
132 |
133 | {dimensions.width}m
134 |
135 |
146 |
147 | 6m
148 | 10m
149 |
150 |
151 |
152 |
153 |
154 |
155 | {dimensions.length}m
156 |
157 |
168 |
169 | 4m
170 | 8m
171 |
172 |
173 |
174 |
175 |
176 |
177 | {dimensions.height}m
178 |
179 |
190 |
191 | 2.5m
192 | 4m
193 |
194 |
195 |
196 |
197 |
198 |
199 | {dimensions.pitch}°
200 |
201 |
212 |
213 | 1°
214 | 6°
215 |
216 |
217 |
218 | }
219 |
220 | )
221 | }
222 |
223 | export default ControlPanel
--------------------------------------------------------------------------------
/src/Component/Env/index.jsx:
--------------------------------------------------------------------------------
1 | import { Environment, OrbitControls, AccumulativeShadows, RandomizedLight, Plane } from "@react-three/drei"
2 | import { useSelector } from "react-redux";
3 | import { ConstProps } from "../../Utils/Constants";
4 | import { useThree } from "@react-three/fiber";
5 | import { useEffect, useRef } from "react";
6 |
7 | const { height } = ConstProps;
8 |
9 | const Env = () => {
10 | // const envHDR = useEnvironment({ files: '/assets/env/env-1.hdr' })
11 |
12 | const isShowBg = useSelector(state => state.buildingCtrl.isShowBg)
13 | const isCamAutoRotate = useSelector(state => state.buildingCtrl.isCamAutoRotate)
14 | const buildingType = useSelector(state => state.buildingCtrl.buildingType)
15 | const { scene } = useThree()
16 |
17 | // Reference to the AccumulativeShadows component
18 | const shadowsRef = useRef();
19 |
20 | // Reset shadows when building type changes
21 | useEffect(() => {
22 | if (shadowsRef.current) {
23 | // This resets the shadow accumulation
24 | shadowsRef.current.temporal = false;
25 |
26 | // We need to delay re-enabling temporal to ensure a clean reset
27 | setTimeout(() => {
28 | if (shadowsRef.current) {
29 | shadowsRef.current.temporal = true;
30 | }
31 | }, 100);
32 | }
33 | }, []);
34 |
35 | return (
36 | <>
37 |
45 |
46 |
57 |
58 | {/* Ambient fill light for better overall illumination */}
59 |
60 |
61 | {/* Main key light with improved shadows */}
62 |
76 |
77 | {/* Secondary fill light to enhance shadows */}
78 |
92 |
93 | {/* Rim light to create separation between objects and background */}
94 |
99 |
100 | {/* Shadow plane that doesn't interact with environment */}
101 | {isShowBg && (
102 | <>
103 | {/* AccumulativeShadows with custom layers to avoid environment */}
104 |
117 |
125 |
126 | >
127 | )}
128 |
129 | {/* Post-processing effects for enhanced visual quality */}
130 | {/*
131 |
132 |
133 |
134 | */}
135 | >
136 | )
137 | }
138 |
139 | export default Env
--------------------------------------------------------------------------------
/src/Component/LoadingProgress/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useSelector } from 'react-redux';
3 |
4 | const LoadingProgress = () => {
5 | const isAllTextureLoaded = useSelector(state => state.texture.isAllTextureLoaded)
6 | const isAllModelLoaded = useSelector(state => state.glbModel.isAllModelLoaded)
7 |
8 | const [progressVal, setProgressVal] = useState(0);
9 |
10 | useEffect(() => {
11 | const interval = setInterval(() => {
12 | setProgressVal(prev => {
13 | if (isAllTextureLoaded && isAllModelLoaded) {
14 | return 100
15 | } else {
16 | const randomStep = Math.floor(Math.random() * 10) + 1;
17 | const newProgress = prev + randomStep;
18 | return newProgress >= 100 ? 100 : newProgress;
19 | }
20 | });
21 | }, 700);
22 |
23 | return () => clearInterval(interval);
24 | }, [isAllTextureLoaded, isAllModelLoaded]);
25 |
26 | return (
27 |
28 |
Please wait ...
29 |
39 |
40 | );
41 | };
42 |
43 | export default LoadingProgress;
--------------------------------------------------------------------------------
/src/Component/Style/controller.scss:
--------------------------------------------------------------------------------
1 | .building-controller {
2 |
3 | .bottom-section {
4 | position: absolute;
5 | left: 50%;
6 | bottom: 0;
7 | transform: translate(-50%, 0);
8 | z-index: 10;
9 |
10 | display: flex;
11 | justify-content: end;
12 | align-items: center;
13 | padding: 3px 15px 5px 12px;
14 | margin: 0px 0px 10px 0px;
15 | gap: 0px;
16 | font-size: 12px;
17 | color: #3C3C3C;
18 | border-radius: 10px;
19 | background-color: white;
20 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
21 |
22 | .select-item {
23 | cursor: pointer;
24 | display: flex;
25 | flex-flow: column;
26 | gap: 5px;
27 | min-width: 65px;
28 | align-items: center;
29 |
30 | &.isSelected {
31 | color: #0066FF;
32 | }
33 |
34 | &.isDisabled {
35 | cursor: not-allowed;
36 | opacity: 50%;
37 | }
38 | }
39 |
40 | .dropdown-menu {
41 | min-width: 70px;
42 |
43 | .dropdown-item {
44 | font-size: 12px;
45 | cursor: pointer;
46 | }
47 | }
48 |
49 | .vl {
50 | border-left: 1px solid #7d7d7d;
51 | height: 20px;
52 | left: 50%;
53 | margin-left: 5px;
54 | margin-right: 6px;
55 | top: 0;
56 | }
57 | }
58 |
59 | .side-section {
60 | position: absolute;
61 | z-index: 10;
62 | top: 30px;
63 | right: 30px;
64 | width: 250px;
65 | height: 185px;
66 | background-color: antiquewhite;
67 | padding: 20px;
68 |
69 | opacity: 80%;
70 | border-radius: 3px 20px;
71 | background-color: white;
72 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
73 |
74 | .size-item {
75 | display: flex;
76 | justify-content: space-between;
77 | margin-bottom: 15px;
78 | }
79 | }
80 |
81 | .tweakpane-container {
82 | position: absolute;
83 | z-index: 10;
84 | top: 30px;
85 | right: 30px;
86 | min-width: 250px;
87 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
88 | border-radius: 6px;
89 | }
90 | }
91 |
92 | .loading-bar {
93 | display: flex;
94 | position: absolute;
95 | z-index: 9999;
96 | flex-flow: column;
97 | width: 100%;
98 | height: 100%;
99 | justify-content: center;
100 | align-items: center;
101 | gap: 50px;
102 | color: #0d6efd;
103 | font-size: 36px;
104 | font-weight: bold;
105 |
106 | background-image: url("/public/assets/loading/loading-bg.jpg");
107 | background-color: #cccccc;
108 | background-size: cover;
109 | }
--------------------------------------------------------------------------------
/src/Component/index.jsx:
--------------------------------------------------------------------------------
1 | import { Canvas } from "@react-three/fiber"
2 | import { Suspense, useEffect, useState } from "react"
3 | import { useSelector } from "react-redux"
4 |
5 | import ControlPanel from "./ControlPanel"
6 | import Env from "./Env"
7 | import Building from "./Building"
8 | import LoadingProgress from "./LoadingProgress"
9 |
10 | const Component = () => {
11 | const isAllTextureLoaded = useSelector(state => state.texture.isAllTextureLoaded)
12 | const isAllModelLoaded = useSelector(state => state.glbModel.isAllModelLoaded)
13 |
14 | const [isReadyForCanvas, setIsReadyForCanvas] = useState(false);
15 |
16 | useEffect(() => {
17 | if (isAllTextureLoaded && isAllModelLoaded) {
18 | setTimeout(() => {setIsReadyForCanvas(true)}, "2500")
19 | }
20 | else setIsReadyForCanvas(false);
21 | }, [isAllTextureLoaded, isAllModelLoaded])
22 |
23 | return (
24 | <>
25 | { !isReadyForCanvas && }
26 | { isReadyForCanvas && }
27 |
43 | >
44 | )
45 | }
46 |
47 | export default Component
--------------------------------------------------------------------------------
/src/Redux/Features/BuildingCtrl/buildingCtrlSlice.jsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | buildingType: 'wood',
5 | isShowBg: true,
6 | isCamAutoRotate: false,
7 | isBuildingOnly: false,
8 | isShowGrass: false,
9 | resetShadows: false,
10 |
11 | width: 6,
12 | length: 4,
13 | height: 2.5,
14 | pitch: 1,
15 | }
16 |
17 | export const buildingCtrlSlice = createSlice({
18 | name: 'buildingCtrl',
19 | initialState,
20 | reducers: {
21 | setBuildingType: (state, action) => {
22 | state.buildingType = action.payload;
23 | // Toggle resetShadows to trigger shadow reset
24 | state.resetShadows = !state.resetShadows;
25 | },
26 | setIsShowBg: state => {
27 | state.isShowBg = !state.isShowBg;
28 | },
29 | setIsCamAutoRotate: state => {
30 | state.isCamAutoRotate = !state.isCamAutoRotate;
31 | },
32 | setIsBuildingOnly: state => {
33 | state.isBuildingOnly = !state.isBuildingOnly;
34 | },
35 | setIsShowGrass: (state, action) => {
36 | if (action.payload !== undefined) state.isShowGrass = false;
37 | else state.isShowGrass = !state.isShowGrass;
38 | },
39 |
40 | setBuildingDimension: (state, action) => {
41 | state[Object.keys(action.payload)[0]] = action.payload[Object.keys(action.payload)[0]];
42 | },
43 | setInitBuildingSize: (state, action) => {
44 | state.width = action.payload.width;
45 | state.length = action.payload.length;
46 | state.height = action.payload.height;
47 | state.pitch = action.payload.pitch;
48 | }
49 | }
50 | })
51 |
52 | export const { setBuildingType, setIsShowBg, setIsCamAutoRotate, setIsBuildingOnly, setIsShowGrass, setBuildingDimension, setInitBuildingSize }= buildingCtrlSlice.actions
53 |
54 | export default buildingCtrlSlice.reducer
--------------------------------------------------------------------------------
/src/Redux/Features/GLBModel/glbModelSlice.jsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit"
2 |
3 | const initialState = {
4 | matModel: null,
5 | sofaModel: null,
6 | lampModel: null,
7 | potModel_1: null,
8 | potModel_2: null,
9 | potModel_3: null,
10 | sofaModel_1: null,
11 | sofaModel_2: null,
12 | tableModel_1: null,
13 | tableModel_3: null,
14 | tableModel_4: null,
15 |
16 | isAllModelLoaded: false,
17 | }
18 |
19 | export const glbModelSlice = createSlice({
20 | name: 'glbModel',
21 | initialState,
22 | reducers: {
23 | loadInitModel: (state, action) => {
24 | state.matModel = action.payload.matModel.scene;
25 | state.sofaModel = action.payload.sofaModel.scene;
26 | state.lampModel = action.payload.lampModel.scene;
27 | state.potModel_1 = action.payload.potModel_1.scene;
28 | state.potModel_2 = action.payload.potModel_2.scene;
29 | state.potModel_3 = action.payload.potModel_3.scene;
30 | state.sofaModel_1 = action.payload.sofaModel_1.scene;
31 | state.tableModel_1 = action.payload.tableModel_1.scene;
32 | state.tableModel_3 = action.payload.tableModel_3.scene;
33 | state.tableModel_4 = action.payload.tableModel_4.scene;
34 |
35 | state.isAllModelLoaded = true;
36 | }
37 | }
38 | })
39 |
40 | export const { loadInitModel } = glbModelSlice.actions;
41 |
42 | export default glbModelSlice.reducer;
--------------------------------------------------------------------------------
/src/Redux/Features/Texture/textureSlice.jsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | textureProps: {
5 | surfaceTexture: null,
6 | woodTexture1: null,
7 | woodTexture2: null,
8 | marbleTexture: null,
9 | metalTexture: null,
10 | stoneWallTexture: null,
11 | grassTexture: null,
12 | roofPanelTileTexture: null,
13 | roofRidgeTileTexture: null,
14 | },
15 |
16 | isAllTextureLoaded: false,
17 | }
18 |
19 | export const textureSlice = createSlice({
20 | name: 'texture',
21 | initialState,
22 | reducers: {
23 | loadInitTexture: (state, action) => {
24 | const { surfaceTexture, woodTexture1, woodTexture2, marbleTexture, metalTexture, stoneWallTexture, grassTexture, roofPanelTileTexture, roofRidgeTileTexture } = action.payload;
25 | state.textureProps.surfaceTexture = surfaceTexture;
26 | state.textureProps.woodTexture1 = woodTexture1;
27 | state.textureProps.woodTexture2 = woodTexture2;
28 | state.textureProps.marbleTexture = marbleTexture;
29 | state.textureProps.metalTexture = metalTexture;
30 | state.textureProps.stoneWallTexture = stoneWallTexture;
31 | state.textureProps.grassTexture = grassTexture;
32 | state.textureProps.roofPanelTileTexture = roofPanelTileTexture;
33 | state.textureProps.roofRidgeTileTexture = roofRidgeTileTexture;
34 |
35 | state.isAllTextureLoaded = true;
36 | },
37 | }
38 | })
39 |
40 | export const { loadInitTexture } = textureSlice.actions
41 |
42 | export default textureSlice.reducer
--------------------------------------------------------------------------------
/src/Redux/Store/index.jsx:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import textureReducer from '../Features/Texture/textureSlice';
3 | import buildingCtrlReducer from '../Features/BuildingCtrl/buildingCtrlSlice';
4 | import glbModelReducer from '../Features/GLBModel/glbModelSlice';
5 |
6 | const store = configureStore({
7 | reducer: {
8 | texture: textureReducer,
9 | buildingCtrl: buildingCtrlReducer,
10 | glbModel: glbModelReducer,
11 | },
12 | middleware: (getDefaultMiddleware) =>
13 | getDefaultMiddleware({
14 | serializableCheck: {
15 | ignoredActions: [
16 | 'texture/loadInitTexture',
17 | 'glbModel/loadInitModel'
18 | ],
19 | ignoredPaths: [
20 | 'texture.textureProps.surfaceTexture',
21 | 'texture.textureProps.woodTexture1',
22 | 'texture.textureProps.woodTexture2',
23 | 'texture.textureProps.marbleTexture',
24 | 'texture.textureProps.metalTexture',
25 | 'texture.textureProps.stoneWallTexture',
26 | 'texture.textureProps.grassTexture',
27 | 'texture.textureProps.roofPanelTileTexture',
28 | 'texture.textureProps.roofRidgeTileTexture',
29 |
30 | 'glbModel.matModel',
31 | 'glbModel.sofaModel',
32 | 'glbModel.lampModel',
33 | 'glbModel.potModel_1',
34 | 'glbModel.potModel_2',
35 | 'glbModel.potModel_3',
36 | 'glbModel.sofaModel_1',
37 | 'glbModel.tableModel_1',
38 | 'glbModel.tableModel_3',
39 | 'glbModel.tableModel_4',
40 | ],
41 | },
42 | }),
43 | });
44 |
45 | export default store;
46 |
--------------------------------------------------------------------------------
/src/Utils/Constants.jsx:
--------------------------------------------------------------------------------
1 | export const ConstProps = {
2 | width: 6,
3 | length: 4,
4 | height: 2.5,
5 | // pitch: 1.5,
6 | pitch: 1,
7 | overhang: 0.25,
8 | freeOverhang: 0.1,
9 | roofAlpha: Math.atan(1 / 12), // Math.atan(pitch / 12)
10 |
11 | woodColor: '#FFD595'
12 | }
13 |
14 | export const ConstFenceProps = {
15 | stoneFencePillarBaseSize: 0.6,
16 | stoneFencePillarSize: 0.5,
17 | stoneFencePillarHeight: ConstProps.height / 3,
18 | stoneFencePillarBaseHeight: 0.03,
19 | }
20 |
21 | export const ConstWoodPergolaProps = {
22 | roofBowHeight: 0.25,
23 | thickness: 0.05,
24 | pillarSize: 0.3,
25 | pillarBaseSize: 0.4,
26 | }
27 |
28 | export const ConstMetalPergolaProps = {
29 | pillarSize: 0.25,
30 | pillarGapSize: 0.25 / 6, // pillarSize / 6
31 | pillarBaseSize: 0.35,
32 | endRoofBowHeight: 0.25,
33 | sideRoofBowHeight: 0.4,
34 | }
35 |
36 | export const ConstStonePergolaProps = {
37 | stonePergolaRoofHeight: 1,
38 | ridgeCoverTopWidth: 0.1,
39 | ridgeCoverSideWidth: 0.1,
40 | ridgeCoverSideThickness: 0.025,
41 | pillarSize: 0.25,
42 | pillarHeight: ConstProps.height / 3 * 2,
43 | pillarBaseSize: 0.35,
44 | roofUnderBowSize1: 0.3,
45 | roofUnderBowSize2: 0.05,
46 | roofUpperBowSize1: 0.2,
47 | roofUpperBowSize2: 0.1,
48 | }
--------------------------------------------------------------------------------
/src/Utils/Function.jsx:
--------------------------------------------------------------------------------
1 | import { RepeatWrapping, TextureLoader } from "three";
2 | import { useDispatch } from "react-redux";
3 | import { GLTFLoader } from "three/examples/jsm/Addons.js";
4 |
5 | import { MarbleImg, MetalImg, StoneWallImg, SurfaceImg, WoodImg1, WoodImg2, GrassImg, RoofPanelTileImage, RoofRidgeTileImage } from "./TextureSource";
6 | import { loadInitTexture } from "../Redux/Features/Texture/textureSlice";
7 | import { loadInitModel } from "../Redux/Features/GLBModel/glbModelSlice";
8 |
9 | export const InitiallyAssetsLoad = async () => {
10 | const dispatch = useDispatch();
11 |
12 | const textureLoader = new TextureLoader();
13 | const gltfLoader = new GLTFLoader();
14 |
15 | try {
16 | const [surfaceTexture, woodTexture1, woodTexture2, marbleTexture, metalTexture, stoneWallTexture, grassTexture, roofPanelTileTexture, roofRidgeTileTexture] = await Promise.all([
17 | textureLoader.loadAsync(SurfaceImg),
18 | textureLoader.loadAsync(WoodImg1),
19 | textureLoader.loadAsync(WoodImg2),
20 | textureLoader.loadAsync(MarbleImg),
21 | textureLoader.loadAsync(MetalImg),
22 | textureLoader.loadAsync(StoneWallImg),
23 | textureLoader.loadAsync(GrassImg),
24 | textureLoader.loadAsync(RoofPanelTileImage),
25 | textureLoader.loadAsync(RoofRidgeTileImage),
26 | ]);
27 |
28 | dispatch(loadInitTexture({ surfaceTexture, woodTexture1, woodTexture2, marbleTexture, metalTexture, stoneWallTexture, grassTexture, roofPanelTileTexture, roofRidgeTileTexture }));
29 | } catch (error) {
30 | console.error('Error loading texture paths: ', error);
31 | }
32 |
33 | try {
34 | const [ matModel, sofaModel, lampModel, potModel_1, potModel_2, potModel_3, sofaModel_1, tableModel_1, tableModel_3, tableModel_4 ] = await Promise.all(
35 | [
36 | gltfLoader.loadAsync('/assets/models/mat.glb'),
37 | gltfLoader.loadAsync('/assets/models/sofa.glb'),
38 | gltfLoader.loadAsync('/assets/models/lamp.glb'),
39 | gltfLoader.loadAsync('/assets/models/pot-1.glb'),
40 | gltfLoader.loadAsync('/assets/models/pot-2.glb'),
41 | gltfLoader.loadAsync('/assets/models/pot-3.glb'),
42 | gltfLoader.loadAsync('/assets/models/sofa-1.glb'),
43 | gltfLoader.loadAsync('/assets/models/table-1.glb'),
44 | gltfLoader.loadAsync('/assets/models/table-3.glb'),
45 | gltfLoader.loadAsync('/assets/models/table-4.glb'),
46 | ]
47 | )
48 | dispatch(loadInitModel({ matModel, sofaModel, lampModel, potModel_1, potModel_2, potModel_3, sofaModel_1, tableModel_1, tableModel_3, tableModel_4 }))
49 | } catch (error) {
50 | console.error("Error loading model paths: ", error)
51 | }
52 | };
53 |
54 | export const extrudeSettings = (depth, bevelThickness, bevelSize, bevelOffset, bevelSegments) => {
55 | const setting = {
56 | steps: 1,
57 | depth: depth,
58 | bevelEnabled: true,
59 | bevelThickness: bevelThickness ?? 0,
60 | bevelSize: bevelSize ?? 0,
61 | bevelOffset: bevelOffset ?? 0,
62 | bevelSegments: bevelSegments ?? 1
63 | }
64 |
65 | return setting;
66 | }
67 |
68 | export const getDistanceAndCount = (initDistance, length) => {
69 | let count, distance = 0;
70 |
71 | const tempCount = Math.floor(length / initDistance);
72 | if (length === tempCount * initDistance) {
73 | count = tempCount;
74 | distance = initDistance;
75 | } else if (length - tempCount * initDistance < initDistance / 2) {
76 | count = tempCount;
77 | distance = length / count;
78 | } else {
79 | count = tempCount + 1;
80 | distance = length / count;
81 | }
82 |
83 | return {
84 | distance, count
85 | }
86 | }
87 |
88 | export const textureAnisotropy = (gl, texture, repeatX, repeatY, rotate) => {
89 | if(texture !== null && texture !== undefined){
90 | texture.anisotropy = Math.min(gl.capabilities.getMaxAnisotropy(), 50);
91 | texture.wrapS = texture.wrapT = RepeatWrapping;
92 | texture.rotation = rotate ? rotate : 0;
93 | texture.repeat.set(repeatX, repeatY);
94 | texture.needsUpdate = true;
95 | }
96 | }
--------------------------------------------------------------------------------
/src/Utils/SvgSource.jsx:
--------------------------------------------------------------------------------
1 | export const SvgBuilding = () => (
2 |
5 | )
6 |
7 | export const SvgImage = ({ color }) => (
8 |
12 | )
13 |
14 | export const SvgRotate = ({ color }) => (
15 |
19 | )
20 |
21 | export const SvgFrameOnly = ({ color }) => (
22 |
25 | )
26 |
27 | export const SvgGrass = ({ color }) => (
28 |
31 | )
--------------------------------------------------------------------------------
/src/Utils/TextureSource.jsx:
--------------------------------------------------------------------------------
1 | import SurfaceImg from '../Assets/Images/surface.jpg'
2 | import WoodImg1 from '../Assets/Images/wood-1.jpg'
3 | import WoodImg2 from '../Assets/Images/wood-2.jpg'
4 | import MarbleImg from '../Assets/Images/marble.jpg'
5 | import MetalImg from '../Assets/Images/metal.jpg'
6 | import StoneWallImg from '../Assets/Images/stone wall.jpg'
7 | import GrassImg from '../Assets/Images/grass.jpg'
8 | import RoofPanelTileImage from '../Assets/Images/roof panel.jpg'
9 | import RoofRidgeTileImage from '../Assets/Images/roof ridge.jpg'
10 |
11 | export {
12 | SurfaceImg,
13 | WoodImg1,
14 | WoodImg2,
15 | MarbleImg,
16 | MetalImg,
17 | StoneWallImg,
18 | GrassImg,
19 | RoofPanelTileImage,
20 | RoofRidgeTileImage
21 | }
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @import url("../src/Component/Style/controller.scss");
6 |
7 | body {
8 | margin: 0;
9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
10 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
11 | sans-serif;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | code {
17 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
18 | monospace;
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import { Provider } from 'react-redux';
4 |
5 | import './index.css';
6 | import App from './App';
7 | import Store from './Redux/Store'
8 |
9 | const root = createRoot(document.getElementById('root'))
10 |
11 | root.render(
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./src/**/*.{js,jsx,ts,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
11 |
12 |
--------------------------------------------------------------------------------