├── .eslintrc.json
├── .gitignore
├── LICENSE.txt
├── PROJECT.md
├── README.md
├── demo
├── README.MD
├── index.js
└── public
│ ├── assets
│ └── css
│ │ └── rac_basic_sample_project.css
│ ├── dxf_test.html
│ ├── fonts
│ └── Microsoft YaHei_Regular.typeface.json
│ ├── images
│ └── skybox
│ │ └── default.png
│ ├── index.html
│ ├── projects
│ ├── dxf_test
│ │ ├── api-cw750-details.dxf
│ │ └── thumbnail.png
│ ├── rac_basic_sample_project
│ │ ├── rac_basic_sample_project.bin
│ │ ├── rac_basic_sample_project.gltf
│ │ └── thumbnail.png
│ └── technical_school_normal
│ │ ├── Technical_school-current_m.bin
│ │ ├── Technical_school-current_m.gltf
│ │ └── thumbnail.png
│ ├── rac_basic_sample_project.html
│ └── technical_school_normal.html
├── docs
├── .nojekyll
├── assets
│ ├── highlight.css
│ ├── icons.css
│ ├── icons.png
│ ├── icons@2x.png
│ ├── main.js
│ ├── search.js
│ ├── style.css
│ ├── widgets.png
│ └── widgets@2x.png
├── classes
│ ├── AxisSectionPlaneController.html
│ ├── AxisSectionPlanePlugin.html
│ ├── BackgroundColorPlugin.html
│ ├── BimTreeController.html
│ ├── BimViewer.html
│ ├── BottomBar.html
│ ├── BoxControl.html
│ ├── CommonUtils.html
│ ├── ComponentPropertyPlugin.html
│ ├── Controller.html
│ ├── CubicBezierCurve.html
│ ├── Curve.html
│ ├── CurvePath.html
│ ├── CustomizedGLTFLoaderPlugin.html
│ ├── DxfLoaderPlugin.html
│ ├── DxfPerformanceModelLoader.html
│ ├── EllipseCurve.html
│ ├── EnhancedDistanceMeasurementPlugin.html
│ ├── FontManager.html
│ ├── FullScreenController.html
│ ├── FullScreenPlugin.html
│ ├── GeometryUtils.html
│ ├── GridPlugin.html
│ ├── HomeViewController.html
│ ├── KeyBoardRotatePlugin.html
│ ├── LineCurve.html
│ ├── Map.html
│ ├── MathUtil.html
│ ├── MeasureAreaController.html
│ ├── MeasureClearController.html
│ ├── MeasureController.html
│ ├── MeasureDistanceController.html
│ ├── NavControlConfig.html
│ ├── ObjectUtil.html
│ ├── OrthoModeController.html
│ ├── OrthoModePlugin.html
│ ├── Path.html
│ ├── PlanViewPlugin.html
│ ├── PlaneControl.html
│ ├── PopPanel.html
│ ├── PropertyController.html
│ ├── QuadraticBezierCurve.html
│ ├── SceneGraphTreeView.html
│ ├── SceneGraphTreeViewPlugin.html
│ ├── SectionBoxController.html
│ ├── SectionBoxPlugin.html
│ ├── SectionController.html
│ ├── SectionCullPlanePlugin.html
│ ├── SectionPlaneController.html
│ ├── SectionPlanePlugin.html
│ ├── SectionPlanePopPanel.html
│ ├── Shape.html
│ ├── ShapePath.html
│ ├── ShapeUtils.html
│ ├── SingleSelectionPlugin.html
│ ├── SplineCurve.html
│ ├── TextGeometry.html
│ ├── Toolbar.html
│ ├── ToolbarMenuBaseController.html
│ ├── Tooltip.html
│ └── ZoomToExtent.html
├── enums
│ ├── AxisType.html
│ ├── BoxSectionPlaneType.html
│ ├── CurveType.html
│ └── ToolbarMenuId.html
├── index.html
├── interfaces
│ ├── BackgroundColorConfig.html
│ ├── BimViewerConfig.html
│ ├── BuildEllipseGeometryConfig.html
│ ├── BuildPlaneGeometryConfig.html
│ ├── BuildPlanePositionConfig.html
│ ├── CameraConfig.html
│ ├── ComponentPropertyConfig.html
│ ├── Context.html
│ ├── ContextMenuConfig.html
│ ├── CustomizedGLTFLoaderConfig.html
│ ├── GridConfig.html
│ ├── GridGeometryConfig.html
│ ├── GridMeshConfig.html
│ ├── IconClass.html
│ ├── ModelConfig.html
│ ├── OrthoModeConfig.html
│ ├── SectionCullPlaneConfig.html
│ ├── SectionOverviewConfig.html
│ ├── SectionPlanePopPanelConfig.html
│ ├── SectionPlanePopPanelItemConfig.html
│ ├── SingleSelectionConfig.html
│ ├── TextGeometryParameter.html
│ └── ToolbarMenuConfig.html
├── modules.html
└── modules
│ └── math.html
├── package.json
├── public
└── snapshots
│ ├── context_menu.gif
│ ├── measure_dist.gif
│ ├── nav_cube.gif
│ ├── ortho.gif
│ ├── pivot.gif
│ ├── section_box.gif
│ └── section_plane.gif
├── script
├── dts.bundle.js
└── dts.copy.js
├── src
├── components
│ ├── BottomBar.ts
│ ├── PopPanel.ts
│ ├── SectionPlanePopPanel.ts
│ ├── Tooltip.ts
│ └── index.ts
├── core
│ ├── BimViewer.ts
│ ├── CallbackTypes.ts
│ ├── Configs.ts
│ ├── Controller.ts
│ ├── DxfPerformanceModelLoader.ts
│ ├── Map.ts
│ ├── SceneGraphTreeView.ts
│ ├── TextGeometry.ts
│ ├── ZoomToExtent.ts
│ ├── index.ts
│ └── paths
│ │ ├── CubicBezierCurve.ts
│ │ ├── Curve.ts
│ │ ├── CurvePath.ts
│ │ ├── Curves.ts
│ │ ├── EllipseCurve.ts
│ │ ├── Interpolations.ts
│ │ ├── LineCurve.ts
│ │ ├── Path.ts
│ │ ├── QuadraticBezierCurve.ts
│ │ ├── Shape.ts
│ │ ├── ShapePath.ts
│ │ ├── ShapeUtils.ts
│ │ ├── SplineCurve.ts
│ │ └── index.ts
├── css
│ ├── bottombar.scss
│ ├── component.scss
│ ├── contextmenu.scss
│ ├── main.scss
│ ├── measurement.scss
│ ├── preset.scss
│ └── toolbar.scss
├── dxf-parser.d.ts
├── index.ts
├── plugins
│ ├── AxisSectionPlanePlugin.ts
│ ├── BackgroundColorPlugin.ts
│ ├── ComponentPropertyPlugin.ts
│ ├── CustomizedGLTFLoaderPlugin.ts
│ ├── DxfLoaderPlugin.ts
│ ├── EnhancedDistanceMeasurementPlugin.ts
│ ├── FullScreenPlugin.ts
│ ├── GridPlugin.ts
│ ├── KeyBoardRotatePlugin.ts
│ ├── OrthoModePlugin.ts
│ ├── PlanViewPlugin.ts
│ ├── SceneGraphTreeViewPlugin.ts
│ ├── SectionBoxPlugin.ts
│ ├── SectionCullPlanePlugin.ts
│ ├── SectionPlanePlugin.ts
│ ├── SingleSelectionPlugin.ts
│ └── index.ts
├── services
│ ├── FontManager.ts
│ └── index.ts
├── utils
│ ├── CommonUtils.ts
│ ├── Consts.ts
│ ├── ContextMenuUtils.ts
│ ├── GeometryUtils.ts
│ ├── Locale.ts
│ ├── MathUtil.ts
│ ├── ObjectUtil.ts
│ └── index.ts
├── widgets
│ ├── index.ts
│ ├── section
│ │ ├── BoxControl.ts
│ │ ├── PlaneControl.ts
│ │ └── index.ts
│ └── toolbar
│ │ ├── AxisSectionPlaneController.ts
│ │ ├── BimTreeController.ts
│ │ ├── FullScreenController.ts
│ │ ├── HomeViewController.ts
│ │ ├── MeasureAreaController.ts
│ │ ├── MeasureClearController.ts
│ │ ├── MeasureController.ts
│ │ ├── MeasureDistanceController.ts
│ │ ├── OrthoModeController.ts
│ │ ├── PropertyController.ts
│ │ ├── SectionBoxController.ts
│ │ ├── SectionController.ts
│ │ ├── SectionPlaneController.ts
│ │ ├── Toolbar.ts
│ │ ├── ToolbarConfig.ts
│ │ ├── ToolbarMenuBaseController.ts
│ │ └── index.ts
└── xeokit-sdk.d.ts
├── tsconfig.json
├── webpack.config.js
├── webpack.esm.config.js
├── webpack.umd.config.js
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "node": true
5 | },
6 | "plugins": [
7 | "prettier"
8 | ],
9 | "extends": [
10 | "eslint:recommended",
11 | "plugin:@typescript-eslint/eslint-recommended",
12 | "plugin:@typescript-eslint/recommended"
13 | ],
14 | "parser": "@typescript-eslint/parser",
15 | "parserOptions": {
16 | "ecmaVersion": 2020
17 | },
18 | "rules": {
19 | "@typescript-eslint/explicit-module-boundary-types": "off",
20 | "no-multi-spaces": 2,
21 | "block-spacing": [
22 | "error",
23 | "always"
24 | ],
25 | "comma-spacing": [
26 | "error",
27 | {
28 | "before": false,
29 | "after": true
30 | }
31 | ],
32 | "key-spacing": [
33 | "error",
34 | {
35 | "beforeColon": false,
36 | "afterColon": true
37 | }
38 | ],
39 | "keyword-spacing": [
40 | "error",
41 | {
42 | "before": true
43 | }
44 | ],
45 | "arrow-spacing": [
46 | "error",
47 | {
48 | "before": true,
49 | "after": true
50 | }
51 | ],
52 | "curly": [
53 | "error",
54 | "all"
55 | ],
56 | "prettier/prettier": [
57 | "error",
58 | {
59 | "endOfLine": "auto",
60 | "printWidth": 128,
61 | "tabWidth": 4,
62 | "useTabs": false,
63 | "semi": true,
64 | "singleQuote": false,
65 | "bracketSpacing": true
66 | }
67 | ]
68 | }
69 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /dist
5 | package-lock.json
6 |
7 | # local env files
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | -------------------------------------------------------------------------
2 | This repo is licensed under the Affero GPL V3 license.
3 |
4 | For more information please reference to: https://github.com/xeokit/xeokit-sdk/blob/master/LICENSE.txt
--------------------------------------------------------------------------------
/PROJECT.md:
--------------------------------------------------------------------------------
1 | # xeokit
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 | ## Project development
8 | ```
9 | npm start
10 | ```
11 | ### Compiles and minifies for production
12 | ```
13 | npm run build
14 | ```
15 |
16 | ### Link npm package for local development
17 | 1. In gemini-viewer's directory, run `npm run build` to generate dist directory
18 | 2. In gemini-viewer's directory, run `npm link`
19 | 3. In your project, run `npm link @pattern-x/gemini-viewer`
20 | 4. In your code, import what you need:
21 | ```
22 | import { BimViewer } from "@pattern-x/gemini-viewer"
23 | import { KeyBoardRotatePlugin } from "@pattern-x/gemini-viewer"
24 | ```
25 |
26 | ### Start demo
27 | ```
28 | npm run demo
29 | ```
30 | and visit [demo](localhost:3000/index.html)
31 |
32 |
--------------------------------------------------------------------------------
/demo/README.MD:
--------------------------------------------------------------------------------
1 | 1. run at root
2 | ```
3 | npm run demo
4 | ```
5 |
6 | 2. visit localhost:3000/index.html
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const port = 3001
4 | const path = require('path')
5 |
6 | const publicPath = path.join(__dirname, './public')
7 | app.use('/', express.static(publicPath))
8 |
9 | const distPath = path.join(__dirname, '../dist')
10 | app.use('/dist', express.static(distPath))
11 |
12 | app.listen(port, () => {
13 | console.log(`Example app listening at http://localhost:${port}`)
14 | })
--------------------------------------------------------------------------------
/demo/public/assets/css/rac_basic_sample_project.css:
--------------------------------------------------------------------------------
1 | .project-panel {
2 | background-color: white;
3 | text-decoration: none;
4 | word-spacing: normal;
5 | text-align: left;
6 | letter-spacing: 0;
7 | -webkit-font-smoothing: antialiased;
8 | overflow-y: hidden;
9 | overflow-x: hidden;
10 | margin: 0;
11 | width: 100%;
12 | height: 100%;
13 | }
14 |
15 | .canvas {
16 | width: 100%;
17 | height: 100%;
18 | position: absolute;
19 | }
20 |
21 | #myNavCubeCanvas {
22 | position: absolute;
23 | width: 200px;
24 | height: 200px;
25 | bottom: 10px;
26 | right: 10px;
27 | z-index: 1;
28 | opacity: 0.8;
29 | }
30 |
31 | #myNavCubeCanvas:hover {
32 | opacity: 1;
33 | }
34 |
35 | #mySectionPlanesOverviewCanvas {
36 | position: absolute;
37 | width: 200px;
38 | height: 200px;
39 | bottom: 200px;
40 | right: 10px;
41 | z-index: 1;
42 | }
43 |
44 | #myAxisGizmoCanvas {
45 | position: absolute;
46 | width: 120px;
47 | height: 120px;
48 | bottom: 10px;
49 | left: 10px;
50 | z-index: 1;
51 | }
52 |
53 | .customize-pivot-marker {
54 | color: #ffffff;
55 | line-height: 1.8;
56 | text-align: center;
57 | font-family: "monospace";
58 | font-weight: bold;
59 | position: absolute;
60 | width: 16px;
61 | height: 16px;
62 | border-radius: 15px;
63 | border: 3px solid #ffffff;
64 | background: #00D2B2;
65 | visibility: hidden;
66 | -webkit-box-shadow: 3px 3px 9px 1px #000000;
67 | box-shadow: 3px 3px 9px 1px #000000;
68 | z-index: 1;
69 | pointer-events: none;
70 | opacity: 0.6;
71 | }
72 |
73 | /* ----------------------------------------------------------------------------------------------------------*/
74 | /* TreeViewPlugin (Put it in a separate css file later)
75 | /* ----------------------------------------------------------------------------------------------------------*/
76 | #treeViewContainer {
77 | visibility: hidden;
78 | pointer-events: all;
79 | height: 100%;
80 | overflow-y: scroll;
81 | overflow-x: hidden;
82 | position: absolute;
83 | background-color: rgba(255, 255, 255, 0.6);
84 | color: black;
85 | z-index: 1;
86 | float: left;
87 | left: 0;
88 | padding: 10px;
89 | font-family: "Roboto", sans-serif;
90 | font-size: 15px;
91 | text-align: left;
92 | user-select: none;
93 | -ms-user-select: none;
94 | -moz-user-select: none;
95 | -webkit-user-select: none;
96 | width: 350px;
97 | }
98 |
99 | #treeViewContainer:hover {
100 | background-color: rgba(255, 255, 255, 0.9);
101 | }
102 |
103 | #treeViewContainer ul {
104 | list-style: none;
105 | padding-left: 1.75em;
106 | pointer-events: none;
107 | }
108 |
109 | #treeViewContainer ul li {
110 | position: relative;
111 | width: 500px;
112 | pointer-events: none;
113 | padding-top: 3px;
114 | padding-bottom: 3px;
115 | vertical-align: middle;
116 | }
117 |
118 | #treeViewContainer ul li a {
119 | background-color: #eee;
120 | border-radius: 50%;
121 | color: #000;
122 | display: inline-block;
123 | height: 1.5em;
124 | left: -1.5em;
125 | position: absolute;
126 | text-align: center;
127 | text-decoration: none;
128 | width: 1.5em;
129 | pointer-events: all;
130 | }
131 |
132 | #treeViewContainer ul li a.plus {
133 | background-color: #ded;
134 | pointer-events: all;
135 | }
136 |
137 | #treeViewContainer ul li a.minus {
138 | background-color: #eee;
139 | pointer-events: all;
140 | }
141 |
142 | #treeViewContainer ul li a:active {
143 | top: 1px;
144 | pointer-events: all;
145 | }
146 |
147 | #treeViewContainer ul li span:hover {
148 | color: white;
149 | cursor: pointer;
150 | background: black;
151 | padding-left: 2px;
152 | pointer-events: all;
153 | }
154 |
155 | #treeViewContainer ul li span {
156 | display: inline-block;
157 | width: calc(100% - 50px);
158 | padding-left: 2px;
159 | pointer-events: all;
160 | height: 23px;
161 | }
162 |
163 | #treeViewContainer .highlighted-node {
164 | /* Appearance of node highlighted with TreeViewPlugin#showNode() */
165 | border: black solid 1px;
166 | background: yellow;
167 | color: black;
168 | padding-left: 1px;
169 | padding-right: 5px;
170 | pointer-events: all;
171 | }
172 |
173 | .xeokit-context-menu {
174 | font-family: "Roboto", sans-serif;
175 | font-size: 15px;
176 | display: none;
177 | z-index: 2;
178 | background: rgba(255, 255, 255, 0.46);
179 | border: 1px solid black;
180 | border-radius: 6px;
181 | padding: 0;
182 | width: 220px;
183 | }
184 |
185 | .xeokit-context-menu ul {
186 | list-style: none;
187 | margin-left: 0;
188 | margin-top: 0;
189 | margin-bottom: 0;
190 | padding: 0;
191 | }
192 |
193 | .xeokit-context-menu ul li {
194 | list-style-type: none;
195 | padding-left: 10px;
196 | padding-right: 20px;
197 | padding-top: 6px;
198 | padding-bottom: 6px;
199 | color: black;
200 | border-bottom: 1px solid gray;
201 | background: rgba(255, 255, 255, 0.46);
202 | cursor: pointer;
203 | width: calc(100% - 1px);
204 | }
205 |
206 | .xeokit-context-menu ul li:hover {
207 | background: black;
208 | color: white;
209 | font-weight: bold;
210 | }
211 |
212 | .xeokit-context-menu ul li span {
213 | display: inline-block;
214 | }
215 |
216 | .xeokit-context-menu .disabled {
217 | display: inline-block;
218 | color: gray;
219 | cursor: default;
220 | font-weight: normal;
221 | }
222 |
223 | .xeokit-context-menu .disabled:hover {
224 | color: gray;
225 | cursor: default;
226 | background: #eeeeee;
227 | font-weight: normal;
228 | }
229 |
230 | .xeokit-context-menu .xeokit-context-submenu {
231 | font-family: "Roboto", sans-serif;
232 | font-size: 15px;
233 | display: none;
234 | z-index: 2;
235 | background: rgba(255, 255, 255, 0.46);
236 | border: 1px solid black;
237 | border-radius: 0 6px 6px 6px !important;
238 | padding: 0;
239 | width: 200px;
240 | }
241 | /*# sourceMappingURL=project.css.map */
--------------------------------------------------------------------------------
/demo/public/dxf_test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/demo/public/images/skybox/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/demo/public/images/skybox/default.png
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demo/public/projects/dxf_test/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/demo/public/projects/dxf_test/thumbnail.png
--------------------------------------------------------------------------------
/demo/public/projects/rac_basic_sample_project/rac_basic_sample_project.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/demo/public/projects/rac_basic_sample_project/rac_basic_sample_project.bin
--------------------------------------------------------------------------------
/demo/public/projects/rac_basic_sample_project/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/demo/public/projects/rac_basic_sample_project/thumbnail.png
--------------------------------------------------------------------------------
/demo/public/projects/technical_school_normal/Technical_school-current_m.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/demo/public/projects/technical_school_normal/Technical_school-current_m.bin
--------------------------------------------------------------------------------
/demo/public/projects/technical_school_normal/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/demo/public/projects/technical_school_normal/thumbnail.png
--------------------------------------------------------------------------------
/demo/public/rac_basic_sample_project.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/demo/public/technical_school_normal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
--------------------------------------------------------------------------------
/docs/assets/highlight.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --light-hl-0: #001080;
3 | --dark-hl-0: #9CDCFE;
4 | --light-hl-1: #000000;
5 | --dark-hl-1: #D4D4D4;
6 | --light-hl-2: #AF00DB;
7 | --dark-hl-2: #C586C0;
8 | --light-hl-3: #A31515;
9 | --dark-hl-3: #CE9178;
10 | --light-hl-4: #0000FF;
11 | --dark-hl-4: #569CD6;
12 | --light-hl-5: #0070C1;
13 | --dark-hl-5: #4FC1FF;
14 | --light-hl-6: #098658;
15 | --dark-hl-6: #B5CEA8;
16 | --light-hl-7: #795E26;
17 | --dark-hl-7: #DCDCAA;
18 | --light-hl-8: #008000;
19 | --dark-hl-8: #6A9955;
20 | --light-code-background: #FFFFFF;
21 | --dark-code-background: #1E1E1E;
22 | }
23 |
24 | @media (prefers-color-scheme: light) { :root {
25 | --hl-0: var(--light-hl-0);
26 | --hl-1: var(--light-hl-1);
27 | --hl-2: var(--light-hl-2);
28 | --hl-3: var(--light-hl-3);
29 | --hl-4: var(--light-hl-4);
30 | --hl-5: var(--light-hl-5);
31 | --hl-6: var(--light-hl-6);
32 | --hl-7: var(--light-hl-7);
33 | --hl-8: var(--light-hl-8);
34 | --code-background: var(--light-code-background);
35 | } }
36 |
37 | @media (prefers-color-scheme: dark) { :root {
38 | --hl-0: var(--dark-hl-0);
39 | --hl-1: var(--dark-hl-1);
40 | --hl-2: var(--dark-hl-2);
41 | --hl-3: var(--dark-hl-3);
42 | --hl-4: var(--dark-hl-4);
43 | --hl-5: var(--dark-hl-5);
44 | --hl-6: var(--dark-hl-6);
45 | --hl-7: var(--dark-hl-7);
46 | --hl-8: var(--dark-hl-8);
47 | --code-background: var(--dark-code-background);
48 | } }
49 |
50 | body.light {
51 | --hl-0: var(--light-hl-0);
52 | --hl-1: var(--light-hl-1);
53 | --hl-2: var(--light-hl-2);
54 | --hl-3: var(--light-hl-3);
55 | --hl-4: var(--light-hl-4);
56 | --hl-5: var(--light-hl-5);
57 | --hl-6: var(--light-hl-6);
58 | --hl-7: var(--light-hl-7);
59 | --hl-8: var(--light-hl-8);
60 | --code-background: var(--light-code-background);
61 | }
62 |
63 | body.dark {
64 | --hl-0: var(--dark-hl-0);
65 | --hl-1: var(--dark-hl-1);
66 | --hl-2: var(--dark-hl-2);
67 | --hl-3: var(--dark-hl-3);
68 | --hl-4: var(--dark-hl-4);
69 | --hl-5: var(--dark-hl-5);
70 | --hl-6: var(--dark-hl-6);
71 | --hl-7: var(--dark-hl-7);
72 | --hl-8: var(--dark-hl-8);
73 | --code-background: var(--dark-code-background);
74 | }
75 |
76 | .hl-0 { color: var(--hl-0); }
77 | .hl-1 { color: var(--hl-1); }
78 | .hl-2 { color: var(--hl-2); }
79 | .hl-3 { color: var(--hl-3); }
80 | .hl-4 { color: var(--hl-4); }
81 | .hl-5 { color: var(--hl-5); }
82 | .hl-6 { color: var(--hl-6); }
83 | .hl-7 { color: var(--hl-7); }
84 | .hl-8 { color: var(--hl-8); }
85 | pre, code { background: var(--code-background); }
86 |
--------------------------------------------------------------------------------
/docs/assets/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/docs/assets/icons.png
--------------------------------------------------------------------------------
/docs/assets/icons@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/docs/assets/icons@2x.png
--------------------------------------------------------------------------------
/docs/assets/widgets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/docs/assets/widgets.png
--------------------------------------------------------------------------------
/docs/assets/widgets@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/docs/assets/widgets@2x.png
--------------------------------------------------------------------------------
/docs/enums/AxisType.html:
--------------------------------------------------------------------------------
1 | AxisType | @pattern-x/gemini-viewerEnumeration members
X
X = "x"
Y
Y = "y"
Z
Z = "z"
--------------------------------------------------------------------------------
/docs/interfaces/BackgroundColorConfig.html:
--------------------------------------------------------------------------------
1 | BackgroundColorConfig | @pattern-x/gemini-viewerInterface BackgroundColorConfig
Properties
Optional backgroundColor
backgroundColor?: number[]
Optional transparent
transparent?: boolean
--------------------------------------------------------------------------------
/docs/interfaces/Context.html:
--------------------------------------------------------------------------------
1 | Context | @pattern-x/gemini-viewerProperties
bimViewer
Optional entity
entity?: any
--------------------------------------------------------------------------------
/docs/interfaces/GridGeometryConfig.html:
--------------------------------------------------------------------------------
1 | GridGeometryConfig | @pattern-x/gemini-viewerInterface GridGeometryConfig
Properties
division
division: number
size
size: number
--------------------------------------------------------------------------------
/docs/interfaces/OrthoModeConfig.html:
--------------------------------------------------------------------------------
1 | OrthoModeConfig | @pattern-x/gemini-viewerInterface OrthoModeConfig
Properties
active
active: boolean
Optional done
--------------------------------------------------------------------------------
/docs/interfaces/SectionCullPlaneConfig.html:
--------------------------------------------------------------------------------
1 | SectionCullPlaneConfig | @pattern-x/gemini-viewerInterface SectionCullPlaneConfig
Properties
active
active: boolean
Optional overviewCfg
--------------------------------------------------------------------------------
/docs/interfaces/SingleSelectionConfig.html:
--------------------------------------------------------------------------------
1 | SingleSelectionConfig | @pattern-x/gemini-viewerInterface SingleSelectionConfig
Properties
active
active: boolean
Optional callback
callback?: PickCallback
--------------------------------------------------------------------------------
/docs/modules/math.html:
--------------------------------------------------------------------------------
1 | math | @pattern-x/gemini-viewer
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@pattern-x/gemini-viewer",
3 | "version": "1.0.8",
4 | "description": "gemini-viewer sdk",
5 | "module": "dist/gemini-viewer.esm.min.js",
6 | "types": "dist/types/index.d.ts",
7 | "scripts": {
8 | "clean": "rimraf dist build",
9 | "build": "npm run clean && npm run lint && webpack --config webpack.esm.config.js --mode=production --node-env=production",
10 | "build:dev": "rimraf build && npm run lint && webpack --config webpack.esm.config.js --mode=development --node-env=development",
11 | "demo": "npm run build:dev && node demo/index.js",
12 | "docs": "typedoc src/index.ts",
13 | "start": "webpack serve --open",
14 | "lint": "eslint --ext .ts src"
15 | },
16 | "keywords": [],
17 | "author": "",
18 | "dependencies": {
19 | "@types/earcut": "^2.1.1",
20 | "@xeokit/xeokit-sdk": "^2.0.7",
21 | "dxf-parser": "^1.0.0-alpha.2",
22 | "earcut": "^2.2.3",
23 | "gl-matrix": "^3.3.0"
24 | },
25 | "devDependencies": {
26 | "@types/lodash": "^4.14.175",
27 | "@typescript-eslint/eslint-plugin": "^5.0.0",
28 | "@typescript-eslint/parser": "^5.0.0",
29 | "@webpack-cli/generators": "^2.3.0",
30 | "cp-file": "^9.1.0",
31 | "css-loader": "^6.2.0",
32 | "dts-bundle": "^0.7.3",
33 | "dts-bundle-webpack": "^1.0.2",
34 | "eslint": "^8.0.0",
35 | "eslint-plugin-prettier": "^4.0.0",
36 | "eslint-webpack-plugin": "^3.0.1",
37 | "express": "^4.17.1",
38 | "html-webpack-plugin": "^5.3.2",
39 | "prettier": "^2.4.1",
40 | "rimraf": "^3.0.2",
41 | "sass": "^1.40.0",
42 | "sass-loader": "^12.1.0",
43 | "style-loader": "^3.2.1",
44 | "terser-webpack-plugin": "^5.2.4",
45 | "ts-loader": "^9.2.5",
46 | "typedoc": "^0.22.7",
47 | "typescript": "^4.4.3",
48 | "webpack": "^5.52.1",
49 | "webpack-cli": "^4.8.0",
50 | "webpack-dev-server": "^4.3.1"
51 | },
52 | "files": [
53 | "/dist"
54 | ],
55 | "license": "UNLICENSED",
56 | "repository": {
57 | "type": "git",
58 | "url": "https://github.com/pattern-x/gemini-viewer"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/public/snapshots/context_menu.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/context_menu.gif
--------------------------------------------------------------------------------
/public/snapshots/measure_dist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/measure_dist.gif
--------------------------------------------------------------------------------
/public/snapshots/nav_cube.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/nav_cube.gif
--------------------------------------------------------------------------------
/public/snapshots/ortho.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/ortho.gif
--------------------------------------------------------------------------------
/public/snapshots/pivot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/pivot.gif
--------------------------------------------------------------------------------
/public/snapshots/section_box.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/section_box.gif
--------------------------------------------------------------------------------
/public/snapshots/section_plane.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pattern-x/gemini-viewer-xeokit/7ea0dd5a69368784d855a4eb824626961ddc9819/public/snapshots/section_plane.gif
--------------------------------------------------------------------------------
/script/dts.bundle.js:
--------------------------------------------------------------------------------
1 | var dts = require('dts-bundle');
2 |
3 | dts.bundle({
4 | name: "gemini-viewer",
5 | main: "build/index.d.ts",
6 | baseDir: "build",
7 | // out: "dist/[name].d.ts"
8 | });
--------------------------------------------------------------------------------
/script/dts.copy.js:
--------------------------------------------------------------------------------
1 | const cpFile = require('cp-file');
2 |
3 | (async () => {
4 | await cpFile('build/gemini-viewer.d.ts', 'dist/gemini-viewer.d.ts');
5 | })();
--------------------------------------------------------------------------------
/src/components/PopPanel.ts:
--------------------------------------------------------------------------------
1 | import { MOUSEMOVE_EVENT, MOUSEUP_EVENT, MOUSEDOWN_EVENT } from "../utils/Consts";
2 |
3 | export class PopPanel {
4 | private _node: HTMLElement;
5 | public _header: HTMLElement;
6 | public _body: HTMLElement;
7 | private _isFollowing = false;
8 | private _diffX = 0;
9 | private _diffY = 0;
10 |
11 | constructor(id: string, content: string | HTMLElement) {
12 | this._node = document.createElement("div");
13 | this._node.id = id;
14 | this._node.classList.add("pop-panel");
15 |
16 | const header = document.createElement("div");
17 | header.classList.add("pop-panel-header");
18 | header.append(content);
19 | this._node.appendChild(header);
20 | this._header = header;
21 |
22 | const info = document.createElement("div");
23 | info.classList.add("pop-panel-body");
24 | this._node.appendChild(info);
25 | this._body = info;
26 |
27 | header.addEventListener(MOUSEDOWN_EVENT, this.start);
28 | header.addEventListener(MOUSEUP_EVENT, this.stop);
29 | document.addEventListener(MOUSEMOVE_EVENT, this.follow);
30 | document.body.appendChild(this._node);
31 | }
32 |
33 | start = (event: MouseEvent) => {
34 | this._isFollowing = true;
35 | this._diffX = event.clientX - this._node.offsetLeft;
36 | this._diffY = event.clientY - this._node.offsetTop;
37 | };
38 |
39 | stop = () => {
40 | this._isFollowing = false;
41 | };
42 |
43 | follow = (event: MouseEvent) => {
44 | if (!this._isFollowing) {
45 | return;
46 | }
47 | this._node.style.left = event.clientX - this._diffX + "px";
48 | this._node.style.top = event.clientY - this._diffY + "px";
49 | };
50 |
51 | destroy() {
52 | document.removeEventListener(MOUSEMOVE_EVENT, this.follow);
53 | this._node.removeEventListener(MOUSEDOWN_EVENT, this.start);
54 | this._node.removeEventListener(MOUSEUP_EVENT, this.stop);
55 | document.body.removeChild(this._node);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Tooltip.ts:
--------------------------------------------------------------------------------
1 | import { MOUSEMOVE_EVENT } from "../utils/Consts";
2 |
3 | interface TooltipConfig {
4 | showOnCreate?: boolean;
5 | followPointer?: boolean;
6 | parentNode?: HTMLElement;
7 | target?: HTMLElement;
8 | }
9 | export class Tooltip {
10 | private _node: HTMLElement;
11 | private _parentNode: HTMLElement;
12 | private _target: HTMLElement | undefined;
13 |
14 | constructor(id: string, content: string | HTMLElement, cfg?: TooltipConfig) {
15 | this._node = document.createElement("div");
16 | this._node.id = id;
17 | this._node.append(content);
18 |
19 | this._target = cfg?.target;
20 |
21 | this._parentNode = cfg?.parentNode || document.body;
22 |
23 | if (cfg?.followPointer) {
24 | this._node.classList.add("follow-tooltip");
25 | document.addEventListener(MOUSEMOVE_EVENT, this.follow);
26 | }
27 | this._parentNode.appendChild(this._node);
28 | !cfg?.showOnCreate && this._node.setAttribute("hidden", "");
29 | }
30 |
31 | follow = (event: MouseEvent) => {
32 | if (this._target) {
33 | event.target === this._target ? this.show() : this.hide();
34 | }
35 | this._node.style.left = event.clientX + 15 + "px";
36 | this._node.style.top = event.clientY - 30 + "px";
37 | };
38 |
39 | show = () => this._node.hasAttribute("hidden") && this._node.removeAttribute("hidden");
40 |
41 | hide = () => !this._node.hasAttribute("hidden") && this._node.setAttribute("hidden", "");
42 |
43 | destroy = () => {
44 | document.removeEventListener(MOUSEMOVE_EVENT, this.follow);
45 | this._parentNode.removeChild(this._node);
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./BottomBar";
2 | export * from "./PopPanel";
3 | export * from "./SectionPlanePopPanel";
4 | export * from "./Tooltip";
5 |
--------------------------------------------------------------------------------
/src/core/CallbackTypes.ts:
--------------------------------------------------------------------------------
1 | //export other callback function type
2 | export type EmptyCallbackType = () => void;
3 | export type CallbackType = (event: T) => U;
4 | export type CallbackRetureVoidType = CallbackType;
5 |
--------------------------------------------------------------------------------
/src/core/Map.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Map class
3 | * Reference to: https://github.com/xeokit/xeokit-bim-viewer/blob/master/src/Map.js
4 | */
5 | export class Map {
6 | private _items: any; // eslint-disable-line
7 | private _lastUniqueId = -1;
8 |
9 | constructor(items?: any, baseId?: number) { // eslint-disable-line
10 | this._items = items || [];
11 | this._lastUniqueId = (baseId || 0) + 1;
12 | }
13 |
14 | /**
15 | * Usage:
16 | *
17 | * id = myMap.addItem("foo") // ID internally generated
18 | * id = myMap.addItem("foo", "bar") // ID is "foo"
19 | */
20 | addItem(...args: any) { // eslint-disable-line
21 | let item;
22 | if (args.length === 2) {
23 | const id = args[0];
24 | item = args[1];
25 | if (this._items[id]) {
26 | // Won't happen if given ID is string
27 | throw "ID clash: '" + id + "'";
28 | }
29 | this._items[id] = item;
30 | return id;
31 | } else {
32 | item = args[0] || {};
33 | while (true) { // eslint-disable-line
34 | const findId = this._lastUniqueId++;
35 | if (!this._items[findId]) {
36 | this._items[findId] = item;
37 | return findId;
38 | }
39 | }
40 | }
41 | }
42 |
43 | removeItem(id: number | string) {
44 | const item = this._items[id];
45 | delete this._items[id];
46 | return item;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./BimViewer";
2 | export * from "./CallbackTypes";
3 | export * from "./Configs";
4 | export * from "./Controller";
5 | export * from "./DxfPerformanceModelLoader";
6 | export * from "./Map";
7 | export * from "./SceneGraphTreeView";
8 | export * from "./TextGeometry";
9 | export * from "./ZoomToExtent";
10 | export * from "./paths";
11 |
--------------------------------------------------------------------------------
/src/core/paths/CubicBezierCurve.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { utils } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
3 | import { CubicBezier } from "./Interpolations";
4 | import { Curve } from "./Curve";
5 | import { CurveType } from "./Curves";
6 | import { vec2 } from "gl-matrix";
7 |
8 | export class CubicBezierCurve extends Curve {
9 | protected _v0: vec2;
10 | protected _v1: vec2;
11 | protected _v2: vec2;
12 | protected _v3: vec2;
13 |
14 | constructor(v0 = vec2.create(), v1 = vec2.create(), v2 = vec2.create(), v3 = vec2.create()) {
15 | super();
16 |
17 | this._type = CurveType.CUBIC_BEZIER_CURVE;
18 |
19 | this._v0 = v0;
20 | this._v1 = v1;
21 | this._v2 = v2;
22 | this._v3 = v3;
23 | }
24 |
25 | getPoint(t: number, optionalTarget = vec2.create()) {
26 | const point = optionalTarget;
27 |
28 | const v0 = this._v0,
29 | v1 = this._v1,
30 | v2 = this._v2,
31 | v3 = this._v3;
32 |
33 | const x = CubicBezier(t, v0[0], v1[0], v2[0], v3[0]);
34 | const y = CubicBezier(t, v0[1], v1[1], v2[1], v3[1]);
35 | vec2.set(point, x, y);
36 |
37 | return point;
38 | }
39 |
40 | copy(source: CubicBezierCurve) {
41 | super.copy(source);
42 |
43 | vec2.copy(this._v0, source._v0);
44 | vec2.copy(this._v1, source._v1);
45 | vec2.copy(this._v2, source._v2);
46 | vec2.copy(this._v3, source._v3);
47 |
48 | return this;
49 | }
50 |
51 | toJSON() {
52 | const data = super.toJSON();
53 |
54 | // data.v0 = this.v0.toArray();
55 | // data.v1 = this.v1.toArray();
56 | // data.v2 = this.v2.toArray();
57 | // data.v3 = this.v3.toArray();
58 |
59 | return utils.apply(data, {
60 | v0: [...this._v0],
61 | v1: [...this._v1],
62 | v2: [...this._v2],
63 | v3: [...this._v3],
64 | });
65 | }
66 |
67 | fromJSON(json: any) {
68 | super.fromJSON(json);
69 |
70 | vec2.set(this._v0, json.v0[0], json.v0[1]);
71 | vec2.set(this._v1, json.v1[0], json.v1[1]);
72 | vec2.set(this._v2, json.v2[0], json.v2[1]);
73 | vec2.set(this._v3, json.v3[0], json.v3[1]);
74 |
75 | return this;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/core/paths/Curves.ts:
--------------------------------------------------------------------------------
1 | // import { ArcCurve } from "./ArcCurve";
2 | // import { CatmullRomCurve3 } from "./CatmullRomCurve3";
3 | import { Curve, VecType } from "./Curve";
4 | import { CubicBezierCurve } from "./CubicBezierCurve";
5 | // import { CubicBezierCurve3 } from "./CubicBezierCurve3";
6 | import { EllipseCurve } from "./EllipseCurve";
7 | import { LineCurve } from "./LineCurve";
8 | // import { LineCurve3 } from "./LineCurve3";
9 | // import { QuadraticBezierCurve3 } from "./QuadraticBezierCurve3";
10 | import { SplineCurve } from "./SplineCurve";
11 | import { QuadraticBezierCurve } from "./QuadraticBezierCurve";
12 |
13 | export enum CurveType {
14 | CURVE = "Curve",
15 | CURVE_PATH = "CurvePath",
16 | CUBIC_BEZIER_CURVE = "CubicBezierCurve",
17 | ELLIPSE_CURVE = "EllipseCurve",
18 | LINE_CURVE = "LineCurve",
19 | PATH = "Path",
20 | QUADRATIC_BEZIER_CURVE = "QuadraticBezierCurve",
21 | SHAPE = "Shape",
22 | SPLINE_CURVE = "SplineCurve",
23 | }
24 |
25 | export function createCurveByType(type: CurveType): Curve | undefined {
26 | switch (type) {
27 | case CurveType.CUBIC_BEZIER_CURVE:
28 | return new CubicBezierCurve();
29 |
30 | case CurveType.ELLIPSE_CURVE:
31 | return new EllipseCurve();
32 |
33 | case CurveType.LINE_CURVE:
34 | return new LineCurve();
35 |
36 | case CurveType.QUADRATIC_BEZIER_CURVE:
37 | return new QuadraticBezierCurve();
38 |
39 | case CurveType.SPLINE_CURVE:
40 | return new SplineCurve();
41 |
42 | default:
43 | return undefined;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/core/paths/EllipseCurve.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { utils } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
3 | import { Curve } from "./Curve";
4 | import { CurveType } from "./Curves";
5 | import { vec2 } from "gl-matrix";
6 |
7 | export class EllipseCurve extends Curve {
8 | protected _aX: number;
9 | protected _aY: number;
10 | protected _xRadius: number;
11 | protected _yRadius: number;
12 | protected _aStartAngle: number;
13 | protected _aEndAngle: number;
14 | protected _aClockwise: boolean;
15 | protected _aRotation: number;
16 |
17 | constructor(
18 | aX = 0,
19 | aY = 0,
20 | xRadius = 1,
21 | yRadius = 1,
22 | aStartAngle = 0,
23 | aEndAngle = Math.PI * 2,
24 | aClockwise = false,
25 | aRotation = 0
26 | ) {
27 | super();
28 |
29 | this._type = CurveType.ELLIPSE_CURVE;
30 |
31 | this._aX = aX;
32 | this._aY = aY;
33 |
34 | this._xRadius = xRadius;
35 | this._yRadius = yRadius;
36 |
37 | this._aStartAngle = aStartAngle;
38 | this._aEndAngle = aEndAngle;
39 |
40 | this._aClockwise = aClockwise;
41 |
42 | this._aRotation = aRotation;
43 | }
44 |
45 | getPoint(t: number, optionalTarget?: vec2) {
46 | const point = optionalTarget || vec2.create();
47 |
48 | const twoPi = Math.PI * 2;
49 | let deltaAngle = this._aEndAngle - this._aStartAngle;
50 | const samePoints = Math.abs(deltaAngle) < Number.EPSILON;
51 |
52 | // ensures that deltaAngle is 0 .. 2 PI
53 | while (deltaAngle < 0) {
54 | deltaAngle += twoPi;
55 | }
56 | while (deltaAngle > twoPi) {
57 | deltaAngle -= twoPi;
58 | }
59 |
60 | if (deltaAngle < Number.EPSILON) {
61 | if (samePoints) {
62 | deltaAngle = 0;
63 | } else {
64 | deltaAngle = twoPi;
65 | }
66 | }
67 |
68 | if (this._aClockwise === true && !samePoints) {
69 | if (deltaAngle === twoPi) {
70 | deltaAngle = -twoPi;
71 | } else {
72 | deltaAngle = deltaAngle - twoPi;
73 | }
74 | }
75 |
76 | const angle = this._aStartAngle + t * deltaAngle;
77 | let x = this._aX + this._xRadius * Math.cos(angle);
78 | let y = this._aY + this._yRadius * Math.sin(angle);
79 |
80 | if (this._aRotation !== 0) {
81 | const cos = Math.cos(this._aRotation);
82 | const sin = Math.sin(this._aRotation);
83 |
84 | const tx = x - this._aX;
85 | const ty = y - this._aY;
86 |
87 | // Rotate the point about the center of the ellipse.
88 | x = tx * cos - ty * sin + this._aX;
89 | y = tx * sin + ty * cos + this._aY;
90 | }
91 |
92 | return vec2.set(point, x, y);
93 | }
94 |
95 | copy(source: EllipseCurve) {
96 | super.copy(source);
97 |
98 | this._aX = source._aX;
99 | this._aY = source._aY;
100 |
101 | this._xRadius = source._xRadius;
102 | this._yRadius = source._yRadius;
103 |
104 | this._aStartAngle = source._aStartAngle;
105 | this._aEndAngle = source._aEndAngle;
106 |
107 | this._aClockwise = source._aClockwise;
108 |
109 | this._aRotation = source._aRotation;
110 |
111 | return this;
112 | }
113 |
114 | toJSON() {
115 | const data = super.toJSON();
116 |
117 | // data.aX = this.aX;
118 | // data.aY = this.aY;
119 |
120 | // data.xRadius = this.xRadius;
121 | // data.yRadius = this.yRadius;
122 |
123 | // data.aStartAngle = this.aStartAngle;
124 | // data.aEndAngle = this.aEndAngle;
125 |
126 | // data.aClockwise = this.aClockwise;
127 |
128 | // data.aRotation = this.aRotation;
129 |
130 | return utils.apply(data, {
131 | aX: this._aX,
132 | aY: this._aY,
133 | xRadius: this._xRadius,
134 | yRadius: this._yRadius,
135 | aStartAngle: this._aStartAngle,
136 | aEndAngle: this._aEndAngle,
137 | aClockwise: this._aClockwise,
138 | aRotation: this._aRotation,
139 | });
140 | }
141 |
142 | fromJSON(json: any) {
143 | super.fromJSON(json);
144 |
145 | this._aX = json.aX;
146 | this._aY = json.aY;
147 |
148 | this._xRadius = json.xRadius;
149 | this._yRadius = json.yRadius;
150 |
151 | this._aStartAngle = json.aStartAngle;
152 | this._aEndAngle = json.aEndAngle;
153 |
154 | this._aClockwise = json.aClockwise;
155 |
156 | this._aRotation = json.aRotation;
157 |
158 | return this;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/core/paths/Interpolations.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Bezier Curves formulas obtained from http://en.wikipedia.org/wiki/Bézier_curve
3 | */
4 |
5 | export function CatmullRom(t: number, p0: number, p1: number, p2: number, p3: number) {
6 | const v0 = (p2 - p0) * 0.5;
7 | const v1 = (p3 - p1) * 0.5;
8 | const t2 = t * t;
9 | const t3 = t * t2;
10 | return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
11 | }
12 |
13 | function QuadraticBezierP0(t: number, p: number) {
14 | const k = 1 - t;
15 | return k * k * p;
16 | }
17 |
18 | function QuadraticBezierP1(t: number, p: number) {
19 | return 2 * (1 - t) * t * p;
20 | }
21 |
22 | function QuadraticBezierP2(t: number, p: number) {
23 | return t * t * p;
24 | }
25 |
26 | export function QuadraticBezier(t: number, p0: number, p1: number, p2: number) {
27 | return QuadraticBezierP0(t, p0) + QuadraticBezierP1(t, p1) + QuadraticBezierP2(t, p2);
28 | }
29 |
30 | function CubicBezierP0(t: number, p: number) {
31 | const k = 1 - t;
32 | return k * k * k * p;
33 | }
34 |
35 | function CubicBezierP1(t: number, p: number) {
36 | const k = 1 - t;
37 | return 3 * k * k * t * p;
38 | }
39 |
40 | function CubicBezierP2(t: number, p: number) {
41 | return 3 * (1 - t) * t * t * p;
42 | }
43 |
44 | function CubicBezierP3(t: number, p: number) {
45 | return t * t * t * p;
46 | }
47 |
48 | export function CubicBezier(t: number, p0: number, p1: number, p2: number, p3: number) {
49 | return CubicBezierP0(t, p0) + CubicBezierP1(t, p1) + CubicBezierP2(t, p2) + CubicBezierP3(t, p3);
50 | }
51 |
--------------------------------------------------------------------------------
/src/core/paths/LineCurve.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { Curve } from "./Curve";
3 | import { CurveType } from "./Curves";
4 | import { vec2 } from "gl-matrix";
5 |
6 | export class LineCurve extends Curve {
7 | private _v1: vec2;
8 | private _v2: vec2;
9 |
10 | constructor(v1 = vec2.create(), v2 = vec2.create()) {
11 | super();
12 |
13 | this._type = CurveType.LINE_CURVE;
14 |
15 | this._v1 = v1;
16 | this._v2 = v2;
17 | }
18 |
19 | getPoint(t: number, optionalTarget = vec2.create()) {
20 | const point = optionalTarget;
21 |
22 | if (t === 1) {
23 | vec2.copy(point, this._v2);
24 | } else {
25 | vec2.copy(point, this._v2);
26 | vec2.sub(point, point, this._v1);
27 | //point.multiplyScalar(t).add(this.v1);
28 | vec2.scale(point, point, t);
29 | vec2.add(point, point, this._v1);
30 | }
31 |
32 | return point;
33 | }
34 |
35 | // Line curve is linear, so we can overwrite default getPointAt
36 | getPointAt(u: number, optionalTarget?: vec2) {
37 | return this.getPoint(u, optionalTarget);
38 | }
39 |
40 | getTangent(t: number, optionalTarget?: vec2) {
41 | const tangent = optionalTarget || vec2.create();
42 |
43 | //tangent.copy(this.v2).sub(this.v1).normalize();
44 |
45 | vec2.copy(tangent, this._v2);
46 | vec2.sub(tangent, tangent, this._v1);
47 | vec2.normalize(tangent, tangent);
48 |
49 | return tangent;
50 | }
51 |
52 | copy(source: LineCurve) {
53 | super.copy(source);
54 |
55 | //this.v1.copy(source.v1);
56 | vec2.copy(this._v1, source._v1);
57 | //this.v2.copy(source.v2);
58 | vec2.copy(this._v2, source._v2);
59 | return this;
60 | }
61 |
62 | toJSON() {
63 | const data = super.toJSON();
64 |
65 | utils.apply(data, {
66 | v1: [...this._v1],
67 | v2: [...this._v2],
68 | });
69 | //data.v1 = this.v1.toArray();
70 | //data.v2 = this.v2.toArray();
71 |
72 | return data;
73 | }
74 |
75 | fromJSON(json: any) { // eslint-disable-line
76 | super.fromJSON(json);
77 |
78 | //this.v1.fromArray(json.v1);
79 | //this.v2.fromArray(json.v2);
80 |
81 | vec2.set(this._v1, json.v1[0], json.v1[1]);
82 | vec2.set(this._v2, json.v2[0], json.v2[1]);
83 |
84 | return this;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/core/paths/Path.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { CubicBezierCurve } from "./CubicBezierCurve";
3 | import { Curve, VecType } from "./Curve";
4 | import { CurveType } from "./Curves";
5 | import { CurvePath } from "./CurvePath";
6 | import { EllipseCurve } from "./EllipseCurve";
7 | import { LineCurve } from "./LineCurve";
8 | import { vec2 } from "gl-matrix";
9 | import { SplineCurve } from "./SplineCurve";
10 | import { QuadraticBezierCurve } from "./QuadraticBezierCurve";
11 |
12 | export class Path extends CurvePath {
13 | protected _currentPoint: vec2;
14 |
15 | constructor(points?: vec2[]) {
16 | super();
17 | this._type = CurveType.PATH;
18 |
19 | this._currentPoint = vec2.create();
20 |
21 | if (points) {
22 | this.setFromPoints(points);
23 | }
24 | }
25 |
26 | setFromPoints(points: vec2[]) {
27 | this.moveTo(points[0][0], points[0][1]);
28 |
29 | for (let i = 1, l = points.length; i < l; i++) {
30 | this.lineTo(points[i][0], points[i][0]);
31 | }
32 |
33 | return this;
34 | }
35 |
36 | setCurves(value: Curve[]) {
37 | this._curves = value;
38 | }
39 |
40 | getCurves() {
41 | return this._curves;
42 | }
43 |
44 | moveTo(x: number, y: number) {
45 | //this.currentPoint.set(x, y); // TODO consider referencing vectors instead of copying?
46 | vec2.set(this._currentPoint, x, y);
47 | return this;
48 | }
49 |
50 | lineTo(x: number, y: number) {
51 | const curve = new LineCurve(vec2.clone(this._currentPoint), vec2.fromValues(x, y));
52 | this._curves.push(curve);
53 |
54 | //this.currentPoint.set(x, y);
55 | vec2.set(this._currentPoint, x, y);
56 | return this;
57 | }
58 |
59 | quadraticCurveTo(aCPx: number, aCPy: number, aX: number, aY: number) {
60 | const curve = new QuadraticBezierCurve(
61 | vec2.clone(this._currentPoint),
62 | vec2.fromValues(aCPx, aCPy),
63 | vec2.fromValues(aX, aY)
64 | );
65 |
66 | this._curves.push(curve);
67 |
68 | //this.currentPoint.set(aX, aY);
69 | vec2.set(this._currentPoint, aX, aY);
70 | return this;
71 | }
72 |
73 | bezierCurveTo(aCP1x: number, aCP1y: number, aCP2x: number, aCP2y: number, aX: number, aY: number) {
74 | const curve = new CubicBezierCurve(
75 | vec2.clone(this._currentPoint),
76 | vec2.fromValues(aCP1x, aCP1y),
77 | vec2.fromValues(aCP2x, aCP2y),
78 | vec2.fromValues(aX, aY)
79 | );
80 |
81 | this._curves.push(curve);
82 |
83 | //this.currentPoint.set(aX, aY);
84 | vec2.set(this._currentPoint, aX, aY);
85 | return this;
86 | }
87 |
88 | splineThru(pts: vec2[]) {
89 | const npts = [vec2.clone(this._currentPoint)].concat(pts);
90 |
91 | const curve = new SplineCurve(npts);
92 | this._curves.push(curve);
93 |
94 | vec2.copy(this._currentPoint, pts[pts.length - 1]);
95 |
96 | return this;
97 | }
98 |
99 | arc(aX: number, aY: number, aRadius: number, aStartAngle: number, aEndAngle: number, aClockwise?: boolean) {
100 | const x0 = this._currentPoint[0];
101 | const y0 = this._currentPoint[1];
102 |
103 | this.absarc(aX + x0, aY + y0, aRadius, aStartAngle, aEndAngle, aClockwise);
104 |
105 | return this;
106 | }
107 |
108 | absarc(aX: number, aY: number, aRadius: number, aStartAngle: number, aEndAngle: number, aClockwise?: boolean) {
109 | this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);
110 |
111 | return this;
112 | }
113 |
114 | ellipse(
115 | aX: number,
116 | aY: number,
117 | xRadius: number,
118 | yRadius: number,
119 | aStartAngle: number,
120 | aEndAngle: number,
121 | aClockwise?: boolean,
122 | aRotation?: number
123 | ) {
124 | const x0 = this._currentPoint[0];
125 | const y0 = this._currentPoint[1];
126 |
127 | this.absellipse(aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation);
128 |
129 | return this;
130 | }
131 |
132 | absellipse(
133 | aX: number,
134 | aY: number,
135 | xRadius: number,
136 | yRadius: number,
137 | aStartAngle: number,
138 | aEndAngle: number,
139 | aClockwise?: boolean,
140 | aRotation?: number
141 | ) {
142 | const curve = new EllipseCurve(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation);
143 |
144 | if (this._curves.length > 0) {
145 | // if a previous curve is present, attempt to join
146 | const firstPoint = curve.getPoint(0);
147 |
148 | if (!vec2.equals(firstPoint, this._currentPoint)) {
149 | this.lineTo(firstPoint[0], firstPoint[1]);
150 | }
151 | }
152 |
153 | this._curves.push(curve);
154 |
155 | const lastPoint = curve.getPoint(1);
156 | //this.currentPoint.copy(lastPoint);
157 | vec2.copy(this._currentPoint, lastPoint);
158 | return this;
159 | }
160 |
161 | copy(source: Path) {
162 | super.copy(source);
163 |
164 | //this.currentPoint.copy(source.currentPoint);
165 | vec2.copy(this._currentPoint, source._currentPoint);
166 | return this;
167 | }
168 |
169 | toJSON() {
170 | const data = super.toJSON();
171 |
172 | data.currentPoint = [...this._currentPoint];
173 |
174 | return data;
175 | }
176 |
177 | fromJSON(json: any) {
178 | super.fromJSON(json);
179 |
180 | //this.currentPoint.fromArray(json.currentPoint);
181 | vec2.set(this._currentPoint, json.currentPoint[0], json.currentPoint[1]);
182 | return this;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/core/paths/QuadraticBezierCurve.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { Curve } from "./Curve";
3 | import { CurveType } from "./Curves";
4 | import { vec2 } from "gl-matrix";
5 | import { QuadraticBezier } from "./Interpolations";
6 |
7 | export class QuadraticBezierCurve extends Curve {
8 | protected _v0: vec2;
9 | protected _v1: vec2;
10 | protected _v2: vec2;
11 |
12 | constructor(v0 = vec2.create(), v1 = vec2.create(), v2 = vec2.create()) {
13 | super();
14 |
15 | this._type = CurveType.QUADRATIC_BEZIER_CURVE;
16 |
17 | this._v0 = v0;
18 | this._v1 = v1;
19 | this._v2 = v2;
20 | }
21 |
22 | getPoint(t: number, optionalTarget = vec2.create()) {
23 | const point = optionalTarget;
24 |
25 | const v0 = this._v0,
26 | v1 = this._v1,
27 | v2 = this._v2;
28 |
29 | const x = QuadraticBezier(t, v0[0], v1[0], v2[0]);
30 | const y = QuadraticBezier(t, v0[1], v1[1], v2[1]);
31 | vec2.set(point, x, y);
32 |
33 | return point;
34 | }
35 |
36 | copy(source: QuadraticBezierCurve) {
37 | super.copy(source);
38 |
39 | // this.v0.copy(source.v0);
40 | // this.v1.copy(source.v1);
41 | // this.v2.copy(source.v2);
42 |
43 | vec2.copy(this._v0, source._v0);
44 | vec2.copy(this._v1, source._v1);
45 | vec2.copy(this._v2, source._v2);
46 |
47 | return this;
48 | }
49 |
50 | toJSON() {
51 | const data = super.toJSON();
52 |
53 | return utils.apply(data, {
54 | v0: [...this._v0],
55 | v1: [...this._v1],
56 | v2: [...this._v2],
57 | });
58 | }
59 |
60 | fromJSON(json: any) { // eslint-disable-line
61 | super.fromJSON(json);
62 |
63 | vec2.set(this._v0, json.v0[0], json.v0[1]);
64 | vec2.set(this._v1, json.v1[0], json.v1[1]);
65 | vec2.set(this._v2, json.v2[0], json.v2[1]);
66 |
67 | return this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/core/paths/Shape.ts:
--------------------------------------------------------------------------------
1 | import { math } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { CurveType } from "./Curves";
3 | import { vec2 } from "gl-matrix";
4 | import { Path } from "./Path";
5 |
6 | export class Shape extends Path {
7 | protected _uuid: string;
8 | protected _holes: Path[];
9 |
10 | constructor(points?: vec2[]) {
11 | super(points);
12 |
13 | this._uuid = math.createUUID();
14 |
15 | this._type = CurveType.SHAPE;
16 |
17 | this._holes = [];
18 | }
19 |
20 | setHoles(value: Path[]) {
21 | this._holes = value;
22 | }
23 |
24 | getPointsHoles(divisions: number) {
25 | const holesPts: vec2[][] = [];
26 |
27 | for (let i = 0, l = this._holes.length; i < l; i++) {
28 | holesPts[i] = this._holes[i].getPoints(divisions) as vec2[];
29 | }
30 |
31 | return holesPts;
32 | }
33 |
34 | // get points of shape and holes (keypoints based on segments parameter)
35 |
36 | extractPoints(divisions: number) {
37 | return {
38 | shape: this.getPoints(divisions),
39 | holes: this.getPointsHoles(divisions),
40 | };
41 | }
42 |
43 | copy(source: Shape) {
44 | super.copy(source);
45 |
46 | this._holes = [];
47 |
48 | for (let i = 0, l = source._holes.length; i < l; i++) {
49 | const hole = source._holes[i];
50 | const newHole = new Path();
51 |
52 | this._holes.push(newHole.copy(hole));
53 | }
54 |
55 | return this;
56 | }
57 |
58 | toJSON() {
59 | const data = super.toJSON();
60 |
61 | data.uuid = this._uuid;
62 | data.holes = [];
63 |
64 | for (let i = 0, l = this._holes.length; i < l; i++) {
65 | const hole = this._holes[i];
66 | data.holes.push(hole.toJSON());
67 | }
68 |
69 | return data;
70 | }
71 |
72 | fromJSON(json: any) { // eslint-disable-line
73 | super.fromJSON(json);
74 |
75 | this._uuid = json.uuid;
76 | this._holes = [];
77 |
78 | for (let i = 0, l = json.holes.length; i < l; i++) {
79 | const hole = json.holes[i];
80 | this._holes.push(new Path().fromJSON(hole));
81 | }
82 |
83 | return this;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/core/paths/ShapeUtils.ts:
--------------------------------------------------------------------------------
1 | import { vec2 } from "gl-matrix";
2 | import Earcut from "earcut";
3 |
4 | export class ShapeUtils {
5 | // calculate area of the contour polygon
6 | static area(contour: vec2[]) {
7 | const n = contour.length;
8 | let a = 0.0;
9 |
10 | for (let p = n - 1, q = 0; q < n; p = q++) {
11 | a += contour[p][0] * contour[q][1] - contour[q][0] * contour[p][1];
12 | }
13 |
14 | return a * 0.5;
15 | }
16 |
17 | static isClockWise(pts: vec2[]) {
18 | return ShapeUtils.area(pts) < 0;
19 | }
20 |
21 | //TODO only conside 2d
22 | static triangulateShape(contour: vec2[], holes: vec2[][]) {
23 | const vertices: number[] = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
24 | const holeIndices: number[] = []; // array of hole indices
25 | const faces: number[][] = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
26 |
27 | removeDupEndPts(contour);
28 | addContour(vertices, contour);
29 |
30 | let holeIndex = contour.length;
31 |
32 | holes.forEach(removeDupEndPts);
33 |
34 | for (let i = 0; i < holes.length; i++) {
35 | holeIndices.push(holeIndex);
36 | holeIndex += holes[i].length;
37 | addContour(vertices, holes[i]);
38 | }
39 | //vertices need to be clockwise. triangels indices are counterclockwise
40 | const triangles = Earcut(vertices, holeIndices, 2);
41 |
42 | for (let i = 0; i < triangles.length; i += 3) {
43 | faces.push(triangles.slice(i, i + 3));
44 | }
45 |
46 | return faces;
47 | }
48 | }
49 |
50 | function removeDupEndPts(points: vec2[]) {
51 | const l = points.length;
52 |
53 | if (l > 2 && vec2.equals(points[l - 1], points[0])) {
54 | points.pop();
55 | }
56 | }
57 |
58 | function addContour(vertices: number[], contour: vec2[]) {
59 | for (let i = 0; i < contour.length; i++) {
60 | vertices.push(contour[i][0]);
61 | vertices.push(contour[i][1]);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/core/paths/SplineCurve.ts:
--------------------------------------------------------------------------------
1 | import { utils } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { Curve } from "./Curve";
3 | import { CurveType } from "./Curves";
4 | import { vec2 } from "gl-matrix";
5 | import { CatmullRom } from "./Interpolations";
6 |
7 | export class SplineCurve extends Curve {
8 | points: vec2[];
9 |
10 | constructor(points: vec2[] = []) {
11 | super();
12 |
13 | this._type = CurveType.SPLINE_CURVE;
14 |
15 | this.points = points;
16 | }
17 |
18 | getPoint(t: number, optionalTarget = vec2.create()) {
19 | const point = optionalTarget;
20 |
21 | const points = this.points;
22 | const p = (points.length - 1) * t;
23 |
24 | const intPoint = Math.floor(p);
25 | const weight = p - intPoint;
26 |
27 | const p0 = points[intPoint === 0 ? intPoint : intPoint - 1];
28 | const p1 = points[intPoint];
29 | const p2 = points[intPoint > points.length - 2 ? points.length - 1 : intPoint + 1];
30 | const p3 = points[intPoint > points.length - 3 ? points.length - 1 : intPoint + 2];
31 |
32 | const x = CatmullRom(weight, p0[0], p1[0], p2[0], p3[0]);
33 | const y = CatmullRom(weight, p0[1], p1[1], p2[1], p3[1]);
34 | vec2.set(point, x, y);
35 |
36 | return point;
37 | }
38 |
39 | copy(source: SplineCurve) {
40 | super.copy(source);
41 |
42 | this.points = [];
43 |
44 | for (let i = 0, l = source.points.length; i < l; i++) {
45 | const point = source.points[i];
46 |
47 | this.points.push(vec2.clone(point));
48 | }
49 |
50 | return this;
51 | }
52 |
53 | toJSON() {
54 | const data = super.toJSON();
55 |
56 | const points = [];
57 |
58 | for (let i = 0, l = this.points.length; i < l; i++) {
59 | const point = this.points[i];
60 | points.push([...point]);
61 | }
62 |
63 | return utils.apply(data, {
64 | points,
65 | });
66 | }
67 |
68 | fromJSON(json: any) { // eslint-disable-line
69 | super.fromJSON(json);
70 |
71 | this.points = [];
72 |
73 | for (let i = 0, l = json.points.length; i < l; i++) {
74 | const point = json.points[i];
75 | this.points.push(vec2.fromValues(point[0], point[1]));
76 | }
77 |
78 | return this;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/core/paths/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./CubicBezierCurve";
2 | export * from "./Curve";
3 | export * from "./CurvePath";
4 | export * from "./Curves";
5 | export * from "./EllipseCurve";
6 | export * from "./Interpolations";
7 | export * from "./LineCurve";
8 | export * from "./Path";
9 | export * from "./QuadraticBezierCurve";
10 | export * from "./Shape";
11 | export * from "./ShapePath";
12 | export * from "./ShapeUtils";
13 | export * from "./SplineCurve";
14 |
--------------------------------------------------------------------------------
/src/css/bottombar.scss:
--------------------------------------------------------------------------------
1 | .gemini-bottom-bar {
2 | position: absolute;
3 | bottom: 0;
4 | display: flex;
5 | color: $gemini-grey2;
6 | z-index: 1;
7 | overflow: hidden;
8 |
9 | i {
10 | font-size: 20px;
11 | padding: 3px;
12 | align-self: center;
13 | opacity: 0.4;
14 | cursor: pointer;
15 |
16 | &:hover {
17 | opacity: 1;
18 | }
19 |
20 | &.item-active {
21 | color: $gemini-active;
22 | opacity: 1;
23 | }
24 | }
25 |
26 | .fps {
27 | width: 4rem;
28 | }
29 |
30 | span {
31 | align-self: center;
32 | padding: 4px;
33 | font-size: 14px;
34 | }
35 | }
36 |
37 | #statistic-tooltip,
38 | #camerainfo-tooltip {
39 | position: absolute;
40 | left: 5px;
41 | bottom: 30px;
42 | background: $gemini-grey2;
43 | color: white;
44 | padding: 8px 8px 5px 8px;
45 | font-size: 14px;
46 | border-radius: 4px;
47 | z-index: 1;
48 | opacity: 0.9;
49 |
50 | p {
51 | padding-bottom: 3px;
52 | span {
53 | float: left;
54 | margin-right: 10px;
55 | }
56 | }
57 | }
58 |
59 | #statistic-tooltip {
60 | p span {
61 | width: 55px;
62 | }
63 | }
64 |
65 | #camerainfo-tooltip {
66 | p span {
67 | width: 24px;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/css/component.scss:
--------------------------------------------------------------------------------
1 | .follow-tooltip {
2 | z-index: 99999999;
3 | position: absolute;
4 | padding: 6px;
5 | background: rgba(25, 25, 25, 0.3);
6 | color: rgba(255, 255, 255, 0.8);
7 | font-size: 12px;
8 | border-radius: 2px;
9 | }
10 |
11 | .pop-panel {
12 | z-index: 99999999;
13 | top: calc(70% - 100px);
14 | left: calc(90% - 160px);
15 | position: absolute;
16 | background: #ffffff;
17 | color: #333333;
18 | border-radius: 4px;
19 | width: 160px;
20 | box-shadow: 0px 12px 48px 16px rgba(0, 0, 0, 0.03), 0px 9px 28px 0px rgba(0, 0, 0, 0.05), 0px 6px 16px -8px rgba(0, 0, 0, 0.08);
21 |
22 | .pop-panel-header {
23 | font-size: 16px;
24 | font-weight: bolder;
25 | padding: 16px 24px 8px 24px;
26 | color: #333333;
27 | border-bottom: 1px solid #EFEFEF;
28 | cursor: move;
29 | }
30 |
31 | .pop-panel-body {
32 | display: flex;
33 | flex-direction: row;
34 | justify-content: space-between;
35 | align-items: center;
36 | padding: 16px 24px 16px 24px;
37 |
38 | .pop-panel-item {
39 | display: inline-block;
40 | cursor: pointer;
41 | margin-right: 16px;
42 | font-size: 16px;
43 | .gemini-viewer-icon {
44 | font-size: 24px;
45 | }
46 | }
47 |
48 | .pop-panel-item:last-child {
49 | margin-right: 0;
50 | }
51 |
52 | .pop-panel-item:hover {
53 | color: $gemini-active;
54 | }
55 |
56 | .pop-panel-item.active {
57 | color: $gemini-active;
58 | }
59 |
60 | .pop-panel-item.disable {
61 | color: #999;
62 | cursor: not-allowed;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/css/contextmenu.scss:
--------------------------------------------------------------------------------
1 | .xeokit-context-menu {
2 | font-family: "Roboto", sans-serif;
3 | font-size: 15px;
4 | display: none;
5 | z-index: 2;
6 | background: rgba(255, 255, 255, 0.46);
7 | border: 5px solid black;
8 | border-radius: 6px;
9 | padding: 0;
10 | width: 220px;
11 | ul {
12 | list-style: none;
13 | margin-left: 0;
14 | margin-top: 0;
15 | margin-bottom: 0;
16 | padding: 0;
17 | }
18 | ul li {
19 | list-style-type: none;
20 | padding-left: 10px;
21 | padding-right: 20px;
22 | padding-top: 6px;
23 | padding-bottom: 6px;
24 | color: black;
25 | border-bottom: 1px solid gray;
26 | background: rgba(255, 255, 255, 0.46);
27 | cursor: pointer;
28 | width: calc(100% - 1px);
29 | }
30 | ul li:hover {
31 | background: black;
32 | color: white;
33 | font-weight: bold;
34 | }
35 | ul li span {
36 | display: inline-block;
37 | }
38 | .disabled {
39 | display: inline-block;
40 | color: gray;
41 | cursor: default;
42 | font-weight: normal;
43 | }
44 | .disabled:hover {
45 | color: gray;
46 | cursor: default;
47 | background: #eeeeee;
48 | font-weight: normal;
49 | }
50 | .xeokit-context-submenu {
51 | font-family: "Roboto", sans-serif;
52 | font-size: 15px;
53 | display: none;
54 | z-index: 2;
55 | background: rgba(255, 255, 255, 0.46);
56 | border: 1px solid black;
57 | border-radius: 0 6px 6px 6px !important;
58 | padding: 0;
59 | width: 200px;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/css/main.scss:
--------------------------------------------------------------------------------
1 | $gemini-yellow: #f99d0b;
2 | $gemini-red: #c9322f;
3 | $gemini-green: #15a05a;
4 |
5 | $gemini-blue: #1894bd;
6 | $gemini-blue2: #2c7be5;
7 |
8 | $gemini-grey: #999999;
9 | $gemini-grey2: #666666;
10 | $gemini-grey-o50: rgba(153, 153, 153, 0.8);
11 |
12 | $gemini-white: #fff;
13 | $gemini-white2: rgba(255, 255, 255, 0.8);
14 |
15 | $gemini-black: #141414;
16 |
17 | // Measurement
18 | $gemini-length-color: $gemini-yellow;
19 | $gemini-x-color: $gemini-red;
20 | $gemini-y-color: $gemini-green;
21 | $gemini-z-color: $gemini-blue;
22 |
23 | // Common
24 | $gemini-active: $gemini-blue2;
25 |
26 | // Global
27 | * {
28 | box-sizing: border-box;
29 | }
30 |
31 | body {
32 | margin: 0;
33 | padding: 0;
34 | }
35 |
36 | @import url("//at.alicdn.com/t/font_2809422_vnk67d7xjsm.css");
37 | @import "preset.scss";
38 | @import "bottombar.scss";
39 | @import "component.scss";
40 | @import "contextmenu.scss";
41 | @import "measurement.scss";
42 | @import "toolbar.scss";
43 |
--------------------------------------------------------------------------------
/src/css/measurement.scss:
--------------------------------------------------------------------------------
1 | .viewer-ruler-dot {
2 | border-left: 2px solid white !important;
3 | border-radius: 50% !important;
4 | width: 12px !important;
5 | height: 12px !important;
6 | background: $gemini-length-color !important;
7 | }
8 |
9 | .viewer-ruler-wire {
10 | border-bottom: none !important;
11 | border-left: none !important;
12 | border-right: none !important;
13 | }
14 |
15 | .viewer-ruler-label {
16 | border-radius: 3px !important;
17 | }
18 |
19 | .length-wire {
20 | border-top: 3px solid $gemini-length-color !important;
21 | }
22 |
23 | .x-axis-wire {
24 | border-top: 3px dashed $gemini-x-color !important;
25 | }
26 |
27 | .y-axis-wire {
28 | border-top: 3px dashed $gemini-y-color !important;
29 | }
30 |
31 | .z-axis-wire {
32 | border-top: 3px dashed $gemini-z-color !important;
33 | }
34 |
35 | .length-label {
36 | background: $gemini-length-color !important;
37 | }
38 |
39 | .x-axis-label {
40 | background: $gemini-x-color !important;
41 | }
42 |
43 | .y-axis-label {
44 | background: $gemini-y-color !important;
45 | }
46 |
47 | .z-axis-label {
48 | background: $gemini-z-color !important;
49 | }
50 |
--------------------------------------------------------------------------------
/src/css/preset.scss:
--------------------------------------------------------------------------------
1 | .canvas {
2 | width: 100%;
3 | height: 100%;
4 | position: absolute;
5 | }
6 |
7 | .camera-pivot-marker {
8 | color: #ffffff;
9 | line-height: 1.8;
10 | text-align: center;
11 | font-family: "monospace";
12 | font-weight: bold;
13 | position: absolute;
14 | width: 16px;
15 | height: 16px;
16 | border-radius: 15px;
17 | border: 2px solid #ebebeb;
18 | background: black;
19 | visibility: hidden;
20 | box-shadow: 3px 3px 9px 1px #000000;
21 | z-index: 1;
22 | pointer-events: none;
23 | opacity: 0.6;
24 | }
25 |
26 | /* ----------------------------------------------------------------------------------------------------------*/
27 | /* TreeViewPlugin (Put it in a separate css file later)
28 | /* ----------------------------------------------------------------------------------------------------------*/
29 | #treeViewContainer {
30 | visibility: hidden;
31 | pointer-events: all;
32 | height: 100%;
33 | overflow-y: scroll;
34 | overflow-x: hidden;
35 | position: absolute;
36 | background-color: rgba(255, 255, 255, 0.6);
37 | color: black;
38 | z-index: 1;
39 | float: left;
40 | left: 0;
41 | padding: 10px;
42 | font-family: "Roboto", sans-serif;
43 | font-size: 15px;
44 | text-align: left;
45 | user-select: none;
46 | -ms-user-select: none;
47 | -moz-user-select: none;
48 | -webkit-user-select: none;
49 | width: 350px;
50 | &:hover {
51 | background-color: rgba(255, 255, 255, 0.9);
52 | }
53 |
54 | ul {
55 | list-style: none;
56 | padding-left: 1.75em;
57 | pointer-events: none;
58 |
59 | li {
60 | position: relative;
61 | width: 500px;
62 | pointer-events: none;
63 | padding-top: 3px;
64 | padding-bottom: 3px;
65 | vertical-align: middle;
66 |
67 | a {
68 | background-color: #eee;
69 | border-radius: 50%;
70 | color: #000;
71 | display: inline-block;
72 | height: 1.5em;
73 | left: -1.5em;
74 | position: absolute;
75 | text-align: center;
76 | text-decoration: none;
77 | width: 1.5em;
78 | pointer-events: all;
79 | }
80 |
81 | a.plus {
82 | background-color: #ded;
83 | pointer-events: all;
84 | }
85 |
86 | a.minus {
87 | background-color: #eee;
88 | pointer-events: all;
89 | }
90 |
91 | a:active {
92 | top: 1px;
93 | pointer-events: all;
94 | }
95 |
96 | span:hover {
97 | color: white;
98 | cursor: pointer;
99 | background: black;
100 | padding-left: 2px;
101 | pointer-events: all;
102 | }
103 |
104 | span {
105 | display: inline-block;
106 | width: calc(100% - 50px);
107 | padding-left: 2px;
108 | pointer-events: all;
109 | height: 23px;
110 | }
111 | }
112 | }
113 |
114 | .highlighted-node {
115 | /* Appearance of node highlighted with TreeViewPlugin#showNode() */
116 | border: black solid 1px;
117 | background: yellow;
118 | color: black;
119 | padding-left: 1px;
120 | padding-right: 5px;
121 | pointer-events: all;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/css/toolbar.scss:
--------------------------------------------------------------------------------
1 | .toolbar {
2 | position: absolute;
3 | display: flex;
4 | height: 68px;
5 | left: 50%;
6 | bottom: 79px;
7 | padding: 8px 20px;
8 | border: $gemini-grey-o50 1px solid;
9 | border-radius: 4px;
10 | background: $gemini-white2;
11 | transform: translate(-50%, 0);
12 | align-items: center;
13 | z-index: 1;
14 |
15 | .toolbar-group {
16 | display: flex;
17 | padding: 0 2px;
18 | box-sizing: border-box;
19 | }
20 |
21 | .toolbar-group-division {
22 | width: 1px;
23 | background-color: $gemini-grey-o50;
24 | height: 60%;
25 | }
26 |
27 | .toolbar-menu {
28 | display: flex;
29 | flex-direction: column;
30 | width: 50px;
31 | height: 50px;
32 | padding: 3px 0;
33 | margin: 0 2px;
34 | transition: all 0.3s;
35 | cursor: pointer;
36 | color: $gemini-grey2;
37 | border-radius: 4px;
38 |
39 | &.active {
40 | color: $gemini-active;
41 | }
42 |
43 | .icon {
44 | width: 24px;
45 | height: 24px;
46 | margin: auto;
47 | font-size: 24px;
48 | line-height: normal;
49 | }
50 |
51 | span {
52 | display: block;
53 | width: 100%;
54 | margin: auto;
55 | font-size: 0.75em;
56 | text-align: center;
57 | }
58 | }
59 |
60 | .toolbar-menu:hover {
61 | color: $gemini-active;
62 | background-color: $gemini-white2;
63 | }
64 |
65 | .toolbar-parent-menu {
66 | position: relative;
67 | }
68 |
69 | .toolbar-parent-menu:hover {
70 | .toolbar-sub-menu {
71 | display: block;
72 | }
73 |
74 | .toolbar-sub-menu-list {
75 | background: $gemini-white2;
76 | }
77 | }
78 |
79 | .toolbar-parent-menu:after {
80 | content: "";
81 | position: absolute;
82 | width: 0;
83 | height: 0;
84 | right: 2px;
85 | top: 2px;
86 | border-width: 4px;
87 | border-style: solid;
88 | border-top-color: $gemini-grey2;
89 | border-right-color: $gemini-grey2;
90 | border-bottom-color: transparent;
91 | border-left-color: transparent;
92 | border-radius: 1px;
93 | }
94 |
95 | .toolbar-sub-menu {
96 | display: none;
97 | position: absolute;
98 | width: 50px;
99 | bottom: 100%;
100 |
101 | .toolbar-sub-menu-list {
102 | margin-bottom: 2px;
103 | border-radius: 4px;
104 |
105 | .toolbar-menu {
106 | margin: 0;
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/dxf-parser.d.ts:
--------------------------------------------------------------------------------
1 | declare module "dxf-parser";
2 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import "./css/main.scss";
2 | import { math } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
3 | export * from "./components";
4 | export * from "./core";
5 | export * from "./plugins";
6 | export * from "./services";
7 | export * from "./utils";
8 | export * from "./widgets";
9 | export { math };
10 |
--------------------------------------------------------------------------------
/src/plugins/BackgroundColorPlugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
3 | export interface BackgroundColorConfig {
4 | transparent?: boolean;
5 | backgroundColor?: number[];
6 | }
7 |
8 | /**
9 | * Background color modifier
10 | * Note that background color only work when transparent is false!
11 | */
12 | export class BackgroundColorPlugin extends Plugin {
13 | private _canvas: any; // eslint-disable-line
14 |
15 | constructor(viewer: any, cfg?: BackgroundColorConfig) { // eslint-disable-line
16 | super("BackgroundColorPlugin", viewer, cfg);
17 |
18 | this._canvas = this.viewer.scene.canvas;
19 |
20 | if (cfg) {
21 | if (cfg.transparent !== undefined) {
22 | this.setTransparent(cfg.transparent);
23 | }
24 | if (cfg.backgroundColor) {
25 | this.setBackgroundColor(cfg.backgroundColor);
26 | }
27 | }
28 | }
29 |
30 | setTransparent(transparent: boolean) {
31 | // this doesn't work yet, don't know why! (TODO: fix it)
32 | this._canvas.transparent = transparent;
33 | this.viewer.scene.render(true);
34 | }
35 |
36 | getTransparent(): boolean {
37 | return this._canvas.transparent;
38 | }
39 |
40 | setBackgroundColor(color: number[]) {
41 | this._canvas.backgroundColor = color;
42 | this.viewer.scene.render(true);
43 | }
44 |
45 | getBackgroundColor(): number[] | undefined {
46 | return this._canvas.backgroundColor;
47 | }
48 |
49 | destroy() {
50 | super.destroy();
51 | this._canvas = undefined;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/plugins/ComponentPropertyPlugin.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-this-alias */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | import { Plugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
4 |
5 | import { SingleSelectionPlugin } from "./SingleSelectionPlugin";
6 |
7 | export interface ComponentPropertyConfig {
8 | active?: boolean;
9 | singleSelectionControl: SingleSelectionPlugin;
10 | }
11 |
12 | export class ComponentPropertyPlugin extends Plugin {
13 | private _active = false;
14 | private _singleSelectionControl: SingleSelectionPlugin;
15 | private _lastEntity: any = undefined;
16 |
17 | constructor(viewer: any, cfg: ComponentPropertyConfig) { // eslint-disable-line
18 | super("ComponentProperty", viewer, cfg);
19 |
20 | this._active = !!cfg.active;
21 | this._singleSelectionControl = cfg.singleSelectionControl;
22 |
23 | this.attachEvent();
24 | }
25 |
26 | setActive(active: boolean) {
27 | if (this._active === active) {
28 | return;
29 | }
30 | this._active = active;
31 | if (this._lastEntity) {
32 | this._lastEntity.selected = false;
33 | }
34 | this.fire("active", this._active);
35 | }
36 |
37 | getActive(): boolean {
38 | return this._active;
39 | }
40 |
41 | destroy(): void {
42 | this.destroyEvent();
43 | super.destroy();
44 | }
45 |
46 | private attachEvent(): void {
47 | const scope = this;
48 | this._singleSelectionControl.on("picked", (entity: any) => {
49 | if (!scope._active) {
50 | return;
51 | }
52 | scope._lastEntity = entity;
53 | this.fire("picked", entity);
54 | console.log(`[ComponentPropertyPlugin] entity:${entity}`);
55 | });
56 | this._singleSelectionControl.on("pickedNothing", () => {
57 | if (!scope._active) {
58 | return;
59 | }
60 | this.fire("pickedNothing");
61 | });
62 | }
63 |
64 | private destroyEvent(): void {
65 | console.warn("[ComponentPropertyPlugin] The plugin base class does not provide an off method.");
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/plugins/DxfLoaderPlugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin, Node, PerformanceModel, utils } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { DxfPerformanceModelLoader } from "../core/DxfPerformanceModelLoader";
3 |
4 | interface DxfLoaderPluginConfig {
5 | id?: string;
6 | }
7 |
8 | interface DxfLoaderPluginParams {
9 | id?: string;
10 | src: string;
11 | // position?: number[];
12 | // scale?: number[];
13 | // rotation?: number[];
14 | // matrix?: number[];
15 | // backfaces?: boolean;
16 | // edgeThreshold?: number;
17 | edges?: boolean;
18 | saoEnabled?: boolean;
19 | performance?: boolean;
20 | //readableGeometry?: boolean;
21 | //handleGLTFNode?(modelId: string, glTFNode: any, actions: any): boolean; // eslint-disable-line
22 | }
23 |
24 | export class DxfLoaderPlugin extends Plugin {
25 | private performanceModelLoader: DxfPerformanceModelLoader;
26 | constructor(viewer: any, cfg?: DxfLoaderPluginConfig) { // eslint-disable-line
27 | super("DxfLoader", viewer, cfg);
28 |
29 | this.performanceModelLoader = new DxfPerformanceModelLoader();
30 | }
31 |
32 | load(params: DxfLoaderPluginParams) {
33 | if (params.id && this.viewer.scene.components[params.id]) {
34 | this.error(`[Dxf] Component with ID "${params.id}" already exists in viewer, will generate a new ID.`);
35 | delete params.id;
36 | }
37 |
38 | //TODO Ignore the non-performance mode for the moment
39 | const performance = params.performance !== false;
40 | params.edges = false;
41 | params.saoEnabled = false;
42 | const model = performance
43 | ? // PerformanceModel provides performance-oriented scene representation
44 | // converting glTF materials to simple flat-shading without textures
45 |
46 | new PerformanceModel(
47 | this.viewer.scene,
48 | utils.apply(params, {
49 | isModel: true,
50 | })
51 | )
52 | : // Scene Node graph supports original glTF materials
53 |
54 | new Node(
55 | this.viewer.scene,
56 | utils.apply(params, {
57 | isModel: true,
58 | })
59 | );
60 |
61 | const modelId = model.id; // In case ID was auto-generated
62 |
63 | if (!params.src) {
64 | this.error("[Dxf] 'src' or 'gltf' param expected for load() method.");
65 | return model; // Return new empty model
66 | }
67 |
68 | const loader = this.performanceModelLoader; //performance ? this._performanceModelLoader : this._sceneGraphLoader;
69 |
70 | // const fixedSuffix = "_";
71 | // if (!params.handleGLTFNode) {
72 | // params.handleGLTFNode = (modelId, glTFNode, actions) => {
73 | // const name = glTFNode.name;
74 | // let id = name;
75 | // if (!name) {
76 | // id = math.createUUID();
77 | // }
78 | // id = math.globalizeObjectId(modelId, id); // Add modelId prefix
79 |
80 | // let count = 1;
81 | // if (nodeIds.has(id)) {
82 | // const originalId = id;
83 | // count = nodeIds.get(originalId) as number;
84 | // id = originalId + fixedSuffix + count.toString();
85 | // count++;
86 | // nodeIds.set(originalId, count);
87 | // } else {
88 | // nodeIds.set(id, count);
89 | // }
90 |
91 | // actions.createEntity = {
92 | // // Create an Entity for this glTF scene node
93 | // id: id,
94 | // isObject: true, // Registers the Entity in Scene#objects
95 | // };
96 |
97 | // return true; // Continue descending this glTF node subtree
98 | // };
99 |
100 | const okFunc = () => {
101 | console.log(`[Dxf] ${params.src} loaded.`);
102 | };
103 |
104 | const errorFunc = (errorMsg?: string) => {
105 | console.error(`[Dxf] Failed to load ${params.src}, error: `, errorMsg);
106 | };
107 |
108 | // if (params.src) {
109 | loader.load(model, params.src, params, okFunc, errorFunc);
110 | // } else {
111 | //loader.parse(this, model, params.gltf, params, okFunc, errorFunc);
112 | //}
113 | //}
114 |
115 | model.once("destroyed", () => {
116 | this.viewer.metaScene.destroyMetaModel(modelId);
117 | });
118 |
119 | return model;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/plugins/EnhancedDistanceMeasurementPlugin.ts:
--------------------------------------------------------------------------------
1 | import { DistanceMeasurementsPlugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { MOUSEMOVE_EVENT, MOUSEUP_EVENT } from "../utils/Consts";
3 | import { Tooltip } from "../components/Tooltip";
4 |
5 | const DISTANCE_MEASUREMENT_TOOLTIP_ID = "distance-measurement-tooltip";
6 |
7 | export class EnhancedDistanceMeasurementPlugin extends DistanceMeasurementsPlugin {
8 | private _active = false;
9 | private _tooltip: Tooltip | undefined;
10 |
11 | // eslint-disable-next-line
12 | constructor(viewer: any, cfg: any = {}) {
13 | super(viewer, cfg);
14 |
15 | this.on("active", (active: boolean) => (active ? this.onActive() : this.onDeactive()));
16 | }
17 |
18 | get active(): boolean {
19 | return this._active;
20 | }
21 |
22 | set active(active: boolean) {
23 | if (this._active === active) {
24 | return;
25 | }
26 | this._active = active;
27 |
28 | this.fire("active", this._active);
29 | }
30 |
31 | private onActive = () => {
32 | this._control.activate();
33 | this.showAllmeasurements();
34 | this.attachEvents();
35 | this.createTooltip();
36 | };
37 |
38 | private onDeactive = () => {
39 | this._control.deactivate();
40 | this.hideAllmeasurements();
41 | this.destroyEvents();
42 | this.removeTooltip();
43 | };
44 |
45 | private changeCursor = () => {
46 | document.body.style.cursor = this._active ? "crosshair" : "default";
47 | };
48 |
49 | private changeStyle = () => {
50 | // eslint-disable-next-line
51 | Object.values(this._measurements).map((measurement: any) => {
52 | measurement._lengthWire._wire.classList.add("length-wire");
53 | measurement._xAxisWire._wire.classList.add("x-axis-wire");
54 | measurement._yAxisWire._wire.classList.add("y-axis-wire");
55 | measurement._zAxisWire._wire.classList.add("z-axis-wire");
56 |
57 | measurement._lengthLabel._label.classList.add("length-label");
58 | measurement._xAxisLabel._label.classList.add("x-axis-label");
59 | measurement._yAxisLabel._label.classList.add("y-axis-label");
60 | measurement._zAxisLabel._label.classList.add("z-axis-label");
61 | });
62 | };
63 |
64 | private attachEvents = () => {
65 | document.addEventListener(MOUSEMOVE_EVENT, this.changeCursor);
66 | document.addEventListener(MOUSEUP_EVENT, this.changeStyle);
67 | };
68 |
69 | private createTooltip = () => {
70 | this._tooltip = new Tooltip(DISTANCE_MEASUREMENT_TOOLTIP_ID, this.viewer.localeService.translate("Tooltip.measure"), {
71 | followPointer: true,
72 | parentNode: this._container,
73 | target: this.viewer.scene.canvas.canvas,
74 | });
75 | };
76 |
77 | private removeTooltip = () => {
78 | this._tooltip?.destroy();
79 | this._tooltip = undefined;
80 | };
81 |
82 | private hideAllmeasurements = () => {
83 | // eslint-disable-next-line
84 | Object.values(this._measurements).map((measurement: any) => {
85 | measurement.visible = false;
86 | });
87 | };
88 |
89 | private showAllmeasurements = () => {
90 | // eslint-disable-next-line
91 | Object.values(this._measurements).map((measurement: any) => {
92 | measurement.visible = true;
93 | });
94 | };
95 |
96 | private destroyEvents = () => {
97 | document.removeEventListener(MOUSEMOVE_EVENT, this.changeCursor);
98 | document.removeEventListener(MOUSEUP_EVENT, this.changeStyle);
99 | console.warn("[Measure] The plugin base class does not provide an off method.");
100 | };
101 |
102 | destroy = () => {
103 | this.destroyEvents();
104 | this.removeTooltip();
105 | super.destroy();
106 | };
107 | }
108 |
--------------------------------------------------------------------------------
/src/plugins/FullScreenPlugin.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | interface FullScreenConfig {
3 | active?: boolean;
4 | element?: HTMLElement;
5 | }
6 |
7 | export class FullScreenPlugin {
8 | private _active = false;
9 | private _element?: HTMLElement;
10 | private _fullScreenChangeListener?: any;
11 |
12 | constructor(cfg?: FullScreenConfig) {
13 | if (cfg) {
14 | this._active = cfg.active !== true;
15 | this._element = cfg.element;
16 | }
17 |
18 | //TODO.Other operations control full screen.
19 | document.addEventListener(
20 | "fullscreenchange",
21 | (this._fullScreenChangeListener = () => {
22 | this._active = !!document.fullscreenElement;
23 | console.log("[FullScreen] fullscreenchange:", this._active);
24 | })
25 | );
26 | }
27 |
28 | setElement(element: HTMLElement) {
29 | if (this._element && this._active) {
30 | console.log(`[FullScreen] When in full screen, element can't be switched`);
31 | return;
32 | }
33 | if (!this._element || this._element.id !== element.id) {
34 | this._element = element;
35 | }
36 | }
37 |
38 | setActive(active = false) {
39 | if (this._active === active) {
40 | return;
41 | }
42 |
43 | if (!this._element) {
44 | console.error(`[FullScreen] element is undefined`);
45 | return;
46 | }
47 | this._active = active;
48 |
49 | if (this._active) {
50 | this.enterFullScreen();
51 | } else {
52 | this.exitFullScreen();
53 | }
54 | }
55 |
56 | getActive() {
57 | return this._active;
58 | }
59 |
60 | private enterFullScreen() {
61 | if (!document.fullscreenElement && this._active) {
62 | this._element?.requestFullscreen();
63 | }
64 | }
65 |
66 | private exitFullScreen() {
67 | if (!this._active && document.fullscreenElement) {
68 | document.exitFullscreen();
69 | }
70 | }
71 |
72 | destroy() {
73 | if (this._fullScreenChangeListener) {
74 | document.removeEventListener("fullscreenchange", this._fullScreenChangeListener);
75 | this._fullScreenChangeListener = undefined;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/plugins/GridPlugin.ts:
--------------------------------------------------------------------------------
1 | import { buildGridGeometry, Mesh, PhongMaterial, Plugin, VBOGeometry } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
3 | // export type NoParamCallback = () => void;// eslint-disable-line
4 |
5 | // // eslint-disable-next-line @typescript-eslint/no-empty-function
6 | // const emptyCallBack: NoParamCallback = () => {};
7 |
8 | const GRID_ID = "grid";
9 | // TODO:It will be modified according to the actual situation.
10 | export interface GridMeshConfig {
11 | position: number[];
12 | pickable?: boolean;
13 | collidable?: boolean;
14 | }
15 |
16 | export interface GridGeometryConfig {
17 | size: number;
18 | division: number;
19 | }
20 |
21 | export interface GridConfig {
22 | active: boolean;
23 | gridMeshCfg?: GridMeshConfig;
24 | gridGeometryCfg?: GridGeometryConfig;
25 | gridMaterialCfg?: any; // eslint-disable-line
26 | }
27 |
28 | export class GridPlugin extends Plugin {
29 | private _gridMesh: any; // eslint-disable-line
30 | private _active: boolean;
31 | private _gridMeshCfg: GridMeshConfig;
32 | private _gridGeometryCfg: GridGeometryConfig;
33 | private _gridMaterialCfg: any; // eslint-disable-line
34 |
35 | constructor(viewer: any, cfg?: GridConfig) { // eslint-disable-line
36 | super("GridPlugin", viewer, cfg);
37 |
38 | this._active = cfg ? cfg.active : false;
39 | if (cfg && cfg.gridMeshCfg) {
40 | this._gridMeshCfg = cfg.gridMeshCfg;
41 | } else {
42 | this._gridMeshCfg = {
43 | position: [0, 0, 0],
44 | pickable: false,
45 | collidable: false,
46 | };
47 | }
48 |
49 | if (cfg && cfg.gridGeometryCfg) {
50 | this._gridGeometryCfg = cfg.gridGeometryCfg;
51 | } else {
52 | this._gridGeometryCfg = {
53 | size: 1000,
54 | division: 60,
55 | };
56 | }
57 |
58 | if (cfg && cfg.gridMaterialCfg) {
59 | this._gridMaterialCfg = cfg.gridMaterialCfg;
60 | } else {
61 | this._gridMaterialCfg = new PhongMaterial(viewer.scene, {
62 | color: [0.0, 0.0, 0.0],
63 | emissive: [0.4, 0.4, 0.4],
64 | alpha: 0.5,
65 | });
66 | }
67 |
68 | this._gridMesh = null;
69 |
70 | if (this._active) {
71 | this._createGrid();
72 | }
73 | }
74 |
75 | setActive(active: boolean) {
76 | if (this._active === active) {
77 | return;
78 | }
79 | this._active = active;
80 | this._controlGridActive();
81 | }
82 |
83 | getActive(): boolean {
84 | return this._active;
85 | }
86 |
87 | setMeshConfig(gridMeshCfg: GridMeshConfig): void {
88 | this._gridMeshCfg = gridMeshCfg;
89 | if (this._gridMesh) {
90 | this._gridMesh.position = gridMeshCfg.position;
91 | if (gridMeshCfg.pickable !== undefined) {
92 | this._gridMesh.pickable = gridMeshCfg.pickable;
93 | }
94 | if (gridMeshCfg.collidable !== undefined) {
95 | this._gridMesh.collidable = gridMeshCfg.collidable;
96 | }
97 | }
98 | }
99 |
100 | setMeshGeometryConfig(gridGeometryCfg: GridGeometryConfig): void {
101 | this._gridGeometryCfg = gridGeometryCfg;
102 | if (this._gridMesh) {
103 | const geometry = this._gridMesh.geometry;
104 | this._gridMesh.geometry = new VBOGeometry(
105 | this.viewer.scene,
106 | buildGridGeometry({
107 | size: gridGeometryCfg.size,
108 | divisions: gridGeometryCfg.division,
109 | })
110 | );
111 |
112 | geometry.destroy();
113 | }
114 | }
115 |
116 | destroy(): void {
117 | if (this._gridMesh) {
118 | this._gridMesh.geometry.destroy();
119 | this._gridMesh.material.destroy();
120 | this._gridMesh.destroy();
121 | this._gridMesh = null;
122 | }
123 | super.destroy();
124 | }
125 |
126 | private _controlGridActive(): void {
127 | // const scene=this.viewer.scene;// eslint-disable-line
128 | // if(scene.objects.hasOwnProperty(GRID_ID)){ // eslint-disable-line
129 | // const mesh = scene.objects[GRID_ID];
130 | if (this._gridMesh) {
131 | const oriVisibleState = this._gridMesh.visible;
132 | if (oriVisibleState !== this._active) {
133 | this._gridMesh.visible = this._active;
134 | }
135 | return;
136 | }
137 |
138 | if (this._active) {
139 | this._createGrid();
140 | }
141 | }
142 |
143 | private _createGrid(): void {
144 | const scene = this.viewer.scene;
145 | const geomtry = new VBOGeometry(
146 | scene,
147 | buildGridGeometry({
148 | size: this._gridGeometryCfg.size,
149 | divisions: this._gridGeometryCfg.division,
150 | })
151 | );
152 | this._gridMesh = new Mesh(scene, {
153 | id: GRID_ID,
154 | isObject: true,
155 | geometry: geomtry,
156 | material: this._gridMaterialCfg,
157 | position: this._gridMeshCfg.position,
158 | collidable: this._gridMeshCfg.collidable || false,
159 | pickable: this._gridMeshCfg.pickable || false,
160 | });
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/plugins/KeyBoardRotatePlugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
3 | type CallbackType = (event: T) => void;
4 | type KeyboardCallbackType = CallbackType;
5 |
6 | /**
7 | * Customize the keyboard rotation.
8 | * The default mode is orbit. Customize to the first people
9 | */
10 | export class KeyBoardRotatePlugin extends Plugin {
11 | private _documentKeyDownHandler: KeyboardCallbackType;
12 | private _documentKeyUpHandler: KeyboardCallbackType;
13 | private _cameraControl: any; // eslint-disable-line
14 | private _originNavMode: any; // eslint-disable-line
15 |
16 | constructor(viewer: any, cfg?: any) { // eslint-disable-line
17 | super("KeyBoardRotate", viewer, cfg);
18 |
19 | const cameraControl: any = viewer.cameraControl; // eslint-disable-line
20 | this._cameraControl = cameraControl;
21 |
22 | const scene: any = viewer.scene; // eslint-disable-line
23 | const configs = cameraControl._configs;
24 | const states = cameraControl._states;
25 | document.addEventListener(
26 | "keydown",
27 | (this._documentKeyDownHandler = (e: KeyboardEvent) => {
28 | const navMode = this._cameraControl.navMode;
29 | if (navMode !== "orbit") {
30 | return;
31 | }
32 |
33 | if (!(configs.active && configs.pointerEnabled) || !scene.input.keyboardEnabled) {
34 | return;
35 | }
36 | if (!states.mouseover) {
37 | return;
38 | }
39 | const keyCode = e.keyCode;
40 | const isRotateKey = this._isKeyForRotate(keyCode);
41 | if (isRotateKey) {
42 | this._cameraControl.navMode = "firstPerson";
43 | this._originNavMode = "orbit";
44 | }
45 | })
46 | );
47 |
48 | document.addEventListener(
49 | "keyup",
50 | (this._documentKeyUpHandler = (e: KeyboardEvent) => {
51 | if (this._originNavMode !== "orbit") {
52 | return;
53 | }
54 |
55 | if (!(configs.active && configs.pointerEnabled) || !scene.input.keyboardEnabled) {
56 | return;
57 | }
58 | if (!states.mouseover) {
59 | return;
60 | }
61 | const keyCode = e.keyCode;
62 |
63 | const isRotateKey = this._isKeyForRotate(keyCode);
64 | if (isRotateKey) {
65 | this._cameraControl.navMode = this._originNavMode;
66 | this._originNavMode = "";
67 | }
68 | })
69 | );
70 | }
71 |
72 | destroy() {
73 | document.removeEventListener("keydown", this._documentKeyDownHandler);
74 | document.removeEventListener("keyup", this._documentKeyUpHandler);
75 |
76 | super.destroy();
77 | }
78 |
79 | private _isKeyForRotate(keyCode: any): boolean {// eslint-disable-line
80 | const rotateYPos: boolean = this._isKeyForAction(this._cameraControl.ROTATE_Y_POS, keyCode);
81 | if (rotateYPos) {
82 | return true;
83 | }
84 | const rotateYNeg = this._isKeyForAction(this._cameraControl.ROTATE_Y_NEG, keyCode);
85 | if (rotateYNeg) {
86 | return true;
87 | }
88 | const rotateXPos = this._isKeyForAction(this._cameraControl.ROTATE_X_POS, keyCode);
89 | if (rotateXPos) {
90 | return true;
91 | }
92 | const rotateXNeg = this._isKeyForAction(this._cameraControl.ROTATE_X_NEG, keyCode);
93 |
94 | return rotateXNeg;
95 | }
96 |
97 | private _isKeyForAction(action: any, keyCode: any): boolean { // eslint-disable-line
98 | const keys = this._cameraControl.keyMap[action];
99 | if (!keys) {
100 | return false;
101 | }
102 | if (!keyCode) {
103 | return false;
104 | }
105 | for (let i = 0, len = keys.length; i < len; i++) {
106 | const key = keys[i];
107 | if (key == keyCode) {
108 | return true;
109 | }
110 | }
111 | return false;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/plugins/OrthoModePlugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
3 | export type NoParamCallback = () => void;
4 |
5 | export interface OrthoModeConfig {
6 | active: boolean;
7 | done?: NoParamCallback;
8 | }
9 |
10 | /**
11 | * Wraps single selection tool
12 | */
13 | export class OrthoModePlugin extends Plugin {
14 | private _active: boolean;
15 |
16 | constructor(viewer: any, cfg?: OrthoModeConfig) { // eslint-disable-line
17 | super("OrthoModePlugin", viewer, cfg);
18 |
19 | this._active = cfg ? cfg.active : false;
20 |
21 | if (this._active) {
22 | if (cfg && cfg.done) {
23 | this._enterOrthoMode(cfg.done);
24 | } else {
25 | this._enterOrthoMode();
26 | }
27 | }
28 | }
29 |
30 | setActive(active: boolean, done?: NoParamCallback) {
31 | if (this._active === active) {
32 | if (done) {
33 | done();
34 | }
35 | return;
36 | }
37 | this._active = active;
38 | if (active) {
39 | this._enterOthoMode(done);
40 | } else {
41 | this._exitOthoMode(done);
42 | }
43 | }
44 |
45 | getActive(): boolean {
46 | return this._active;
47 | }
48 |
49 | destroy(): void {
50 | super.destroy();
51 | }
52 |
53 | private _enterOthoMode(done?: NoParamCallback) {
54 | this._flyToEnterOrthoMode(() => {
55 | this.fire("active", this._active);
56 | done && done();
57 | });
58 | }
59 |
60 | private _exitOthoMode(done?: NoParamCallback) {
61 | this._flyToExitOrthoMode(() => {
62 | this.fire("active", this._active);
63 | done && done();
64 | });
65 | }
66 |
67 | private _flyToEnterOrthoMode(done?: NoParamCallback) {
68 | this.viewer.cameraFlight.flyTo({ projection: "ortho", duration: 0.5, aabb: this.viewer.scene.aabb }, done);
69 | }
70 |
71 | private _flyToExitOrthoMode(done?: NoParamCallback) {
72 | this.viewer.cameraFlight.flyTo(
73 | {
74 | projection: "perspective",
75 | duration: 0.5,
76 | aabb: this.viewer.scene.aabb,
77 | },
78 | done
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/plugins/PlanViewPlugin.ts:
--------------------------------------------------------------------------------
1 | import { math, Plugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 | import { ZoomToExtent } from "../core/ZoomToExtent";
3 | import { CameraConfig } from "../core/Configs";
4 |
5 | /**
6 | * 2d plan view plugin
7 | */
8 | export class PlanViewPlugin extends Plugin {
9 | private _active = false;
10 | private _zoomToExtent: ZoomToExtent;
11 | private _originalSettings: {
12 | navMode?: string;
13 | projection?: string;
14 | cameraCfg?: CameraConfig;
15 | navCubeVisible?: boolean;
16 | } = {};
17 |
18 | constructor(viewer: any) { // eslint-disable-line
19 | super("PlanViewPlugin", viewer, {});
20 |
21 | this._zoomToExtent = new ZoomToExtent(viewer);
22 | }
23 |
24 | setActive(active: boolean) {
25 | if (this._active === active) {
26 | return;
27 | }
28 | active ? this.enter2dMode() : this.exit2dMode();
29 | this._active = active;
30 | this.fire("active", active);
31 | }
32 |
33 | getActive(): boolean {
34 | return this._active;
35 | }
36 |
37 | // reference to: https://github.com/xeokit/xeokit-bim-viewer/blob/master/src/toolbar/ThreeDMode.js
38 | enter2dMode() {
39 | const os = this._originalSettings;
40 | os.navMode = this.viewer.cameraControl.navMode;
41 | if (os.navMode !== "planView") {
42 | this.viewer.cameraControl.navMode = "planView";
43 | }
44 | os.projection = this.viewer.camera.projection;
45 |
46 | const tempVec3a = math.vec3();
47 | const viewer = this.viewer;
48 | const scene = viewer.scene;
49 | const camera = scene.camera;
50 | const aabb = scene.getAABB(scene.visibleObjectIds);
51 | const look2 = math.getAABB3Center(aabb);
52 | const diag = math.getAABB3Diag(aabb);
53 | const fitFOV = 45; // fitFOV;
54 | const sca = Math.abs(diag / Math.tan(fitFOV * math.DEGTORAD));
55 | const orthoScale2 = diag * 1.3;
56 | const eye2 = tempVec3a;
57 |
58 | eye2[0] = look2[0] + camera.worldUp[0] * sca;
59 | eye2[1] = look2[1] + camera.worldUp[1] * sca;
60 | eye2[2] = look2[2] + camera.worldUp[2] * sca;
61 |
62 | const up2 = math.mulVec3Scalar(camera.worldForward, -1, []);
63 |
64 | viewer.cameraFlight.flyTo(
65 | {
66 | eye: eye2,
67 | look: look2,
68 | up: up2,
69 | orthoScale: orthoScale2,
70 | projection: "ortho",
71 | },
72 | () => {
73 | const navCubePlugin = this.viewer._plugins.find((plugin: any) => plugin.id === "NavCube"); // eslint-disable-line
74 | if (navCubePlugin && navCubePlugin.getVisible() === true) {
75 | os.navCubeVisible = true;
76 | navCubePlugin.setVisible(false);
77 | }
78 | }
79 | );
80 |
81 | this._zoomToExtent.setActive(true);
82 | }
83 |
84 | exit2dMode() {
85 | const os = this._originalSettings;
86 | this.viewer.cameraControl.navMode = os.navMode;
87 |
88 | const tempVec3a = math.vec3();
89 | const viewer = this.viewer;
90 | const scene = viewer.scene;
91 | const aabb = scene.getAABB(scene.visibleObjectIds);
92 | const diag = math.getAABB3Diag(aabb);
93 | const center = math.getAABB3Center(aabb, tempVec3a);
94 | const dist = Math.abs(diag / Math.tan(65.0 / 2));
95 | const camera = scene.camera;
96 | const dir = camera.yUp ? [-1, -1, -1] : [1, 1, 1];
97 | const up = camera.yUp ? [-1, 1, -1] : [-1, 1, 1];
98 |
99 | viewer.cameraControl.pivotPos = center;
100 |
101 | if (os.navCubeVisible) {
102 | const navCubePlugin = this.viewer._plugins.find((plugin: any) => plugin.id === "NavCube"); // eslint-disable-line
103 | if (navCubePlugin) {
104 | navCubePlugin.setVisible(true);
105 | }
106 | }
107 |
108 | viewer.cameraFlight.flyTo({
109 | look: center,
110 | eye: [center[0] - dist * dir[0], center[1] - dist * dir[1], center[2] - dist * dir[2]],
111 | up: up,
112 | orthoScale: diag * 1.3,
113 | duration: 1,
114 | projection: os.projection,
115 | });
116 |
117 | this._zoomToExtent.setActive(false);
118 | }
119 |
120 | destroy(): void {
121 | super.destroy();
122 | this._zoomToExtent.destroy();
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/plugins/SceneGraphTreeViewPlugin.ts:
--------------------------------------------------------------------------------
1 | import {
2 | // ModelTreeView,
3 | TreeViewPlugin,
4 | } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
5 |
6 | import { SceneGraphTreeView } from "../core/SceneGraphTreeView";
7 |
8 | interface SceneGraphTreeViewPluginConfig {
9 | containerElement: HTMLElement;
10 | //autoAddModels?: boolean;
11 | autoExpandDepth?: number;
12 | hierarchy?: string;
13 | sortNodes?: boolean;
14 | pruneEmptyNodes?: boolean;
15 | }
16 |
17 | interface TreeViewPluginConfig extends SceneGraphTreeViewPluginConfig {
18 | autoAddModels: boolean;
19 | }
20 |
21 | interface TreeViewAddModelConfig {
22 | rootName?: string;
23 | }
24 |
25 | export class SceneGraphTreeViewPlugin extends TreeViewPlugin {
26 | private _active: boolean;
27 |
28 | constructor(viewer: any, cfg: SceneGraphTreeViewPluginConfig) { // eslint-disable-line
29 | const treeViewPluginCfg: TreeViewPluginConfig = {
30 | containerElement: cfg.containerElement,
31 | autoExpandDepth: cfg.autoExpandDepth,
32 | hierarchy: cfg.hierarchy,
33 | sortNodes: cfg.sortNodes,
34 | pruneEmptyNodes: cfg.pruneEmptyNodes,
35 | autoAddModels: false, //must be false
36 | };
37 | super(viewer, treeViewPluginCfg);
38 |
39 | this._active = false;
40 | }
41 |
42 | addModel(modelId: string, options: TreeViewAddModelConfig) {
43 | if (!this._containerElement) {
44 | return;
45 | }
46 | const model = this.viewer.scene.models[modelId];
47 | if (!model) {
48 | throw "Model not found: " + modelId;
49 | }
50 |
51 | if (this._modelTreeViews[modelId]) {
52 | this.warn("Model already added: " + modelId);
53 | return;
54 | }
55 |
56 | let modelTreeView: SceneGraphTreeView | undefined = undefined;
57 | const metaModel = this.viewer.metaScene.metaModels[modelId];
58 | if (!metaModel) {
59 | // this.error("MetaModel not found: " + modelId);
60 | modelTreeView = new SceneGraphTreeView(this.viewer, this, model, {
61 | containerElement: this._containerElement,
62 | autoExpandDepth: this._autoExpandDepth,
63 | rootName: options.rootName,
64 | });
65 | } else {
66 | console.error("ModelTreeView mode not implemented yet!"); // TODO
67 | return;
68 | // modelTreeView = new ModelTreeView(this.viewer, this, model, metaModel, {
69 | // containerElement: this._containerElement,
70 | // autoExpandDepth: this._autoExpandDepth,
71 | // hierarchy: this._hierarchy,
72 | // sortNodes: this._sortNodes,
73 | // pruneEmptyNodes: this._pruneEmptyNodes,
74 | // rootName: options.rootName,
75 | // });
76 | }
77 | this._modelTreeViews[modelId] = modelTreeView;
78 | model.on("destroyed", () => {
79 | this.removeModel(model.id);
80 | });
81 | return modelTreeView;
82 | }
83 |
84 | setActive(active: boolean) {
85 | if (this._active === active) {
86 | return;
87 | }
88 | this._active = active;
89 |
90 | if (!this._active) {
91 | this._containerElement.style.visibility = "hidden";
92 | } else {
93 | this._containerElement.style.visibility = "visible";
94 | //metaModels
95 | const modelIds = Object.keys(this.viewer.metaScene.metaModels);
96 | for (let i = 0, len = modelIds.length; i < len; i++) {
97 | const modelId = modelIds[i];
98 | this.addModel(modelId, {});
99 | }
100 | //models
101 | const sceneModelIds = Object.keys(this.viewer.scene.models);
102 | for (let i = 0, len = sceneModelIds.length; i < len; i++) {
103 | const modelId = sceneModelIds[i];
104 | if (modelIds.includes(modelId)) {
105 | continue;
106 | }
107 | this.addModel(modelId, {});
108 | }
109 | }
110 | }
111 |
112 | getActive(): boolean {
113 | return this._active;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/plugins/SectionBoxPlugin.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { Plugin, SectionPlane } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
3 | import { BoxControl, BoxSectionPlaneType } from "../widgets/section/BoxControl";
4 | import { MathUtil } from "../utils/MathUtil";
5 |
6 | interface SectionBoxConfig {
7 | active: boolean;
8 | aabb?: number[];
9 | }
10 |
11 | const AABB_MAGNIFICATAION = 1.1;
12 |
13 | /**
14 | * An axis-aligned world-space clipping box. Don't consider rotation and translation of box.
15 | * Sectionplane's dir must be normalized
16 | */
17 | export class SectionBoxPlugin extends Plugin {
18 | private _active: boolean;
19 | private _aabb?: number[];
20 | private _sectionPlaneMap = new Map();
21 | private _control: BoxControl;
22 |
23 | constructor(viewer: any, cfg?: SectionBoxConfig) {
24 | super("SectionBox", viewer);
25 |
26 | this._active = !!(cfg && cfg.active);
27 | this._aabb = cfg && cfg.aabb?.slice();
28 |
29 | this._control = new BoxControl(this);
30 |
31 | this.initSectionBox();
32 |
33 | this.on("active", (active: boolean) => this.activeSectionBox(active));
34 | }
35 |
36 | set active(value: boolean) {
37 | if (this._active === value) {
38 | return;
39 | }
40 | this._active = value !== false;
41 | if (!this._active) {
42 | this.visible = false; // when not active, make sure to hide controls
43 | }
44 |
45 | this.fire("active", this._active);
46 | }
47 |
48 | get active() {
49 | return this._active;
50 | }
51 |
52 | // Controls the visibility of box
53 | set visible(value: boolean) {
54 | if (this._sectionPlaneMap.size === 0) {
55 | console.warn(`[SectionBox] These is no section planes`);
56 | return;
57 | }
58 | this._control.setVisible(value);
59 | }
60 |
61 | get visible() {
62 | return this._control.getVisible();
63 | }
64 |
65 | private activeSectionBox(active: boolean) {
66 | for (const plane of this._sectionPlaneMap.values()) {
67 | plane.active = active;
68 | }
69 | }
70 |
71 | private rebuildSectionBox() {
72 | const sectionPlaneMap = this._sectionPlaneMap;
73 | if (sectionPlaneMap.size === 0) {
74 | this.buildSectionPlanes();
75 | }
76 |
77 | const aabb = this._aabb as number[];
78 |
79 | for (const [key, sectionPlane] of sectionPlaneMap) {
80 | switch (key) {
81 | case BoxSectionPlaneType.RIGHT:
82 | case BoxSectionPlaneType.TOP:
83 | case BoxSectionPlaneType.FRONT:
84 | sectionPlane.pos = [aabb[3], aabb[4], aabb[5]];
85 | break;
86 | case BoxSectionPlaneType.LEFT:
87 | case BoxSectionPlaneType.BOTTOM:
88 | case BoxSectionPlaneType.BACK:
89 | sectionPlane.pos = [aabb[0], aabb[1], aabb[2]];
90 | break;
91 | default:
92 | break;
93 | }
94 | }
95 |
96 | this._control.rebuildBoxMesh(aabb);
97 | }
98 |
99 | get aabb() {
100 | return this._aabb as number[];
101 | }
102 |
103 | // Changes the aabb range of box
104 | set aabb(value: number[]) {
105 | if (value[3] < value[0] || value[4] < value[1] || value[5] < value[2]) {
106 | return;
107 | }
108 | this._aabb = [...value];
109 | MathUtil.expandAABB(this._aabb, AABB_MAGNIFICATAION);
110 | this.rebuildSectionBox();
111 | }
112 |
113 | reset() {
114 | const scene = this._viewer && this._viewer.scene;
115 | if (scene) {
116 | this.aabb = scene.getAABB(scene.visibleObjectIds);
117 | }
118 | }
119 |
120 | private initSectionBox() {
121 | if (this._sectionPlaneMap.size > 0) {
122 | return;
123 | }
124 |
125 | if (!this._aabb) {
126 | this._aabb = [...this.viewer.scene.aabb];
127 | }
128 | }
129 |
130 | private buildSectionPlanes() {
131 | if (this._sectionPlaneMap.size > 0) {
132 | return;
133 | }
134 | const active = this._active;
135 | const aabb = this._aabb as number[];
136 |
137 | const createSectionPlane = (id: BoxSectionPlaneType, pos: number[], dir: number[]) => {
138 | const plane = new SectionPlane(this.viewer.scene, { id, pos, dir, active });
139 | this._sectionPlaneMap.set(id, plane);
140 | };
141 | createSectionPlane(BoxSectionPlaneType.RIGHT, [aabb[3], aabb[4], aabb[5]], [-1, 0, 0]); // positive x axis
142 | createSectionPlane(BoxSectionPlaneType.TOP, [aabb[3], aabb[4], aabb[5]], [0, -1, 0]); // positive y axis
143 | createSectionPlane(BoxSectionPlaneType.FRONT, [aabb[3], aabb[4], aabb[5]], [0, 0, -1]); // positive z axis
144 | createSectionPlane(BoxSectionPlaneType.LEFT, [aabb[0], aabb[1], aabb[2]], [1, 0, 0]); // negative x axis
145 | createSectionPlane(BoxSectionPlaneType.BOTTOM, [aabb[0], aabb[1], aabb[2]], [0, 1, 0]); // negative y axis
146 | createSectionPlane(BoxSectionPlaneType.BACK, [aabb[0], aabb[1], aabb[2]], [0, 0, 1]); // negative z axis
147 |
148 | this._control.initSectionPlanes(this._sectionPlaneMap, aabb);
149 | this.visible = active;
150 | }
151 |
152 | private destroySectionPlane() {
153 | for (const plane of this._sectionPlaneMap.values()) {
154 | plane.destroy();
155 | }
156 |
157 | this._sectionPlaneMap.clear();
158 | }
159 |
160 | destroy() {
161 | this.destroySectionPlane();
162 | if (this._control) {
163 | this._control.destroy();
164 | }
165 | super.destroy();
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/plugins/SectionCullPlanePlugin.ts:
--------------------------------------------------------------------------------
1 | import { SectionPlanesPlugin, math } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
3 | export interface SectionOverviewConfig {
4 | overviewCanvasId?: string; // show cull plane overview
5 | overviewVisible?: boolean;
6 | }
7 |
8 | export interface SectionCullPlaneConfig {
9 | active: boolean;
10 | overviewCfg?: SectionOverviewConfig;
11 | }
12 |
13 | /**
14 | * This is a wrapper of section cull plane.
15 | * TODO
16 | */
17 | export class SectionCullPlanePlugin extends SectionPlanesPlugin {
18 | private _onMouseClicked: any; // eslint-disable-line
19 | private _active: boolean;
20 |
21 | constructor(viewer: any, cfg?: SectionCullPlaneConfig) { // eslint-disable-line
22 | const active = cfg ? cfg.active : false;
23 | const overviewCfg = { overviewCanvasId: "", overviewVisible: active };
24 | if (cfg && cfg.overviewCfg) {
25 | if (cfg.overviewCfg.overviewCanvasId !== undefined) {
26 | overviewCfg.overviewCanvasId = cfg.overviewCfg.overviewCanvasId;
27 | }
28 | if (cfg.overviewCfg.overviewVisible !== undefined) {
29 | overviewCfg.overviewVisible = cfg.overviewCfg.overviewVisible;
30 | }
31 | }
32 | super(viewer, overviewCfg);
33 |
34 | this.setOverviewVisible(active);
35 |
36 | this._active = active;
37 |
38 | this.initSectionMode();
39 | }
40 |
41 | setActive(active: boolean) {
42 | if (this._active === active) {
43 | return;
44 | }
45 | this._active = active;
46 |
47 | this.setOverviewVisible(this._active);
48 | if (!this._active) {
49 | this.hideControl();
50 | this.clear();
51 | }
52 | }
53 |
54 | getActive(): boolean {
55 | return this._active;
56 | }
57 |
58 | destroy(): void {
59 | this.destroyEvent();
60 | super.destroy();
61 | }
62 |
63 | private initSectionMode(): void {
64 | if (this._onMouseClicked) {
65 | return;
66 | }
67 | this._onMouseClicked = this.viewer.scene.input.on("mouseclicked", (coords: any) => { // eslint-disable-line
68 | if (!this.getActive()) {
69 | return;
70 | }
71 |
72 | const pickResult = this.viewer.scene.pick({
73 | canvasPos: coords,
74 | pickSurface: true, // <<------ This causes picking to find the intersection point on the entity
75 | });
76 |
77 | if (pickResult) {
78 | const sectionPlane = this.createSectionPlane({
79 | pos: pickResult.worldPos,
80 | dir: math.mulVec3Scalar(pickResult.worldNormal, -1),
81 | });
82 |
83 | this.showControl(sectionPlane.id);
84 | }
85 | });
86 | }
87 |
88 | private destroyEvent(): void {
89 | if (this._onMouseClicked) {
90 | this.viewer.scene.input.off(this._onMouseClicked);
91 | this._onMouseClicked = undefined;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/plugins/SingleSelectionPlugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin } from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
3 | type PickCallback = (pickResult: any) => void; // eslint-disable-line
4 |
5 | // eslint-disable-next-line @typescript-eslint/no-empty-function
6 | const emptyCallBack: PickCallback = () => {};
7 |
8 | export interface SingleSelectionConfig {
9 | active: boolean;
10 | callback?: PickCallback;
11 | }
12 |
13 | /**
14 | * wrap single selection tool
15 | */
16 | export class SingleSelectionPlugin extends Plugin {
17 | private _lastEntity: any; // eslint-disable-line
18 | private _cameraControlSubIds: number[] = [];
19 | private _pickCallback: PickCallback;
20 | private _active: boolean;
21 |
22 | constructor(viewer: any, cfg?: SingleSelectionConfig) { // eslint-disable-line
23 | super("SingleSelectionTool", viewer, cfg);
24 |
25 | this._active = cfg ? cfg.active : false;
26 |
27 | this._pickCallback = cfg && cfg.callback ? cfg.callback : emptyCallBack; // customize pick callback event
28 |
29 | this._lastEntity = null;
30 |
31 | if (this._active) {
32 | this.onPickEvent();
33 | }
34 |
35 | this.on("active", (active: boolean) => {
36 | if (active) {
37 | this.onPickEvent();
38 | } else {
39 | this.destroyEvent();
40 | }
41 | });
42 | }
43 |
44 | setActive(active: boolean) {
45 | if (this._active === active) {
46 | return;
47 | }
48 | this._active = active;
49 | if (this._lastEntity) {
50 | this._lastEntity.selected = false;
51 | }
52 | this.fire("active", this._active);
53 | }
54 |
55 | getActive(): boolean {
56 | return this._active;
57 | }
58 |
59 | destroy(): void {
60 | this.destroyEvent();
61 | super.destroy();
62 | }
63 |
64 | private onPickEvent(): void {
65 | const cameraControl = this.viewer.cameraControl;
66 | const picked = cameraControl.on("picked", (pickResult: any) => { // eslint-disable-line
67 | if (!this._active) {
68 | return;
69 | }
70 | this._pickCallback(pickResult);
71 |
72 | if (!pickResult.entity) {
73 | return;
74 | }
75 |
76 | console.log(`[SingleSelection] picked ID: ${pickResult.entity.id}`);
77 | if (!this._lastEntity || pickResult.entity.id !== this._lastEntity.id) {
78 | if (this._lastEntity) {
79 | this._lastEntity.selected = false;
80 | }
81 |
82 | this._lastEntity = pickResult.entity;
83 |
84 | // TODO: highlighted or selected
85 | pickResult.entity.selected = true;
86 | //pickResult.entity.highlighted=true;
87 | this.fire("picked", pickResult.entity);
88 | } else {
89 | pickResult.entity.selected = !pickResult.entity.selected;
90 | if (!pickResult.entity.selected) {
91 | this._lastEntity = undefined;
92 | this.fire("pickedNothing");
93 | }
94 | }
95 | },
96 | this
97 | );
98 |
99 | const pickNone = cameraControl.on(
100 | "pickedNothing",
101 | () => {
102 | if (!this._active) {
103 | return;
104 | }
105 | if (this._lastEntity) {
106 | this._lastEntity.selected = false;
107 | }
108 |
109 | this.fire("pickedNothing");
110 | },
111 | this
112 | );
113 |
114 | this._cameraControlSubIds.push(picked, pickNone);
115 | }
116 |
117 | private destroyEvent(): void {
118 | this._cameraControlSubIds.forEach((subId: number) => this.viewer.cameraControl.off(subId));
119 | this._cameraControlSubIds = [];
120 |
121 | if (this._lastEntity) {
122 | this._lastEntity.selected = false;
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./AxisSectionPlanePlugin";
2 | export * from "./BackgroundColorPlugin";
3 | export * from "./ComponentPropertyPlugin";
4 | export * from "./CustomizedGLTFLoaderPlugin";
5 | export * from "./DxfLoaderPlugin";
6 | export * from "./EnhancedDistanceMeasurementPlugin";
7 | export * from "./FullScreenPlugin";
8 | export * from "./GridPlugin";
9 | export * from "./KeyBoardRotatePlugin";
10 | export * from "./OrthoModePlugin";
11 | export * from "./PlanViewPlugin";
12 | export * from "./SceneGraphTreeViewPlugin";
13 | export * from "./SectionBoxPlugin";
14 | export * from "./SectionCullPlanePlugin";
15 | export * from "./SectionPlanePlugin";
16 | export * from "./SingleSelectionPlugin";
17 |
--------------------------------------------------------------------------------
/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./FontManager";
2 |
--------------------------------------------------------------------------------
/src/utils/CommonUtils.ts:
--------------------------------------------------------------------------------
1 | export class CommonUtils {
2 | /**
3 | * Converts a number to a string with proper fraction digits
4 | */
5 | static numberToString(num: number): string {
6 | // const isPositive = num > 0;
7 | const posNum = Math.abs(num);
8 | if (posNum < 0.0001) {
9 | return num.toString();
10 | }
11 | let fractionDigits = 2;
12 | if (posNum < 0.01) {
13 | fractionDigits = 4;
14 | } else if (posNum < 0.1) {
15 | fractionDigits = 3;
16 | }
17 | return num.toFixed(fractionDigits);
18 | }
19 |
20 | /**
21 | * Converts numbers to a string with proper fraction digits
22 | */
23 | static numbersToString(nums: number[]): string {
24 | return nums.map((num: number) => this.numberToString(num)).join(", ");
25 | }
26 |
27 | static joinStrings(...args: string[]) {
28 | return args.join("");
29 | }
30 | }
31 |
32 | export const addPrefix =
33 | (prefix: string, concat = "-") =>
34 | (str: string) =>
35 | `${prefix}${concat}${str}`;
36 |
--------------------------------------------------------------------------------
/src/utils/Consts.ts:
--------------------------------------------------------------------------------
1 | export const KEYDOWN_EVENT = "keydown";
2 | export const KEYUP_EVENT = "keyup";
3 | export const MOUSEMOVE_EVENT = "mousemove";
4 | export const MOUSEUP_EVENT = "mouseup";
5 | export const MOUSEDOWN_EVENT = "mousedown";
6 |
7 | export const ESC_KEY = "Escape";
8 | export const ENTER_KEY = "Enter";
9 |
10 | export const ICON_FONT_CLASS = "gemini-viewer-icon";
11 |
12 | export const AXIS_SECTION_PLANE_ID = "axis-section-plane";
13 | export const AXIS_SECTION_PLANE_CONTROL_ID = "axis-section-plane-control";
14 | export const SECTION_PLANE_ID = "section-plane";
15 | export const SECTION_PLANE_CONTROL_ID = "section-plane-control";
16 | export const SECTION_BOX_ID = "section-box";
17 |
--------------------------------------------------------------------------------
/src/utils/GeometryUtils.ts:
--------------------------------------------------------------------------------
1 | export interface BuildEllipseGeometryConfig {
2 | center?: number[];
3 | xRadius?: number;
4 | yRadius?: number;
5 | startAngle: number;
6 | endAngle: number;
7 | segments?: number;
8 | }
9 |
10 | export interface BuildPlaneGeometryConfig {
11 | width?: number;
12 | height?: number;
13 | isClockwise?: boolean;
14 | }
15 |
16 | export interface BuildPlanePositionConfig {
17 | left: number;
18 | right: number;
19 | bottom: number;
20 | top: number;
21 | }
22 |
23 | export class GeometryUtils {
24 | //Get the ellipse curve on the y plane
25 | static buildEllipseGeometry(cfg: BuildEllipseGeometryConfig) {
26 | let xRadius = cfg.xRadius || 1;
27 | if (xRadius < 0) {
28 | console.error("[GeometryUtil] xRadius shouldn't be negative, will use its absolute value.");
29 | xRadius *= -1;
30 | }
31 |
32 | let yRadius = cfg.yRadius || 1;
33 | if (yRadius < 0) {
34 | console.error("[GeometryUtil] yRadius shouldn't be negative, will use its absolute value.");
35 | yRadius *= -1;
36 | }
37 |
38 | let segments = cfg.segments || 32;
39 | if (segments < 0) {
40 | console.error("[GeometryUtil] segments shouldn't be negative, will use its absolute value.");
41 | segments *= -1;
42 | }
43 | if (segments < 3) {
44 | segments = 3;
45 | }
46 |
47 | const twoPi = Math.PI * 2;
48 | let deltaAngle = cfg.endAngle - cfg.startAngle;
49 | const samePoints = Math.abs(deltaAngle) < Number.EPSILON;
50 |
51 | // ensures that deltaAngle is 0 .. 2 PI
52 | while (deltaAngle < 0) {
53 | deltaAngle += twoPi;
54 | }
55 | while (deltaAngle > twoPi) {
56 | deltaAngle -= twoPi;
57 | }
58 |
59 | if (deltaAngle < Number.EPSILON) {
60 | if (samePoints) {
61 | deltaAngle = 0;
62 | } else {
63 | deltaAngle = twoPi;
64 | }
65 | }
66 |
67 | const perSegmentAngle = deltaAngle / segments;
68 |
69 | const center = cfg.center;
70 | const centerX = center ? center[0] : 0;
71 | const centerY = center ? center[1] : 0;
72 | const centerZ = center ? center[2] : 0;
73 |
74 | let x: number, z: number;
75 | let angle: number;
76 | const positions: number[] = [];
77 | const indices: number[] = [];
78 |
79 | for (let i = 0; i <= segments; i++) {
80 | angle = cfg.startAngle + i * perSegmentAngle;
81 | x = xRadius * Math.cos(angle);
82 | z = yRadius * Math.sin(angle);
83 |
84 | positions.push(x + centerX);
85 | positions.push(centerY);
86 | positions.push(z + centerZ);
87 |
88 | if (i != segments) {
89 | indices.push(i, i + 1);
90 | }
91 | }
92 |
93 | //TODO Consider a triangular index
94 |
95 | return {
96 | positions: positions,
97 | indices: indices,
98 | };
99 | }
100 | //Generate plane geometry information on z=0 and center=[0,0]
101 | static buildPlaneGeometry(cfg: BuildPlaneGeometryConfig) {
102 | let width = cfg.width || 1;
103 | if (width < 0) {
104 | console.error("[GeometryUtil] Width shouldn't be negative, will use its absolute value.");
105 | width *= -1;
106 | }
107 |
108 | let height = cfg.height || 1;
109 | if (height < 0) {
110 | console.error("[GeometryUtil] Height shouldn't be negative, will use its absolute value.");
111 | height *= -1;
112 | }
113 | const halfWidth = width / 2.0;
114 | const halfHeight = height / 2.0;
115 | const zValue = 0.0;
116 | const positions: number[] = [];
117 | //right top
118 | positions.push(halfWidth, halfHeight, zValue);
119 | //right bottom
120 | positions.push(halfWidth, -halfHeight, zValue);
121 | //left bottom
122 | positions.push(-halfWidth, -halfHeight, zValue);
123 | //left top
124 | positions.push(-halfWidth, halfHeight, zValue);
125 |
126 | let isClockwise = false;
127 | if (cfg.isClockwise !== undefined) {
128 | isClockwise = cfg.isClockwise;
129 | }
130 | const indices = [0, 1, 2, 2, 3, 0];
131 | if (!isClockwise) {
132 | indices.reverse();
133 | }
134 | const normals = [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1];
135 | const primitive = "triangles";
136 | return {
137 | primitive,
138 | positions,
139 | indices,
140 | normals,
141 | };
142 | }
143 |
144 | //Generate plane geometry information on z=0
145 | static buildPlanePosition(cfg: BuildPlanePositionConfig) {
146 | const right = Math.max(cfg.right, cfg.left);
147 | const left = Math.min(cfg.right, cfg.left);
148 |
149 | const top = Math.max(cfg.top, cfg.bottom);
150 | const bottom = Math.min(cfg.top, cfg.bottom);
151 |
152 | const zValue = 0.0;
153 | const positions: number[] = [];
154 | //right top
155 | positions.push(right, top, zValue);
156 | //right bottom
157 | positions.push(right, bottom, zValue);
158 | //left bottom
159 | positions.push(left, bottom, zValue);
160 | //left top
161 | positions.push(left, top, zValue);
162 |
163 | return positions;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/utils/Locale.ts:
--------------------------------------------------------------------------------
1 | export const en = {
2 | // English
3 | NavCube: {
4 | front: "Front",
5 | back: "Back",
6 | top: "Top",
7 | bottom: "Bottom",
8 | left: "Left",
9 | right: "Right",
10 | },
11 | ContextMenu: {
12 | viewFitAll: "View Fit All",
13 | hideAll: "Hide All",
14 | showAll: "Show All",
15 | xRayAll: "X-Ray",
16 | xRayNone: "X-Ray None",
17 | selectNone: "Select None",
18 | resetView: "Reset View",
19 | viewFitEntity: "View Fit",
20 | hideEntity: "Hide",
21 | hideOthers: "Hide Others",
22 | xRayEntity: "X-Ray",
23 | xRayOthers: "X-Ray Others",
24 | select: "Select",
25 | deselect: "Undo Select",
26 | showSectionPlane: "Show Section Plane",
27 | showSectionBox: "Show Section Box",
28 | showAxisSection: "Show Axis Section",
29 | hideSectionPlane: "Hide Section Plane",
30 | hideSectionBox: "Hide Section Box",
31 | hideAxisSection: "Hide Axis Section",
32 | undoSection: "Undo Section",
33 | },
34 | Toolbar: {
35 | homeView: "Home",
36 | orthoView: "Ortho View",
37 | measurement: "Measure",
38 | distanceMeasurement: "Distance Measurement",
39 | areaMeasurement: "Area Measurement",
40 | clearMeasurement: "Clear Measurement",
41 | section: "Section",
42 | axisSection: "Axis Section",
43 | pickSectionPlane: "Pick Section Plane",
44 | sectionBox: "Section Box",
45 | bimTree: "BIM Tree",
46 | viewpoint: "Viewpoint",
47 | annotation: "Annotation",
48 | property: "Property",
49 | settings: "Settings",
50 | fullscreen: "Full Screen",
51 | },
52 | Tooltip: {
53 | measure: "Pick a point to continue measuring, pressing ESC could exit measure mode.",
54 | section: "Click to pick a section plane",
55 | },
56 | PopPanel: {
57 | reset: "Reset",
58 | },
59 | };
60 |
61 | export const cn = {
62 | // Chinese
63 | NavCube: {
64 | front: "前",
65 | back: "后",
66 | top: "上",
67 | bottom: "下",
68 | left: "左",
69 | right: "右",
70 | },
71 | ContextMenu: {
72 | viewFitAll: "缩放视口到所有模型",
73 | hideAll: "全部隐藏",
74 | showAll: "全部显示",
75 | xRayAll: "应用 X 光模式",
76 | xRayNone: "清除 X 光模式",
77 | selectNone: "清空选择",
78 | resetView: "重置视图",
79 | viewFitEntity: "缩放视口到实体",
80 | hideEntity: "隐藏",
81 | hideOthers: "隐藏其他 (隔离)",
82 | xRayEntity: "应用 X 光模式",
83 | xRayOthers: "对其他实体应用 X 光模式",
84 | select: "选择",
85 | deselect: "取消选择",
86 | showSectionPlane: "显示剖切面",
87 | showSectionBox: "显示剖切盒",
88 | showAxisSection: "显示轴向剖切",
89 | hideSectionPlane: "隐藏剖切面",
90 | hideSectionBox: "隐藏剖切盒",
91 | hideAxisSection: "隐藏轴向剖切",
92 | undoSection: "取消剖切",
93 | },
94 | Toolbar: {
95 | homeView: "主视图",
96 | orthoView: "正交视图",
97 | measurement: "测量",
98 | distanceMeasurement: "距离测量",
99 | areaMeasurement: "面积测量",
100 | clearMeasurement: "清除测量",
101 | section: "剖切",
102 | axisSection: "轴向剖切",
103 | pickSectionPlane: "拾取面剖切",
104 | sectionBox: "剖切盒",
105 | bimTree: "BIM树",
106 | viewpoint: "视点",
107 | annotation: "批注",
108 | property: "属性",
109 | settings: "配置",
110 | fullscreen: "全屏",
111 | },
112 | Tooltip: {
113 | measure: "点击继续测量, ESC 取消测量",
114 | section: "点击确定剖切面",
115 | },
116 | PopPanel: {
117 | reset: "重置",
118 | },
119 | };
120 |
--------------------------------------------------------------------------------
/src/utils/MathUtil.ts:
--------------------------------------------------------------------------------
1 | export class MathUtil {
2 | // TODO: this is not a helper class, it should be util class instead
3 | static expandAABB(aabb: number[], scale: number) {
4 | let xWidth = aabb[3] - aabb[0];
5 | let yWidth = aabb[4] - aabb[1];
6 | let zWidth = aabb[5] - aabb[2];
7 | const center = [(aabb[3] + aabb[0]) / 2.0, (aabb[4] + aabb[1]) / 2.0, (aabb[5] + aabb[2]) / 2.0];
8 |
9 | xWidth *= scale;
10 | yWidth *= scale;
11 | zWidth *= scale;
12 |
13 | aabb[0] = center[0] - xWidth / 2.0;
14 | aabb[3] = center[0] + xWidth / 2.0;
15 | aabb[1] = center[1] - yWidth / 2.0;
16 | aabb[4] = center[1] + yWidth / 2.0;
17 | aabb[2] = center[2] - zWidth / 2.0;
18 | aabb[5] = center[2] + zWidth / 2.0;
19 |
20 | return aabb;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/ObjectUtil.ts:
--------------------------------------------------------------------------------
1 | export class ObjectUtil {
2 | // TODO: this is not a helper class, it should be util class instead
3 | static getKeyValue(obj: T, key: K): T[K] {
4 | return obj[key];
5 | }
6 |
7 | /**
8 | * Deep clone function for TypeScript.
9 | * @param T Generic type of target/copied value.
10 | * @param target Target value to be copied.
11 | * @see Source project, ts-deeply https://github.com/ykdr2017/ts-deepcopy
12 | * @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
13 | */
14 | static deepClone(target: T): T {
15 | if (target === null) {
16 | return target;
17 | }
18 | if (target instanceof Date) {
19 | return new Date(target.getTime()) as any; // eslint-disable-line
20 | }
21 | // First part is for array and second part is for Realm.Collection
22 | // if (target instanceof Array || typeof (target as any).type === 'string') {
23 | if (typeof target === "object") {
24 | if (typeof (target as { [key: string]: any })[(Symbol as any).iterator] === "function") { // eslint-disable-line
25 | const cp = [] as any[]; // eslint-disable-line
26 | if ((target as any as any[]).length > 0) { // eslint-disable-line
27 | for (const arrayMember of target as any as any[]) { // eslint-disable-line
28 | cp.push(ObjectUtil.deepClone(arrayMember));
29 | }
30 | }
31 | return cp as any as T; // eslint-disable-line
32 | } else {
33 | const targetKeys = Object.keys(target);
34 | const cp = {} as { [key: string]: any }; // eslint-disable-line
35 | if (targetKeys.length > 0) {
36 | for (const key of targetKeys) {
37 | cp[key] = ObjectUtil.deepClone((target as { [key: string]: any })[key]); // eslint-disable-line
38 | }
39 | }
40 | return cp as T;
41 | }
42 | }
43 | // Means that object is atomic
44 | return target;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./CommonUtils";
2 | export * from "./Consts";
3 | export * from "./ContextMenuUtils";
4 | export * from "./GeometryUtils";
5 | export * from "./Locale";
6 | export * from "./MathUtil";
7 | export * from "./ObjectUtil";
8 |
--------------------------------------------------------------------------------
/src/widgets/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./section";
2 | export * from "./toolbar";
3 |
--------------------------------------------------------------------------------
/src/widgets/section/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./BoxControl";
2 | export * from "./PlaneControl";
3 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/AxisSectionPlaneController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * SectionPlaneController
5 | */
6 | export class AxisSectionPlaneController extends ToolbarMenuBaseController {
7 | /**
8 | * onActive can be triggered by 'click' event, also can be triggered by other events, e.g. anohter conflicted button is clicked.
9 | */
10 | protected onActive(active: boolean) {
11 | super.onActive(active);
12 | this.bimViewer.suppressSingleSelection(active);
13 | this.bimViewer.activeAxisSectionPlane(active);
14 | //Reset. Remove the last result
15 | if (!active) {
16 | this.bimViewer.axisSectionPlanePlugin.reset();
17 | }
18 | if (this.parent) {
19 | this.parent.setActive(active); // also update parent's status
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/BimTreeController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * BimTreeController
5 | */
6 | export class BimTreeController extends ToolbarMenuBaseController {
7 | protected onClick(event: Event) {
8 | super.onClick(event);
9 | this.bimViewer.activeBimTree(this._active);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/FullScreenController.ts:
--------------------------------------------------------------------------------
1 | import { Controller } from "../../core/Controller";
2 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
3 | import { ToolbarMenuConfig } from "./ToolbarConfig";
4 |
5 | /**
6 | * FullScreenController
7 | */
8 | export class FullScreenController extends ToolbarMenuBaseController {
9 | constructor(parent: Controller, cfg: ToolbarMenuConfig, node: HTMLElement) {
10 | super(parent, cfg, node);
11 | document.addEventListener("fullscreenchange", () => {
12 | this._active = !!document.fullscreenElement;
13 | this.fire("active", this._active); // trigger it to add/remove corresponding css class in base class
14 | });
15 | }
16 |
17 | protected onClick(event: Event) {
18 | super.onClick(event);
19 | this.bimViewer.activeFullScreen(this._active);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/HomeViewController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * HomeViewController
5 | * Reference to: https://github.com/xeokit/xeokit-bim-viewer/blob/master/src/toolbar/ResetAction.js
6 | */
7 | export class HomeViewController extends ToolbarMenuBaseController {
8 | /**
9 | * HomeViewController doesn't need 'active' state, we use it to improve user experience.
10 | */
11 | protected onActive(active: boolean) {
12 | super.onActive(active);
13 | if (active) {
14 | const self = this; // eslint-disable-line
15 | setTimeout(() => self.setActive(false), 300); // de-active it after 0.3s
16 | }
17 | }
18 |
19 | protected onClick(event: Event) {
20 | super.onClick(event);
21 | this.bimViewer.goToHomeView();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/MeasureAreaController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * MeasureAreaController
5 | */
6 | export class MeasureAreaController extends ToolbarMenuBaseController {
7 | protected onClick(event: Event) {
8 | if (!this.getEnabled()) {
9 | return;
10 | }
11 | super.onClick(event);
12 | this.onActive(this._active);
13 | }
14 |
15 | /**
16 | * onActive can be triggered by 'click' event, also can be triggered by other events, e.g. anohter conflicted button is clicked.
17 | */
18 | protected onActive(active: boolean) {
19 | super.onActive(active);
20 | // this.bimViewer.activeAreaMeasurement(active);
21 | if (this.parent) {
22 | this.parent.setActive(active); // also update parent's status
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/MeasureClearController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * MeasureClearController
5 | */
6 | export class MeasureClearController extends ToolbarMenuBaseController {
7 | protected onActive(active: boolean) {
8 | super.onActive(active);
9 | setTimeout(() => this.setActive(false), 300); // de-active it after 0.3s
10 | }
11 |
12 | protected onClick(event: Event) {
13 | super.onClick(event);
14 | this.bimViewer.distanceMeasurementsPlugin.clear();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/MeasureController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * MeasureController
5 | */
6 | export class MeasureController extends ToolbarMenuBaseController {
7 | protected onClick(/* event: Event */) {
8 | // do nothing
9 | }
10 |
11 | /**
12 | * A parent's 'active' status is decided by it's children rather than itself. So, we won't set this._active directly here.
13 | */
14 | setActive() {
15 | const active = this.anyChildrenActive();
16 | super.setActive(active);
17 | }
18 |
19 | /**
20 | * Checks if any child is active
21 | */
22 | protected anyChildrenActive(): boolean {
23 | for (let i = 0; i < this.children.length; ++i) {
24 | if (this.children[i].getActive()) {
25 | return true;
26 | }
27 | }
28 | return false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/MeasureDistanceController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 | import { ESC_KEY, KEYUP_EVENT } from "../../utils/Consts";
3 |
4 | enum MeasurementState {
5 | HOVERING,
6 | FINDING_ORIGIN,
7 | FINDING_TARGET,
8 | }
9 |
10 | /**
11 | * MeasureDistanceController
12 | */
13 | export class MeasureDistanceController extends ToolbarMenuBaseController {
14 | /**
15 | * onActive can be triggered by 'click' event, also can be triggered by other events, e.g. anohter conflicted button is clicked.
16 | */
17 | protected onActive(active: boolean) {
18 | super.onActive(active);
19 | this.bimViewer.suppressSingleSelection(active);
20 | this.bimViewer.activeDistanceMeasurement(active);
21 | if (this.parent) {
22 | this.parent.setActive(active); // also update parent's status
23 | }
24 | active
25 | ? document.addEventListener(KEYUP_EVENT, this.handleKeyUp)
26 | : document.removeEventListener(KEYUP_EVENT, this.handleKeyUp);
27 | }
28 |
29 | private handleKeyUp = (event: KeyboardEvent) => {
30 | const { key } = event;
31 | const control = this.bimViewer.distanceMeasurementsPlugin.control;
32 | if (key === ESC_KEY) {
33 | if (control._state === MeasurementState.FINDING_TARGET) {
34 | control._currentDistMeasurement.destroy();
35 | control._currentDistMeasurement = null;
36 | control._state = MeasurementState.HOVERING;
37 | } else {
38 | this.onClick(event);
39 | }
40 | }
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/OrthoModeController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * OrthoModeController
5 | */
6 | export class OrthoModeController extends ToolbarMenuBaseController {
7 | protected onClick(event: Event) {
8 | super.onClick(event);
9 | this.bimViewer.activeOrthoMode(this._active);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/PropertyController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * PropertyController
5 | */
6 | export class PropertyController extends ToolbarMenuBaseController {
7 | /**
8 | * onActive can be triggered by 'click' event, also can be triggered by other events, e.g. anohter conflicted button is clicked.
9 | */
10 | protected onActive(active: boolean) {
11 | super.onActive(active);
12 | this.bimViewer.suppressSingleSelection(!active);
13 | this.bimViewer.activeProperty(active);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/SectionBoxController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * SectionBoxController
5 | */
6 | export class SectionBoxController extends ToolbarMenuBaseController {
7 | /**
8 | * onActive can be triggered by 'click' event, also can be triggered by other events, e.g. anohter conflicted button is clicked.
9 | */
10 | protected onActive(active: boolean) {
11 | super.onActive(active);
12 | this.bimViewer.suppressSingleSelection(active);
13 | this.bimViewer.activeSectionBox(active);
14 | if (this.parent) {
15 | this.parent.setActive(active); // also update parent's status
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/SectionController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * SectionController
5 | * This is a parent controller, which doesn't have any action except that it need to reflect children's active status.
6 | */
7 | export class SectionController extends ToolbarMenuBaseController {
8 | protected onClick(/* event: Event */) {
9 | // do nothing
10 | }
11 |
12 | /**
13 | * A parent's 'active' status is decided by it's children rather than itself. So, we won't set this._active directly here.
14 | */
15 | setActive() {
16 | const active = this.anyChildrenActive();
17 | super.setActive(active);
18 | }
19 |
20 | /**
21 | * Checks if any child is active
22 | */
23 | protected anyChildrenActive(): boolean {
24 | for (let i = 0; i < this.children.length; ++i) {
25 | if (this.children[i].getActive()) {
26 | return true;
27 | }
28 | }
29 | return false;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/SectionPlaneController.ts:
--------------------------------------------------------------------------------
1 | import { ToolbarMenuBaseController } from "./ToolbarMenuBaseController";
2 |
3 | /**
4 | * SectionPlaneController
5 | */
6 | export class SectionPlaneController extends ToolbarMenuBaseController {
7 | /**
8 | * onActive can be triggered by 'click' event, also can be triggered by other events, e.g. anohter conflicted button is clicked.
9 | */
10 | protected onActive(active: boolean) {
11 | super.onActive(active);
12 | this.bimViewer.suppressSingleSelection(active);
13 | this.bimViewer.activeSectionPlane(active);
14 | //Reset. Remove the last result
15 | if (active) {
16 | this.bimViewer.sectionPlanePlugin.reset();
17 | }
18 | if (this.parent) {
19 | this.parent.setActive(active); // also update parent's status
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/ToolbarMenuBaseController.ts:
--------------------------------------------------------------------------------
1 | import { Controller } from "../../core/Controller";
2 | import { forEach } from "lodash";
3 | import { ICON_FONT_CLASS } from "../../utils/Consts";
4 | import { IconClass, ToolbarMenuConfig } from "./ToolbarConfig";
5 |
6 | /**
7 | * @description
8 | * @export
9 | * @class ToolbarMenuBaseController
10 | * @extends {Controller}
11 | */
12 | export class ToolbarMenuBaseController extends Controller {
13 | protected _element: HTMLElement;
14 | private _cfg: ToolbarMenuConfig;
15 |
16 | constructor(parent: Controller, cfg: ToolbarMenuConfig, node: HTMLElement) {
17 | super(parent, cfg);
18 |
19 | this._element = node;
20 | this._cfg = cfg;
21 | if (!this._element) {
22 | throw `Failed to get toolbar menu button: ${node}`;
23 | }
24 |
25 | this.on("active", (active: boolean) => this.onActive(active));
26 | this.on("click", (event: Event) => this.onClick(event));
27 | this._element.addEventListener("click", (event: Event) => this.onClick(event));
28 | }
29 |
30 | /**
31 | * Default onActive behavior
32 | */
33 | protected onActive(active: boolean) {
34 | const changeIconStyle = (classList: DOMTokenList, iconClass: IconClass, active: boolean) => {
35 | const { default: defaultClass, active: activeClass } = iconClass;
36 | if (active) {
37 | if (classList.contains(defaultClass)) {
38 | classList.remove(defaultClass);
39 | }
40 | classList.add(activeClass || defaultClass);
41 | } else {
42 | if (activeClass && classList.contains(activeClass)) {
43 | classList.remove(activeClass);
44 | }
45 | classList.add(defaultClass);
46 | }
47 | };
48 |
49 | const icon = this._element.getElementsByClassName(ICON_FONT_CLASS)[0];
50 | changeIconStyle(icon.classList, this._cfg.icon, active);
51 | if (active) {
52 | this._element.classList.add("active");
53 | forEach(this._cfg.mutexIds, (toolbarMenuId) => {
54 | this.bimViewer.toolbar.controllers[toolbarMenuId]?.setActive(false);
55 | });
56 |
57 | if (this._cfg.onActive) {
58 | this._cfg.onActive();
59 | }
60 | } else {
61 | this._element.classList.remove("active");
62 |
63 | if (this._cfg.onDeactive) {
64 | this._cfg.onDeactive();
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * Default onClick behavior
71 | */
72 | protected onClick(event: Event) {
73 | if (this.getEnabled()) {
74 | this._active = !this._active;
75 | this.fire("active", this._active);
76 | }
77 | event && event.preventDefault();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/widgets/toolbar/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./AxisSectionPlaneController";
2 | export * from "./BimTreeController";
3 | export * from "./FullScreenController";
4 | export * from "./HomeViewController";
5 | export * from "./MeasureAreaController";
6 | export * from "./MeasureClearController";
7 | export * from "./MeasureController";
8 | export * from "./MeasureDistanceController";
9 | export * from "./OrthoModeController";
10 | export * from "./PropertyController";
11 | export * from "./SectionBoxController";
12 | export * from "./SectionController";
13 | export * from "./SectionPlaneController";
14 | export * from "./Toolbar";
15 | export * from "./ToolbarConfig";
16 | export * from "./ToolbarMenuBaseController";
17 |
--------------------------------------------------------------------------------
/src/xeokit-sdk.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "./dist/types",
5 | "declaration": true,
6 | "declarationDir": "dist/types",
7 | "strict": true,
8 | "importsNotUsedAsValues": "preserve",
9 | "downlevelIteration": true,
10 | "sourceMap": true,
11 | "module": "es6",
12 | "target": "es6",
13 | "moduleResolution": "node",
14 | "lib": [
15 | "dom"
16 | ],
17 | "noImplicitAny": true,
18 | "paths": {
19 | "src/*": [
20 | "src/*"
21 | ]
22 | }
23 | },
24 | "exclude": [
25 | "node_modules",
26 | "dist"
27 | ]
28 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli
2 |
3 | const path = require("path");
4 | const TerserWebpackPlugin = require('terser-webpack-plugin');
5 | const ESLintPlugin = require('eslint-webpack-plugin');
6 |
7 | const isProduction = process.env.NODE_ENV == "production";
8 |
9 | const stylesHandler = "style-loader";
10 |
11 | const config = {
12 | devServer: {
13 | client: {
14 | overlay: {
15 | errors: true,
16 | warnings: false,
17 | },
18 | },
19 | static: {
20 | directory: path.join(__dirname, "/demo/public"),
21 | },
22 | port: 9000,
23 | },
24 | entry: {
25 | 'gemini-viewer.esm': "./src/index.ts",
26 | },
27 | output: {
28 | path: path.resolve(__dirname, "/dist"),
29 | publicPath: "/dist/",
30 | filename: '[name].js',
31 | // library: 'GeminiViewer',
32 | libraryTarget: 'module'
33 | },
34 | experiments: {
35 | outputModule: true
36 | },
37 | plugins: [
38 | new ESLintPlugin({
39 | extensions: "ts",
40 | })
41 | ],
42 | module: {
43 | rules: [
44 | {
45 | test: /\.(ts|tsx)$/i,
46 | use: [{
47 | loader: 'ts-loader',
48 | options: {
49 | configFile: "tsconfig.json"
50 | }
51 | }],
52 | exclude: ["/node_modules/"],
53 | },
54 | {
55 | test: /\.css$/i,
56 | use: [stylesHandler, "css-loader"],
57 | },
58 | {
59 | test: /\.s[ac]ss$/i,
60 | use: [stylesHandler, "css-loader", "sass-loader"],
61 | },
62 | {
63 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
64 | type: "asset",
65 | },
66 |
67 | // Add your rules for custom modules here
68 | // Learn more about loaders from https://webpack.js.org/loaders/
69 | ],
70 | },
71 | resolve: {
72 | alias: {
73 | '@': path.resolve(__dirname, 'src')
74 | },
75 | extensions: [".tsx", ".ts", ".js"],
76 | },
77 | // devtool: 'source-map',
78 | optimization: {
79 | minimize: isProduction,
80 | minimizer: [
81 | new TerserWebpackPlugin({
82 | include: /\.min/,
83 | terserOptions: {
84 | format: {
85 | comments: false,
86 | },
87 | },
88 | extractComments: false,
89 | })
90 | ]
91 | }
92 | };
93 |
94 | module.exports = () => {
95 | if (isProduction) {
96 | config.mode = "production";
97 | } else {
98 | config.mode = "development";
99 | }
100 | return config;
101 | };
102 |
--------------------------------------------------------------------------------
/webpack.esm.config.js:
--------------------------------------------------------------------------------
1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli
2 |
3 | const path = require("path");
4 | const TerserWebpackPlugin = require('terser-webpack-plugin');
5 |
6 | const isProduction = process.env.NODE_ENV == "production";
7 |
8 | const stylesHandler = "style-loader";
9 |
10 | const config = {
11 | entry: {
12 | 'gemini-viewer.esm': "./src/index.ts",
13 | // 'gemini-viewer.esm.min': './src/index.ts'
14 | },
15 | output: {
16 | path: path.resolve(__dirname, "dist"),
17 | filename: isProduction ? '[name].min.js' : '[name].js',
18 | // library: 'GeminiViewer',
19 | libraryTarget: 'module'
20 | },
21 | experiments: {
22 | outputModule: true
23 | },
24 | plugins: [
25 | ],
26 | module: {
27 | rules: [
28 | {
29 | test: /\.(ts|tsx)$/i,
30 | use: [{
31 | loader: 'ts-loader',
32 | }],
33 | exclude: ["/node_modules/"],
34 | },
35 | {
36 | test: /\.css$/i,
37 | use: [stylesHandler, "css-loader"],
38 | },
39 | {
40 | test: /\.s[ac]ss$/i,
41 | use: [stylesHandler, "css-loader", "sass-loader"],
42 | },
43 | {
44 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
45 | type: "asset",
46 | },
47 |
48 | // Add your rules for custom modules here
49 | // Learn more about loaders from https://webpack.js.org/loaders/
50 | ],
51 | },
52 | resolve: {
53 | alias: {
54 | '@': path.resolve(__dirname, 'src')
55 | },
56 | extensions: [".tsx", ".ts", ".js"],
57 | },
58 | // devtool: 'source-map',
59 | optimization: {
60 | minimize: isProduction,
61 | minimizer: [
62 | new TerserWebpackPlugin({
63 | include: /\.min/,
64 | terserOptions: {
65 | format: {
66 | comments: false,
67 | },
68 | },
69 | extractComments: false,
70 | })
71 | ]
72 | }
73 | };
74 |
75 | module.exports = () => {
76 | if (isProduction) {
77 | config.mode = "production";
78 | } else {
79 | config.mode = "development";
80 | }
81 | return config;
82 | };
83 |
--------------------------------------------------------------------------------
/webpack.umd.config.js:
--------------------------------------------------------------------------------
1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli
2 |
3 | const path = require("path");
4 | // const HtmlWebpackPlugin = require("html-webpack-plugin");
5 | const TerserWebpackPlugin = require('terser-webpack-plugin');
6 |
7 | const isProduction = process.env.NODE_ENV == "production";
8 |
9 | const stylesHandler = "style-loader";
10 |
11 | const config = {
12 | entry: {
13 | 'gemini-viewer.umd': "./src/index.ts",
14 | // 'gemini-viewer.umd.min': './src/index.ts'
15 | },
16 | output: {
17 | path: path.resolve(__dirname, "dist"),
18 | filename: isProduction ? '[name].min.js' : '[name].js',
19 | library: 'GeminiViewer',
20 | libraryTarget: 'umd'
21 | },
22 | // experiments: {
23 | // outputModule: true
24 | // },
25 | plugins: [
26 | // new HtmlWebpackPlugin({
27 | // template: "index.html",
28 | // }),
29 | // Add your plugins here
30 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/
31 | ],
32 | module: {
33 | rules: [
34 | {
35 | test: /\.(ts|tsx)$/i,
36 | loader: "ts-loader",
37 | exclude: ["/node_modules/"],
38 | },
39 | {
40 | test: /\.css$/i,
41 | use: [stylesHandler, "css-loader"],
42 | },
43 | {
44 | test: /\.s[ac]ss$/i,
45 | use: [stylesHandler, "css-loader", "sass-loader"],
46 | },
47 | {
48 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
49 | type: "asset",
50 | },
51 |
52 | // Add your rules for custom modules here
53 | // Learn more about loaders from https://webpack.js.org/loaders/
54 | ],
55 | },
56 | resolve: {
57 | alias: {
58 | '@': path.resolve(__dirname, 'src')
59 | },
60 | extensions: [".tsx", ".ts", ".js"],
61 | },
62 | // devtool: 'source-map',
63 | // optimization: {
64 | // minimize: true,
65 | // minimizer: [
66 | // new TerserWebpackPlugin({
67 | // include: /\.min/
68 | // })
69 | // ]
70 | // }
71 | };
72 |
73 | module.exports = () => {
74 | if (isProduction) {
75 | config.mode = "production";
76 | } else {
77 | config.mode = "development";
78 | }
79 | return config;
80 | };
81 |
--------------------------------------------------------------------------------
Context for ContextMenu 3 | \xeokit-sdk\src\extras\ContextMenu\ContextMenu.js
4 |