├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── index.html
├── package.json
├── src
├── App.vue
├── assets
│ └── logo.png
├── common
│ └── tool
│ │ ├── CSS2DRenderer.js
│ │ └── OrbitControls.js
├── components
│ └── Three.vue
├── main.js
└── router
│ └── index.js
└── static
├── .gitkeep
└── models
├── 亚托克斯.obj
├── 厂房无盖.mtl
├── 厂房无盖.obj
└── 厂房盖子.obj
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # three
2 |
3 | > vue + three 3d pro
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 |
17 | # build for production and view the bundle analyzer report
18 | npm run build --report
19 | ```
20 |
21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
22 |
23 | ## 概括
24 | #### 本项目使用Vue框架,利用three,实现简单3d场景的搭建和交互。
25 |
26 | ## 实现功能
27 |
28 | #### 多个相同obj模型的加载(一个加载,其余克隆)
29 | #### 旋转,移动,缩放功能
30 | #### 点击房内模型更改其颜色
31 | #### 缩放时控制屋顶的显示状态
32 | #### 给模型增加自定义文字标签
33 |
34 | 更多请参考three文档 [three](https://www.techbrood.com/threejs/docs/)
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ling-zer/three/824c495170e6ac3fab7b9776beb83abc31fec2c5/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 |
12 |
13 | module.exports = {
14 | context: path.resolve(__dirname, '../'),
15 | entry: {
16 | app: './src/main.js'
17 | },
18 | output: {
19 | path: config.build.assetsRoot,
20 | filename: '[name].js',
21 | publicPath: process.env.NODE_ENV === 'production'
22 | ? config.build.assetsPublicPath
23 | : config.dev.assetsPublicPath
24 | },
25 | resolve: {
26 | extensions: ['.js', '.vue', '.json'],
27 | alias: {
28 | 'vue$': 'vue/dist/vue.esm.js',
29 | '@': resolve('src'),
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.vue$/,
36 | loader: 'vue-loader',
37 | options: vueLoaderConfig
38 | },
39 | {
40 | test: /\.js$/,
41 | loader: 'babel-loader',
42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46 | loader: 'url-loader',
47 | options: {
48 | limit: 10000,
49 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
50 | }
51 | },
52 | {
53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
54 | loader: 'url-loader',
55 | options: {
56 | limit: 10000,
57 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
58 | }
59 | },
60 | {
61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
62 | loader: 'url-loader',
63 | options: {
64 | limit: 10000,
65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
66 | }
67 | }
68 | ]
69 | },
70 | node: {
71 | // prevent webpack from injecting useless setImmediate polyfill because Vue
72 | // source contains it (although only uses it if it's native).
73 | setImmediate: false,
74 | // prevent webpack from injecting mocks to Node native modules
75 | // that does not make sense for the client
76 | dgram: 'empty',
77 | fs: 'empty',
78 | net: 'empty',
79 | tls: 'empty',
80 | child_process: 'empty'
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true
59 | }),
60 | // copy custom static assets
61 | new CopyWebpackPlugin([
62 | {
63 | from: path.resolve(__dirname, '../static'),
64 | to: config.dev.assetsSubDirectory,
65 | ignore: ['.*']
66 | }
67 | ])
68 | ]
69 | })
70 |
71 | module.exports = new Promise((resolve, reject) => {
72 | portfinder.basePort = process.env.PORT || config.dev.port
73 | portfinder.getPort((err, port) => {
74 | if (err) {
75 | reject(err)
76 | } else {
77 | // publish the new Port, necessary for e2e tests
78 | process.env.PORT = port
79 | // add port to devServer config
80 | devWebpackConfig.devServer.port = port
81 |
82 | // Add FriendlyErrorsPlugin
83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
84 | compilationSuccessInfo: {
85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
86 | },
87 | onErrors: config.dev.notifyOnErrors
88 | ? utils.createNotifierCallback()
89 | : undefined
90 | }))
91 |
92 | resolve(devWebpackConfig)
93 | }
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = require('../config/prod.env')
15 |
16 | const webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true,
21 | usePostCSS: true
22 | })
23 | },
24 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 | output: {
26 | path: config.build.assetsRoot,
27 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29 | },
30 | plugins: [
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | new UglifyJsPlugin({
36 | uglifyOptions: {
37 | compress: {
38 | warnings: false
39 | }
40 | },
41 | sourceMap: config.build.productionSourceMap,
42 | parallel: true
43 | }),
44 | // extract css into its own file
45 | new ExtractTextPlugin({
46 | filename: utils.assetsPath('css/[name].[contenthash].css'),
47 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51 | allChunks: true,
52 | }),
53 | // Compress extracted CSS. We are using this plugin so that possible
54 | // duplicated CSS from different components can be deduped.
55 | new OptimizeCSSPlugin({
56 | cssProcessorOptions: config.build.productionSourceMap
57 | ? { safe: true, map: { inline: false } }
58 | : { safe: true }
59 | }),
60 | // generate dist index.html with correct asset hash for caching.
61 | // you can customize output by editing /index.html
62 | // see https://github.com/ampedandwired/html-webpack-plugin
63 | new HtmlWebpackPlugin({
64 | filename: config.build.index,
65 | template: 'index.html',
66 | inject: true,
67 | minify: {
68 | removeComments: true,
69 | collapseWhitespace: true,
70 | removeAttributeQuotes: true
71 | // more options:
72 | // https://github.com/kangax/html-minifier#options-quick-reference
73 | },
74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
75 | chunksSortMode: 'dependency'
76 | }),
77 | // keep module.id stable when vendor modules does not change
78 | new webpack.HashedModuleIdsPlugin(),
79 | // enable scope hoisting
80 | new webpack.optimize.ModuleConcatenationPlugin(),
81 | // split vendor js into its own file
82 | new webpack.optimize.CommonsChunkPlugin({
83 | name: 'vendor',
84 | minChunks (module) {
85 | // any required modules inside node_modules are extracted to vendor
86 | return (
87 | module.resource &&
88 | /\.js$/.test(module.resource) &&
89 | module.resource.indexOf(
90 | path.join(__dirname, '../node_modules')
91 | ) === 0
92 | )
93 | }
94 | }),
95 | // extract webpack runtime and module manifest to its own file in order to
96 | // prevent vendor hash from being updated whenever app bundle is updated
97 | new webpack.optimize.CommonsChunkPlugin({
98 | name: 'manifest',
99 | minChunks: Infinity
100 | }),
101 | // This instance extracts shared chunks from code splitted chunks and bundles them
102 | // in a separate chunk, similar to the vendor chunk
103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
104 | new webpack.optimize.CommonsChunkPlugin({
105 | name: 'app',
106 | async: 'vendor-async',
107 | children: true,
108 | minChunks: 3
109 | }),
110 |
111 | // copy custom static assets
112 | new CopyWebpackPlugin([
113 | {
114 | from: path.resolve(__dirname, '../static'),
115 | to: config.build.assetsSubDirectory,
116 | ignore: ['.*']
117 | }
118 | ])
119 | ]
120 | })
121 |
122 | if (config.build.productionGzip) {
123 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
124 |
125 | webpackConfig.plugins.push(
126 | new CompressionWebpackPlugin({
127 | asset: '[path].gz[query]',
128 | algorithm: 'gzip',
129 | test: new RegExp(
130 | '\\.(' +
131 | config.build.productionGzipExtensions.join('|') +
132 | ')$'
133 | ),
134 | threshold: 10240,
135 | minRatio: 0.8
136 | })
137 | )
138 | }
139 |
140 | if (config.build.bundleAnalyzerReport) {
141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
143 | }
144 |
145 | module.exports = webpackConfig
146 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | three
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three-test",
3 | "version": "1.0.0",
4 | "description": "vue + three, 3d pro",
5 | "author": "113231244@qq.com",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "node build/build.js"
11 | },
12 | "dependencies": {
13 | "three": "^0.114.0",
14 | "three-obj-mtl-loader": "^1.0.3",
15 | "vue": "^2.5.2",
16 | "vue-router": "^3.0.1"
17 | },
18 | "devDependencies": {
19 | "autoprefixer": "^7.1.2",
20 | "babel-core": "^6.22.1",
21 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
22 | "babel-loader": "^7.1.1",
23 | "babel-plugin-syntax-jsx": "^6.18.0",
24 | "babel-plugin-transform-runtime": "^6.22.0",
25 | "babel-plugin-transform-vue-jsx": "^3.5.0",
26 | "babel-preset-env": "^1.3.2",
27 | "babel-preset-stage-2": "^6.22.0",
28 | "chalk": "^2.0.1",
29 | "copy-webpack-plugin": "^4.0.1",
30 | "css-loader": "^0.28.0",
31 | "extract-text-webpack-plugin": "^3.0.0",
32 | "file-loader": "^1.1.4",
33 | "friendly-errors-webpack-plugin": "^1.6.1",
34 | "html-webpack-plugin": "^2.30.1",
35 | "node-notifier": "^5.1.2",
36 | "optimize-css-assets-webpack-plugin": "^3.2.0",
37 | "ora": "^1.2.0",
38 | "portfinder": "^1.0.13",
39 | "postcss-import": "^11.0.0",
40 | "postcss-loader": "^2.0.8",
41 | "postcss-url": "^7.2.1",
42 | "rimraf": "^2.6.0",
43 | "semver": "^5.3.0",
44 | "shelljs": "^0.7.6",
45 | "uglifyjs-webpack-plugin": "^1.1.1",
46 | "url-loader": "^0.5.8",
47 | "vue-loader": "^13.3.0",
48 | "vue-style-loader": "^3.0.1",
49 | "vue-template-compiler": "^2.5.2",
50 | "webpack": "^3.6.0",
51 | "webpack-bundle-analyzer": "^3.9.0",
52 | "webpack-dev-server": "^2.9.1",
53 | "webpack-merge": "^4.1.0"
54 | },
55 | "engines": {
56 | "node": ">= 6.0.0",
57 | "npm": ">= 3.0.0"
58 | },
59 | "browserslist": [
60 | "> 1%",
61 | "last 2 versions",
62 | "not ie <= 8"
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
23 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ling-zer/three/824c495170e6ac3fab7b9776beb83abc31fec2c5/src/assets/logo.png
--------------------------------------------------------------------------------
/src/common/tool/CSS2DRenderer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 |
5 | import {
6 | Matrix4,
7 | Object3D,
8 | Vector3
9 | } from "../../../node_modules/three/build/three.module.js";
10 |
11 | var CSS2DObject = function ( element ) {
12 |
13 | Object3D.call( this );
14 |
15 | this.element = element;
16 | this.element.style.position = 'absolute';
17 |
18 | this.addEventListener( 'removed', function () {
19 |
20 | this.traverse( function ( object ) {
21 |
22 | if ( object.element instanceof Element && object.element.parentNode !== null ) {
23 |
24 | object.element.parentNode.removeChild( object.element );
25 |
26 | }
27 |
28 | } );
29 |
30 | } );
31 |
32 | };
33 |
34 | CSS2DObject.prototype = Object.create( Object3D.prototype );
35 | CSS2DObject.prototype.constructor = CSS2DObject;
36 |
37 | //
38 |
39 | var CSS2DRenderer = function () {
40 |
41 | var _this = this;
42 |
43 | var _width, _height;
44 | var _widthHalf, _heightHalf;
45 |
46 | var vector = new Vector3();
47 | var viewMatrix = new Matrix4();
48 | var viewProjectionMatrix = new Matrix4();
49 |
50 | var cache = {
51 | objects: new WeakMap()
52 | };
53 |
54 | var domElement = document.createElement( 'div' );
55 | domElement.style.overflow = 'hidden';
56 |
57 | this.domElement = domElement;
58 |
59 | this.getSize = function () {
60 |
61 | return {
62 | width: _width,
63 | height: _height
64 | };
65 |
66 | };
67 |
68 | this.setSize = function ( width, height ) {
69 |
70 | _width = width;
71 | _height = height;
72 |
73 | _widthHalf = _width / 2;
74 | _heightHalf = _height / 2;
75 |
76 | domElement.style.width = width + 'px';
77 | domElement.style.height = height + 'px';
78 |
79 | };
80 |
81 | var renderObject = function ( object, scene, camera ) {
82 |
83 | if ( object instanceof CSS2DObject ) {
84 |
85 | object.onBeforeRender( _this, scene, camera );
86 |
87 | vector.setFromMatrixPosition( object.matrixWorld );
88 | vector.applyMatrix4( viewProjectionMatrix );
89 |
90 | var element = object.element;
91 | var scale = 1;
92 | if(element.$data) {
93 | scale = 10 / element.$data;
94 | }
95 | var style = 'translate(-50%,-50%) translate(' + ( vector.x * _widthHalf + _widthHalf ) + 'px,'
96 | + ( - vector.y * _heightHalf + _heightHalf ) + 'px) scale('+scale+')';
97 |
98 | element.style.WebkitTransform = style;
99 | element.style.MozTransform = style;
100 | element.style.oTransform = style;
101 | element.style.transform = style;
102 |
103 | element.style.display = ( object.visible && vector.z >= - 1 && vector.z <= 1 ) ? '' : 'none';
104 |
105 | var objectData = {
106 | distanceToCameraSquared: getDistanceToSquared( camera, object )
107 | };
108 |
109 | cache.objects.set( object, objectData );
110 |
111 | if ( element.parentNode !== domElement ) {
112 |
113 | domElement.appendChild( element );
114 |
115 | }
116 |
117 | object.onAfterRender( _this, scene, camera );
118 |
119 | }
120 |
121 | for ( var i = 0, l = object.children.length; i < l; i ++ ) {
122 |
123 | renderObject( object.children[ i ], scene, camera );
124 |
125 | }
126 |
127 | };
128 |
129 | var getDistanceToSquared = function () {
130 |
131 | var a = new Vector3();
132 | var b = new Vector3();
133 |
134 | return function ( object1, object2 ) {
135 |
136 | a.setFromMatrixPosition( object1.matrixWorld );
137 | b.setFromMatrixPosition( object2.matrixWorld );
138 |
139 | return a.distanceToSquared( b );
140 |
141 | };
142 |
143 | }();
144 |
145 | var filterAndFlatten = function ( scene ) {
146 |
147 | var result = [];
148 |
149 | scene.traverse( function ( object ) {
150 |
151 | if ( object instanceof CSS2DObject ) result.push( object );
152 |
153 | } );
154 |
155 | return result;
156 |
157 | };
158 |
159 | var zOrder = function ( scene ) {
160 |
161 | var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
162 |
163 | var distanceA = cache.objects.get( a ).distanceToCameraSquared;
164 | var distanceB = cache.objects.get( b ).distanceToCameraSquared;
165 |
166 | return distanceA - distanceB;
167 |
168 | } );
169 |
170 | var zMax = sorted.length;
171 |
172 | for ( var i = 0, l = sorted.length; i < l; i ++ ) {
173 |
174 | sorted[ i ].element.style.zIndex = zMax - i;
175 |
176 | }
177 |
178 | };
179 |
180 | this.render = function ( scene, camera ) {
181 |
182 | if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
183 | if ( camera.parent === null ) camera.updateMatrixWorld();
184 |
185 | viewMatrix.copy( camera.matrixWorldInverse );
186 | viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, viewMatrix );
187 |
188 | renderObject( scene, scene, camera );
189 | zOrder( scene );
190 |
191 | };
192 |
193 | };
194 |
195 | export { CSS2DObject, CSS2DRenderer };
196 |
--------------------------------------------------------------------------------
/src/common/tool/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | * @author ScieCode / http://github.com/sciecode
8 | */
9 |
10 | import {
11 | EventDispatcher,
12 | MOUSE,
13 | Quaternion,
14 | Spherical,
15 | TOUCH,
16 | Vector2,
17 | Vector3
18 | } from "../../../node_modules/three/build/three.module.js";
19 |
20 | // This set of controls performs orbiting, dollying (zooming), and panning.
21 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
22 | //
23 | // Orbit - left mouse / touch: one-finger move
24 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
25 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
26 |
27 | var OrbitControls = function ( object, domElement ) {
28 |
29 | if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
30 | if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
31 |
32 | this.object = object;
33 | this.domElement = domElement;
34 |
35 | // Set to false to disable this control
36 | this.enabled = true;
37 |
38 | // "target" sets the location of focus, where the object orbits around
39 | this.target = new Vector3();
40 |
41 | // How far you can dolly in and out ( PerspectiveCamera only )
42 | this.minDistance = 0;
43 | this.maxDistance = Infinity;
44 |
45 | // How far you can zoom in and out ( OrthographicCamera only )
46 | this.minZoom = 0;
47 | this.maxZoom = Infinity;
48 |
49 | // How far you can orbit vertically, upper and lower limits.
50 | // Range is 0 to Math.PI radians.
51 | this.minPolarAngle = 0; // radians
52 | this.maxPolarAngle = Math.PI; // radians
53 |
54 | // How far you can orbit horizontally, upper and lower limits.
55 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
56 | this.minAzimuthAngle = - Infinity; // radians
57 | this.maxAzimuthAngle = Infinity; // radians
58 |
59 | // Set to true to enable damping (inertia)
60 | // If damping is enabled, you must call controls.update() in your animation loop
61 | this.enableDamping = false;
62 | this.dampingFactor = 0.05;
63 |
64 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
65 | // Set to false to disable zooming
66 | this.enableZoom = true;
67 | this.zoomSpeed = 1.0;
68 |
69 | // Set to false to disable rotating
70 | this.enableRotate = true;
71 | this.rotateSpeed = 1.0;
72 |
73 | // Set to false to disable panning
74 | this.enablePan = true;
75 | this.panSpeed = 1.0;
76 | this.screenSpacePanning = false; // if true, pan in screen-space
77 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
78 |
79 | // Set to true to automatically rotate around the target
80 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
81 | this.autoRotate = false;
82 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
83 |
84 | // Set to false to disable use of the keys
85 | this.enableKeys = true;
86 |
87 | // The four arrow keys
88 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
89 |
90 | // Mouse buttons
91 | this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
92 |
93 | // Touch fingers
94 | this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
95 |
96 | // for reset
97 | this.target0 = this.target.clone();
98 | this.position0 = this.object.position.clone();
99 | this.zoom0 = this.object.zoom;
100 |
101 | //
102 | // public methods
103 | //
104 | this.getSpherical = function() {
105 | return spherical;
106 | }
107 | this.getPolarAngle = function () {
108 |
109 | return spherical.phi;
110 |
111 | };
112 |
113 | this.getAzimuthalAngle = function () {
114 |
115 | return spherical.theta;
116 |
117 | };
118 |
119 | this.saveState = function () {
120 |
121 | scope.target0.copy( scope.target );
122 | scope.position0.copy( scope.object.position );
123 | scope.zoom0 = scope.object.zoom;
124 |
125 | };
126 |
127 | this.reset = function () {
128 |
129 | scope.target.copy( scope.target0 );
130 | scope.object.position.copy( scope.position0 );
131 | scope.object.zoom = scope.zoom0;
132 |
133 | scope.object.updateProjectionMatrix();
134 | scope.dispatchEvent( changeEvent );
135 |
136 | scope.update();
137 |
138 | state = STATE.NONE;
139 |
140 | };
141 |
142 | // this method is exposed, but perhaps it would be better if we can make it private...
143 | this.update = function () {
144 |
145 | var offset = new Vector3();
146 |
147 | // so camera.up is the orbit axis
148 | var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
149 | var quatInverse = quat.clone().inverse();
150 |
151 | var lastPosition = new Vector3();
152 | var lastQuaternion = new Quaternion();
153 |
154 | return function update() {
155 |
156 | var position = scope.object.position;
157 |
158 | offset.copy( position ).sub( scope.target );
159 |
160 | // rotate offset to "y-axis-is-up" space
161 | offset.applyQuaternion( quat );
162 |
163 | // angle from z-axis around y-axis
164 | spherical.setFromVector3( offset );
165 |
166 | if ( scope.autoRotate && state === STATE.NONE ) {
167 |
168 | rotateLeft( getAutoRotationAngle() );
169 |
170 | }
171 |
172 | if ( scope.enableDamping ) {
173 |
174 | spherical.theta += sphericalDelta.theta * scope.dampingFactor;
175 | spherical.phi += sphericalDelta.phi * scope.dampingFactor;
176 |
177 | } else {
178 |
179 | spherical.theta += sphericalDelta.theta;
180 | spherical.phi += sphericalDelta.phi;
181 |
182 | }
183 |
184 | // restrict theta to be between desired limits
185 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
186 |
187 | // restrict phi to be between desired limits
188 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
189 |
190 | spherical.makeSafe();
191 |
192 |
193 | spherical.radius *= scale;
194 |
195 | // restrict radius to be between desired limits
196 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
197 |
198 | // move target to panned location
199 |
200 | if ( scope.enableDamping === true ) {
201 |
202 | scope.target.addScaledVector( panOffset, scope.dampingFactor );
203 |
204 | } else {
205 |
206 | scope.target.add( panOffset );
207 |
208 | }
209 |
210 | offset.setFromSpherical( spherical );
211 |
212 | // rotate offset back to "camera-up-vector-is-up" space
213 | offset.applyQuaternion( quatInverse );
214 |
215 | position.copy( scope.target ).add( offset );
216 |
217 | scope.object.lookAt( scope.target );
218 |
219 | if ( scope.enableDamping === true ) {
220 |
221 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
222 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
223 |
224 | panOffset.multiplyScalar( 1 - scope.dampingFactor );
225 |
226 | } else {
227 |
228 | sphericalDelta.set( 0, 0, 0 );
229 |
230 | panOffset.set( 0, 0, 0 );
231 |
232 | }
233 |
234 | scale = 1;
235 |
236 | // update condition is:
237 | // min(camera displacement, camera rotation in radians)^2 > EPS
238 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
239 |
240 | if ( zoomChanged ||
241 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
242 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
243 |
244 | scope.dispatchEvent( changeEvent );
245 |
246 | lastPosition.copy( scope.object.position );
247 | lastQuaternion.copy( scope.object.quaternion );
248 | zoomChanged = false;
249 |
250 | return true;
251 |
252 | }
253 |
254 | return false;
255 |
256 | };
257 |
258 | }();
259 |
260 | this.dispose = function () {
261 |
262 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
263 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
264 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
265 |
266 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
267 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
268 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
269 |
270 | document.removeEventListener( 'mousemove', onMouseMove, false );
271 | document.removeEventListener( 'mouseup', onMouseUp, false );
272 |
273 | scope.domElement.removeEventListener( 'keydown', onKeyDown, false );
274 |
275 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
276 |
277 | };
278 |
279 | //
280 | // internals
281 | //
282 |
283 | var scope = this;
284 |
285 | var changeEvent = { type: 'change' };
286 | var startEvent = { type: 'start' };
287 | var endEvent = { type: 'end' };
288 |
289 | var STATE = {
290 | NONE: - 1,
291 | ROTATE: 0,
292 | DOLLY: 1,
293 | PAN: 2,
294 | TOUCH_ROTATE: 3,
295 | TOUCH_PAN: 4,
296 | TOUCH_DOLLY_PAN: 5,
297 | TOUCH_DOLLY_ROTATE: 6
298 | };
299 |
300 | var state = STATE.NONE;
301 |
302 | var EPS = 0.000001;
303 |
304 | // current position in spherical coordinates
305 | var spherical = new Spherical();
306 | var sphericalDelta = new Spherical();
307 |
308 | var scale = 1;
309 | var panOffset = new Vector3();
310 | var zoomChanged = false;
311 |
312 | var rotateStart = new Vector2();
313 | var rotateEnd = new Vector2();
314 | var rotateDelta = new Vector2();
315 |
316 | var panStart = new Vector2();
317 | var panEnd = new Vector2();
318 | var panDelta = new Vector2();
319 |
320 | var dollyStart = new Vector2();
321 | var dollyEnd = new Vector2();
322 | var dollyDelta = new Vector2();
323 |
324 | function getAutoRotationAngle() {
325 |
326 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
327 |
328 | }
329 |
330 | function getZoomScale() {
331 |
332 | return Math.pow( 0.95, scope.zoomSpeed );
333 |
334 | }
335 |
336 | function rotateLeft( angle ) {
337 |
338 | sphericalDelta.theta -= angle;
339 |
340 | }
341 |
342 | function rotateUp( angle ) {
343 |
344 | sphericalDelta.phi -= angle;
345 |
346 | }
347 |
348 | var panLeft = function () {
349 |
350 | var v = new Vector3();
351 |
352 | return function panLeft( distance, objectMatrix ) {
353 |
354 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
355 | v.multiplyScalar( - distance );
356 |
357 | panOffset.add( v );
358 |
359 | };
360 |
361 | }();
362 |
363 | var panUp = function () {
364 |
365 | var v = new Vector3();
366 |
367 | return function panUp( distance, objectMatrix ) {
368 |
369 | if ( scope.screenSpacePanning === true ) {
370 |
371 | v.setFromMatrixColumn( objectMatrix, 1 );
372 |
373 | } else {
374 |
375 | v.setFromMatrixColumn( objectMatrix, 0 );
376 | v.crossVectors( scope.object.up, v );
377 |
378 | }
379 |
380 | v.multiplyScalar( distance );
381 |
382 | panOffset.add( v );
383 |
384 | };
385 |
386 | }();
387 |
388 | // deltaX and deltaY are in pixels; right and down are positive
389 | var pan = function () {
390 |
391 | var offset = new Vector3();
392 |
393 | return function pan( deltaX, deltaY ) {
394 |
395 | var element = scope.domElement;
396 |
397 | if ( scope.object.isPerspectiveCamera ) {
398 |
399 | // perspective
400 | var position = scope.object.position;
401 | offset.copy( position ).sub( scope.target );
402 | var targetDistance = offset.length();
403 |
404 | // half of the fov is center to top of screen
405 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
406 |
407 | // we use only clientHeight here so aspect ratio does not distort speed
408 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
409 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
410 |
411 | } else if ( scope.object.isOrthographicCamera ) {
412 |
413 | // orthographic
414 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
415 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
416 |
417 | } else {
418 |
419 | // camera neither orthographic nor perspective
420 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
421 | scope.enablePan = false;
422 |
423 | }
424 |
425 | };
426 |
427 | }();
428 |
429 | function dollyIn( dollyScale ) {
430 |
431 | if ( scope.object.isPerspectiveCamera ) {
432 |
433 | scale /= dollyScale;
434 |
435 | } else if ( scope.object.isOrthographicCamera ) {
436 |
437 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
438 | scope.object.updateProjectionMatrix();
439 | zoomChanged = true;
440 |
441 | } else {
442 |
443 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
444 | scope.enableZoom = false;
445 |
446 | }
447 |
448 | }
449 |
450 | function dollyOut( dollyScale ) {
451 |
452 | if ( scope.object.isPerspectiveCamera ) {
453 |
454 | scale *= dollyScale;
455 |
456 | } else if ( scope.object.isOrthographicCamera ) {
457 |
458 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
459 | scope.object.updateProjectionMatrix();
460 | zoomChanged = true;
461 |
462 | } else {
463 |
464 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
465 | scope.enableZoom = false;
466 |
467 | }
468 |
469 | }
470 |
471 | //
472 | // event callbacks - update the object state
473 | //
474 |
475 | function handleMouseDownRotate( event ) {
476 |
477 | rotateStart.set( event.clientX, event.clientY );
478 |
479 | }
480 |
481 | function handleMouseDownDolly( event ) {
482 |
483 | dollyStart.set( event.clientX, event.clientY );
484 |
485 | }
486 |
487 | function handleMouseDownPan( event ) {
488 |
489 | panStart.set( event.clientX, event.clientY );
490 |
491 | }
492 |
493 | function handleMouseMoveRotate( event ) {
494 |
495 | rotateEnd.set( event.clientX, event.clientY );
496 |
497 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
498 |
499 | var element = scope.domElement;
500 |
501 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
502 |
503 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
504 |
505 | rotateStart.copy( rotateEnd );
506 |
507 | scope.update();
508 |
509 | }
510 |
511 | function handleMouseMoveDolly( event ) {
512 |
513 | dollyEnd.set( event.clientX, event.clientY );
514 |
515 | dollyDelta.subVectors( dollyEnd, dollyStart );
516 |
517 | if ( dollyDelta.y > 0 ) {
518 |
519 | dollyIn( getZoomScale() );
520 |
521 | } else if ( dollyDelta.y < 0 ) {
522 |
523 | dollyOut( getZoomScale() );
524 |
525 | }
526 |
527 | dollyStart.copy( dollyEnd );
528 |
529 | scope.update();
530 |
531 | }
532 |
533 | function handleMouseMovePan( event ) {
534 |
535 | panEnd.set( event.clientX, event.clientY );
536 |
537 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
538 |
539 | pan( panDelta.x, panDelta.y );
540 |
541 | panStart.copy( panEnd );
542 |
543 | scope.update();
544 |
545 | }
546 |
547 | function handleMouseUp( /*event*/ ) {
548 |
549 | // no-op
550 |
551 | }
552 |
553 | function handleMouseWheel( event ) {
554 |
555 | if ( event.deltaY < 0 ) {
556 |
557 | dollyOut( getZoomScale() );
558 |
559 | } else if ( event.deltaY > 0 ) {
560 |
561 | dollyIn( getZoomScale() );
562 |
563 | }
564 |
565 | scope.update();
566 |
567 | }
568 |
569 | function handleKeyDown( event ) {
570 |
571 | var needsUpdate = false;
572 |
573 | switch ( event.keyCode ) {
574 |
575 | case scope.keys.UP:
576 | pan( 0, scope.keyPanSpeed );
577 | needsUpdate = true;
578 | break;
579 |
580 | case scope.keys.BOTTOM:
581 | pan( 0, - scope.keyPanSpeed );
582 | needsUpdate = true;
583 | break;
584 |
585 | case scope.keys.LEFT:
586 | pan( scope.keyPanSpeed, 0 );
587 | needsUpdate = true;
588 | break;
589 |
590 | case scope.keys.RIGHT:
591 | pan( - scope.keyPanSpeed, 0 );
592 | needsUpdate = true;
593 | break;
594 |
595 | }
596 |
597 | if ( needsUpdate ) {
598 |
599 | // prevent the browser from scrolling on cursor keys
600 | event.preventDefault();
601 |
602 | scope.update();
603 |
604 | }
605 |
606 |
607 | }
608 |
609 | function handleTouchStartRotate( event ) {
610 |
611 | if ( event.touches.length == 1 ) {
612 |
613 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
614 |
615 | } else {
616 |
617 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
618 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
619 |
620 | rotateStart.set( x, y );
621 |
622 | }
623 |
624 | }
625 |
626 | function handleTouchStartPan( event ) {
627 |
628 | if ( event.touches.length == 1 ) {
629 |
630 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
631 |
632 | } else {
633 |
634 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
635 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
636 |
637 | panStart.set( x, y );
638 |
639 | }
640 |
641 | }
642 |
643 | function handleTouchStartDolly( event ) {
644 |
645 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
646 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
647 |
648 | var distance = Math.sqrt( dx * dx + dy * dy );
649 |
650 | dollyStart.set( 0, distance );
651 |
652 | }
653 |
654 | function handleTouchStartDollyPan( event ) {
655 |
656 | if ( scope.enableZoom ) handleTouchStartDolly( event );
657 |
658 | if ( scope.enablePan ) handleTouchStartPan( event );
659 |
660 | }
661 |
662 | function handleTouchStartDollyRotate( event ) {
663 |
664 | if ( scope.enableZoom ) handleTouchStartDolly( event );
665 |
666 | if ( scope.enableRotate ) handleTouchStartRotate( event );
667 |
668 | }
669 |
670 | function handleTouchMoveRotate( event ) {
671 |
672 | if ( event.touches.length == 1 ) {
673 |
674 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
675 |
676 | } else {
677 |
678 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
679 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
680 |
681 | rotateEnd.set( x, y );
682 |
683 | }
684 |
685 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
686 |
687 | var element = scope.domElement;
688 |
689 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
690 |
691 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
692 |
693 | rotateStart.copy( rotateEnd );
694 |
695 | }
696 |
697 | function handleTouchMovePan( event ) {
698 |
699 | if ( event.touches.length == 1 ) {
700 |
701 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
702 |
703 | } else {
704 |
705 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
706 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
707 |
708 | panEnd.set( x, y );
709 |
710 | }
711 |
712 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
713 |
714 | pan( panDelta.x, panDelta.y );
715 |
716 | panStart.copy( panEnd );
717 |
718 | }
719 |
720 | function handleTouchMoveDolly( event ) {
721 |
722 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
723 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
724 |
725 | var distance = Math.sqrt( dx * dx + dy * dy );
726 |
727 | dollyEnd.set( 0, distance );
728 |
729 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
730 |
731 | dollyIn( dollyDelta.y );
732 |
733 | dollyStart.copy( dollyEnd );
734 |
735 | }
736 |
737 | function handleTouchMoveDollyPan( event ) {
738 |
739 | if ( scope.enableZoom ) handleTouchMoveDolly( event );
740 |
741 | if ( scope.enablePan ) handleTouchMovePan( event );
742 |
743 | }
744 |
745 | function handleTouchMoveDollyRotate( event ) {
746 |
747 | if ( scope.enableZoom ) handleTouchMoveDolly( event );
748 |
749 | if ( scope.enableRotate ) handleTouchMoveRotate( event );
750 |
751 | }
752 |
753 | function handleTouchEnd( /*event*/ ) {
754 |
755 | // no-op
756 |
757 | }
758 |
759 | //
760 | // event handlers - FSM: listen for events and reset state
761 | //
762 |
763 | function onMouseDown( event ) {
764 |
765 | if ( scope.enabled === false ) return;
766 |
767 | // Prevent the browser from scrolling.
768 | event.preventDefault();
769 |
770 | // Manually set the focus since calling preventDefault above
771 | // prevents the browser from setting it automatically.
772 |
773 | scope.domElement.focus ? scope.domElement.focus() : window.focus();
774 |
775 | var mouseAction;
776 |
777 | switch ( event.button ) {
778 |
779 | case 0:
780 |
781 | mouseAction = scope.mouseButtons.LEFT;
782 | break;
783 |
784 | case 1:
785 |
786 | mouseAction = scope.mouseButtons.MIDDLE;
787 | break;
788 |
789 | case 2:
790 |
791 | mouseAction = scope.mouseButtons.RIGHT;
792 | break;
793 |
794 | default:
795 |
796 | mouseAction = - 1;
797 |
798 | }
799 |
800 | switch ( mouseAction ) {
801 |
802 | case MOUSE.DOLLY:
803 |
804 | if ( scope.enableZoom === false ) return;
805 |
806 | handleMouseDownDolly( event );
807 |
808 | state = STATE.DOLLY;
809 |
810 | break;
811 |
812 | case MOUSE.ROTATE:
813 |
814 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
815 |
816 | if ( scope.enablePan === false ) return;
817 |
818 | handleMouseDownPan( event );
819 |
820 | state = STATE.PAN;
821 |
822 | } else {
823 |
824 | if ( scope.enableRotate === false ) return;
825 |
826 | handleMouseDownRotate( event );
827 |
828 | state = STATE.ROTATE;
829 |
830 | }
831 |
832 | break;
833 |
834 | case MOUSE.PAN:
835 |
836 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
837 |
838 | if ( scope.enableRotate === false ) return;
839 |
840 | handleMouseDownRotate( event );
841 |
842 | state = STATE.ROTATE;
843 |
844 | } else {
845 |
846 | if ( scope.enablePan === false ) return;
847 |
848 | handleMouseDownPan( event );
849 |
850 | state = STATE.PAN;
851 |
852 | }
853 |
854 | break;
855 |
856 | default:
857 |
858 | state = STATE.NONE;
859 |
860 | }
861 |
862 | if ( state !== STATE.NONE ) {
863 |
864 | document.addEventListener( 'mousemove', onMouseMove, false );
865 | document.addEventListener( 'mouseup', onMouseUp, false );
866 |
867 | scope.dispatchEvent( startEvent );
868 |
869 | }
870 |
871 | }
872 |
873 | function onMouseMove( event ) {
874 |
875 | if ( scope.enabled === false ) return;
876 |
877 | event.preventDefault();
878 |
879 | switch ( state ) {
880 |
881 | case STATE.ROTATE:
882 |
883 | if ( scope.enableRotate === false ) return;
884 |
885 | handleMouseMoveRotate( event );
886 |
887 | break;
888 |
889 | case STATE.DOLLY:
890 |
891 | if ( scope.enableZoom === false ) return;
892 |
893 | handleMouseMoveDolly( event );
894 |
895 | break;
896 |
897 | case STATE.PAN:
898 |
899 | if ( scope.enablePan === false ) return;
900 |
901 | handleMouseMovePan( event );
902 |
903 | break;
904 |
905 | }
906 |
907 | }
908 |
909 | function onMouseUp( event ) {
910 |
911 | if ( scope.enabled === false ) return;
912 |
913 | handleMouseUp( event );
914 |
915 | document.removeEventListener( 'mousemove', onMouseMove, false );
916 | document.removeEventListener( 'mouseup', onMouseUp, false );
917 |
918 | scope.dispatchEvent( endEvent );
919 |
920 | state = STATE.NONE;
921 |
922 | }
923 |
924 | function onMouseWheel( event ) {
925 |
926 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
927 |
928 | event.preventDefault();
929 | event.stopPropagation();
930 |
931 | scope.dispatchEvent( startEvent );
932 |
933 | handleMouseWheel( event );
934 |
935 | scope.dispatchEvent( endEvent );
936 |
937 | }
938 |
939 | function onKeyDown( event ) {
940 |
941 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
942 |
943 | handleKeyDown( event );
944 |
945 | }
946 |
947 | function onTouchStart( event ) {
948 |
949 | if ( scope.enabled === false ) return;
950 |
951 | event.preventDefault(); // prevent scrolling
952 |
953 | switch ( event.touches.length ) {
954 |
955 | case 1:
956 |
957 | switch ( scope.touches.ONE ) {
958 |
959 | case TOUCH.ROTATE:
960 |
961 | if ( scope.enableRotate === false ) return;
962 |
963 | handleTouchStartRotate( event );
964 |
965 | state = STATE.TOUCH_ROTATE;
966 |
967 | break;
968 |
969 | case TOUCH.PAN:
970 |
971 | if ( scope.enablePan === false ) return;
972 |
973 | handleTouchStartPan( event );
974 |
975 | state = STATE.TOUCH_PAN;
976 |
977 | break;
978 |
979 | default:
980 |
981 | state = STATE.NONE;
982 |
983 | }
984 |
985 | break;
986 |
987 | case 2:
988 |
989 | switch ( scope.touches.TWO ) {
990 |
991 | case TOUCH.DOLLY_PAN:
992 |
993 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
994 |
995 | handleTouchStartDollyPan( event );
996 |
997 | state = STATE.TOUCH_DOLLY_PAN;
998 |
999 | break;
1000 |
1001 | case TOUCH.DOLLY_ROTATE:
1002 |
1003 | if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1004 |
1005 | handleTouchStartDollyRotate( event );
1006 |
1007 | state = STATE.TOUCH_DOLLY_ROTATE;
1008 |
1009 | break;
1010 |
1011 | default:
1012 |
1013 | state = STATE.NONE;
1014 |
1015 | }
1016 |
1017 | break;
1018 |
1019 | default:
1020 |
1021 | state = STATE.NONE;
1022 |
1023 | }
1024 |
1025 | if ( state !== STATE.NONE ) {
1026 |
1027 | scope.dispatchEvent( startEvent );
1028 |
1029 | }
1030 |
1031 | }
1032 |
1033 | function onTouchMove( event ) {
1034 |
1035 | if ( scope.enabled === false ) return;
1036 |
1037 | event.preventDefault(); // prevent scrolling
1038 | event.stopPropagation();
1039 |
1040 | switch ( state ) {
1041 |
1042 | case STATE.TOUCH_ROTATE:
1043 |
1044 | if ( scope.enableRotate === false ) return;
1045 |
1046 | handleTouchMoveRotate( event );
1047 |
1048 | scope.update();
1049 |
1050 | break;
1051 |
1052 | case STATE.TOUCH_PAN:
1053 |
1054 | if ( scope.enablePan === false ) return;
1055 |
1056 | handleTouchMovePan( event );
1057 |
1058 | scope.update();
1059 |
1060 | break;
1061 |
1062 | case STATE.TOUCH_DOLLY_PAN:
1063 |
1064 | if ( scope.enableZoom === false && scope.enablePan === false ) return;
1065 |
1066 | handleTouchMoveDollyPan( event );
1067 |
1068 | scope.update();
1069 |
1070 | break;
1071 |
1072 | case STATE.TOUCH_DOLLY_ROTATE:
1073 |
1074 | if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1075 |
1076 | handleTouchMoveDollyRotate( event );
1077 |
1078 | scope.update();
1079 |
1080 | break;
1081 |
1082 | default:
1083 |
1084 | state = STATE.NONE;
1085 |
1086 | }
1087 |
1088 | }
1089 |
1090 | function onTouchEnd( event ) {
1091 |
1092 | if ( scope.enabled === false ) return;
1093 |
1094 | handleTouchEnd( event );
1095 |
1096 | scope.dispatchEvent( endEvent );
1097 |
1098 | state = STATE.NONE;
1099 |
1100 | }
1101 |
1102 | function onContextMenu( event ) {
1103 |
1104 | if ( scope.enabled === false ) return;
1105 |
1106 | event.preventDefault();
1107 |
1108 | }
1109 |
1110 | //
1111 |
1112 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
1113 |
1114 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
1115 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
1116 |
1117 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
1118 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
1119 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
1120 |
1121 | scope.domElement.addEventListener( 'keydown', onKeyDown, false );
1122 |
1123 | // make sure element can receive keys.
1124 |
1125 | if ( scope.domElement.tabIndex === - 1 ) {
1126 |
1127 | scope.domElement.tabIndex = 0;
1128 |
1129 | }
1130 |
1131 | // force an update at start
1132 |
1133 | this.update();
1134 |
1135 | };
1136 |
1137 | OrbitControls.prototype = Object.create( EventDispatcher.prototype );
1138 | OrbitControls.prototype.constructor = OrbitControls;
1139 |
1140 |
1141 | // This set of controls performs orbiting, dollying (zooming), and panning.
1142 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
1143 | // This is very similar to OrbitControls, another set of touch behavior
1144 | //
1145 | // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
1146 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
1147 | // Pan - left mouse, or arrow keys / touch: one-finger move
1148 |
1149 | var MapControls = function ( object, domElement ) {
1150 |
1151 | OrbitControls.call( this, object, domElement );
1152 |
1153 | this.mouseButtons.LEFT = MOUSE.PAN;
1154 | this.mouseButtons.RIGHT = MOUSE.ROTATE;
1155 |
1156 | this.touches.ONE = TOUCH.PAN;
1157 | this.touches.TWO = TOUCH.DOLLY_ROTATE;
1158 |
1159 | };
1160 |
1161 | MapControls.prototype = Object.create( EventDispatcher.prototype );
1162 | MapControls.prototype.constructor = MapControls;
1163 |
1164 | export { OrbitControls, MapControls };
1165 |
--------------------------------------------------------------------------------
/src/components/Three.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
309 |
310 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 |
7 | Vue.config.productionTip = false
8 |
9 | /* eslint-disable no-new */
10 | new Vue({
11 | el: '#app',
12 | router,
13 | components: { App },
14 | template: ''
15 | })
16 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Three from '@/components/Three'
4 |
5 | Vue.use(Router)
6 |
7 | export default new Router({
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'Three',
12 | component: Three
13 | }
14 | ]
15 | })
16 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ling-zer/three/824c495170e6ac3fab7b9776beb83abc31fec2c5/static/.gitkeep
--------------------------------------------------------------------------------
/static/models/厂房无盖.mtl:
--------------------------------------------------------------------------------
1 | # Rhino
2 | newmtl 白色 - 消光
3 | Ka 0.0000 0.0000 0.0000
4 | Kd 0.9961 1.0000 0.8431
5 | Ks 1.0000 1.0000 1.0000
6 | Tf 0.0000 0.0000 0.0000
7 | d 1.0000
8 | Ns 0
9 |
10 | newmtl 4___Default
11 | Ka 0.6353 0.6039 0.5529
12 | Kd 0.9647 0.9451 0.9176
13 | Ks 0.1059 0.1059 0.1059
14 | Tf 0.0000 0.0000 0.0000
15 | d 0.3448
16 | Ns 150.4
17 |
18 | newmtl 电镀铝
19 | Ka 0.0000 0.0000 0.0000
20 | Kd 0.2471 0.7490 0.7490
21 | Ks 0.0000 1.0000 1.0000
22 | Tf 0.0000 0.0000 0.0000
23 | d 1.0000
24 | Ns 178.5
25 |
26 | newmtl 黑色 - 消光
27 | Ka 0.0000 0.0000 0.0000
28 | Kd 0.0039 0.1922 0.0000
29 | Ks 1.0000 1.0000 1.0000
30 | Tf 0.0000 0.0000 0.0000
31 | d 1.0000
32 | Ns 0
33 |
34 | newmtl 胡桃木 - 抛光
35 | Ka 0.0000 0.0000 0.0000
36 | Kd 0.3137 0.1490 0.0118
37 | Ks 1.0000 1.0000 1.0000
38 | Tf 0.0000 0.0000 0.0000
39 | d 1.0000
40 | Ns 216.7
41 |
42 |
--------------------------------------------------------------------------------
/static/models/厂房盖子.obj:
--------------------------------------------------------------------------------
1 | # Rhino
2 |
3 | o object_1
4 | v -2.3e+03 2.7e+02 84
5 | v -2.3e+03 2.5e+02 84
6 | v -2.3e+03 3e+02 5.3e+02
7 | v -2.3e+03 3.2e+02 5.3e+02
8 | v -2.3e+03 2.5e+02 1.1e+03
9 | v -2.3e+03 2.6e+02 1.1e+03
10 | v 1.6e+02 2.7e+02 87
11 | v 1.6e+02 3.2e+02 5.3e+02
12 | v 1.6e+02 3e+02 5.3e+02
13 | v 1.6e+02 2.5e+02 87
14 | v 1.6e+02 2.5e+02 1.1e+03
15 | v 1.6e+02 2.6e+02 1.1e+03
16 | v 1.6e+02 2.7e+02 87
17 | v 1.6e+02 2.5e+02 87
18 | v -2.3e+03 2.5e+02 84
19 | v -2.3e+03 2.7e+02 84
20 | v 1.6e+02 3.2e+02 5.3e+02
21 | v 1.6e+02 2.7e+02 87
22 | v -2.3e+03 2.7e+02 84
23 | v -2.3e+03 3.2e+02 5.3e+02
24 | v 1.6e+02 2.6e+02 1.1e+03
25 | v 1.6e+02 3.2e+02 5.3e+02
26 | v -2.3e+03 3.2e+02 5.3e+02
27 | v -2.3e+03 2.6e+02 1.1e+03
28 | v 1.6e+02 2.5e+02 1.1e+03
29 | v 1.6e+02 2.6e+02 1.1e+03
30 | v -2.3e+03 2.6e+02 1.1e+03
31 | v -2.3e+03 2.5e+02 1.1e+03
32 | v 1.6e+02 3e+02 5.3e+02
33 | v 1.6e+02 2.5e+02 1.1e+03
34 | v -2.3e+03 2.5e+02 1.1e+03
35 | v -2.3e+03 3e+02 5.3e+02
36 | v 1.6e+02 2.5e+02 87
37 | v 1.6e+02 3e+02 5.3e+02
38 | v -2.3e+03 3e+02 5.3e+02
39 | v -2.3e+03 2.5e+02 84
40 | f 1 2 3 4
41 | f 5 6 4 3
42 | f 7 8 9 10
43 | f 11 9 8 12
44 | f 13 14 15 16
45 | f 17 18 19 20
46 | f 21 22 23 24
47 | f 25 26 27 28
48 | f 29 30 31 32
49 | f 33 34 35 36
50 |
--------------------------------------------------------------------------------