├── .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 |
6 | Project Technical School Normal 7 |
8 |
9 | Rac Basic Sample Project 10 |
11 |
12 | Project Dxf Test 13 |
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 |
7 | 8 |
9 |
10 |
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-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Index

Enumeration members

Enumeration members

X

X = "x"

Y

Y = "y"

Z

Z = "z"

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/BackgroundColorConfig.html: -------------------------------------------------------------------------------- 1 | BackgroundColorConfig | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface BackgroundColorConfig

Hierarchy

  • BackgroundColorConfig

Index

Properties

Optional backgroundColor

backgroundColor?: number[]

Optional transparent

transparent?: boolean

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/Context.html: -------------------------------------------------------------------------------- 1 | Context | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu
2 |

Context for ContextMenu 3 | \xeokit-sdk\src\extras\ContextMenu\ContextMenu.js

4 |

Hierarchy

  • Context

Index

Properties

Properties

bimViewer

bimViewer: BimViewer

Optional entity

entity?: any

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/GridGeometryConfig.html: -------------------------------------------------------------------------------- 1 | GridGeometryConfig | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface GridGeometryConfig

Hierarchy

  • GridGeometryConfig

Index

Properties

Properties

division

division: number

size

size: number

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/OrthoModeConfig.html: -------------------------------------------------------------------------------- 1 | OrthoModeConfig | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • OrthoModeConfig

Index

Properties

Properties

active

active: boolean

Optional done

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/SectionCullPlaneConfig.html: -------------------------------------------------------------------------------- 1 | SectionCullPlaneConfig | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface SectionCullPlaneConfig

Hierarchy

  • SectionCullPlaneConfig

Index

Properties

active

active: boolean

Optional overviewCfg

overviewCfg?: SectionOverviewConfig

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/SingleSelectionConfig.html: -------------------------------------------------------------------------------- 1 | SingleSelectionConfig | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface SingleSelectionConfig

Hierarchy

  • SingleSelectionConfig

Index

Properties

Properties

active

active: boolean

Optional callback

callback?: PickCallback

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules/math.html: -------------------------------------------------------------------------------- 1 | math | @pattern-x/gemini-viewer
Options
All
  • Public
  • Public/Protected
  • All
Menu

Generated using TypeDoc

-------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------