├── .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 | 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 | 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 | --------------------------------------------------------------------------------