├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .neutrinorc.js ├── .prettierrc.js ├── README.md ├── babel.config.js ├── build-tools ├── config │ └── dev-server.js ├── npm-scripts │ ├── webpack-dev.js │ └── webpack-live.js ├── webpack-config │ ├── webpack-dev.js │ └── webpack-live.js └── webpack-dev-server │ ├── middleware │ ├── api │ │ ├── _coverage.js │ │ ├── _reports.js │ │ └── api.js │ └── extra-pages.js │ ├── static-pages │ └── devices │ │ └── index.html │ └── webpack-dev-server.js ├── package.json ├── src ├── client │ ├── game │ │ ├── @types │ │ │ ├── chunk.types.ts │ │ │ ├── cube.types.ts │ │ │ └── locations.types.ts │ │ ├── app.ts │ │ ├── config │ │ │ ├── assets.ts │ │ │ ├── general.ts │ │ │ ├── index.ts │ │ │ └── world.ts │ │ ├── helpers │ │ │ ├── dat-gui │ │ │ │ ├── dat-gui.ts │ │ │ │ └── index.ts │ │ │ └── dat │ │ │ │ ├── dat.ts │ │ │ │ ├── index.ts │ │ │ │ ├── space.ts │ │ │ │ └── space.types.ts │ │ ├── objects │ │ │ ├── chunk-noise-viewer.ts │ │ │ └── cube.ts │ │ ├── resources │ │ │ └── textures │ │ │ │ ├── blocks │ │ │ │ ├── cobblestone_mossy.png │ │ │ │ ├── crafting_table_top.png │ │ │ │ ├── destroy_stage_9.png │ │ │ │ ├── dirt.png │ │ │ │ ├── grass_side.png │ │ │ │ ├── hardened_clay_stained_green.png │ │ │ │ ├── iron_block.png │ │ │ │ ├── leaves.png │ │ │ │ ├── log_spruce.png │ │ │ │ ├── planks_oak.png │ │ │ │ ├── sand.png │ │ │ │ ├── sky.jpg │ │ │ │ ├── sky_01.png │ │ │ │ ├── sky_02.png │ │ │ │ ├── sky_03.png │ │ │ │ ├── sky_04.png │ │ │ │ ├── sky_05.png │ │ │ │ └── water.png │ │ │ │ └── skydome │ │ │ │ └── sky.jpg │ │ ├── system │ │ │ ├── engine │ │ │ │ └── renderer │ │ │ │ │ ├── index.ts │ │ │ │ │ └── renderer.ts │ │ │ └── modules │ │ │ │ ├── assets-loader │ │ │ │ ├── assets-loader.ts │ │ │ │ └── index.ts │ │ │ │ ├── biomes │ │ │ │ └── biome-grass │ │ │ │ │ ├── biome-grass.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── camera │ │ │ │ ├── camera.ts │ │ │ │ ├── controls.ts │ │ │ │ └── index.ts │ │ │ │ ├── game-events │ │ │ │ ├── game-events.ts │ │ │ │ └── index.ts │ │ │ │ ├── noise-helper │ │ │ │ ├── index.ts │ │ │ │ ├── noise-helper.ts │ │ │ │ └── utils │ │ │ │ │ ├── createCtx.ts │ │ │ │ │ └── createScroller.ts │ │ │ │ ├── world-chunks-generator │ │ │ │ ├── generate-chunk-data │ │ │ │ │ ├── chunk-vegetation │ │ │ │ │ │ ├── draw-cube-circle.ts │ │ │ │ │ │ ├── draw-tree.ts │ │ │ │ │ │ ├── get-cubes-around.ts │ │ │ │ │ │ ├── get-leaves-cube.ts │ │ │ │ │ │ └── get-tree-cube.ts │ │ │ │ │ ├── generate-chunk-data.worker.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ │ ├── is-above-threshold.ts │ │ │ │ │ │ ├── is-all-around-surrounded.ts │ │ │ │ │ │ └── is-cube-at-location.ts │ │ │ │ ├── generate-chunk-geometries │ │ │ │ │ ├── createChunkBoxHelper │ │ │ │ │ │ ├── createChunkBoxHelper.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createCubePlanes │ │ │ │ │ │ ├── createCubePlanes.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createOptimizedCubeGeometry │ │ │ │ │ │ ├── createOptimizedCubeGeometry.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── createPlaneGeometry │ │ │ │ │ │ ├── createPlaneGeometry.d.ts │ │ │ │ │ │ ├── createPlaneGeometry.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── generate-chunk-geometries.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── generate-chunk-noise │ │ │ │ │ ├── generate-chunk-noise.worker.ts │ │ │ │ │ ├── generate-chunk-noise.worker.types.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils │ │ │ │ │ │ ├── get-cube-noise.ts │ │ │ │ │ │ ├── get-cube.ts │ │ │ │ │ │ └── get-tree-noise.ts │ │ │ │ ├── index.ts │ │ │ │ ├── utils │ │ │ │ │ ├── forEachRange.ts │ │ │ │ │ └── useWorker.ts │ │ │ │ ├── world-chunks-generator.memory.ts │ │ │ │ ├── world-chunks-generator.memory.types.ts │ │ │ │ ├── world-chunks-generator.ts │ │ │ │ └── world-chunks-generator.types.ts │ │ │ │ └── world │ │ │ │ ├── comp │ │ │ │ ├── ambient-light.ts │ │ │ │ ├── directional-light.ts │ │ │ │ ├── hemi-light.ts │ │ │ │ ├── skybox.ts │ │ │ │ ├── spot-light.ts │ │ │ │ └── test.ts │ │ │ │ ├── helpers │ │ │ │ └── getCubeMaterials │ │ │ │ │ ├── getCubeMaterials.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── world.ts │ │ │ │ └── world.types.ts │ │ └── utils │ │ │ ├── _deg-abs.ts │ │ │ ├── _is-defined.ts │ │ │ ├── _perf.ts │ │ │ ├── _random.ts │ │ │ ├── _to-deg.ts │ │ │ ├── _to-rad.ts │ │ │ ├── index.ts │ │ │ └── toRange.ts │ ├── index.html │ ├── index.ts │ ├── shared │ │ └── stores │ │ │ ├── assets-loader.ts │ │ │ ├── gameLoader │ │ │ ├── game-loader.ts │ │ │ └── index.ts │ │ │ └── index.ts │ └── web │ │ ├── app.tsx │ │ ├── app │ │ └── components │ │ │ ├── containers │ │ │ ├── index.js │ │ │ ├── wrapper.jsx │ │ │ └── wrapper.scss │ │ │ ├── help │ │ │ ├── help.jsx │ │ │ ├── help.scss │ │ │ └── index.js │ │ │ ├── logo │ │ │ ├── index.js │ │ │ ├── logo.jsx │ │ │ └── logo.scss │ │ │ └── preloader │ │ │ ├── index.js │ │ │ ├── preloader.jsx │ │ │ └── preloader.scss │ │ ├── components │ │ ├── containers │ │ │ ├── App │ │ │ │ ├── App.tsx │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ ├── Center │ │ │ │ ├── Center.tsx │ │ │ │ └── index.ts │ │ │ └── Container │ │ │ │ ├── Container.tsx │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ └── preloaders │ │ │ └── Linear │ │ │ ├── Linear.tsx │ │ │ ├── index.ts │ │ │ └── styles.ts │ │ ├── routes.d.ts │ │ ├── routes.tsx │ │ ├── routes │ │ └── Home │ │ │ ├── Home.tsx │ │ │ ├── components │ │ │ └── Logo │ │ │ │ ├── Logo.tsx │ │ │ │ ├── index.ts │ │ │ │ └── styles.ts │ │ │ └── index.ts │ │ └── static │ │ ├── bg-dirt.png │ │ ├── dirt.jpg │ │ ├── logo-minecraft.png │ │ └── screenshot.jpg └── libraries │ ├── first-person-controls.ts │ ├── pointer-lock-controls.ts │ ├── scrollable.ts │ ├── trackball.js │ └── typedarray.js ├── tools └── webpack │ ├── neutrino.config.ts │ └── presets │ ├── babel │ ├── babel.ts │ └── index.ts │ ├── react │ ├── index.ts │ ├── react.ts │ └── react.types.ts │ ├── staticAssets │ ├── index.ts │ ├── staticAssets.d.ts │ └── staticAssets.ts │ ├── typescript │ ├── index.ts │ └── typescript.ts │ └── webWorker │ ├── index.ts │ └── webWorker.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=lf 6 | insert_final_newline=false 7 | indent_style=space 8 | indent_size=2 9 | max_line_length = 140 10 | wrap_width = 140 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.export = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint'], 4 | parserOptions: { 5 | ecmaVersion: 2020, 6 | sourceType: 'module', 7 | ecmaFeatures: { 8 | jsx: true, 9 | modules: true, 10 | }, 11 | }, 12 | settings: { 13 | react: { 14 | version: 'detect', 15 | }, 16 | }, 17 | extends: [ 18 | 'plugin:react/recommended', 19 | 'plugin:@typescript-eslint/recommended', 20 | 'prettier/@typescript-eslint', 21 | 'plugin:prettier/recommended', 22 | ], 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | build: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 21 | - uses: actions/checkout@v2 22 | 23 | - name: Yarn install 24 | run: yarn install 25 | 26 | # Runs a single command using the runners shell 27 | - name: Build production 28 | run: yarn build 29 | 30 | - name: S3 Sync 31 | uses: jakejarvis/s3-sync-action@v0.5.1 32 | env: 33 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 34 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 35 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 36 | AWS_REGION: 'eu-west-1' # optional: defaults to us-east-1 37 | SOURCE_DIR: 'build' # optional: defaults to entire repository 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | build/ 4 | lessons/ 5 | src/seemore.html 6 | src/seemore/ 7 | *.log.* 8 | *.log 9 | dist 10 | 11 | -------------------------------------------------------------------------------- /.neutrinorc.js: -------------------------------------------------------------------------------- 1 | // Add support for typescript in neutrino config 2 | require('@babel/register')({ 3 | extensions: ['.es6', '.es', '.jsx', '.js', '.ts'], 4 | presets: [ 5 | '@babel/preset-env', 6 | '@babel/typescript', 7 | ], 8 | plugins: [], 9 | }) 10 | 11 | module.exports = require('./tools/webpack/neutrino.config').default() 12 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 140, 6 | tabWidth: 2, 7 | arrowParens: 'always', 8 | bracketSpacing: true, 9 | jsxBracketSameLine: true, 10 | jsxSingleQuote: true, 11 | proseWrap: 'preserve', 12 | quoteProps: 'as-needed', 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minecraft - Javascript - ThreeJS 2 | 3 | 4 | 5 | # About 6 | 7 | Javascript implementation of minecraft with dynamic world based on a SEED. 8 | 9 | ## Features 10 | - seeded world 11 | - terrain generator 12 | - chunks 13 | 14 | 15 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function babelConfig(api) { 2 | api.cache(true) 3 | 4 | /** 5 | * Babel presets 6 | */ 7 | const presets = ['@babel/preset-env', '@babel/preset-react', '@babel/typescript'] 8 | 9 | /** 10 | * Babel plugins 11 | */ 12 | const plugins = [ 13 | '@babel/plugin-transform-async-to-generator', 14 | '@babel/plugin-proposal-optional-chaining', 15 | 'babel-plugin-styled-components', 16 | '@babel/plugin-syntax-dynamic-import', 17 | '@babel/plugin-proposal-export-default-from', 18 | '@babel/plugin-transform-regenerator', 19 | [ 20 | '@babel/plugin-proposal-decorators', 21 | { 22 | legacy: true, 23 | }, 24 | ], 25 | [ 26 | '@babel/plugin-proposal-class-properties', 27 | { 28 | loose: true, 29 | }, 30 | ], 31 | 'jsx-control-statements', 32 | [ 33 | '@babel/plugin-transform-runtime', 34 | { 35 | corejs: false, 36 | helpers: false, 37 | regenerator: true, 38 | useESModules: true, 39 | }, 40 | ], 41 | [ 42 | 'module-resolver', 43 | { 44 | root: ['./'], 45 | alias: { 46 | '@web': './src/client/web/', 47 | '@game': './src/client/game/', 48 | '@shared': './src/client/shared/', 49 | '@libraries': './src/libraries/', 50 | }, 51 | }, 52 | ], 53 | ] 54 | 55 | return { 56 | presets, 57 | plugins, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /build-tools/config/dev-server.js: -------------------------------------------------------------------------------- 1 | export default { 2 | port: 3000, 3 | host: 'localhost', 4 | protocol: 'http', 5 | quiet: true, 6 | openBrowser: false, 7 | paths: { 8 | contentBase: 'public', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /build-tools/npm-scripts/webpack-dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | import webpack from 'webpack'; 3 | import { createCompiler, prepareUrls } from 'react-dev-utils/WebpackDevServerUtils'; 4 | 5 | /** 6 | * Webpack debug build runner 7 | * - run webpack in debug mode, 8 | * - no dev server 9 | * - will give you the webpack output in /build 10 | */ 11 | export default new class WebpackDebug { 12 | 13 | // require webpack config 14 | webpackConfig = require('../webpack-config/webpack-dev'); 15 | devServerConfig = require('../config/dev-server').default; 16 | WebpackDevServer = require('../webpack-dev-server/webpack-dev-server').default; 17 | 18 | /** 19 | * Prepare dev configuration 20 | * - prepare config 21 | * - create dev server 22 | */ 23 | constructor() { 24 | 25 | // extract from devServerConfig 26 | const { port, host, protocol, appName } = this.devServerConfig; 27 | 28 | // prepare urls 29 | const urls = prepareUrls(protocol, host, port); 30 | 31 | // prepare webpack compiler & webpack dev server 32 | const compiler = createCompiler(webpack, this.webpackConfig, appName, urls); 33 | 34 | this.createDevServer(compiler, host); 35 | } 36 | 37 | /** 38 | * Create dev server 39 | */ 40 | createDevServer(compiler, host) { 41 | // create web-dev server 42 | const customWebpackDevServer = new this.WebpackDevServer({ 43 | compiler, 44 | devServerConfig: this.devServerConfig, 45 | publicPath: this.webpackConfig.output.publicPath, 46 | host, 47 | }); 48 | 49 | // start webpack dev server 50 | customWebpackDevServer.launch(); 51 | } 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /build-tools/npm-scripts/webpack-live.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | import webpack from 'webpack'; 3 | import chalk from 'chalk'; 4 | 5 | /** 6 | * Webpack debug build runner 7 | * - run webpack in debug mode, 8 | * - no dev server 9 | * - will give you the webpack output in /build 10 | */ 11 | export default new class WebpackDebug { 12 | 13 | // require webpack config 14 | webpackConfig = require('../webpack-config/webpack-live'); 15 | 16 | /** 17 | * Prepare dev configuration 18 | * - prepare config 19 | * - create dev server 20 | */ 21 | constructor() { 22 | 23 | // prepare webpack compiler & webpack dev server 24 | const compiler = webpack(this.webpackConfig); 25 | 26 | console.log(chalk.bgYellow.black(' webpack live starting ... ')); 27 | 28 | compiler.run((err, stats) => { 29 | console.log('[webpack:build]', stats.toString({ 30 | colors: true, 31 | })); 32 | }); 33 | } 34 | 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /build-tools/webpack-config/webpack-dev.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import webpack from 'webpack'; 4 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 5 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 6 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 7 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 8 | 9 | const appBuildPath = path.resolve(process.cwd(), 'build/'); 10 | const appMainJS = path.resolve(process.cwd(), 'src/main.js'); 11 | const typedArrayJS = path.resolve(process.cwd(), 'src/lib/typedarray.js'); 12 | 13 | module.exports = { 14 | devtool: 'cheap-module-source-map', 15 | entry: [ 16 | // Include babel-polyfills 17 | 'babel-polyfill', 18 | // Errors should be considered fatal in development 19 | require.resolve('react-error-overlay'), 20 | // Finally, this is your app's code: 21 | appMainJS, 22 | typedArrayJS, 23 | ], 24 | output: { 25 | // Next line is not used in dev but WebpackDevServer crashes without it: 26 | path: appBuildPath, 27 | // Add /* filename */ comments to generated require()s in the output. 28 | pathinfo: true, 29 | // virtual path to file that is served by WebpackDevServer in development. 30 | filename: 'static/js/bundle.js', 31 | // There are also additional JS chunk files if you use code splitting. 32 | chunkFilename: 'static/js/[name].chunk.js', 33 | // This is the URL that app is served from. We use "/" in development. 34 | publicPath: '/', 35 | // Point sourcemap entries to original disk location (format as URL on Windows) 36 | // devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), 37 | }, 38 | resolve: { 39 | // // This allows you to set a fallback for where Webpack should look for modules. 40 | // // We placed these paths second because we want `node_modules` to "win" 41 | // // if there are any conflicts. This matches Node resolution mechanism. 42 | // // https://github.com/facebookincubator/create-react-app/issues/253 43 | // modules: ['node_modules', paths.appNodeModules].concat( 44 | // // It is guaranteed to exist because we tweak it in `env.js` 45 | // process.env.NODE_PATH.split(path.delimiter).filter(Boolean), 46 | // ), 47 | extensions: [ 48 | '.web.js', '.js', '.json', '.web.jsx', '.jsx', '.eot', 49 | '.svg', '.woff2', '.woff', '.tff', '.css', '.scss', '.png', 50 | ], 51 | }, 52 | module: { 53 | strictExportPresence: true, 54 | rules: [ 55 | { 56 | // "oneOf" will traverse all following loaders until one will match 57 | oneOf: [ 58 | { 59 | test: /\.(js|jsx)$/, 60 | loader: 'babel-loader', 61 | exclude: /node_modules/, 62 | }, 63 | // { 64 | // test: /\.html$/, 65 | // loader: 'raw-loader' 66 | // }, 67 | { 68 | test: /\.(scss|css)$/, 69 | exclude: /node_modules/, 70 | use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({ 71 | fallback: 'style-loader', 72 | use: [ 73 | { 74 | loader: 'css-loader', 75 | options: { 76 | importLoaders: 2, 77 | sourceMap: true, 78 | }, 79 | }, 80 | { 81 | loader: 'sass-loader', 82 | options: { 83 | sourceMap: true, 84 | }, 85 | }, 86 | ], 87 | })), 88 | }, 89 | { 90 | test: /\.(gif|png|jpe?g|svg)$/i, 91 | use: [ 92 | 'file-loader', 93 | { 94 | loader: 'image-webpack-loader', 95 | options: { 96 | bypassOnDebug: true, 97 | }, 98 | }, 99 | ], 100 | }, 101 | { 102 | // Fallback file loader 103 | // it will match all not matched files! 104 | // ANY loader below this point WILL BE IGNORED! 105 | exclude: [/\.js$/, /\.html$/, /\.json$/], 106 | loader: require.resolve('file-loader'), 107 | options: { 108 | name: 'static/media/[name].[ext]', 109 | }, 110 | }, 111 | ], 112 | }, 113 | // ** STOP ** Are you adding a new loader? 114 | // Make sure to add the new loader(s) before the "Fallback file loader" loader. 115 | ], 116 | }, 117 | plugins: [ 118 | // we can't clean due to src/content/api running simultaneously 119 | new CleanWebpackPlugin(appBuildPath, { allowExternal: true }), 120 | // Add module names to factory functions so they appear in browser profiler. 121 | new webpack.NamedModulesPlugin(), 122 | // This is necessary to emit hot updates (currently CSS only): 123 | new webpack.HotModuleReplacementPlugin(), 124 | // extract style.css 125 | new ExtractTextPlugin({ 126 | filename: 'style.css?v=[contenthash]', 127 | allChunks: true, 128 | ignoreOrder: true, 129 | }), 130 | // copy assets 131 | new CopyWebpackPlugin([ 132 | { context: path.resolve(process.cwd(), 'src/assets'), from: '**/*', to: 'assets' }, 133 | ]), 134 | // create index.html 135 | new HtmlWebpackPlugin({ 136 | title: 'ThreeJS', 137 | filename: 'index.html', 138 | template: path.resolve(process.cwd(), 'src/app/web/index.html'), 139 | }), 140 | ], 141 | // Some libraries import Node modules but don't use them in the browser. 142 | // Tell Webpack to provide empty mocks for them so importing them works. 143 | node: { 144 | dgram: 'empty', 145 | fs: 'empty', 146 | net: 'empty', 147 | tls: 'empty', 148 | dns: 'empty', 149 | }, 150 | // Turn off performance hints during development because we don't do any 151 | // splitting or minification in interest of speed. 152 | performance: { 153 | hints: false, 154 | }, 155 | }; 156 | -------------------------------------------------------------------------------- /build-tools/webpack-config/webpack-live.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 5 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 6 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 7 | import UglifyJsPlugin from 'uglifyjs-webpack-plugin'; 8 | 9 | const appBuildPath = path.resolve(process.cwd(), 'dist/'); 10 | const appMainJS = path.resolve(process.cwd(), 'src/main.js'); 11 | const typedArrayJS = path.resolve(process.cwd(), 'src/lib/typedarray.js'); 12 | 13 | module.exports = { 14 | devtool: 'cheap-module-source-map', 15 | entry: [ 16 | // Include babel-polyfills 17 | 'babel-polyfill', 18 | // Finally, this is your app's code: 19 | appMainJS, 20 | typedArrayJS, 21 | ], 22 | output: { 23 | // Next line is not used in dev but WebpackDevServer crashes without it: 24 | path: appBuildPath, 25 | // Add /* filename */ comments to generated require()s in the output. 26 | pathinfo: true, 27 | // virtual path to file that is served by WebpackDevServer in development. 28 | filename: 'static/js/bundle.js', 29 | // There are also additional JS chunk files if you use code splitting. 30 | chunkFilename: 'static/js/[name].chunk.js', 31 | // This is the URL that app is served from. We use "/" in development. 32 | publicPath: '/', 33 | // Point sourcemap entries to original disk location (format as URL on Windows) 34 | // devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), 35 | }, 36 | resolve: { 37 | // // This allows you to set a fallback for where Webpack should look for modules. 38 | // // We placed these paths second because we want `node_modules` to "win" 39 | // // if there are any conflicts. This matches Node resolution mechanism. 40 | // // https://github.com/facebookincubator/create-react-app/issues/253 41 | // modules: ['node_modules', paths.appNodeModules].concat( 42 | // // It is guaranteed to exist because we tweak it in `env.js` 43 | // process.env.NODE_PATH.split(path.delimiter).filter(Boolean), 44 | // ), 45 | extensions: [ 46 | '.web.js', '.js', '.json', '.web.jsx', '.jsx', '.eot', 47 | '.svg', '.woff2', '.woff', '.tff', '.css', '.scss', '.png', 48 | ], 49 | }, 50 | module: { 51 | strictExportPresence: true, 52 | rules: [ 53 | { 54 | // "oneOf" will traverse all following loaders until one will match 55 | oneOf: [ 56 | { 57 | test: /\.(js|jsx)$/, 58 | loader: 'babel-loader', 59 | exclude: /node_modules/, 60 | }, 61 | // { 62 | // test: /\.html$/, 63 | // loader: 'raw-loader' 64 | // }, 65 | { 66 | test: /\.(scss|css)$/, 67 | exclude: /node_modules/, 68 | use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({ 69 | fallback: 'style-loader', 70 | use: [ 71 | { 72 | loader: 'css-loader', 73 | options: { 74 | importLoaders: 2, 75 | sourceMap: true, 76 | }, 77 | }, 78 | { 79 | loader: 'sass-loader', 80 | options: { 81 | sourceMap: true, 82 | }, 83 | }, 84 | ], 85 | })), 86 | }, 87 | { 88 | test: /\.(gif|png|jpe?g|svg)$/i, 89 | use: [ 90 | 'file-loader', 91 | { 92 | loader: 'image-webpack-loader', 93 | options: { 94 | bypassOnDebug: true, 95 | }, 96 | }, 97 | ], 98 | }, 99 | { 100 | // Fallback file loader 101 | // it will match all not matched files! 102 | // ANY loader below this point WILL BE IGNORED! 103 | exclude: [/\.js$/, /\.html$/, /\.json$/], 104 | loader: require.resolve('file-loader'), 105 | options: { 106 | name: 'static/media/[name].[ext]', 107 | }, 108 | }, 109 | ], 110 | }, 111 | // ** STOP ** Are you adding a new loader? 112 | // Make sure to add the new loader(s) before the "Fallback file loader" loader. 113 | ], 114 | }, 115 | plugins: [ 116 | // we can't clean due to src/content/api running simultaneously 117 | new CleanWebpackPlugin(appBuildPath, { allowExternal: true }), 118 | // uglify js 119 | new UglifyJsPlugin(), 120 | // extract style.css 121 | new ExtractTextPlugin({ 122 | filename: 'style.css?v=[contenthash]', 123 | allChunks: true, 124 | ignoreOrder: true, 125 | }), 126 | // copy assets 127 | new CopyWebpackPlugin([ 128 | { context: path.resolve(process.cwd(), 'src/assets'), from: '**/*', to: 'assets' }, 129 | ]), 130 | // create index.html 131 | new HtmlWebpackPlugin({ 132 | title: 'ThreeJS', 133 | filename: 'index.html', 134 | template: path.resolve(process.cwd(), 'src/app/web/index.html'), 135 | }), 136 | ], 137 | // Some libraries import Node modules but don't use them in the browser. 138 | // Tell Webpack to provide empty mocks for them so importing them works. 139 | node: { 140 | dgram: 'empty', 141 | fs: 'empty', 142 | net: 'empty', 143 | tls: 'empty', 144 | dns: 'empty', 145 | }, 146 | // Turn off performance hints during development because we don't do any 147 | // splitting or minification in interest of speed. 148 | performance: { 149 | hints: false, 150 | }, 151 | }; 152 | -------------------------------------------------------------------------------- /build-tools/webpack-dev-server/middleware/api/_coverage.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import cheerio from 'cheerio'; 4 | import to from 'await-to-js'; 5 | 6 | import { readFile } from '../../../helpers/utils/utils'; 7 | import config from '../../../config/middleware/api'; 8 | 9 | /** 10 | * Get percentage for selector 11 | * @param $ 12 | * @param selector 13 | * @return {number} 14 | */ 15 | function getPercentage($, selector) { 16 | const container = $(selector).parent(); 17 | const val = container.find('span').first().html(); 18 | return parseInt(val, 10); 19 | } 20 | 21 | /** 22 | * Get coverage scores 23 | * - returns total, statements, branches etc as a percentage coverage 24 | * - we have to read this from html file! 25 | * - returns null if file doesn't exists 26 | * @param req 27 | * @param res 28 | * @return {Promise} 29 | */ 30 | export default async function coverage(req, res) { 31 | 32 | // prepare coverage file path 33 | const coverageFile = path.resolve(process.cwd(), config.coverage.indexFilePath); 34 | 35 | // read coverage html file 36 | const [err, content] = await to(readFile(coverageFile)); 37 | 38 | if (err) { 39 | res.json(null); 40 | return; 41 | } 42 | 43 | // load coverage html into cheerio for querying 44 | const $ = cheerio.load(content); 45 | 46 | // get percentage values 47 | const statements = getPercentage($, 'span:contains(Statements)'); 48 | const branches = getPercentage($, 'span:contains(Branches)'); 49 | const functions = getPercentage($, 'span:contains(Functions)'); 50 | const lines = getPercentage($, 'span:contains(Lines)'); 51 | 52 | // calculate total coverage 53 | const total = (statements + branches + functions + lines) * 100 / 400; 54 | 55 | res.json({ 56 | total, 57 | statements, 58 | branches, 59 | functions, 60 | lines, 61 | }); 62 | 63 | } 64 | -------------------------------------------------------------------------------- /build-tools/webpack-dev-server/middleware/api/_reports.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import clone from 'lodash/cloneDeep'; 4 | import to from 'await-to-js'; 5 | 6 | import log from '../../../helpers/log/log'; 7 | import { readFile, spawn } from '../../../helpers/utils/utils'; 8 | import config from '../../../config/middleware/api'; 9 | 10 | let updateInProgress = false; 11 | 12 | /** 13 | * Get time difference in seconds 14 | * @param time 15 | * @return {number} 16 | */ 17 | function getDiffTime(time) { 18 | return Math.floor(((Date.now()) - time) / 1000); 19 | } 20 | 21 | /** 22 | * Update tests by running "npm run start:tests" 23 | */ 24 | function updateTests() { 25 | 26 | log.info('updating unit tests', '...'); 27 | 28 | updateInProgress = true; 29 | 30 | // fix process.env 31 | // react-create-app is adding BABEL and NODE env into process.env 32 | // for our tests to run without crashing we have to remove them 33 | const env = clone(process.env); 34 | delete env.BABEL_ENV; 35 | delete env.NODE_ENV; 36 | delete env.NODE_PATH; 37 | 38 | // start tests process 39 | const ls = spawn('npm', ['run', 'start:tests'], { 40 | cwd: path.resolve(process.cwd()), 41 | env, 42 | }); 43 | 44 | // handle end of tests process 45 | // set updateInProgress to false 46 | ls.on('close', () => { 47 | log.status('unit tests updated'); 48 | updateInProgress = false; 49 | }); 50 | 51 | setTimeout(() => { 52 | }, config.reports.estimatedUpdateDuration); 53 | } 54 | 55 | /** 56 | * Get jest unit tests reports 57 | * @param req 58 | * @param res 59 | * @return {Promise} 60 | */ 61 | export default async function reports(req, res) { 62 | 63 | // get reports file 64 | const coverageFile = path.resolve(process.cwd(), 'coverage/report.json'); 65 | 66 | // read coverage html file 67 | const [err, data] = await to(readFile(coverageFile)); 68 | 69 | // if error or no data at all 70 | if (err || !data) { 71 | updateTests(); 72 | res.json(null); 73 | return; 74 | } 75 | 76 | // parse json 77 | const json = JSON.parse(data); 78 | 79 | // should update tests 80 | // update tests every x seconds ( default 600 = 5 min ) 81 | if (getDiffTime(json.startTime) >= config.reports.updateAfter && !updateInProgress) { 82 | updateTests(); 83 | } 84 | 85 | // if we don't have any json results, run tests again ignoring time diff 86 | if (!json.numTotalTests && !updateInProgress) { 87 | updateTests(); 88 | } 89 | 90 | res.json({ 91 | total: json.numTotalTests, 92 | passed: json.numPassedTests, 93 | failed: json.numFailedTests, 94 | lastUpdateTime: getDiffTime(json.startTime), 95 | }); 96 | } 97 | 98 | -------------------------------------------------------------------------------- /build-tools/webpack-dev-server/middleware/api/api.js: -------------------------------------------------------------------------------- 1 | import coverage from './_coverage'; 2 | import reports from './_reports'; 3 | 4 | export default function api(app) { 5 | 6 | app.get('/__api/:method', (req, res) => { 7 | 8 | if (req.params.method === 'coverage') { 9 | coverage(req, res); 10 | } 11 | 12 | if (req.params.method === 'reports') { 13 | reports(req, res); 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /build-tools/webpack-dev-server/middleware/extra-pages.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import express from 'express'; 4 | 5 | import { spawn } from '../../helpers/utils/utils'; 6 | import config from '../../config/middleware/extra-pages'; 7 | 8 | // lets start styleguide process 9 | // it will be available under: localhost:6060 10 | spawn('npm', ['run', config.styleguideCmd]); 11 | 12 | /** 13 | * Include any extra pages into webpack dev server 14 | * This will allow us to host some of the external pages like: 15 | * - coverage report 16 | * @param app 17 | */ 18 | export default function extraPages(app) { 19 | app.use(config.coverageUrl, express.static(path.resolve(process.cwd(), config.coveragePath))); 20 | } 21 | -------------------------------------------------------------------------------- /build-tools/webpack-dev-server/static-pages/devices/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | asdasasd 9 | 10 | 11 | -------------------------------------------------------------------------------- /build-tools/webpack-dev-server/webpack-dev-server.js: -------------------------------------------------------------------------------- 1 | /* eslint "filenames/match-exported": "off" */ 2 | const path = require('path'); 3 | 4 | const chalk = require('chalk'); 5 | const openBrowser = require('react-dev-utils/openBrowser'); 6 | const clearConsole = require('react-dev-utils/clearConsole'); 7 | const WebpackDevServer = require('webpack-dev-server'); 8 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); 9 | 10 | export default class CustomWebpackDevServer { 11 | 12 | constructor({ compiler, devServerConfig, publicPath }) { 13 | this.compiler = compiler; 14 | this.config = devServerConfig; 15 | this.publicPath = publicPath; 16 | this._createWebpackDevServer(); 17 | } 18 | 19 | /** 20 | * Create webpack dev server 21 | * - create server using webpack compiler and config 22 | * @private 23 | */ 24 | _createWebpackDevServer() { 25 | this.server = new WebpackDevServer(this.compiler, { 26 | // Enable gzip compression of generated files. 27 | compress: true, 28 | // Silence WebpackDevServer's own logs since they're generally not useful. 29 | // It will still show compile warnings and errors with this setting. 30 | clientLogLevel: 'none', 31 | // By default WebpackDevServer serves physical files from current directory 32 | // in addition to all the virtual build products that it serves from memory. 33 | contentBase: path.resolve(this.config.paths.contentBase), 34 | // By default files from `contentBase` will not trigger a page reload. 35 | watchContentBase: true, 36 | // Enable hot reloading server. 37 | hot: true, 38 | // It is important to tell WebpackDevServer to use the same "root" path 39 | // as we specified in the config. In development, we always serve from /. 40 | publicPath: this.publicPath, 41 | // WebpackDevServer is noisy by default so we emit custom message instead 42 | // by listening to the compiler events with `compiler.plugin` calls above. 43 | quiet: this.config.quiet, 44 | // Reportedly, this avoids CPU overload on some systems. 45 | // https://github.com/facebookincubator/create-react-app/issues/293 46 | watchOptions: { 47 | ignored: /node_modules/, 48 | }, 49 | host: this.config.host, 50 | overlay: false, 51 | historyApiFallback: { 52 | // Paths with dots should still use the history fallback. 53 | // See https://github.com/facebookincubator/create-react-app/issues/387. 54 | disableDotRule: true, 55 | }, 56 | setup(app) { 57 | // Dev api middleware 58 | // api(app); 59 | // Include extra pages into the webpack dev server 60 | // extraPages(app); 61 | // This lets us open files from the runtime error overlay. 62 | // app.use(errorOverlayMiddleware()); 63 | // This service worker file is effectively a 'no-op' that will reset any 64 | // previous service worker registered for the same host:port combination. 65 | app.use(noopServiceWorkerMiddleware()); 66 | }, 67 | }); 68 | } 69 | 70 | /** 71 | * Launch webpack dev server 72 | * - clear console 73 | * - console log info 74 | * - open browser 75 | */ 76 | launch() { 77 | this.server.listen(this.config.port, this.config.host, err => { 78 | if (err) { 79 | return console.log(err); 80 | } 81 | clearConsole(); 82 | console.log(chalk.cyan('Starting the development server...\n')); 83 | if (this.config.openBrowser) { 84 | console.log(`${this.config.protocol}://${this.config.host}:${this.config.port}`); 85 | openBrowser(`${this.config.protocol}://${this.config.host}:${this.config.port}`); 86 | } 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minecraft-threejs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "ISC", 8 | "scripts": { 9 | "eslint": "eslint ./src/**/*.js", 10 | "start": " cross-env NEUTRINO_CONFIG=client webpack-dev-server --mode development --env=int1", 11 | "build": "webpack --mode production" 12 | }, 13 | "dependencies": { 14 | "@types/dat.gui": "^0.7.5", 15 | "@types/noisejs": "^0.0.28", 16 | "@types/three": "^0.103.2", 17 | "classnames": "^2.2.6", 18 | "dat.gui": "^0.7.7", 19 | "history": "^5.0.0", 20 | "lodash": "^4.17.15", 21 | "mobx": "^5.0.3", 22 | "mobx-react": "^5.2.3", 23 | "noisejs": "^2.1.0", 24 | "p-queue": "^6.4.0", 25 | "react": "^16.13.1", 26 | "react-dom": "^16.13.1", 27 | "react-router": "^5.2.0", 28 | "react-router-config": "^5.1.1", 29 | "react-router-dom": "^5.2.0", 30 | "three": "^0.117.1", 31 | "worker-loader": "^2.0.0", 32 | "jquery": "^3.5.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/cli": "^7.10.1", 36 | "@babel/core": "^7.10.2", 37 | "@babel/node": "^7.10.1", 38 | "@babel/plugin-proposal-class-properties": "^7.5.5", 39 | "@babel/plugin-proposal-decorators": "^7.4.4", 40 | "@babel/plugin-proposal-export-default-from": "^7.10.1", 41 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", 42 | "@babel/plugin-proposal-optional-chaining": "^7.6.0", 43 | "@babel/plugin-syntax-async-generators": "^7.2.0", 44 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 45 | "@babel/plugin-transform-async-to-generator": "^7.5.0", 46 | "@babel/plugin-transform-modules-commonjs": "^7.5.0", 47 | "@babel/plugin-transform-runtime": "^7.5.5", 48 | "@babel/preset-env": "^7.5.5", 49 | "@babel/preset-react": "^7.0.0", 50 | "@babel/preset-typescript": "^7.6.0", 51 | "@neutrinojs/copy": "^9.2.0", 52 | "@neutrinojs/react": "^9.2.0", 53 | "@types/lodash": "^4.14.155", 54 | "@types/react": "^16.8.23", 55 | "@types/react-dom": "^16.8.5", 56 | "@types/react-router": "^5.0.3", 57 | "@types/react-router-config": "^5.0.0", 58 | "@types/react-router-dom": "^4.3.4", 59 | "@types/styled-components": "^5.1.0", 60 | "@typescript-eslint/eslint-plugin": "^3.2.0", 61 | "@typescript-eslint/parser": "^3.2.0", 62 | "babel-eslint": "^10.1.0", 63 | "babel-plugin-jsx-control-statements": "^4.0.0", 64 | "babel-plugin-module-resolver": "^3.2.0", 65 | "babel-plugin-styled-components": "^1.10.6", 66 | "cross-env": "^7.0.2", 67 | "eslint": "^7.2.0", 68 | "eslint-config-prettier": "^6.11.0", 69 | "eslint-plugin-prettier": "^3.1.3", 70 | "express": "^4.16.3", 71 | "fork-ts-checker-webpack-plugin": "^5.0.0", 72 | "jsx-control-statements": "^3.2.8", 73 | "neutrino": "^9.2.0", 74 | "node-sass": "^4.14.1", 75 | "prettier": "^2.0.5", 76 | "sass-loader": "^8.0.2", 77 | "styled-bootstrap-grid": "^3.1.0", 78 | "styled-components": "^5.1.1", 79 | "styled-spinkit": "^1.1.0", 80 | "stylelint-config-recommended": "^3.0.0", 81 | "stylelint-config-styled-components": "^0.1.1", 82 | "stylelint-custom-processor-loader": "^0.6.0", 83 | "stylelint-processor-styled-components": "^1.10.0", 84 | "typescript": "^3.9.5", 85 | "webpack": "^4.43.0", 86 | "webpack-chain": "^6.4.0", 87 | "webpack-cli": "^3.3.11", 88 | "webpack-dev-server": "^3.11.0" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/client/game/@types/chunk.types.ts: -------------------------------------------------------------------------------- 1 | import { BufferGeometry, Material, BoxHelper } from 'three' 2 | 3 | export interface WorldChunk { 4 | geometry: BufferGeometry 5 | helperGeometries: BufferGeometry[] | BoxHelper[] 6 | params?: Material[] 7 | } 8 | -------------------------------------------------------------------------------- /src/client/game/@types/cube.types.ts: -------------------------------------------------------------------------------- 1 | import { Geometry } from 'three' 2 | 3 | export interface ChunkCubesData { 4 | material: number 5 | location: { 6 | x: number 7 | y: number 8 | z: number 9 | } 10 | noiseValue: number 11 | surrounding: { 12 | px: boolean 13 | nz: boolean 14 | nx: boolean 15 | pz: boolean 16 | py: boolean 17 | ny: boolean 18 | } 19 | } 20 | 21 | // Cube planes 22 | export interface CubePlanes { 23 | px: Geometry 24 | nz: Geometry 25 | nx: Geometry 26 | pz: Geometry 27 | py: Geometry 28 | ny: Geometry 29 | } 30 | -------------------------------------------------------------------------------- /src/client/game/@types/locations.types.ts: -------------------------------------------------------------------------------- 1 | export interface Location2D { 2 | x: number 3 | y: number 4 | } 5 | -------------------------------------------------------------------------------- /src/client/game/app.ts: -------------------------------------------------------------------------------- 1 | import { AxesHelper, DirectionalLight, Scene } from 'three' 2 | 3 | import renderer from '@game/system/engine/renderer' 4 | import dat from '@game/helpers/dat-gui' 5 | import { worldConfig } from '@game/config' 6 | import AssetsLoader from '@game/system/modules/assets-loader' 7 | import Camera from '@game/system/modules/camera' 8 | import World from '@game/system/modules/world' 9 | import { Perf } from '@game/utils' 10 | import { storeAssetsLoader } from '@shared/stores' 11 | 12 | // import three js extensions 13 | import '@libraries/first-person-controls' 14 | 15 | export default class App { 16 | scene 17 | camera 18 | renderer 19 | updateStack = [] 20 | 21 | def = {} 22 | 23 | constructor() { 24 | Perf.get('Engine starts') 25 | this.init() 26 | } 27 | 28 | async init() { 29 | // storeAssetsLoader.setLoading(true) 30 | // 31 | // // load assets 32 | // const assetsLoader = new AssetsLoader() 33 | // await assetsLoader.loadAssets() 34 | 35 | // create new scene 36 | this.scene = new Scene() 37 | 38 | // create new world 39 | this.world = new World(this, { 40 | seed: worldConfig.seed, 41 | chunkOptions: { 42 | mod: 30, 43 | }, 44 | }) 45 | 46 | // dat.onChange('world:chunk:mod', async (value) => { 47 | // if (storeAssetsLoader.isLoading) return 48 | // storeAssetsLoader.setLoading(true) 49 | // this.world.destroy() 50 | // this.world = new World({ 51 | // app: this, 52 | // seed: worldConfig.seed, 53 | // chunkOptions: { 54 | // mod: value, 55 | // }, 56 | // }) 57 | // await this.world.generateWorld() 58 | // this.world.renderWorld() 59 | // storeAssetsLoader.setLoading(false) 60 | // }) 61 | 62 | // generate world chunks 63 | // - we won't be rendering it yet 64 | await this.world.generateWorld() 65 | 66 | // create renderer 67 | // required before rendering anything to the scene 68 | // - at this point, react will be replaced with threejs! 69 | renderer.create('#WebGL-output') 70 | 71 | storeAssetsLoader.setLoading(false) 72 | 73 | // axis helper 74 | // show axis for debugging 75 | this.createAxis() 76 | 77 | // render pre-generated world 78 | this.world.renderWorld() 79 | 80 | // apply lights to the world 81 | this.createLights() 82 | this.camera = new Camera(this) 83 | Perf.get('Engine starts').end() 84 | 85 | // start renderer loop 86 | // - it will initialize infinite rendering 87 | renderer.render(this.scene, this.camera) 88 | } 89 | 90 | createAxis() { 91 | const axis = new AxesHelper(200) 92 | this.scene.add(axis) 93 | } 94 | 95 | createLights() { 96 | const directionalLight = new DirectionalLight(0xffffff, 1.4) 97 | directionalLight.position.set(-50, 50, 50) 98 | this.scene.add(directionalLight) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/client/game/config/assets.ts: -------------------------------------------------------------------------------- 1 | export const assetsConfig = { 2 | SKY: { 3 | 1: '../resources/textures/blocks/sky_01.png', 4 | 2: '../resources/textures/blocks/sky_02.png', 5 | 3: '../resources/textures/blocks/sky_03.png', 6 | 4: '../resources/textures/blocks/sky_04.png', 7 | 5: '../resources/textures/blocks/sky_05.png', 8 | }, 9 | BLOCKS: { 10 | 1: '../resources/textures/blocks/hardened_clay_stained_green.png', 11 | 2: '../resources/textures/blocks/dirt.png', 12 | 3: '../resources/textures/blocks/cobblestone_mossy.png', 13 | 4: '../resources/textures/blocks/log_spruce.png', 14 | 5: '../resources/textures/blocks/leaves.png', 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /src/client/game/config/general.ts: -------------------------------------------------------------------------------- 1 | export const generalConfig = { 2 | test: 1, 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/config/index.ts: -------------------------------------------------------------------------------- 1 | export { worldConfig } from './world' 2 | export { generalConfig } from './general' 3 | export { assetsConfig } from './assets' 4 | -------------------------------------------------------------------------------- /src/client/game/config/world.ts: -------------------------------------------------------------------------------- 1 | export const worldConfig = { 2 | // world seed ID 3 | seed: 0, 4 | // size of each cube 5 | cubeSize: 20, 6 | // Number of chunks 7 | // Min: 9 8 | // Pattern. 9, 25, 49, 81, 121, 169, 225, 289, 361, 441 9 | chunks: 25, 10 | // size of each chunks 11 | chunkSize: 25, // 26 12 | // how many chunks to generate at once 13 | parallelChunkGeneration: 20, 14 | // CHunk modifier 15 | chunkMod: 30, 16 | } 17 | -------------------------------------------------------------------------------- /src/client/game/helpers/dat-gui/dat-gui.ts: -------------------------------------------------------------------------------- 1 | import Dat from 'dat.gui/build/dat.gui.js' 2 | 3 | import { isDefined } from '@game/utils' 4 | 5 | export default new (class DatGUI { 6 | events = {} 7 | 8 | controlsGroups = [ 9 | ['World', 'world', [['add', 'chunk:mod', 30.0, -60.0, 60.0]]], 10 | [ 11 | 'Spot light', 12 | 'spot:light', 13 | [ 14 | ['addColor', 'color', 0x8c8c8c], 15 | ['add', 'shadow', 4096, 0, 100000], 16 | ['add', 'position:x', -441, -100000, 100000], 17 | ['add', 'position:y', 889, -100000, 100000], 18 | ['add', 'position:z', -1563, -100000, 100000], 19 | ['add', 'angle', 2.2, -10.0, 10.0], 20 | ['add', 'penumbra', 0.0, 0, 1], 21 | ['add', 'decay', 1, -10, 10], 22 | ['add', 'distance', 10000, 0, 20000], 23 | ['add', 'bias', 0.0, -0.0003, 0.0003], 24 | ], 25 | ], 26 | ['Ambient light', 'ambient:light', [['addColor', 'color', 0x919191]]], 27 | [ 28 | 'Hemi light', 29 | 'hemi:light', 30 | [ 31 | ['addColor', 'color1', 0x6a777d], 32 | ['addColor', 'color2', 0x848484], 33 | ['add', 'intensity', 0.6, -1, 1], 34 | ], 35 | ], 36 | ] 37 | 38 | constructor() { 39 | // const gui = new Dat.GUI() 40 | // 41 | // // generate default DAT values 42 | // const Text = function (controlsGroups) { 43 | // for (const [groupName, groupTag, items] of controlsGroups) { 44 | // for (const [type, name, value] of items) { 45 | // this[name] = value 46 | // } 47 | // } 48 | // } 49 | // 50 | // // create default values 51 | // const text = new Text(this.controlsGroups) 52 | // 53 | // // build dat GUI 54 | // for (const [groupName, groupTag, items] of this.controlsGroups) { 55 | // const guiGroup = gui.addFolder(groupName) 56 | // for (const [type, name, value, min, max] of items) { 57 | // guiGroup[type](text, name, min, max).onChange((v) => this.handleChange(`${groupTag}:${name}`, v)) 58 | // } 59 | // } 60 | } 61 | 62 | onChange(type, cb) { 63 | // if (!isDefined(this.events[type])) { 64 | // this.events[type] = [] 65 | // } 66 | // this.events[type].push(cb) 67 | } 68 | 69 | handleChange(type, value) { 70 | // if (isDefined(this.events[type])) { 71 | // this.events[type].map((cb) => cb(value)) 72 | // } 73 | } 74 | })() 75 | -------------------------------------------------------------------------------- /src/client/game/helpers/dat-gui/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './dat-gui'; 2 | -------------------------------------------------------------------------------- /src/client/game/helpers/dat/dat.ts: -------------------------------------------------------------------------------- 1 | import dat, { GUI } from 'dat.gui' 2 | 3 | import Space from './space' 4 | 5 | export class Dat { 6 | private gui: GUI 7 | private folders: { [key: string]: GUI } = {} 8 | 9 | constructor() { 10 | this.gui = new dat.GUI() 11 | this.gui.close() 12 | } 13 | 14 | createSpace(name: string, targetContext: unknown) { 15 | this.ensureFolder(name) 16 | return new Space(this, this.folders[name], targetContext) 17 | } 18 | 19 | private ensureFolder(folder: string) { 20 | if (typeof this.folders[folder] === 'undefined') { 21 | this.folders[folder] = this.gui.addFolder(folder) 22 | this.folders[folder].open() 23 | } 24 | } 25 | 26 | add(folder: string, label, { data, opts }, cb) { 27 | this.ensureFolder(folder) 28 | this.folders[folder].add(data, label, ...opts).onChange(cb) 29 | } 30 | 31 | addColor(folder, label, { data, opts }, cb) { 32 | this.ensureFolder(folder) 33 | this.folders[folder].addColor(data, label, ...opts).onChange(cb) 34 | } 35 | 36 | addTo(folder) { 37 | this.ensureFolder(folder) 38 | return this.folder[follder] 39 | } 40 | } 41 | 42 | export default new Dat() 43 | -------------------------------------------------------------------------------- /src/client/game/helpers/dat/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './dat' 2 | -------------------------------------------------------------------------------- /src/client/game/helpers/dat/space.ts: -------------------------------------------------------------------------------- 1 | import { Math as ThreeMath } from 'three' 2 | import { GUI } from 'dat.gui' 3 | import { Color } from 'three' 4 | 5 | import get from 'lodash/get' 6 | import set from 'lodash/set' 7 | 8 | import { Dat } from './dat' 9 | 10 | import { AddOpts, TargetContextThree, ConverterTypes, RgbaColor } from './space.types' 11 | 12 | const valueConverters = { 13 | default: { 14 | get: (val: number) => val, 15 | set: (val: number) => val, 16 | }, 17 | deg: { 18 | get: (val: number) => ThreeMath.radToDeg(val), 19 | set: (val: number) => ThreeMath.degToRad(val), 20 | }, 21 | } 22 | 23 | export default class DatSpace { 24 | private context: Dat = null 25 | private space: GUI = null 26 | private readonly targetContext: TargetContextThree = null 27 | private onChangeFn: Function = null 28 | 29 | constructor(context: Dat, space: GUI, targetContext: TargetContextThree) { 30 | this.context = context 31 | this.space = space 32 | this.targetContext = targetContext 33 | } 34 | 35 | add(key: string, { range, opts, convert = 'default' }: AddOpts) { 36 | if (range) { 37 | const value = get(this.targetContext, key) 38 | opts = [-(-value + range), (-value - range) * -1, ...opts] 39 | } 40 | const data = { [key]: valueConverters[convert].get(get(this.targetContext, key)) } 41 | this.space.add(data, key, ...opts).onChange(this.handleOnChange.bind(this, key, convert)) 42 | } 43 | 44 | addColor(key: string) { 45 | const data = { [key]: get(this.targetContext, key) } 46 | this.space.addColor(data, key).onChange(this.handleOnChangeColor.bind(this, key)) 47 | } 48 | 49 | handleOnChange = (key: string, converter: ConverterTypes, val: number) => { 50 | set(this.targetContext, key, valueConverters[converter].set(val)) 51 | if (this.onChangeFn) this.onChangeFn() 52 | } 53 | 54 | handleOnChangeColor = (key: string, val: RgbaColor) => { 55 | set(this.targetContext, key, new Color(`rgb(${Math.round(val.r)}, ${Math.round(val.g)}, ${Math.round(val.b)})`)) 56 | if (this.onChangeFn) this.onChangeFn() 57 | } 58 | 59 | onChange(fn: Function) { 60 | this.onChangeFn = fn 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/client/game/helpers/dat/space.types.ts: -------------------------------------------------------------------------------- 1 | export type TargetContextThree = {[key: string]: number} 2 | 3 | export type ConverterTypes = 'default' | 'deg' 4 | 5 | export interface RgbaColor { 6 | r: number; 7 | g: number; 8 | b: number; 9 | } 10 | 11 | export interface AddOpts { 12 | range?: number; 13 | opts: number[]; 14 | convert?: ConverterTypes; 15 | } 16 | -------------------------------------------------------------------------------- /src/client/game/objects/chunk-noise-viewer.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import Promise from 'bluebird'; 3 | var canvasID = 0; 4 | 5 | export default class ChunkNoiseViewer { 6 | 7 | mode = 'x'; 8 | modeTypes = { 9 | 'x' : ['x', 'y'], 10 | 'y' : ['y', 'z'], 11 | 'z' : ['z', 'x'] 12 | } 13 | recSize = 5; 14 | points = []; 15 | threads = []; 16 | maxNoise = 0; 17 | minNoise = 0; 18 | maxY = 0; 19 | minY = 0; 20 | maxX = 0; 21 | minX = 0; 22 | maxZ = 0; 23 | minZ = 0; 24 | 25 | /** 26 | * Convert noise to RGB gray color 27 | * @param noise 28 | * @returns {string} 29 | */ 30 | noiseToRgb(noise) { 31 | if(noise === null) { 32 | return `rgb(${255},${190},${190})`; 33 | } 34 | let n = Math.round(( 100 * noise ) / this.maxNoise); 35 | let color = Math.round((255 * n) / 100); 36 | return `rgb(${color},${color},${color})`; 37 | } 38 | 39 | /** 40 | * Translate points to ABS points ( no negatives ) 41 | * @param type 42 | * @param point 43 | * @returns {*} 44 | */ 45 | translatePointToABS(type, point) { 46 | let min = this[`min${type.toUpperCase()}`]; 47 | return point + Math.abs(min); 48 | } 49 | 50 | /** 51 | * Constructor 52 | * @param options 53 | */ 54 | constructor(options) { 55 | this.createCanvas(options); 56 | } 57 | 58 | /** 59 | * Create canvas element 60 | * @param css 61 | */ 62 | createCanvas(css) { 63 | this.canvasID = canvasID; 64 | this.$canvas = $(''); 65 | canvasID++; 66 | this.$canvas.css(css); 67 | this.$canvas.css({ 68 | position: 'fixed', 69 | 'z-index' : 10000, 70 | border: '1px solid gray' 71 | }); 72 | $('body').append(this.$canvas); 73 | this.canvas = document.getElementById('SeedCanvas_'+this.canvasID); 74 | let canvas = this.$canvas[0]; 75 | this.ctx = canvas.getContext('2d'); 76 | this.$canvas.on('click', this.changeMode.bind(this)); 77 | } 78 | 79 | changeMode() { 80 | let index = Object.keys(this.modeTypes).indexOf(this.mode); 81 | if(typeof Object.keys(this.modeTypes)[index + 1] === 'undefined') { index = -1; } 82 | this.mode = Object.keys(this.modeTypes)[index + 1]; 83 | this.draw(); 84 | } 85 | 86 | /** 87 | * Add point 88 | * @param x 89 | * @param y 90 | * @param noise 91 | */ 92 | add(x, y, z, noise) { 93 | // async 94 | var thread = new Promise((resolve) => { 95 | requestAnimationFrame(() => { 96 | this.points.push({ 97 | x: x, 98 | y: y, 99 | z: z, 100 | noise: noise 101 | }); 102 | if(x > this.maxX) this.maxX = x; 103 | if(x < this.minX) this.minX = x; 104 | if(y > this.maxY) this.maxY = y; 105 | if(y < this.minY) this.minY = y; 106 | if(z > this.maxZ) this.maxZ = z; 107 | if(z < this.minZ) this.minZ = z; 108 | if(noise > this.maxNoise) this.maxNoise = noise; 109 | if(noise < this.minNoise) this.minNoise = noise; 110 | resolve(); 111 | }); 112 | }); 113 | this.threads.push(thread); 114 | } 115 | 116 | /** 117 | * Draw final canvas points 118 | */ 119 | end() { 120 | Promise.all(this.threads).then(() => { 121 | // adjust canvas size 122 | let size = Math.round(Math.pow(this.points.length, 1/3)) * this.recSize; 123 | this.$canvas[0].width = size; 124 | this.$canvas[0].height = size; 125 | this.draw(); 126 | }) 127 | } 128 | 129 | draw() { 130 | this.points.forEach((point) => { 131 | this.rect(point[this.modeTypes[this.mode][0]], point[this.modeTypes[this.mode][1]], point.noise) 132 | }) 133 | } 134 | 135 | rect(pos1 , pos2, strength) { 136 | this.ctx.fillStyle=this.noiseToRgb(strength); 137 | this.ctx.fillRect( 138 | this.recSize * this.translatePointToABS(this.modeTypes[this.mode][0], pos1), 139 | this.recSize * this.translatePointToABS(this.modeTypes[this.mode][1], pos2), 140 | this.recSize, 141 | this.recSize 142 | ); 143 | } 144 | 145 | } -------------------------------------------------------------------------------- /src/client/game/objects/cube.ts: -------------------------------------------------------------------------------- 1 | import { Mesh, MeshLambertMaterial, BoxGeometry, ImageUtils } from 'three' 2 | 3 | export default class Cube { 4 | static isGui = false 5 | 6 | constructor(app, gui, params) { 7 | this.app = app 8 | this.gui = gui 9 | this.params = params 10 | this.create() 11 | return this.cube 12 | } 13 | 14 | create() { 15 | // loading texture 16 | const texture = ImageUtils.loadTexture('../resources/textures/blocks/hardened_clay_stained_green.png') 17 | const cubeGeometry = new BoxGeometry(this.params.size, this.params.size, this.params.size) 18 | const cubeMaterial = new MeshLambertMaterial() 19 | cubeMaterial.map = texture 20 | this.cube = new Mesh(cubeGeometry, cubeMaterial) 21 | this.cube.castShadow = true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/cobblestone_mossy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/cobblestone_mossy.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/crafting_table_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/crafting_table_top.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/destroy_stage_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/destroy_stage_9.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/dirt.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/grass_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/grass_side.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/hardened_clay_stained_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/hardened_clay_stained_green.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/iron_block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/iron_block.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/leaves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/leaves.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/log_spruce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/log_spruce.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/planks_oak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/planks_oak.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sand.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sky.jpg -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sky_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sky_01.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sky_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sky_02.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sky_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sky_03.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sky_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sky_04.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/sky_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/sky_05.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/blocks/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/blocks/water.png -------------------------------------------------------------------------------- /src/client/game/resources/textures/skydome/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/resources/textures/skydome/sky.jpg -------------------------------------------------------------------------------- /src/client/game/system/engine/renderer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './renderer' 2 | -------------------------------------------------------------------------------- /src/client/game/system/engine/renderer/renderer.ts: -------------------------------------------------------------------------------- 1 | import { PCFSoftShadowMap, WebGLRenderer, Raycaster } from 'three' 2 | 3 | import gameEvents from '@game/system/modules/game-events' 4 | 5 | export default new (class Renderer { 6 | updateStack = [] 7 | 8 | create(selector) { 9 | this.renderer = new WebGLRenderer() 10 | this.renderer.setClearColor(0xeeeeee, 1.0) 11 | this.renderer.setSize(window.innerWidth, window.innerHeight) 12 | this.renderer.shadowMap.enabled = true 13 | this.renderer.shadowMap.type = PCFSoftShadowMap 14 | document.querySelector(selector).appendChild(this.renderer.domElement) 15 | } 16 | 17 | onUpdate(cb) { 18 | this.updateStack.push(cb) 19 | } 20 | 21 | render = (scene, camera) => { 22 | this.updateStack.map((cb) => { 23 | gameEvents.trigger('render') 24 | cb() 25 | }) 26 | this.renderer.render(scene, camera) 27 | requestAnimationFrame(this.render.bind(this, scene, camera)) 28 | } 29 | })() 30 | -------------------------------------------------------------------------------- /src/client/game/system/modules/assets-loader/assets-loader.ts: -------------------------------------------------------------------------------- 1 | import { TextureLoader } from 'three' 2 | import flatten from 'lodash/flatten' 3 | 4 | import { assetsConfig } from '@game/config' 5 | 6 | import { storeAssetsLoader } from '@shared/stores' 7 | 8 | export default class AssetsLoader { 9 | private readonly assets: string[] = [] 10 | 11 | constructor() { 12 | this.assets = flatten(assetsConfig) 13 | console.log('-------------') 14 | console.log(assetsConfig) 15 | console.log(this.assets) 16 | } 17 | 18 | async loadAssets() { 19 | // set max assets count in store 20 | storeAssetsLoader.setMax(this.assets.length) 21 | // start loading all assets 22 | await this.loadAll() 23 | } 24 | 25 | private async loadAll() { 26 | for (const asset of this.assets) { 27 | await this.load(asset.url) 28 | storeAssetsLoader.incrementProgress() 29 | } 30 | } 31 | 32 | private load(assetUrl: string) { 33 | return new Promise((resolve) => { 34 | const loader = new TextureLoader() 35 | loader.load('../resources/textures/blocks/sky_01.png', (texture) => { 36 | resolve(texture) 37 | }) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/client/game/system/modules/assets-loader/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './assets-loader'; 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/biomes/biome-grass/biome-grass.ts: -------------------------------------------------------------------------------- 1 | import { random } from '@game/utils' 2 | 3 | export default new (class BiomeGrass { 4 | public name = 'grass' 5 | public translate = (x: number, y: number, z: number) => { 6 | return [x, y, z] 7 | } 8 | public applyMaterial(noiseValue, surroundingNoiseValues, x, y, z) { 9 | if (y < 6) return 2 10 | // if (y > random(8, 12)) return 2 11 | // if (y < random(1, 4)) return 1 12 | return 0 13 | } 14 | })() 15 | -------------------------------------------------------------------------------- /src/client/game/system/modules/biomes/biome-grass/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './biome-grass' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/camera/camera.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { FirstPersonControls, PerspectiveCamera, Clock, Object3D } from 'three' 3 | 4 | import renderer from '@game/system/engine/renderer' 5 | import { toRad } from '@game/utils/_to-rad' 6 | 7 | import '@libraries/trackball' 8 | import { PointerLockControls } from '@libraries/pointer-lock-controls' 9 | import '@libraries/first-person-controls' 10 | import gameEvents from '@game/system/modules/game-events' 11 | import controlsMove from './controls' 12 | 13 | const defaults = { 14 | rotation: { 15 | x: toRad(180), 16 | y: 0, 17 | z: 0, 18 | }, 19 | position: { 20 | x: -1100.2243563049192, 21 | y: 196.94912220775572, 22 | z: -165, 23 | }, 24 | } 25 | 26 | export default class Plane { 27 | public camera: PerspectiveCamera = null 28 | 29 | constructor(app, gui, params) { 30 | this.app = app 31 | this.gui = gui 32 | this.params = params 33 | this.clock = new Clock() 34 | Object3D.DefaultUp.set(0, 0, 1) 35 | this.createCamera() 36 | this.createControls() 37 | this.setDefaults() 38 | this.update() 39 | return this.camera 40 | } 41 | 42 | createCamera() { 43 | this.camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 600000) 44 | // this.app.registerUpdate(this.update.bind(this)); 45 | renderer.onUpdate(this.update.bind(this)) 46 | } 47 | 48 | createControls() { 49 | // TODO: new controls 50 | // const controls = new PointerLockControls(this.camera, document.body) 51 | // this.controls = controls 52 | // controlsMove(controls) 53 | // document.getElementsByTagName('body')[0].addEventListener( 54 | // 'click', 55 | // function () { 56 | // controls.lock() 57 | // }, 58 | // false, 59 | // ) 60 | 61 | this.controls = new FirstPersonControls(this.camera) 62 | this.controls.movementSpeed = 320 63 | this.controls.lookSpeed = 0.18 64 | // initial camera rotation using lon and lat 65 | // this.camera.rotate wont work! 66 | this.controls.lon = -115 67 | this.controls.lat = -30 68 | this.controls.enabled = true 69 | gameEvents.addArgs('render', [{ controls: this.controls }]) 70 | } 71 | 72 | setDefaults() { 73 | this.camera.position.x = defaults.position.x 74 | this.camera.position.y = defaults.position.y 75 | this.camera.position.z = defaults.position.z 76 | } 77 | 78 | update() { 79 | this.controls.update(this.clock.getDelta()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/client/game/system/modules/camera/controls.ts: -------------------------------------------------------------------------------- 1 | import { Raycaster, Vector3 } from 'three' 2 | 3 | var velocity = new Vector3() 4 | var direction = new Vector3() 5 | var prevTime = performance.now() 6 | var moveForward = false 7 | var moveBackward = false 8 | var moveLeft = false 9 | var moveRight = false 10 | var canJump = false 11 | var objects = [] 12 | 13 | const raycaster = new Raycaster(new Vector3(), new Vector3(0, -1, 0), 0, 10) 14 | 15 | var onKeyDown = function (event) { 16 | switch (event.keyCode) { 17 | case 38: // up 18 | case 87: // w 19 | moveForward = true 20 | break 21 | 22 | case 37: // left 23 | case 65: // a 24 | moveLeft = true 25 | break 26 | 27 | case 40: // down 28 | case 83: // s 29 | moveBackward = true 30 | break 31 | 32 | case 39: // right 33 | case 68: // d 34 | moveRight = true 35 | break 36 | 37 | case 32: // space 38 | if (canJump === true) velocity.z += 350 39 | canJump = false 40 | break 41 | } 42 | } 43 | 44 | var onKeyUp = function (event) { 45 | switch (event.keyCode) { 46 | case 38: // up 47 | case 87: // w 48 | moveForward = false 49 | break 50 | 51 | case 37: // left 52 | case 65: // a 53 | moveLeft = false 54 | break 55 | 56 | case 40: // down 57 | case 83: // s 58 | moveBackward = false 59 | break 60 | 61 | case 39: // right 62 | case 68: // d 63 | moveRight = false 64 | break 65 | } 66 | } 67 | 68 | document.addEventListener('keydown', onKeyDown, false) 69 | document.addEventListener('keyup', onKeyUp, false) 70 | 71 | export default function controlsMove(controls) { 72 | window.requestAnimationFrame(() => { 73 | controlsMove(controls) 74 | }) 75 | if (controls.isLocked === true) { 76 | raycaster.ray.origin.copy(controls.getObject().position) 77 | raycaster.ray.origin.z -= 10 78 | 79 | var intersections = raycaster.intersectObjects(objects) 80 | 81 | var onObject = intersections.length > 0 82 | 83 | var time = performance.now() 84 | var delta = (time - prevTime) / 1000 85 | 86 | velocity.x -= velocity.x * 10.0 * delta 87 | velocity.y -= velocity.y * 10.0 * delta 88 | 89 | velocity.z -= 9.8 * 100.0 * delta // 100.0 = mass 90 | 91 | direction.y = Number(moveForward) - Number(moveBackward) 92 | direction.x = Number(moveRight) - Number(moveLeft) 93 | direction.normalize() // this ensures consistent movements in all directions 94 | 95 | if (moveForward || moveBackward) velocity.y -= direction.y * 400.0 * delta 96 | if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta 97 | 98 | if (onObject === true) { 99 | velocity.z = Math.max(0, velocity.z) 100 | canJump = true 101 | } 102 | 103 | controls.moveRight(-velocity.x * delta) 104 | controls.moveForward(-velocity.y * delta) 105 | 106 | controls.getObject().position.z += velocity.z * delta // new behavior 107 | 108 | if (controls.getObject().position.z < 0) { 109 | velocity.z = 0 110 | controls.getObject().position.z = 0 111 | 112 | canJump = true 113 | } 114 | 115 | prevTime = time 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/client/game/system/modules/camera/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './camera'; 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/game-events/game-events.ts: -------------------------------------------------------------------------------- 1 | export default new (class GameEvents { 2 | eventsArgs: { [key: string]: unknown } = {} 3 | events: { [key: string]: Function[] } = {} 4 | 5 | register(eventType: string, fn: Function) { 6 | if (typeof this.events[eventType] === 'undefined') this.events[eventType] = [] 7 | this.events[eventType].push(fn) 8 | } 9 | 10 | trigger(eventType: string) { 11 | if (!this.events[eventType]) return 12 | const args = this.eventsArgs[eventType] 13 | this.events[eventType].forEach((fn) => fn(...args)) 14 | } 15 | 16 | addArgs(eventType, args) { 17 | if (!this.eventsArgs[eventType]) this.eventsArgs[eventType] = [] 18 | this.eventsArgs[eventType].push(...args) 19 | } 20 | })() 21 | -------------------------------------------------------------------------------- /src/client/game/system/modules/game-events/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './game-events' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/noise-helper/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './noise-helper' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/noise-helper/noise-helper.ts: -------------------------------------------------------------------------------- 1 | import { worldConfig } from '@game/config/world' 2 | import createCtx from './utils/createCtx' 3 | import createScroller from './utils/createScroller' 4 | import { Chunk } from '@game/system/modules/world-chunks-generator/world-chunks-generator.types' 5 | 6 | // let sizeFactor = 6 7 | const borderSize = 0 8 | 9 | export default class NoiseHelper { 10 | private chunks: Chunk[] = [] 11 | 12 | addChunk(chunk: Chunk) { 13 | this.chunks.push(chunk) 14 | } 15 | 16 | render() { 17 | let sizeFactor = 12 / Math.sqrt(worldConfig.chunks) 18 | // calc canvas size based on chunk size and size factor 19 | let canvasSize = worldConfig.chunkSize * sizeFactor + borderSize 20 | createScroller(-12, [-12, 12], (layer: number) => { 21 | console.log(`🏞 noise-helper: layer: ${layer}`) 22 | let currentRow = 0 23 | this.chunks.forEach((chunk: Chunk, idx: number) => { 24 | const rowModulo = idx % Math.sqrt(worldConfig.chunks) 25 | if (rowModulo === 0) currentRow++ 26 | const ctx = createCtx(canvasSize, { 27 | top: canvasSize * (currentRow - 1), 28 | left: canvasSize * rowModulo, 29 | id: `${chunk.chunkId}${layer}`, 30 | }) 31 | // If cached, skip 32 | if (!ctx) return 33 | // filter noise by visible Z axis 34 | const noise = chunk.data.filter(({ absLocation }) => { 35 | return absLocation.z === layer 36 | }) 37 | 38 | noise.forEach(({ absLocation: { x, y }, noiseValue }) => { 39 | x += 12 40 | y += 12 41 | ctx.beginPath() 42 | ctx.rect(Math.abs(x) * sizeFactor, Math.abs(y) * sizeFactor, sizeFactor, sizeFactor) 43 | ctx.fillStyle = `rgba(0, 0, 0, ${noiseValue})` 44 | ctx.fill() 45 | }) 46 | }) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/client/game/system/modules/noise-helper/utils/createCtx.ts: -------------------------------------------------------------------------------- 1 | let zIndex = 1000000000 2 | const canvasCache = {} 3 | export default function createCtx(canvasSize: number, { left, top, id }: { left: number; top: number }): CanvasRenderingContext2D { 4 | const canvas = canvasCache[id] 5 | zIndex++ 6 | if (canvas) { 7 | canvas.style = `position: absolute; top: ${top}px; left: ${left}px; z-index: 1000000000; background: #c3c3c3; z-index: ${zIndex}` 8 | return null 9 | } else { 10 | const canvas = document.createElement('canvas') 11 | canvas.classList = ['noise-canvas'] 12 | canvas.width = canvasSize 13 | canvas.height = canvasSize 14 | // @ts-ignore 15 | canvas.style = `position: absolute; top: ${top}px; left: ${left}px; z-index: 1000000000; background: #c3c3c3; z-index: ${zIndex}` 16 | document.body.appendChild(canvas) 17 | canvasCache[id] = canvas 18 | return canvas.getContext('2d') 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/client/game/system/modules/noise-helper/utils/createScroller.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | import '@libraries/scrollable' 3 | 4 | let hasBeenCreated = false 5 | let currentLayer: number = null 6 | export default function createScroller(initialLayer: number, range, cb: Function): void { 7 | currentLayer = initialLayer 8 | if (hasBeenCreated) return 9 | 10 | const height = 300 11 | 12 | const rangeValue = Math.abs(range[0] - range[1]) + 1 13 | 14 | const scrollContainer = $('
') 15 | scrollContainer.css({ position: 'absolute', top: 0, left: '300px', width: '10px', height: `${height}px`, background: 'red' }) 16 | 17 | const scrollRails = $('
') 18 | scrollRails.css({ position: 'relative', width: '100%', height: '100%', background: 'gray' }) 19 | 20 | const scroll = $('
') 21 | scroll.css({ 22 | position: 'absolute', 23 | top: 0, 24 | left: 0, 25 | width: '10px', 26 | height: `${height / rangeValue}`, 27 | background: 'yellow', 28 | cursor: 'pointer', 29 | }) 30 | 31 | $.dragScroll(scroll, scrollRails, (scrollPositionY: number) => { 32 | scroll.css({ top: `${Math.abs(scrollPositionY)}px` }) 33 | const percScrollPosition = Math.abs((scrollPositionY * 100) / height) 34 | const layer = Math.round((rangeValue * percScrollPosition) / 100) - range[1] 35 | console.log(`layer: ${layer}`) 36 | cb(layer) 37 | }) 38 | 39 | $('body').append(scrollContainer) 40 | scrollContainer.append(scrollRails) 41 | scrollRails.append(scroll) 42 | 43 | hasBeenCreated = true 44 | cb(currentLayer) 45 | } 46 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/draw-cube-circle.ts: -------------------------------------------------------------------------------- 1 | import getLeavesCube from '@game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-leaves-cube' 2 | 3 | export default function drawCubeCircle(location: { x: number; y: number; z: number }, r: number) { 4 | const cubes = [] 5 | var x0 = location.x 6 | var y0 = location.y 7 | var z0 = location.z 8 | // lest make leaves around 9 | var y = 0 10 | var decisionOver2 = 1 - r 11 | while (r >= y) { 12 | cubes.push(getLeavesCube(r + x0, y + y0, z0)) 13 | cubes.push(getLeavesCube(y + x0, r + y0, z0)) 14 | cubes.push(getLeavesCube(-r + x0, y + y0, z0)) 15 | cubes.push(getLeavesCube(-y + x0, r + y0, z0)) 16 | cubes.push(getLeavesCube(-r + x0, -y + y0, z0)) 17 | cubes.push(getLeavesCube(-y + x0, -r + y0, z0)) 18 | cubes.push(getLeavesCube(r + x0, -y + y0, z0)) 19 | cubes.push(getLeavesCube(y + x0, -r + y0, z0)) 20 | y++ 21 | if (decisionOver2 <= 0) { 22 | decisionOver2 += 2 * z0 + 1 // Change in decision criterion for y -> y+1 23 | } else { 24 | r-- 25 | decisionOver2 += 2 * (z0 - r) + 1 26 | } 27 | } 28 | return cubes 29 | } 30 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/draw-tree.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { Noise } from 'noisejs' 3 | import { worldConfig } from '@game/config' 4 | import getTreeCube from '@game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-tree-cube' 5 | import getLeavesCube from '@game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-leaves-cube' 6 | import drawCubeCircle from '@game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/draw-cube-circle' 7 | import { Cubes3D } from '@game/system/modules/world-chunks-generator/world-chunks-generator.types' 8 | import getCubesAround from '@game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-cubes-around' 9 | 10 | export default function drawTree(noise: Noise, noiseValue: number, cubes3D: Cubes3D, location: { x: number; y: number; z: number }) { 11 | var currHeight 12 | const cubes = [] 13 | 14 | // how much space left? 15 | let heightLeft = worldConfig.chunkSize - location.z 16 | 17 | // won't be able to draw soo tiny tree 18 | if (heightLeft < 6) { 19 | return [] 20 | } 21 | 22 | // tree needs at least x blocks around space 23 | let anyCubesAround = false 24 | getCubesAround(1, { x: location.x, y: location.y, z: location.z + 1 }, (x: number, y: number, z: number) => { 25 | if (cubes3D[`${x}:${y}:${z}`]) { 26 | anyCubesAround = true 27 | } 28 | }) 29 | 30 | if (anyCubesAround) return [] 31 | 32 | // lets draw base, base is always min 4 - 6 33 | let mod = 150 34 | let height = Math.round((3 * (Math.round(Math.abs(noise.simplex3(location.x / mod, location.y / mod, location.z / mod) * 100)) / 10)) / 2) 35 | 36 | if (height < 3) height = 3 37 | 38 | for (currHeight = location.z; currHeight <= location.z + height; currHeight++) { 39 | cubes.push(getTreeCube(location.x, location.y, currHeight)) 40 | } 41 | 42 | cubes.push(getLeavesCube(location.x, location.y, currHeight)) 43 | cubes.push(...drawCubeCircle({ x: location.x, y: location.y, z: currHeight - 1 }, 1)) 44 | cubes.push(...drawCubeCircle({ x: location.x, y: location.y, z: currHeight - 2 }, 1)) 45 | cubes.push(...drawCubeCircle({ x: location.x, y: location.y, z: currHeight - 2 }, 2)) 46 | return cubes 47 | } 48 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-cubes-around.ts: -------------------------------------------------------------------------------- 1 | export default function getCubesAround(radius: number, location: { x: number; y: number; z: number }, cb: Function) { 2 | let x0 = radius 3 | let y0 = 0 4 | let radiusError = 1 - x0 5 | 6 | while (x0 >= y0) { 7 | cb(x0 + location.x, y0 + location.y, location.z) 8 | cb(y0 + location.x, x0 + location.y, location.z) 9 | cb(-x0 + location.x, y0 + location.y, location.z) 10 | cb(-y0 + location.x, x0 + location.y, location.z) 11 | cb(-x0 + location.x, -y0 + location.y, location.z) 12 | cb(-y0 + location.x, -x0 + location.y, location.z) 13 | cb(x0 + location.x, -y0 + location.y, location.z) 14 | cb(y0 + location.x, -x0 + location.y, location.z) 15 | y0++ 16 | if (radiusError < 0) { 17 | radiusError += 2 * y0 + 1 18 | } else { 19 | x0-- 20 | radiusError += 2 * (y0 - x0 + 1) 21 | } 22 | } 23 | 24 | // while (r <= radius) { 25 | // cb(r + location.x, location.y + location.y, location.z) 26 | // cb(location.y + location.x, r + location.y, location.z) 27 | // cb(-r + location.x, location.y + location.y, location.z) 28 | // cb(-location.y + location.x, r + location.y, location.z) 29 | // cb(-r + location.x, -location.y + location.y, location.z) 30 | // cb(-location.y + location.x, -r + location.y, location.z) 31 | // cb(r + location.x, -location.y + location.y, location.z) 32 | // cb(location.y + location.x, -r + location.y, location.z) 33 | // r++ 34 | // } 35 | } 36 | 37 | // export default function getCubesAround(radius: number, location: { x: number; y: number; z: number }, cb: Function) { 38 | // let r = 0 39 | // while (r <= radius) { 40 | // cb(location.x, location.y - r, location.z) 41 | // cb(location.x + r, location.y - r, location.z) 42 | // cb(location.x + r, location.y, location.z) 43 | // cb(location.x + r, location.y + r, location.z) 44 | // cb(location.x, location.y + r, location.z) 45 | // cb(location.x - r, location.y + r, location.z) 46 | // cb(location.x - r, location.y, location.z) 47 | // cb(location.x - r, location.y - r, location.z) 48 | // r++ 49 | // } 50 | // } 51 | // // 52 | // // -1-1 0-1 1-1 53 | // // -1 0 0 0 1 0 54 | // // -1 -1 0 1 1 1 55 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-leaves-cube.ts: -------------------------------------------------------------------------------- 1 | import { ChunkData } from '@game/system/modules/world-chunks-generator/world-chunks-generator.types' 2 | 3 | export default function getLeavesCube(x: number, y: number, z: number): ChunkData { 4 | return { 5 | chunkDataId: `${x}:${y}:${z}`, 6 | noiseValue: 1, 7 | location: { 8 | x, 9 | y, 10 | z, 11 | }, 12 | absLocation: { 13 | x, 14 | y, 15 | z, 16 | }, 17 | surrounding: { 18 | px: false, 19 | nx: false, 20 | py: false, 21 | pz: false, 22 | nz: false, 23 | ny: false, 24 | }, 25 | material: 4, 26 | isTransparent: true, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/chunk-vegetation/get-tree-cube.ts: -------------------------------------------------------------------------------- 1 | import { ChunkData } from '@game/system/modules/world-chunks-generator/world-chunks-generator.types' 2 | 3 | export default function getTreeCube(x: number, y: number, z: number): ChunkData { 4 | return { 5 | chunkDataId: `${x}:${y}:${z}`, 6 | noiseValue: 1, 7 | location: { 8 | x, 9 | y, 10 | z, 11 | }, 12 | absLocation: { 13 | x, 14 | y, 15 | z, 16 | }, 17 | surrounding: { 18 | px: false, 19 | nx: false, 20 | py: undefined, 21 | pz: false, 22 | nz: false, 23 | ny: undefined, 24 | }, 25 | material: 3, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/generate-chunk-data.worker.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { Noise } from 'noisejs' 3 | import { Perf } from '@game/utils' 4 | import toRange from '@game/utils/toRange' 5 | 6 | import isAboveThreshold from './utils/is-above-threshold' 7 | import isCubeAtLocation from './utils/is-cube-at-location' 8 | import isAllAroundSurrounded from './utils/is-all-around-surrounded' 9 | import drawTree from './chunk-vegetation/draw-tree' 10 | 11 | import { Chunks2D, Cubes3D, Chunk, ChunksArr, ChunkData } from '../world-chunks-generator.types' 12 | 13 | const ctx: Worker = self as any 14 | 15 | ctx.addEventListener( 16 | 'message', 17 | async ({ data }) => { 18 | const chunks2D: Chunks2D = JSON.parse(data.chunks) 19 | const cubes3D: Cubes3D = {} 20 | const cubeNoiseMax = Math.max(...Object.values(chunks2D).map((chunk: Chunk) => chunk.noiseMaps.cubeMax)) || data?.noiseMaps?.cubeMax 21 | const cubeNoiseMin = Math.min(...Object.values(chunks2D).map((chunk: Chunk) => chunk.noiseMaps.cubeMin)) || data?.noiseMaps?.cubeMin 22 | const treeNoiseMax = data?.noiseMaps?.treeMax || Math.max(...Object.values(chunks2D).map((chunk: Chunk) => chunk.noiseMaps.treeMax)) 23 | const treeNoiseMin = data?.noiseMaps?.treeMin || Math.min(...Object.values(chunks2D).map((chunk: Chunk) => chunk.noiseMaps.treeMin)) 24 | const noiseRenderThreshold = data.noiseRenderThreshold 25 | const noiseRenderThresholdMod = data.noiseRenderThresholdMod 26 | const waterLevel = data.waterLevel 27 | 28 | const chunksArr: ChunksArr = Object.values(chunks2D) 29 | 30 | // ======================================================= 31 | // Normalize chunks data and filter threshold 32 | // ======================================================= 33 | Perf.get(` 🧩 normalize noise`) 34 | chunksArr.forEach((chunk: Chunk) => { 35 | chunk.data = chunk.data.map((data) => { 36 | data.noiseValue = toRange(data.noiseValue, cubeNoiseMax, cubeNoiseMin, 1, 0) 37 | return data 38 | }) 39 | }) 40 | 41 | // Filter blocks by min threshold & build up cubes3D data 42 | // Has to happen after normalize ? 43 | chunksArr.forEach((chunk: Chunk) => { 44 | chunk.data = chunk.data.filter((chunk: ChunkData) => { 45 | if ( 46 | chunk.location.z === waterLevel || 47 | isAboveThreshold(chunk.noiseValue, noiseRenderThreshold, noiseRenderThresholdMod, chunk.absLocation.z) 48 | ) { 49 | cubes3D[`${chunk.location.x}:${chunk.location.y}:${chunk.location.z}`] = chunk 50 | return true 51 | } 52 | return false 53 | }) 54 | }) 55 | Perf.get(` 🧩 normalize noise`).end() 56 | 57 | // ======================================================= 58 | // Optimize chunks surroundings 59 | // ======================================================= 60 | Perf.get(` 🏎 optimize surroundings`) 61 | chunksArr.forEach((chunk: Chunk) => { 62 | chunk.data = chunk.data.reduce((acc, data) => { 63 | const { x, y, z } = data.location 64 | data.surrounding = { 65 | px: !!isCubeAtLocation(cubes3D, x + 1, y, z), 66 | nx: !!isCubeAtLocation(cubes3D, x - 1, y, z), 67 | py: !!isCubeAtLocation(cubes3D, x, y + 1, z), 68 | pz: !!isCubeAtLocation(cubes3D, x, y, z + 1), 69 | nz: !!isCubeAtLocation(cubes3D, x, y, z - 1), 70 | ny: !!isCubeAtLocation(cubes3D, x, y - 1, z), 71 | } 72 | if (!isAllAroundSurrounded(data.surrounding)) { 73 | acc.push(data) 74 | } 75 | return acc 76 | }, []) 77 | ctx.postMessage({ done: false }) 78 | }) 79 | Perf.get(` 🏎 optimize surroundings`).end() 80 | 81 | // ======================================================= 82 | // Vegetation 83 | // ======================================================= 84 | const vegetationTreeCubes: ChunkData[] = [] 85 | const noise = new Noise(1) 86 | Perf.get(` 🏎 vegetation`) 87 | chunksArr.forEach((chunk: Chunk) => { 88 | chunk.data = chunk.data.map((data: ChunkData) => { 89 | data.vegetation.treeNoise = toRange(data.vegetation.treeNoise, treeNoiseMax, treeNoiseMin, 1, 0) 90 | if (data.vegetation.treeNoise < 0.23 && data.absLocation.z >= -10) { 91 | const treeCubes = drawTree(noise, data.vegetation.treeNoise, cubes3D, data.location) 92 | // add to optimization 93 | treeCubes.forEach((chunk: ChunkData) => { 94 | cubes3D[`${chunk.location.x}:${chunk.location.y}:${chunk.location.z}`] = chunk 95 | }) 96 | vegetationTreeCubes.push(...treeCubes) 97 | } 98 | return data 99 | }) 100 | chunk.data.push(...vegetationTreeCubes) 101 | }) 102 | Perf.get(` 🏎 vegetation`).end() 103 | 104 | // ======================================================= 105 | // Finish 106 | // ======================================================= 107 | Perf.get(` ⚙ postMessage worker`) 108 | ctx.postMessage({ 109 | done: true, 110 | data: JSON.stringify(chunks2D), 111 | noiseMaps: { 112 | cubeMax: cubeNoiseMax, 113 | cubeMin: cubeNoiseMin, 114 | treeMax: treeNoiseMax, 115 | treeMin: treeNoiseMin, 116 | }, 117 | }) 118 | Perf.get(` ⚙ postMessage worker`).end() 119 | }, 120 | false, 121 | ) 122 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | export { default } from 'worker-loader!./generate-chunk-data.worker' 3 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/utils/is-above-threshold.ts: -------------------------------------------------------------------------------- 1 | export default function isAboveThreshold(noise, threshold, thresholdMod, z) { 2 | return noise < threshold - Math.pow(z + 13, (z + 13) / thresholdMod) / 100 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/utils/is-all-around-surrounded.ts: -------------------------------------------------------------------------------- 1 | export default function isAllAroundSurrounded(surrounding): boolean { 2 | return surrounding.px && surrounding.nx && surrounding.py && surrounding.pz && surrounding.nz && surrounding.ny 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-data/utils/is-cube-at-location.ts: -------------------------------------------------------------------------------- 1 | import { Cubes3D } from '@game/system/modules/world-chunks-generator/world-chunks-generator.types' 2 | 3 | export default function isCubeAtLocation(cubes: Cubes3D, x: number, y: number, z: number) { 4 | const cube = cubes[`${x}:${y}:${z}`] 5 | if (cube && cube.isTransparent) return false 6 | return !!cubes[`${x}:${y}:${z}`] 7 | } 8 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createChunkBoxHelper/createChunkBoxHelper.ts: -------------------------------------------------------------------------------- 1 | import { BoxGeometry, BoxHelper, Mesh, MeshBasicMaterial } from 'three' 2 | 3 | import { worldConfig } from '@game/config/world' 4 | import { Location2D } from '@game/@types/locations.types' 5 | 6 | export default function createChunkBoxHelper(location: Location2D) { 7 | const chunkDimension = worldConfig.chunkSize * worldConfig.cubeSize 8 | const boxGeometry = new BoxGeometry(chunkDimension, chunkDimension, chunkDimension) 9 | boxGeometry.translate(location.x * chunkDimension + chunkDimension / 2, location.y * chunkDimension + chunkDimension / 2, 0) 10 | const object = new Mesh(boxGeometry, new MeshBasicMaterial()) 11 | return new BoxHelper(object, 0xffff00) 12 | } 13 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createChunkBoxHelper/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './createChunkBoxHelper' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createCubePlanes/createCubePlanes.ts: -------------------------------------------------------------------------------- 1 | import { CubePlanes } from '@game/@types/cube.types' 2 | 3 | import createPlaneGeometry from '../createPlaneGeometry' 4 | import { worldConfig } from '@game/config' 5 | 6 | /** 7 | * Create array of cube planes for each side 8 | */ 9 | export default function createCubePlanes(): CubePlanes { 10 | const cubeSize = worldConfig.cubeSize 11 | // o 12 | // o o o 13 | // x 14 | const px = createPlaneGeometry(cubeSize, { rotateY: Math.PI / 2, translateX: cubeSize / 2, translateY: 0, translateZ: 0 }) 15 | // o 16 | // o o x 17 | // o 18 | const nz = createPlaneGeometry(cubeSize, { rotateY: Math.PI, translateX: 0, translateY: 0, translateZ: -cubeSize / 2 }) 19 | // x 20 | // o o o 21 | // o 22 | const nx = createPlaneGeometry(cubeSize, { rotateY: -Math.PI / 2, translateX: -cubeSize / 2, translateY: 0, translateZ: 0 }) 23 | // o 24 | // x o o 25 | // o 26 | const pz = createPlaneGeometry(cubeSize, { translateX: 0, translateY: 0, translateZ: cubeSize / 2 }) 27 | // o 28 | // o x o // top 29 | // o 30 | const py = createPlaneGeometry(cubeSize, { rotateX: -Math.PI / 2, translateX: 0, translateY: cubeSize / 2, translateZ: 0 }) 31 | // o 32 | // o x o // bottom 33 | // o 34 | const ny = createPlaneGeometry(cubeSize, { rotateX: Math.PI / 2, translateX: 0, translateY: -cubeSize / 2, translateZ: 0 }) 35 | 36 | return { 37 | px, 38 | nz, 39 | nx, 40 | pz, 41 | py, 42 | ny, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createCubePlanes/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './createCubePlanes' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createOptimizedCubeGeometry/createOptimizedCubeGeometry.ts: -------------------------------------------------------------------------------- 1 | import { BufferGeometry, Geometry, Matrix4 } from 'three' 2 | 3 | import { CubePlanes, ChunkCubesData } from '@game/@types/cube.types' 4 | import { worldConfig } from '@game/config' 5 | 6 | import { Chunk } from '../../world-chunks-generator.types' 7 | 8 | /** 9 | * Create optimized cube geometry 10 | * Creates geometry for the chunk with optimized cubes - eg no invisible sides would have a geometry 11 | * @param chunk 12 | * @param cubePlanes 13 | */ 14 | export default function createOptimizedCubeGeometry(chunk: Chunk, cubePlanes: CubePlanes) { 15 | const cubeSize = worldConfig.cubeSize 16 | const matrix = new Matrix4() 17 | const tmpGeometry = new Geometry() 18 | // For each cube data 19 | chunk.data.forEach((chunk) => { 20 | if (chunk.location.z === -12) chunk.material = 5 21 | if (chunk.location.z === -11) chunk.material = 6 22 | if (chunk.location.z === -10) chunk.material = 6 23 | // if (chunk.location.z === -9) chunk.material = 6 24 | // Apply chunk location to the matrix 25 | matrix.makeTranslation(chunk.location.x * cubeSize, chunk.location.y * cubeSize, chunk.location.z * cubeSize) 26 | // Check what chunk geometry should be drawn 27 | if (!chunk.surrounding.px) tmpGeometry.merge(cubePlanes.px, matrix, chunk.material) 28 | if (!chunk.surrounding.nx) tmpGeometry.merge(cubePlanes.nx, matrix, chunk.material) 29 | if (!chunk.surrounding.py) tmpGeometry.merge(cubePlanes.py, matrix, chunk.material) 30 | if (!chunk.surrounding.pz) tmpGeometry.merge(cubePlanes.pz, matrix, chunk.material) 31 | if (!chunk.surrounding.nz) tmpGeometry.merge(cubePlanes.nz, matrix, chunk.material) 32 | if (!chunk.surrounding.ny) tmpGeometry.merge(cubePlanes.ny, matrix, chunk.material) 33 | }) 34 | 35 | return new BufferGeometry().fromGeometry(tmpGeometry) 36 | } 37 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createOptimizedCubeGeometry/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './createOptimizedCubeGeometry' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createPlaneGeometry/createPlaneGeometry.d.ts: -------------------------------------------------------------------------------- 1 | export interface CreatePlaneGeometryOpts { 2 | rotateY?: number; 3 | rotateX?: number; 4 | translateX: number; 5 | translateY: number; 6 | translateZ: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createPlaneGeometry/createPlaneGeometry.ts: -------------------------------------------------------------------------------- 1 | import { Geometry, PlaneBufferGeometry } from 'three' 2 | 3 | import { CreatePlaneGeometryOpts } from './createPlaneGeometry.d' 4 | 5 | export default function createPlaneGeometry( 6 | cubeSize: number, 7 | { rotateY = 0, rotateX = 0, translateX, translateY, translateZ }: CreatePlaneGeometryOpts, 8 | ): Geometry { 9 | const geometry = new PlaneBufferGeometry(cubeSize, cubeSize) 10 | geometry.rotateY(rotateY) 11 | geometry.rotateX(rotateX) 12 | geometry.translate(translateX, translateY, translateZ) 13 | return new Geometry().fromBufferGeometry(geometry) 14 | } 15 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/createPlaneGeometry/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './createPlaneGeometry' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/generate-chunk-geometries.ts: -------------------------------------------------------------------------------- 1 | import { worldConfig } from '@game/config' 2 | import { Location2D } from '@game/@types/locations.types' 3 | 4 | import createCubePlanes from './createCubePlanes' 5 | import createOptimizedCubeGeometry from './createOptimizedCubeGeometry' 6 | import createChunkBoxHelper from './createChunkBoxHelper' 7 | 8 | import { Chunk, ChunkGeometries } from '../world-chunks-generator.types' 9 | 10 | /** 11 | * Generate single chunk 12 | * @param chunk 13 | * @param chunkNoise 14 | */ 15 | export default async function generateChunkGeometries(chunk: Chunk, { location }): Promise { 16 | return new Promise(async (resolve) => { 17 | // Create cube planes 18 | // all sides of the cube as separate plane geometries 19 | const cubePlanes = createCubePlanes() 20 | 21 | // Create optimized cube geometry 22 | // Contains only the cube size that are visible 23 | const geometry = createOptimizedCubeGeometry(chunk, cubePlanes) 24 | geometry.computeBoundingSphere() 25 | 26 | resolve({ 27 | geometry, 28 | helperGeometries: [], 29 | // helperGeometries: [createChunkBoxHelper(location)], 30 | }) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-geometries/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './generate-chunk-geometries' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-noise/generate-chunk-noise.worker.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { Noise } from 'noisejs' 3 | import { Perf } from '@game/utils' 4 | 5 | import getCubeNoise from './utils/get-cube-noise' 6 | import getTreeNoise from './utils/get-tree-noise' 7 | 8 | import { ChunkDataArr } from '../world-chunks-generator.types' 9 | 10 | const ctx: Worker = self as any 11 | 12 | ctx.addEventListener( 13 | 'message', 14 | ({ data }) => { 15 | const seedId = data.seedId 16 | const chunkId = data.chunkId 17 | const chunkSize = data.chunkSize 18 | const noise = new Noise(seedId) 19 | const chunkDataLocation = data.chunkLocation 20 | Perf.get(` ⚙ noise worker: ${chunkId}`) 21 | 22 | let chunkDataNoiseArr: ChunkDataArr = [] 23 | 24 | for (let x = Math.floor(chunkSize / 2) * -1; x < Math.ceil(chunkSize / 2); x++) { 25 | for (let y = Math.floor(chunkSize / 2) * -1; y < Math.ceil(chunkSize / 2); y++) { 26 | for (let z = Math.floor(chunkSize / 2) * -1; z < Math.ceil(chunkSize / 2); z++) { 27 | // combine chunk location with the x,y,z loop 28 | let [tx, ty, tz] = [chunkDataLocation.x * chunkSize + x, chunkDataLocation.y * chunkSize + y, z] 29 | 30 | tx += 14 31 | ty += 14 32 | 33 | chunkDataNoiseArr.push({ 34 | chunkDataId: `${tx}_${ty}_${tz}`, 35 | location: { 36 | x: tx, 37 | y: ty, 38 | z: tz, 39 | }, 40 | absLocation: { 41 | x, 42 | y, 43 | z, 44 | }, 45 | noiseValue: getCubeNoise(noise, tx, ty, z), 46 | vegetation: { 47 | treeNoise: getTreeNoise(noise, tx, ty, z), 48 | }, 49 | }) 50 | } 51 | } 52 | } 53 | 54 | // normalize noises to fit range 0-1 55 | const cubeNoiseMap = chunkDataNoiseArr.map(({ noiseValue }) => noiseValue) 56 | const treeNoiseMap = chunkDataNoiseArr.map((chunkData) => chunkData.vegetation.treeNoise) 57 | const cubeMax = Math.max(...cubeNoiseMap) 58 | const cubeMin = Math.min(...cubeNoiseMap) 59 | const treeMax = Math.max(...treeNoiseMap) 60 | const treeMin = Math.min(...treeNoiseMap) 61 | 62 | Perf.get(` ⚙ noise worker: ${chunkId}`).end() 63 | 64 | Perf.get(` ⚙ postMessage worker`) 65 | ctx.postMessage({ 66 | done: true, 67 | data: JSON.stringify({ 68 | chunkId, 69 | noiseMaps: { 70 | treeMax: treeMax, 71 | treeMin: treeMin, 72 | cubeMax, 73 | cubeMin, 74 | }, 75 | data: Object.values(chunkDataNoiseArr), 76 | }), 77 | }) 78 | Perf.get(` ⚙ postMessage worker`).end() 79 | }, 80 | false, 81 | ) 82 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-noise/generate-chunk-noise.worker.types.ts: -------------------------------------------------------------------------------- 1 | import { Location2D } from '@game/@types/locations.types' 2 | 3 | export interface GenerateNoiseOpts { 4 | location: Location2D 5 | chunkMod: number 6 | } 7 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-noise/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | export { default } from 'worker-loader!./generate-chunk-noise.worker' 3 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-noise/utils/get-cube-noise.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { Noise } from 'noisejs' 3 | 4 | /** 5 | * Get noise for location 6 | * - using cache 7 | * @param noise 8 | * @param x 9 | * @param y 10 | * @param z 11 | */ 12 | export default function isCubeFilled(noise: Noise, x: number, y: number, z: number): number { 13 | const frequency = 1.5 14 | const mod = 150 15 | 16 | const noiseValue = 17 | 1 * noise.perlin3((1 * x) / mod, (1 * y) / mod, (1 * z) / mod) + 18 | 0.5 * noise.perlin3((2 * x) / mod, (2 * y) / mod, (2 * z) / mod) + 19 | 0.25 * noise.perlin3((4 * x) / mod, (4 * y) / mod, (4 * z) / mod) * frequency 20 | 21 | return Math.round(noiseValue * 2) / 2 22 | } 23 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-noise/utils/get-cube.ts: -------------------------------------------------------------------------------- 1 | // export default function getCube(noiseValue, x, y, z) { 2 | // return { 3 | // chunkDataId: `${tx}_${ty}_${tz}`, 4 | // location: { 5 | // x: tx, 6 | // y: ty, 7 | // z: tz, 8 | // }, 9 | // absLocation: { 10 | // x, 11 | // y, 12 | // z, 13 | // }, 14 | // noiseValue, 15 | // } 16 | // } 17 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/generate-chunk-noise/utils/get-tree-noise.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { Noise } from 'noisejs' 3 | 4 | /** 5 | * Get noise for location 6 | * - using cache 7 | * @param noise 8 | * @param noiseChunks 9 | * @param x 10 | * @param y 11 | * @param z 12 | */ 13 | export default function getNoiseForLocation(noise: Noise, x: number, y: number, z: number): number { 14 | const frequency = 500 15 | const mod = 5 16 | 17 | let noiseValue = 18 | 1 * noise.perlin3((1 * x) / mod, (1 * y) / mod, (1 * z) / mod) + 19 | 0.5 * noise.perlin3((2 * x) / mod, (2 * y) / mod, (2 * z) / mod) + 20 | 0.25 * noise.perlin3((4 * x) / mod, (4 * y) / mod, (4 * z) / mod) * frequency 21 | 22 | return Math.round(noiseValue * 4) / 4 23 | } 24 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './world-chunks-generator' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/utils/forEachRange.ts: -------------------------------------------------------------------------------- 1 | import PromiseQueue from 'p-queue' 2 | 3 | /** 4 | * 5 | * @param range 6 | * @param parallel 7 | * @param cb 8 | */ 9 | export default function forEachRange(range: [number[], number[]], parallel: number | Function, cb?: Function) { 10 | const defer = [] 11 | // const range = Math.floor(Math.sqrt(rangeNumber) / 2) 12 | 13 | // handle parallel or cb 14 | if (typeof parallel === 'function') { 15 | cb = parallel 16 | parallel = 6 17 | } 18 | 19 | const queue = new PromiseQueue({ concurrency: parallel }) 20 | for (let x = range[0][0]; x <= range[0][1]; x++) { 21 | for (let y = range[1][0]; y <= range[1][1]; y++) { 22 | defer.push( 23 | queue.add(async () => { 24 | return await cb(x, y) 25 | }), 26 | ) 27 | } 28 | } 29 | return Promise.all(defer) 30 | } 31 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/utils/useWorker.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default function useWorker(Worker, opts, cb?): Promise { 3 | return new Promise((resolve) => { 4 | const worker = new Worker() 5 | worker.postMessage(opts) 6 | worker.onmessage = ({ data }: { data: T }) => { 7 | if (data.done) { 8 | const { data: _data, ...others } = data 9 | resolve({ 10 | data: JSON.parse(_data), 11 | ...others, 12 | }) 13 | } else { 14 | if (typeof cb === 'function') { 15 | cb(data) 16 | } 17 | } 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/world-chunks-generator.memory.ts: -------------------------------------------------------------------------------- 1 | import { ChunkCoordinated } from './world-chunks-generator.types' 2 | 3 | export default new (class Memory { 4 | chunksCache: ChunkCoordinated = {} 5 | 6 | public get(key, subKey) {} 7 | })() 8 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/world-chunks-generator.memory.types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/game/system/modules/world-chunks-generator/world-chunks-generator.memory.types.ts -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/world-chunks-generator.ts: -------------------------------------------------------------------------------- 1 | import { worldConfig } from '@game/config' 2 | import gameLoaderStore from '@shared/stores/gameLoader' 3 | import { Perf } from '@game/utils' 4 | 5 | import { WorldChunk } from '@game/@types/chunk.types' 6 | 7 | import NoiseHelper from '@game/system/modules/noise-helper' 8 | import gameEvents from '@game/system/modules/game-events' 9 | 10 | import forEachRange from './utils/forEachRange' 11 | import useWorker from './utils/useWorker' 12 | 13 | import NoiseWorker from './generate-chunk-noise' 14 | import DataWorker from './generate-chunk-data' 15 | 16 | import generateChunkGeometries from './generate-chunk-geometries' 17 | import memory from './world-chunks-generator.memory' 18 | 19 | import { Chunks2D, Chunk, WorldChunksGeneratorOpts } from './world-chunks-generator.types' 20 | import { memo } from 'react' 21 | 22 | /** 23 | * Generate chunks 24 | * @param seedId 25 | * @param opts 26 | * @param opts.startChunkLocation 27 | */ 28 | export default async function worldChunksGenerator( 29 | seedId: number, 30 | { startChunkLocation = [0, 0] }: WorldChunksGeneratorOpts, 31 | ): Promise { 32 | // Chunk promises 33 | const chunksDefer: WorldChunk[] = [] 34 | 35 | // list of all chunks 36 | let chunks: Chunks2D = {} 37 | 38 | // Get chunk drawing range 39 | // see: Centered octagonal number 40 | const range = Math.floor(Math.sqrt(worldConfig.chunks) / 2) 41 | const rangeArr: [number[], number[]] = [ 42 | [startChunkLocation[0] - range, startChunkLocation[0] + range], 43 | [startChunkLocation[1] - range, startChunkLocation[1] + range], 44 | ] 45 | 46 | // generate-chunk-noise 47 | Perf.get(`🧩 chunks noise`) 48 | gameLoaderStore.setNewTask('Generate chunks noise', { max: worldConfig.chunks }) 49 | await forEachRange(rangeArr, async (x: number, y: number) => { 50 | if (memory.chunksCache[`${x}${y}`]) return 51 | const { data } = await useWorker(NoiseWorker, { 52 | seedId, 53 | chunkId: `${x}:${y}`, 54 | chunkLocation: { x, y }, 55 | chunkSize: worldConfig.chunkSize, 56 | normalize: { 57 | max: null, 58 | min: null, 59 | }, 60 | }) 61 | chunks[`${x}${y}`] = data 62 | gameLoaderStore.increment() 63 | }) 64 | gameLoaderStore.setTaskFinished() 65 | Perf.get(`🧩 chunks noise`).end() 66 | 67 | // normalize chunk noise 68 | // uses: @web-worker 69 | Perf.get(`🧩 chunk data`) 70 | gameLoaderStore.setNewTask('Chunk data', { max: worldConfig.chunks }) 71 | const { data, noiseMaps } = await useWorker( 72 | DataWorker, 73 | { 74 | chunks: JSON.stringify(chunks), 75 | noiseRenderThreshold: 0.65, 76 | noiseRenderThresholdMod: 20, 77 | waterLevel: -12, 78 | noiseMaps: memory.noiseMaps, 79 | }, 80 | () => gameLoaderStore.increment(), 81 | ) 82 | chunks = data 83 | memory.noiseMaps = noiseMaps 84 | gameLoaderStore.setTaskFinished() 85 | Perf.get(`🧩 chunk data`).end() 86 | 87 | // render geometries 88 | Perf.get(`🌍 geometries`) 89 | gameLoaderStore.setNewTask('Render geometries', { max: worldConfig.chunks }) 90 | await forEachRange(rangeArr, async (x: number, y: number) => { 91 | if (memory.chunksCache[`${x}${y}`]) return 92 | memory.chunksCache[`${x}${y}`] = true 93 | chunksDefer.push( 94 | await generateChunkGeometries(chunks[`${x}${y}`], { 95 | location: { x, y }, 96 | }), 97 | ) 98 | gameLoaderStore.increment() 99 | }) 100 | gameLoaderStore.setTaskFinished() 101 | Perf.get(`🌍 geometries`).end() 102 | 103 | gameLoaderStore.setNewTask('Starting world...', { max: 1 }) 104 | return chunksDefer 105 | } 106 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world-chunks-generator/world-chunks-generator.types.ts: -------------------------------------------------------------------------------- 1 | import { BufferGeometry, BoxHelper } from 'three' 2 | 3 | export interface WorldChunksGeneratorOpts { 4 | startChunkLocation?: [number, number] 5 | } 6 | 7 | type Location2D = { 8 | x: number 9 | y: number 10 | } 11 | 12 | type Location = { 13 | x: number 14 | y: number 15 | z: number 16 | } 17 | 18 | export type ChunkCoordinated = { [key: string]: Chunk } 19 | 20 | export type ChunkDataCoordinated = { [key: string]: ChunkData } 21 | 22 | export interface ChunkNoiseOpts { 23 | location: Location2D 24 | chunkMod: number 25 | chunkId: string 26 | } 27 | 28 | export interface ChunkGeometries { 29 | geometry: BufferGeometry 30 | helperGeometries: BufferGeometry[] | BoxHelper[] 31 | } 32 | 33 | // -------------- FIXED 34 | export interface Chunk { 35 | chunkId: string 36 | noiseMaps: { 37 | treeMax: number 38 | treeMin: number 39 | cubeMax: number 40 | cubeMin: number 41 | } 42 | data: ChunkData[] 43 | } 44 | 45 | export interface ChunkData { 46 | chunkDataId: string 47 | noiseValue: number 48 | location: Location 49 | absLocation: Location 50 | vegetation: Vegetation 51 | material?: number 52 | surrounding?: Surroundings 53 | isTransparent?: boolean 54 | } 55 | 56 | export interface Surroundings { 57 | nx: boolean 58 | ny: boolean 59 | nz: boolean 60 | px: boolean 61 | py: boolean 62 | pz: boolean 63 | } 64 | 65 | export interface Vegetation { 66 | treeNoise: number 67 | } 68 | 69 | export type Chunks2D = { [key: string]: Chunk } 70 | export type ChunksArr = Chunk[] 71 | export type ChunkData2D = { [key: string]: ChunkData } 72 | export type ChunkDataArr = ChunkData[] 73 | export type Cubes3D = { [key: string]: ChunkData } 74 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/comp/ambient-light.ts: -------------------------------------------------------------------------------- 1 | import { AmbientLight } from 'three' 2 | 3 | import dat from '@game/helpers/dat' 4 | 5 | export default function ambientLight(scene) { 6 | const light = new AmbientLight(0x6e6e6e) 7 | const datHelper = dat.createSpace('AmbientLight', light) 8 | datHelper.addColor('color') 9 | return light 10 | } 11 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/comp/directional-light.ts: -------------------------------------------------------------------------------- 1 | import { DirectionalLight, CameraHelper } from 'three' 2 | 3 | import dat from '@game/helpers/dat' 4 | 5 | export default function directionalLight(scene) { 6 | const light = new DirectionalLight(0x6e6e6e, 1, 1000) 7 | light.position.set(0, 1, 0) 8 | light.castShadow = true 9 | 10 | light.shadow.mapSize.width = 512 11 | light.shadow.mapSize.height = 512 12 | light.shadow.camera.near = 55.5 13 | light.shadow.camera.far = 50000 14 | 15 | const datHelper = dat.createSpace('DirectionalLight', light) 16 | datHelper.addColor('color') 17 | datHelper.add('position.x', { range: 10000, opts: [1] }) 18 | datHelper.add('position.y', { range: 10000, opts: [1] }) 19 | datHelper.add('position.z', { range: 10000, opts: [1] }) 20 | 21 | return [light, new CameraHelper(light.shadow.camera)] 22 | } 23 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/comp/hemi-light.ts: -------------------------------------------------------------------------------- 1 | import { HemisphereLight } from 'three' 2 | 3 | import dat from '@game/helpers/dat' 4 | 5 | export default function hemiLight(scene) { 6 | const light = new HemisphereLight(0x6a777d, 0x848484, 0.6) 7 | 8 | const datHelper = dat.createSpace('HemiLight', light) 9 | 10 | datHelper.addColor('color') 11 | datHelper.addColor('groundColor') 12 | datHelper.add('intensity', { range: 10000, opts: [1] }) 13 | 14 | return light 15 | } 16 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/comp/skybox.ts: -------------------------------------------------------------------------------- 1 | import { SphereGeometry, Mesh, ShaderMaterial, RepeatWrapping, TextureLoader, BackSide, MeshPhongMaterial, MeshBasicMaterial } from 'three' 2 | import { toRad } from '@game/utils' 3 | 4 | export default function skybox(scene) { 5 | const skyboxSize = 20000 6 | const geometry = new SphereGeometry(skyboxSize, 60, 40) 7 | geometry.scale(-1, 1, 1) 8 | geometry.rotateX(toRad(90)) 9 | const texture = new TextureLoader().load('../resources/textures/skydome/sky.jpg') 10 | var material = new MeshBasicMaterial({ map: texture }) 11 | material.fog = false 12 | return new Mesh(geometry, material) 13 | } 14 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/comp/spot-light.ts: -------------------------------------------------------------------------------- 1 | import { SpotLight as _SpotLight, SpotLightHelper, Scene, Object3D, SpotLightShadow, PerspectiveCamera } from 'three' 2 | 3 | // import dat from '@game/helpers/dat-gui' 4 | import dat from '@game/helpers/dat' 5 | import renderer from '@game/system/engine/renderer' 6 | 7 | export default class SpotLight { 8 | private target: Object3D = null 9 | private light: _SpotLight = null 10 | private scene: Scene = null 11 | private lightHelper: SpotLightHelper = null 12 | 13 | constructor(scene: Scene, position = null, target = null) { 14 | this.scene = scene 15 | this._createLight() 16 | this._createLightHelper() 17 | this._bindDat() 18 | this.position = position 19 | this.targetPosition = target 20 | renderer.onUpdate(this._update) 21 | } 22 | 23 | /** 24 | * Create spot light 25 | * @private 26 | */ 27 | _createLight() { 28 | this.target = new Object3D() 29 | this.target.position.set(-1074, 260, -347) 30 | if (this.targetPosition) this.target.position.set(...this.targetPosition) 31 | this.light = new _SpotLight(0x693e3e) 32 | this.light.target = this.target 33 | this.light.position.set(-941, 3657, 2303) 34 | if (this.position) this.light.position.set(...this.position) 35 | this.light.angle = Math.PI / 2.2 36 | this.light.penumbra = 0.0 37 | this.light.decay = 1 38 | this.light.distance = 10000 39 | this.light.shadow = new SpotLightShadow(new PerspectiveCamera(20, 1, 1, 250)) 40 | this.light.shadow.bias = 0 // -0.00001 41 | this.light.shadow.mapSize.width = 2048 42 | this.light.shadow.mapSize.height = 2048 43 | this.light.shadow.camera.near = 1 44 | this.light.shadow.camera.far = 2000 45 | this.light.castShadow = true 46 | this.light.receiveShadow = true 47 | } 48 | 49 | /** 50 | * Create light helper 51 | * @private 52 | */ 53 | _createLightHelper() { 54 | this.lightHelper = new SpotLightHelper(this.light) 55 | } 56 | 57 | /** 58 | * Bind DAT.GUI 59 | * @private 60 | */ 61 | _bindDat() { 62 | const datSpotLight = dat.createSpace('SpotLight', this.light) 63 | datSpotLight.onChange(() => { 64 | this.lightHelper.update() 65 | }) 66 | datSpotLight.addColor('color') 67 | datSpotLight.add('position.x', { range: 10000, opts: [1] }) 68 | datSpotLight.add('position.y', { range: 10000, opts: [1] }) 69 | datSpotLight.add('position.z', { range: 10000, opts: [1] }) 70 | datSpotLight.add('target.position.x', { range: 10000, opts: [1] }) 71 | datSpotLight.add('target.position.y', { range: 10000, opts: [1] }) 72 | datSpotLight.add('target.position.z', { range: 10000, opts: [1] }) 73 | datSpotLight.add('shadow.mapSize.width', { range: 10000, opts: [1] }) 74 | datSpotLight.add('shadow.mapSize.height', { range: 10000, opts: [1] }) 75 | datSpotLight.add('shadow.bias', { range: 10000, opts: [0.00001] }) 76 | datSpotLight.add('angle', { opts: [0, 90, 1], convert: 'deg' }) 77 | datSpotLight.add('penumbra', { range: 10000, opts: [1] }) 78 | datSpotLight.add('decay', { range: 10000, opts: [1] }) 79 | datSpotLight.add('distance', { range: 10000, opts: [1] }) 80 | } 81 | 82 | render() { 83 | this.scene.add(this.light) 84 | this.scene.add(this.lightHelper) 85 | this.scene.add(this.target) 86 | } 87 | 88 | _update = () => { 89 | this.light.position.z -= 0.005 90 | this.light.position.x += 0.005 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/comp/test.ts: -------------------------------------------------------------------------------- 1 | import { MeshLambertMaterial, TextureLoader, BoxGeometry, Mesh } from 'three' 2 | import { toRad } from '@game/utils' 3 | import dat from '@game/helpers/dat' 4 | 5 | export default function (scene) { 6 | const x = -0 7 | // box 1 8 | const grassTexture = new TextureLoader().load('../resources/textures/blocks/hardened_clay_stained_green.png') 9 | const matGrass = new MeshLambertMaterial({ 10 | map: grassTexture, 11 | }) 12 | 13 | var geometry = new BoxGeometry(100, 100, 100) 14 | const mesh = new Mesh(geometry, matGrass) 15 | geometry.translate(x, -1000, 0) 16 | mesh.castShadow = true 17 | mesh.receiveShadow = true 18 | mesh.rotateX(toRad(-87)) 19 | mesh.rotateY(toRad(23)) 20 | mesh.rotateZ(toRad(-79)) 21 | scene.add(mesh) 22 | 23 | // box 2 24 | var geometry2 = new BoxGeometry(1000, 10, 1000) 25 | const mesh2 = new Mesh(geometry2, matGrass) 26 | geometry2.translate(x, -0, 0) 27 | mesh2.castShadow = true 28 | mesh2.receiveShadow = true 29 | mesh2.rotateX(toRad(-87)) 30 | mesh2.rotateY(toRad(23)) 31 | mesh2.rotateZ(toRad(-79)) 32 | scene.add(mesh2) 33 | 34 | const datHelper = dat.createSpace('test', mesh2) 35 | datHelper.add('rotation.x', { range: 10000, opts: [1], convert: 'deg' }) 36 | datHelper.add('rotation.y', { range: 10000, opts: [1], convert: 'deg' }) 37 | datHelper.add('rotation.z', { range: 10000, opts: [1], convert: 'deg' }) 38 | } 39 | // import gameEvents from '@game/system/modules/game-events' 40 | // import { toRad } from '@game/utils' 41 | // 42 | // export default function () { 43 | // // TODO: We need to get rid of the materials from here 44 | // // Those should not be injected when generating the chunk 45 | // const grassTexture = new TextureLoader().load('../resources/textures/blocks/hardened_clay_stained_green.png') 46 | // const matGrass = new MeshLambertMaterial({ 47 | // map: grassTexture, 48 | // }) 49 | // // matGrass.eu 50 | // const matDirt = new MeshLambertMaterial({ 51 | // map: new TextureLoader().load('../resources/textures/blocks/dirt.png'), 52 | // }) 53 | // const matStone = new MeshLambertMaterial({ 54 | // map: new TextureLoader().load('../resources/textures/blocks/cobblestone_mossy.png'), 55 | // }) 56 | // const textureLog = new TextureLoader().load('../resources/textures/blocks/log_spruce.png') 57 | // textureLog.rotation = toRad(-90) 58 | // const matLog = new MeshLambertMaterial({ 59 | // map: textureLog, 60 | // }) 61 | // const matLeaves = new MeshLambertMaterial({ 62 | // map: new TextureLoader().load('../resources/textures/blocks/leaves.png'), 63 | // transparent: true, 64 | // }) 65 | // 66 | // const waterTexture = new TextureLoader().load('../resources/textures/blocks/water.png') 67 | // waterTexture.wrapS = RepeatWrapping 68 | // waterTexture.wrapT = RepeatWrapping 69 | // const matWater: Material = new MeshLambertMaterial({ 70 | // map: waterTexture, 71 | // transparent: true, 72 | // opacity: 0.5, 73 | // }) 74 | // 75 | // const matSand = new MeshLambertMaterial({ 76 | // map: new TextureLoader().load('../resources/textures/blocks/sand.png'), 77 | // }) 78 | // 79 | // // animate water 80 | // gameEvents.register('render', () => { 81 | // matWater.map.offset.x += 0.002 82 | // matWater.map.offset.y += 0.002 83 | // }) 84 | // 85 | // return [matGrass, matDirt, matStone, matLog, matLeaves, matWater, matSand] 86 | // } 87 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/helpers/getCubeMaterials/getCubeMaterials.ts: -------------------------------------------------------------------------------- 1 | import { MeshLambertMaterial, TextureLoader, Material, RepeatWrapping } from 'three' 2 | import gameEvents from '@game/system/modules/game-events' 3 | import { toRad } from '@game/utils' 4 | 5 | export default function () { 6 | // TODO: We need to get rid of the materials from here 7 | // Those should not be injected when generating the chunk 8 | const grassTexture = new TextureLoader().load('../resources/textures/blocks/hardened_clay_stained_green.png') 9 | const matGrass = new MeshLambertMaterial({ 10 | map: grassTexture, 11 | }) 12 | // matGrass.eu 13 | const matDirt = new MeshLambertMaterial({ 14 | map: new TextureLoader().load('../resources/textures/blocks/dirt.png'), 15 | }) 16 | const matStone = new MeshLambertMaterial({ 17 | map: new TextureLoader().load('../resources/textures/blocks/cobblestone_mossy.png'), 18 | }) 19 | const textureLog = new TextureLoader().load('../resources/textures/blocks/log_spruce.png') 20 | textureLog.rotation = toRad(-90) 21 | const matLog = new MeshLambertMaterial({ 22 | map: textureLog, 23 | }) 24 | const matLeaves = new MeshLambertMaterial({ 25 | map: new TextureLoader().load('../resources/textures/blocks/leaves.png'), 26 | transparent: true, 27 | }) 28 | 29 | const waterTexture = new TextureLoader().load('../resources/textures/blocks/water.png') 30 | waterTexture.wrapS = RepeatWrapping 31 | waterTexture.wrapT = RepeatWrapping 32 | const matWater: Material = new MeshLambertMaterial({ 33 | map: waterTexture, 34 | transparent: true, 35 | opacity: 0.5, 36 | }) 37 | 38 | const matSand = new MeshLambertMaterial({ 39 | map: new TextureLoader().load('../resources/textures/blocks/sand.png'), 40 | }) 41 | 42 | // animate water 43 | gameEvents.register('render', () => { 44 | matWater.map.offset.x += 0.002 45 | matWater.map.offset.y += 0.002 46 | }) 47 | 48 | return [matGrass, matDirt, matStone, matLog, matLeaves, matWater, matSand] 49 | } 50 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/helpers/getCubeMaterials/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './getCubeMaterials' 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './world'; 2 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/world.ts: -------------------------------------------------------------------------------- 1 | import { Mesh, Fog } from 'three' 2 | 3 | import App from '@game/app' 4 | import { Perf } from '@game/utils' 5 | import { WorldChunk } from '@game/@types/chunk.types' 6 | 7 | import biomeGrass from '@game/system/modules/biomes/biome-grass/biome-grass' 8 | 9 | import SpotLight from './comp/spot-light' 10 | import hemiLight from './comp/hemi-light' 11 | import ambientLight from './comp/ambient-light' 12 | import skybox from './comp/skybox' 13 | import directionalLight from './comp/directional-light' 14 | import testMesh from './comp/test' 15 | 16 | import worldChunksGenerator from '@game/system/modules/world-chunks-generator' 17 | 18 | import getCubeMaterials from './helpers/getCubeMaterials' 19 | 20 | import { WorldOpts, ChunkOptions } from './world.types' 21 | import gameEvents from '@game/system/modules/game-events/game-events' 22 | import { worldConfig } from '@game/config' 23 | 24 | export default class World { 25 | private readonly app: App 26 | private readonly chunkOptions: ChunkOptions 27 | // private readonly chunkSeed: Chunk 28 | private worldChunks: WorldChunk[] 29 | 30 | meshes = [] 31 | lights = [] 32 | objects = [] 33 | 34 | constructor(app: App, { seedId, chunkOptions }: WorldOpts) { 35 | Perf.get('Generate world init') 36 | this.app = app 37 | this.seedId = seedId 38 | // this.chunkSeed = new Chunk(seedId) 39 | this.chunkOptions = chunkOptions 40 | this.currentChunk = [-3, 0] 41 | gameEvents.register('render', async ({ controls }) => { 42 | const playerPosition = controls.object.position 43 | const x = Math.floor(playerPosition.x / (worldConfig.chunkSize * worldConfig.cubeSize)) 44 | const y = Math.floor(playerPosition.y / (worldConfig.chunkSize * worldConfig.cubeSize)) 45 | // console.log(`x: ${playerPosition.x} | y: ${playerPosition.y} | z: ${playerPosition.z}`) 46 | // console.log(`current chunk: ${x}:${y}`) 47 | if (this.currentChunk[0] !== x || this.currentChunk[1] !== y) { 48 | console.warn(`x: ${x} | y: ${y}`) 49 | this.currentChunk = [x, y] 50 | await this.generateWorld() 51 | for (const chunk of this.worldChunks) { 52 | const worldChunkMesh = new Mesh(chunk.geometry, [...getCubeMaterials()]) 53 | worldChunkMesh.castShadow = true 54 | worldChunkMesh.receiveShadow = true 55 | this.meshes.push(worldChunkMesh) 56 | this.app.scene.add(worldChunkMesh) 57 | chunk.helperGeometries.forEach((obj) => { 58 | this.app.scene.add(obj) 59 | }) 60 | } 61 | } 62 | }) 63 | Perf.get('Generate world init').end() 64 | } 65 | 66 | /** 67 | * Generate world 68 | * - prepare world 69 | * @return {Promise} 70 | */ 71 | async generateWorld() { 72 | Perf.get('Generate world chunks') 73 | this.worldChunks = await worldChunksGenerator(1, { startChunkLocation: this.currentChunk }) 74 | Perf.get('Generate world chunks').end() 75 | } 76 | 77 | renderWorld() { 78 | Perf.get('Render world chunks') 79 | // render world chunks 80 | for (const chunk of this.worldChunks) { 81 | const worldChunkMesh = new Mesh(chunk.geometry, [...getCubeMaterials()]) 82 | worldChunkMesh.castShadow = true 83 | worldChunkMesh.receiveShadow = true 84 | this.meshes.push(worldChunkMesh) 85 | this.app.scene.add(worldChunkMesh) 86 | chunk.helperGeometries.forEach((obj) => { 87 | this.app.scene.add(obj) 88 | }) 89 | } 90 | Perf.get('Render world chunks').end() 91 | Perf.get('Render sky & lights') 92 | // testMesh(this.app.scene) 93 | // render skybox 94 | const skyboxMesh = skybox() 95 | this.app.scene.fog = new Fog('#efdec4', 800, 3500) 96 | this.app.scene.add(skyboxMesh) 97 | this.meshes.push(skyboxMesh) 98 | 99 | // add spotlight 1 100 | const spotLight = new SpotLight(this.app.scene, [-900, 3057, 2003], [-1074, 260, -347]) 101 | spotLight.render() 102 | this.objects.push(spotLight) 103 | 104 | // const spotLight2 = new SpotLight(this.app.scene, [-941, 3657, 2303], [-1074, 260, -347]) 105 | // spotLight2.render() 106 | // this.objects.push(spotLight2) 107 | 108 | // hemi light 109 | const hemiLightMesh = hemiLight() 110 | this.app.scene.add(hemiLightMesh) 111 | this.lights.push(hemiLightMesh) 112 | 113 | // ambient light 114 | const ambientLightMesh = ambientLight() 115 | this.app.scene.add(ambientLightMesh) 116 | this.lights.push(ambientLightMesh) 117 | 118 | // const [directionalLightMesh, helper] = directionalLight() 119 | // this.app.scene.add(directionalLightMesh) 120 | // this.app.scene.add(helper) 121 | // this.lights.push(directionalLightMesh) 122 | 123 | Perf.get('Render sky & lights').end() 124 | } 125 | 126 | destroy() { 127 | for (const mesh of this.meshes) { 128 | this.app.scene.remove(mesh) 129 | } 130 | for (const light of this.lights) { 131 | this.app.scene.remove(light) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/client/game/system/modules/world/world.types.ts: -------------------------------------------------------------------------------- 1 | export interface ChunkOptions { 2 | mod: number 3 | } 4 | 5 | export interface WorldOpts { 6 | seedId: number | string 7 | chunkOptions: ChunkOptions 8 | } 9 | -------------------------------------------------------------------------------- /src/client/game/utils/_deg-abs.ts: -------------------------------------------------------------------------------- 1 | export function degAbs(deg) { 2 | return deg - Math.floor(deg / 360) * 360 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/utils/_is-defined.ts: -------------------------------------------------------------------------------- 1 | export function isDefined(value) { 2 | return typeof value !== 'undefined' 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/utils/_perf.ts: -------------------------------------------------------------------------------- 1 | import { isDefined } from './_is-defined' 2 | 3 | class _Perf { 4 | timeStart 5 | timeEnd 6 | name 7 | 8 | constructor(name) { 9 | this.name = name 10 | this.timeStart = new Date().getTime() 11 | } 12 | 13 | fillSpace(textBefore) { 14 | let fillText = '' 15 | for (let i = 30 - textBefore.length; i; i--) fillText += ' ' 16 | return fillText 17 | } 18 | 19 | end() { 20 | this.timeEnd = new Date().getTime() 21 | console.log( 22 | `%c ${this.name} ${this.fillSpace(this.name)} [${(this.timeEnd - this.timeStart) / 1000}s]`, 23 | 'color: blue; font-size: 12px; background: #f1f1f1; padding: 1px 5px;', 24 | ) 25 | } 26 | } 27 | 28 | /** 29 | * Perf 30 | * proxy class to handle perf instances by name 31 | * @static 32 | */ 33 | export class Perf { 34 | static instances = {} 35 | 36 | static get(name) { 37 | if (!isDefined(Perf.instances[name])) { 38 | Perf.instances[name] = new _Perf(name) 39 | } 40 | return Perf.instances[name] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/client/game/utils/_random.ts: -------------------------------------------------------------------------------- 1 | export function random(min, max) { 2 | return Math.random() * (max - min) + min 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/utils/_to-deg.ts: -------------------------------------------------------------------------------- 1 | export function toDeg(rad) { 2 | return (rad * 180) / Math.PI 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/utils/_to-rad.ts: -------------------------------------------------------------------------------- 1 | export function toRad(deg) { 2 | return (deg * Math.PI) / 180 3 | } 4 | -------------------------------------------------------------------------------- /src/client/game/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { degAbs } from './_deg-abs' 2 | export { isDefined } from './_is-defined' 3 | export { Perf } from './_perf' 4 | export { random } from './_random' 5 | export { toDeg } from './_to-deg' 6 | export { toRad } from './_to-rad' 7 | -------------------------------------------------------------------------------- /src/client/game/utils/toRange.ts: -------------------------------------------------------------------------------- 1 | export default function toRange(value: number, fromMax: number, fromMin: number, toMax: number, toMin: number): number { 2 | return ((value - fromMin) / (fromMax - fromMin)) * (toMax - toMin) + toMin 3 | } 4 | -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ThreeJS seeded world algorithm 5 | 6 | 15 | 16 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | import GameApp from './game/app' 2 | import reactApp from './web/app' 3 | 4 | reactApp() 5 | new GameApp() 6 | -------------------------------------------------------------------------------- /src/client/shared/stores/assets-loader.ts: -------------------------------------------------------------------------------- 1 | import { observable, action, configure } from 'mobx' 2 | 3 | configure({ 4 | enforceActions: true, 5 | }) 6 | 7 | /** 8 | * Demo of MobX store 9 | */ 10 | export const storeAssetsLoader = new (class AssetsLoader { 11 | // make type observable 12 | // component will be updated when it will change 13 | @observable progress = 0 14 | @observable max = 0 15 | @observable isLoading = false 16 | 17 | @action reset() { 18 | this.title = '' 19 | this.max = 0 20 | this.progress = 0 21 | } 22 | 23 | @action setTitle(title) { 24 | this.reset() 25 | this.title = title 26 | } 27 | 28 | @action setMax(max) { 29 | this.max = max 30 | } 31 | 32 | @action incrementProgress() { 33 | this.progress++ 34 | } 35 | 36 | @action setLoading(value) { 37 | this.isLoading = !!value 38 | } 39 | })() 40 | -------------------------------------------------------------------------------- /src/client/shared/stores/gameLoader/game-loader.ts: -------------------------------------------------------------------------------- 1 | import { observable, action, configure } from 'mobx' 2 | 3 | configure({ 4 | enforceActions: true, 5 | }) 6 | 7 | /** 8 | * Game loader store 9 | */ 10 | export default new (class GameLoaderStore { 11 | // Is game fully loaded 12 | // And no more things would be loading 13 | @observable isGameLoaded = false 14 | 15 | // Current task title 16 | // Only one task allowed at the time 17 | @observable taskTitle = null 18 | 19 | // Current task progress 20 | @observable taskCurrent = 0 21 | 22 | // Current task max 23 | @observable taskMax = 0 24 | 25 | @action setNewTask(title, { max }) { 26 | this.taskTitle = title 27 | this.taskMax = max 28 | this.taskCurrent = 0 29 | } 30 | 31 | @action setTaskFinished() { 32 | this.taskCurrent = this.taskMax 33 | } 34 | 35 | @action setCurrent(value) { 36 | this.taskCurrent = value 37 | } 38 | 39 | @action reset() { 40 | // this.title = '' 41 | // this.max = 0 42 | // this.progress = 0 43 | } 44 | 45 | @action setTitle(title) { 46 | // this.reset() 47 | // this.title = title 48 | } 49 | 50 | @action setMax(max) { 51 | // this.max = max 52 | } 53 | 54 | @action increment() { 55 | this.taskCurrent++ 56 | } 57 | 58 | @action setLoading(value) { 59 | // this.isLoading = !!value 60 | } 61 | })() 62 | -------------------------------------------------------------------------------- /src/client/shared/stores/gameLoader/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './game-loader' 2 | -------------------------------------------------------------------------------- /src/client/shared/stores/index.ts: -------------------------------------------------------------------------------- 1 | export { storeAssetsLoader } from './assets-loader' 2 | -------------------------------------------------------------------------------- /src/client/web/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Router } from 'react-router' 3 | import { createBrowserHistory } from 'history' 4 | import { render } from 'react-dom' 5 | import { BrowserRouter } from 'react-router-dom' 6 | import { renderRoutes } from 'react-router-config' 7 | 8 | import routes from './routes' 9 | 10 | export default function App() { 11 | render( 12 | 13 | {renderRoutes(routes)} 14 | , 15 | document.getElementById('root'), 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/client/web/app/components/containers/index.js: -------------------------------------------------------------------------------- 1 | export Wrapper from './wrapper'; 2 | -------------------------------------------------------------------------------- /src/client/web/app/components/containers/wrapper.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | 5 | import { observer } from 'mobx-react/index'; 6 | import { storeAssetsLoader } from 'shared/store'; 7 | 8 | import './wrapper.scss'; 9 | 10 | export class Wrapper extends Component { 11 | 12 | static propTypes = { 13 | children: PropTypes.node.isRequired, 14 | }; 15 | 16 | render() { 17 | return ( 18 |
22 |
23 |
24 | {this.props.children} 25 |
26 |
27 | ); 28 | } 29 | 30 | } 31 | 32 | export default observer(Wrapper); 33 | -------------------------------------------------------------------------------- /src/client/web/app/components/containers/wrapper.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | width: 100vw; 3 | height: 100vh; 4 | position: relative; 5 | background-image: url('../../../../../assets/web/dark-dirt.png'); 6 | 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | 11 | &--visible { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | } 16 | 17 | &--hidden { 18 | display: none; 19 | } 20 | 21 | &__foreground { 22 | 23 | } 24 | 25 | &__container { 26 | width: 60%; 27 | height: 10%; 28 | //background: rgba(160, 161, 158, 0.8); 29 | //border: 1px solid #bfbfbf; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/client/web/app/components/help/help.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | 5 | import './help.scss'; 6 | 7 | export default class Help extends Component { 8 | 9 | render() { 10 | return ( 11 |
12 |

Controls

13 |
    14 |
  • left mouse button - move forward
  • 15 |
  • right mouse button - move backward
  • 16 |
  • middle mouse button - lock controls
  • 17 |
  • "L" - lock controls
  • 18 |
19 |
20 | ); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/client/web/app/components/help/help.scss: -------------------------------------------------------------------------------- 1 | .help { 2 | margin-top: 200px; 3 | color: #9e9e9e; 4 | } 5 | -------------------------------------------------------------------------------- /src/client/web/app/components/help/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './help'; 2 | -------------------------------------------------------------------------------- /src/client/web/app/components/logo/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './logo'; 2 | -------------------------------------------------------------------------------- /src/client/web/app/components/logo/logo.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import './logo.scss'; 4 | 5 | export default class Logo extends Component { 6 | 7 | render() { 8 | return ( 9 |
10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/client/web/app/components/logo/logo.scss: -------------------------------------------------------------------------------- 1 | .logo { 2 | position: absolute; 3 | top: 20%; 4 | left: 50%; 5 | 6 | width: 600px; 7 | height: 180px; 8 | 9 | margin-left: -300px; 10 | 11 | background-size: 600px auto; 12 | background-image: url('../../../../../assets/web/minecraft.png'); 13 | } 14 | -------------------------------------------------------------------------------- /src/client/web/app/components/preloader/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './preloader'; 2 | -------------------------------------------------------------------------------- /src/client/web/app/components/preloader/preloader.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import { storeAssetsLoader } from 'shared/store'; 5 | 6 | import './preloader.scss'; 7 | 8 | export class Preloader extends Component { 9 | 10 | render() { 11 | const progress = Math.round((100 * storeAssetsLoader.progress) / storeAssetsLoader.max); 12 | return ( 13 |
14 |
{storeAssetsLoader.title} ( {storeAssetsLoader.progress} / {storeAssetsLoader.max} )
15 |
16 |
17 |
18 |
19 |
20 | ); 21 | } 22 | } 23 | 24 | export default observer(Preloader); 25 | -------------------------------------------------------------------------------- /src/client/web/app/components/preloader/preloader.scss: -------------------------------------------------------------------------------- 1 | .preloader { 2 | &__text { 3 | color: white; 4 | font-size: 26px; 5 | width: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | } 11 | 12 | 13 | $bar-height: 10px; 14 | 15 | .bar { 16 | width: 100%; 17 | height: $bar-height; 18 | background: red; 19 | position: relative; 20 | margin-top: 100px; 21 | 22 | &__foreground { 23 | width: 100%; 24 | height: $bar-height; 25 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#26481b+0,388224+10,4a9d35+30,4a9d35+57,326823+88,326823+88,26481b+100 */ 26 | background: #26481b; /* Old browsers */ 27 | background: -moz-linear-gradient(top, #26481b 0%, #388224 10%, #4a9d35 30%, #4a9d35 57%, #326823 88%, #326823 88%, #26481b 100%); /* FF3.6-15 */ 28 | background: -webkit-linear-gradient(top, #26481b 0%,#388224 10%,#4a9d35 30%,#4a9d35 57%,#326823 88%,#326823 88%,#26481b 100%); /* Chrome10-25,Safari5.1-6 */ 29 | background: linear-gradient(to bottom, #26481b 0%,#388224 10%,#4a9d35 30%,#4a9d35 57%,#326823 88%,#326823 88%,#26481b 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 30 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#26481b', endColorstr='#26481b',GradientType=0 ); /* IE6-9 */ 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | z-index: 2; 35 | } 36 | 37 | &__background { 38 | width: 100%; 39 | height: $bar-height; 40 | background: #0b0b0b; 41 | 42 | position: absolute; 43 | top: 0; 44 | left: 0; 45 | z-index: 1; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/client/web/components/containers/App/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import * as Styled from './styles' 4 | 5 | export default function App(props) { 6 | return ( 7 | 8 | 9 | <>{props.children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/client/web/components/containers/App/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './App' 2 | -------------------------------------------------------------------------------- /src/client/web/components/containers/App/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { createGlobalStyle } from 'styled-components' 2 | 3 | export const GlobalStyle = createGlobalStyle` 4 | body { 5 | padding: 0; 6 | margin: 0; 7 | background-image: url('static/bg-dirt.png'); 8 | font-family: 'VT323', monospace; 9 | } 10 | ` 11 | 12 | export const App = styled.div`` 13 | -------------------------------------------------------------------------------- /src/client/web/components/containers/Center/Center.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Container, Row } from 'styled-bootstrap-grid' 3 | 4 | export default function Center(props) { 5 | return ( 6 | 7 | {props.children} 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/client/web/components/containers/Center/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Center' 2 | -------------------------------------------------------------------------------- /src/client/web/components/containers/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Container, Row, Col } from 'styled-bootstrap-grid' 4 | 5 | import * as Styled from './styles' 6 | 7 | export default function HomeRoute(props) { 8 | return ( 9 | 10 | 11 | {props.children} 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/client/web/components/containers/Container/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Container' 2 | -------------------------------------------------------------------------------- /src/client/web/components/containers/Container/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { createGlobalStyle } from 'styled-components' 2 | 3 | export const Container = styled.div` 4 | width: 100vw; 5 | height: 100vh; 6 | display: flex; 7 | align-items: center; 8 | ` 9 | -------------------------------------------------------------------------------- /src/client/web/components/preloaders/Linear/Linear.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { Container, Row, Col } from 'styled-bootstrap-grid' 4 | 5 | import * as Styled from './styles' 6 | 7 | export default function HomeRoute(props) { 8 | return ( 9 | 10 | 11 | {props.title} ( {props.current} / {props.max} ) 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/client/web/components/preloaders/Linear/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Linear' 2 | -------------------------------------------------------------------------------- /src/client/web/components/preloaders/Linear/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Linear = styled.div` 4 | width: 600px; 5 | height: 180px; 6 | ` 7 | 8 | export const ProgressText = styled.div` 9 | width: 100%; 10 | margin: 20px 0; 11 | font-size: 26px; 12 | color: whitesmoke; 13 | text-align: center; 14 | ` 15 | 16 | export const ProgressContainer = styled.div` 17 | width: 100%; 18 | height: 14px; 19 | background: #b3b3b3; 20 | padding: 1px 1px; 21 | box-sizing: border-box; 22 | ` 23 | 24 | export const ProgressBar = styled.div( 25 | ({ fillPercentage }) => ` 26 | width: ${fillPercentage}%; 27 | height: 12px; 28 | background: green; 29 | `, 30 | ) 31 | -------------------------------------------------------------------------------- /src/client/web/routes.d.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from 'react' 2 | 3 | export type ReactPage = { 4 | default: ComponentType; 5 | } 6 | -------------------------------------------------------------------------------- /src/client/web/routes.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | import React, { Suspense, ReactElement } from 'react' 3 | import { RouteConfig } from 'react-router-config' 4 | 5 | import { ReactPage } from './routes.d' 6 | 7 | const routes = [ 8 | ['/', React.lazy((): Promise => import(/* webpackChunkName: "dashboard-route" */ '@web/routes/Home'))], 9 | ] 10 | 11 | export default routes.map( 12 | ([routeUrl, RoutePage]): RouteConfig => ({ 13 | path: `/${routeUrl}`, 14 | component: (): ReactElement => ( 15 | 16 | 17 | 18 | ), 19 | exact: true, 20 | }), 21 | ) 22 | -------------------------------------------------------------------------------- /src/client/web/routes/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { observer } from 'mobx-react' 4 | 5 | import App from '@web/components/containers/App' 6 | import Container from '@web/components/containers/Container' 7 | import Center from '@web/components/containers/Center' 8 | import PreloderLinear from '@web/components/preloaders/Linear' 9 | 10 | import gameLoaderStore from '@shared/stores/gameLoader' 11 | 12 | import Logo from './components/Logo/Logo' 13 | 14 | export default observer(function HomeRoute() { 15 | return ( 16 | 17 | 18 |
19 | 20 | 21 |
22 |
23 |
24 | ) 25 | }) 26 | -------------------------------------------------------------------------------- /src/client/web/routes/Home/components/Logo/Logo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import * as Styled from './styles' 4 | 5 | export default function Logo() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /src/client/web/routes/Home/components/Logo/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Container' 2 | -------------------------------------------------------------------------------- /src/client/web/routes/Home/components/Logo/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Logo = styled.div` 4 | width: 600px; 5 | height: 180px; 6 | background-image: url('static/logo-minecraft.png'); 7 | background-size: 100% 100%; 8 | ` 9 | -------------------------------------------------------------------------------- /src/client/web/routes/Home/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Home' 2 | -------------------------------------------------------------------------------- /src/client/web/static/bg-dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/web/static/bg-dirt.png -------------------------------------------------------------------------------- /src/client/web/static/dirt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/web/static/dirt.jpg -------------------------------------------------------------------------------- /src/client/web/static/logo-minecraft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/web/static/logo-minecraft.png -------------------------------------------------------------------------------- /src/client/web/static/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/client/web/static/screenshot.jpg -------------------------------------------------------------------------------- /src/libraries/first-person-controls.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | /** 4 | * @author mrdoob / http://mrdoob.com/ 5 | * @author alteredq / http://alteredqualia.com/ 6 | * @author paulirish / http://paulirish.com/ 7 | */ 8 | import * as THREE from 'three' 9 | 10 | THREE.FirstPersonControls = function (object, domElement) { 11 | this.object = object 12 | this.target = new THREE.Vector3(0, 0, 0) 13 | 14 | this.domElement = domElement !== undefined ? domElement : document 15 | 16 | this.enabled = false 17 | 18 | this.movementSpeed = 1.0 19 | this.lookSpeed = 0.005 20 | 21 | this.lookVertical = true 22 | this.autoForward = false 23 | 24 | this.activeLook = true 25 | 26 | this.heightSpeed = false 27 | this.heightCoef = 1.0 28 | this.heightMin = 0.0 29 | this.heightMax = 1.0 30 | 31 | this.constrainVertical = false 32 | this.verticalMin = 0 33 | this.verticalMax = Math.PI 34 | 35 | this.autoSpeedFactor = 0.0 36 | 37 | this.mouseX = 0 38 | this.mouseY = 0 39 | 40 | this.lat = 0 41 | this.lon = 0 42 | this.phi = 0 43 | this.theta = 0 44 | 45 | this.moveForward = false 46 | this.moveBackward = false 47 | this.moveLeft = false 48 | this.moveRight = false 49 | 50 | this.mouseDragOn = false 51 | 52 | this.viewHalfX = 0 53 | this.viewHalfY = 0 54 | 55 | if (this.domElement !== document) { 56 | this.domElement.setAttribute('tabindex', -1) 57 | } 58 | 59 | // 60 | 61 | this.handleResize = function () { 62 | if (this.domElement === document) { 63 | this.viewHalfX = window.innerWidth / 2 64 | this.viewHalfY = window.innerHeight / 2 65 | } else { 66 | this.viewHalfX = this.domElement.offsetWidth / 2 67 | this.viewHalfY = this.domElement.offsetHeight / 2 68 | } 69 | } 70 | 71 | this.onMouseDown = function (event) { 72 | var isRightMB 73 | if ('which' in event) 74 | // Gecko (Firefox), WebKit (Safari/Chrome) & Opera 75 | isRightMB = event.which == 3 76 | else if ('button' in event) 77 | // IE, Opera 78 | isRightMB = event.button == 2 79 | if (isRightMB) { 80 | isRightMB = event.button == 2 81 | } 82 | 83 | if (event.button === 1) { 84 | this.enabled = !this.enabled 85 | return 86 | } 87 | 88 | if (this.domElement !== document) { 89 | this.domElement.focus() 90 | } 91 | 92 | event.preventDefault() 93 | event.stopPropagation() 94 | 95 | if (this.activeLook) { 96 | switch (event.button) { 97 | case 0: 98 | this.moveForward = true 99 | break 100 | case 2: 101 | this.moveBackward = true 102 | break 103 | } 104 | } 105 | 106 | this.mouseDragOn = true 107 | } 108 | 109 | this.onMouseUp = function (event) { 110 | // event.preventDefault(); 111 | // event.stopPropagation(); 112 | 113 | if (this.activeLook) { 114 | switch (event.button) { 115 | case 0: 116 | this.moveForward = false 117 | break 118 | case 2: 119 | this.moveBackward = false 120 | break 121 | } 122 | } 123 | 124 | this.mouseDragOn = false 125 | } 126 | 127 | this.onMouseMove = function (event) { 128 | if (this.domElement === document) { 129 | this.mouseX = event.pageX - this.viewHalfX 130 | this.mouseY = event.pageY - this.viewHalfY 131 | } else { 132 | this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX 133 | this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY 134 | } 135 | } 136 | 137 | this.onKeyDown = function (event) { 138 | //event.preventDefault(); 139 | 140 | switch (event.keyCode) { 141 | case 38: /*up*/ 142 | case 87: 143 | /*W*/ this.moveForward = true 144 | break 145 | 146 | case 37: /*left*/ 147 | case 65: 148 | /*A*/ this.moveLeft = true 149 | break 150 | 151 | case 40: /*down*/ 152 | case 83: 153 | /*S*/ this.moveBackward = true 154 | break 155 | 156 | case 39: /*right*/ 157 | case 68: 158 | /*D*/ this.moveRight = true 159 | break 160 | 161 | case 82: 162 | /*R*/ this.moveUp = true 163 | break 164 | case 70: 165 | /*F*/ this.moveDown = true 166 | break 167 | case 76: 168 | /*F*/ this.enabled = !this.enabled 169 | break 170 | } 171 | } 172 | 173 | this.onKeyUp = function (event) { 174 | switch (event.keyCode) { 175 | case 38: /*up*/ 176 | case 87: 177 | /*W*/ this.moveForward = false 178 | break 179 | 180 | case 37: /*left*/ 181 | case 65: 182 | /*A*/ this.moveLeft = false 183 | break 184 | 185 | case 40: /*down*/ 186 | case 83: 187 | /*S*/ this.moveBackward = false 188 | break 189 | 190 | case 39: /*right*/ 191 | case 68: 192 | /*D*/ this.moveRight = false 193 | break 194 | 195 | case 82: 196 | /*R*/ this.moveUp = false 197 | break 198 | case 70: 199 | /*F*/ this.moveDown = false 200 | break 201 | } 202 | } 203 | 204 | this.update = function (delta) { 205 | if (this.enabled === false) return 206 | 207 | if (this.heightSpeed) { 208 | var y = THREE.Math.clamp(this.object.position.y, this.heightMin, this.heightMax) 209 | var heightDelta = y - this.heightMin 210 | 211 | this.autoSpeedFactor = delta * (heightDelta * this.heightCoef) 212 | } else { 213 | this.autoSpeedFactor = 0.0 214 | } 215 | 216 | var actualMoveSpeed = delta * this.movementSpeed 217 | 218 | if (this.moveForward || (this.autoForward && !this.moveBackward)) this.object.translateZ(-(actualMoveSpeed + this.autoSpeedFactor)) 219 | if (this.moveBackward) this.object.translateZ(actualMoveSpeed) 220 | 221 | if (this.moveLeft) this.object.translateX(-actualMoveSpeed) 222 | if (this.moveRight) this.object.translateX(actualMoveSpeed) 223 | 224 | if (this.moveUp) this.object.translateY(actualMoveSpeed) 225 | if (this.moveDown) this.object.translateY(-actualMoveSpeed) 226 | 227 | var actualLookSpeed = delta * this.lookSpeed 228 | 229 | if (!this.activeLook) { 230 | actualLookSpeed = 0 231 | } 232 | 233 | var verticalLookRatio = 1 234 | 235 | if (this.constrainVertical) { 236 | verticalLookRatio = Math.PI / (this.verticalMax - this.verticalMin) 237 | } 238 | 239 | this.lon += this.mouseX * actualLookSpeed 240 | if (this.lookVertical) this.lat -= this.mouseY * actualLookSpeed * verticalLookRatio 241 | 242 | this.lat = Math.max(-85, Math.min(85, this.lat)) 243 | this.phi = THREE.Math.degToRad(90 - this.lat) 244 | 245 | this.theta = THREE.Math.degToRad(this.lon) 246 | 247 | if (this.constrainVertical) { 248 | this.phi = THREE.Math.mapLinear(this.phi, 0, Math.PI, this.verticalMin, this.verticalMax) 249 | } 250 | 251 | var targetPosition = this.target, 252 | position = this.object.position 253 | 254 | targetPosition.x = position.x - 100 * Math.sin(this.phi) * Math.cos(this.theta) 255 | targetPosition.z = position.z + 100 * Math.cos(this.phi) 256 | targetPosition.y = position.y + 100 * Math.sin(this.phi) * Math.sin(this.theta) 257 | 258 | this.object.lookAt(targetPosition) 259 | } 260 | 261 | function contextmenu(event) { 262 | event.preventDefault() 263 | } 264 | 265 | this.dispose = function () { 266 | this.domElement.removeEventListener('contextmenu', contextmenu, false) 267 | this.domElement.removeEventListener('mousedown', _onMouseDown, false) 268 | this.domElement.removeEventListener('mousemove', _onMouseMove, false) 269 | this.domElement.removeEventListener('mouseup', _onMouseUp, false) 270 | 271 | window.removeEventListener('keydown', _onKeyDown, false) 272 | window.removeEventListener('keyup', _onKeyUp, false) 273 | } 274 | 275 | var _onMouseMove = bind(this, this.onMouseMove) 276 | var _onMouseDown = bind(this, this.onMouseDown) 277 | var _onMouseUp = bind(this, this.onMouseUp) 278 | var _onKeyDown = bind(this, this.onKeyDown) 279 | var _onKeyUp = bind(this, this.onKeyUp) 280 | 281 | this.domElement.addEventListener('contextmenu', contextmenu, false) 282 | this.domElement.addEventListener('mousemove', _onMouseMove, false) 283 | this.domElement.addEventListener('mousedown', _onMouseDown, false) 284 | this.domElement.addEventListener('mouseup', _onMouseUp, false) 285 | 286 | window.addEventListener('keydown', _onKeyDown, false) 287 | window.addEventListener('keyup', _onKeyUp, false) 288 | 289 | function bind(scope, fn) { 290 | return function () { 291 | fn.apply(scope, arguments) 292 | } 293 | } 294 | 295 | this.handleResize() 296 | } 297 | -------------------------------------------------------------------------------- /src/libraries/pointer-lock-controls.ts: -------------------------------------------------------------------------------- 1 | import { Euler, EventDispatcher, Vector3 } from 'three' 2 | 3 | var PointerLockControls = function (camera, domElement) { 4 | if (domElement === undefined) { 5 | console.warn('THREE.PointerLockControls: The second parameter "domElement" is now mandatory.') 6 | domElement = document.body 7 | } 8 | 9 | this.domElement = domElement 10 | this.isLocked = false 11 | 12 | // 13 | // internals 14 | // 15 | 16 | var scope = this 17 | 18 | var changeEvent = { type: 'change' } 19 | var lockEvent = { type: 'lock' } 20 | var unlockEvent = { type: 'unlock' } 21 | 22 | var euler = new Euler(0, 0, 90, 'ZXY') 23 | 24 | var PI_2 = Math.PI / 2 25 | 26 | var vec = new Vector3() 27 | 28 | function onMouseMove(event) { 29 | if (scope.isLocked === false) return 30 | 31 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0 32 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0 33 | 34 | euler.setFromQuaternion(camera.quaternion) 35 | 36 | euler.z -= movementX * 0.002 37 | euler.x -= movementY * 0.002 38 | 39 | euler.x = Math.max(-PI_2, Math.min(PI_2, euler.x)) 40 | 41 | camera.quaternion.setFromEuler(euler) 42 | 43 | scope.dispatchEvent(changeEvent) 44 | } 45 | 46 | function onPointerlockChange() { 47 | if (document.pointerLockElement === scope.domElement) { 48 | scope.dispatchEvent(lockEvent) 49 | 50 | scope.isLocked = true 51 | } else { 52 | scope.dispatchEvent(unlockEvent) 53 | 54 | scope.isLocked = false 55 | } 56 | } 57 | 58 | function onPointerlockError() { 59 | console.error('THREE.PointerLockControls: Unable to use Pointer Lock API') 60 | } 61 | 62 | this.connect = function () { 63 | document.addEventListener('mousemove', onMouseMove, false) 64 | document.addEventListener('pointerlockchange', onPointerlockChange, false) 65 | document.addEventListener('pointerlockerror', onPointerlockError, false) 66 | } 67 | 68 | this.disconnect = function () { 69 | document.removeEventListener('mousemove', onMouseMove, false) 70 | document.removeEventListener('pointerlockchange', onPointerlockChange, false) 71 | document.removeEventListener('pointerlockerror', onPointerlockError, false) 72 | } 73 | 74 | this.dispose = function () { 75 | this.disconnect() 76 | } 77 | 78 | this.getObject = function () { 79 | // retaining this method for backward compatibility 80 | 81 | return camera 82 | } 83 | 84 | this.getDirection = (function () { 85 | var direction = new Vector3(0, 0, -1) 86 | 87 | return function (v) { 88 | return v.copy(direction).applyQuaternion(camera.quaternion) 89 | } 90 | })() 91 | 92 | this.moveForward = function (distance) { 93 | // move forward parallel to the xz-plane 94 | // assumes camera.up is y-up 95 | 96 | vec.setFromMatrixColumn(camera.matrix, 0) 97 | 98 | vec.crossVectors(camera.up, vec) 99 | 100 | camera.position.addScaledVector(vec, distance) 101 | } 102 | 103 | this.moveRight = function (distance) { 104 | vec.setFromMatrixColumn(camera.matrix, 0) 105 | 106 | camera.position.addScaledVector(vec, distance) 107 | } 108 | 109 | this.lock = function () { 110 | this.domElement.requestPointerLock() 111 | } 112 | 113 | this.unlock = function () { 114 | document.exitPointerLock() 115 | } 116 | 117 | this.connect() 118 | } 119 | 120 | PointerLockControls.prototype = Object.create(EventDispatcher.prototype) 121 | PointerLockControls.prototype.constructor = PointerLockControls 122 | 123 | export { PointerLockControls } 124 | -------------------------------------------------------------------------------- /src/libraries/scrollable.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery' 2 | ;(function ($) { 3 | $.dragScroll = function (target, container, cb) { 4 | let clicked = false 5 | let clickY = 0 6 | let clickX = 0 7 | let clickTarget 8 | 9 | $(container).on('mousemove', function (e) { 10 | if (clicked) { 11 | console.log('top: ', $(target).position().top) 12 | console.log('clickY: ', clickY) 13 | console.log('e.clientY: ', e.clientY) 14 | cb(clickTarget - clickY - e.clientY) 15 | } 16 | }) 17 | 18 | $(target).on('mousedown', function (e) { 19 | clicked = true 20 | clickY = e.clientY 21 | clickX = e.clientX 22 | clickTarget = $(target).position().top + $(target).height() / 2 23 | }) 24 | 25 | $('body').on('mouseup', function (e) { 26 | clicked = false 27 | }) 28 | } 29 | })($) 30 | -------------------------------------------------------------------------------- /src/libraries/trackball.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radswiat/minecraft-threejs/76a8362fe137ae701ec7f728051fa89b72c3f014/src/libraries/trackball.js -------------------------------------------------------------------------------- /src/libraries/typedarray.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /* 4 | Copyright (c) 2010, Linden Research, Inc. 5 | Copyright (c) 2014, Joshua Bell 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | $/LicenseInfo$ 25 | */ 26 | 27 | // Original can be found at: 28 | // https://bitbucket.org/lindenlab/llsd 29 | // Modifications by Joshua Bell inexorabletash@gmail.com 30 | // https://github.com/inexorabletash/polyfill 31 | 32 | // ES3/ES5 implementation of the Krhonos Typed Array Specification 33 | // Ref: http://www.khronos.org/registry/typedarray/specs/latest/ 34 | // Date: 2011-02-01 35 | // 36 | // Variations: 37 | // * Allows typed_array.get/set() as alias for subscripts (typed_array[]) 38 | // * Gradually migrating structure from Khronos spec to ES2015 spec 39 | // 40 | // Caveats: 41 | // * Beyond 10000 or so entries, polyfilled array accessors (ta[0], 42 | // etc) become memory-prohibitive, so array creation will fail. Set 43 | // self.TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS=true to disable 44 | // creation of accessors. Your code will need to use the 45 | // non-standard get()/set() instead, and will need to add those to 46 | // native arrays for interop. 47 | 48 | (function(global) { 49 | 'use strict'; 50 | var undefined = (void 0); // Paranoia 51 | 52 | // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to 53 | // create, and consume so much memory, that the browser appears frozen. 54 | var MAX_ARRAY_LENGTH = 1e5; 55 | 56 | // Approximations of internal ECMAScript conversion functions 57 | function Type(v) { 58 | switch(typeof v) { 59 | case 'undefined': return 'undefined'; 60 | case 'boolean': return 'boolean'; 61 | case 'number': return 'number'; 62 | case 'string': return 'string'; 63 | default: return v === null ? 'null' : 'object'; 64 | } 65 | } 66 | 67 | // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: 68 | function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); } 69 | function IsCallable(o) { return typeof o === 'function'; } 70 | function ToObject(v) { 71 | if (v === null || v === undefined) throw TypeError(); 72 | return Object(v); 73 | } 74 | function ToInt32(v) { return v >> 0; } 75 | function ToUint32(v) { return v >>> 0; } 76 | 77 | // Snapshot intrinsics 78 | var LN2 = Math.LN2, 79 | abs = Math.abs, 80 | floor = Math.floor, 81 | log = Math.log, 82 | max = Math.max, 83 | min = Math.min, 84 | pow = Math.pow, 85 | round = Math.round; 86 | 87 | // emulate ES5 getter/setter API using legacy APIs 88 | // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx 89 | // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but 90 | // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) 91 | 92 | (function() { 93 | var orig = Object.defineProperty; 94 | var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}()); 95 | 96 | if (!orig || dom_only) { 97 | Object.defineProperty = function (o, prop, desc) { 98 | // In IE8 try built-in implementation for defining properties on DOM prototypes. 99 | if (orig) 100 | try { return orig(o, prop, desc); } catch (_) {} 101 | if (o !== Object(o)) 102 | throw TypeError('Object.defineProperty called on non-object'); 103 | if (Object.prototype.__defineGetter__ && ('get' in desc)) 104 | Object.prototype.__defineGetter__.call(o, prop, desc.get); 105 | if (Object.prototype.__defineSetter__ && ('set' in desc)) 106 | Object.prototype.__defineSetter__.call(o, prop, desc.set); 107 | if ('value' in desc) 108 | o[prop] = desc.value; 109 | return o; 110 | }; 111 | } 112 | }()); 113 | 114 | // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) 115 | // for index in 0 ... obj.length 116 | function makeArrayAccessors(obj) { 117 | if ('TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS' in global) 118 | return; 119 | 120 | if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill'); 121 | 122 | function makeArrayAccessor(index) { 123 | Object.defineProperty(obj, index, { 124 | 'get': function() { return obj._getter(index); }, 125 | 'set': function(v) { obj._setter(index, v); }, 126 | enumerable: true, 127 | configurable: false 128 | }); 129 | } 130 | 131 | var i; 132 | for (i = 0; i < obj.length; i += 1) { 133 | makeArrayAccessor(i); 134 | } 135 | } 136 | 137 | // Internal conversion functions: 138 | // pack() - take a number (interpreted as Type), output a byte array 139 | // unpack() - take a byte array, output a Type-like number 140 | 141 | function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } 142 | function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } 143 | 144 | function packI8(n) { return [n & 0xff]; } 145 | function unpackI8(bytes) { return as_signed(bytes[0], 8); } 146 | 147 | function packU8(n) { return [n & 0xff]; } 148 | function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } 149 | 150 | function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } 151 | 152 | function packI16(n) { return [n & 0xff, (n >> 8) & 0xff]; } 153 | function unpackI16(bytes) { return as_signed(bytes[1] << 8 | bytes[0], 16); } 154 | 155 | function packU16(n) { return [n & 0xff, (n >> 8) & 0xff]; } 156 | function unpackU16(bytes) { return as_unsigned(bytes[1] << 8 | bytes[0], 16); } 157 | 158 | function packI32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; } 159 | function unpackI32(bytes) { return as_signed(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); } 160 | 161 | function packU32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; } 162 | function unpackU32(bytes) { return as_unsigned(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); } 163 | 164 | function packIEEE754(v, ebits, fbits) { 165 | 166 | var bias = (1 << (ebits - 1)) - 1; 167 | 168 | function roundToEven(n) { 169 | var w = floor(n), f = n - w; 170 | if (f < 0.5) 171 | return w; 172 | if (f > 0.5) 173 | return w + 1; 174 | return w % 2 ? w + 1 : w; 175 | } 176 | 177 | // Compute sign, exponent, fraction 178 | var s, e, f; 179 | if (v !== v) { 180 | // NaN 181 | // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping 182 | e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; 183 | } else if (v === Infinity || v === -Infinity) { 184 | e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; 185 | } else if (v === 0) { 186 | e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; 187 | } else { 188 | s = v < 0; 189 | v = abs(v); 190 | 191 | if (v >= pow(2, 1 - bias)) { 192 | // Normalized 193 | e = min(floor(log(v) / LN2), 1023); 194 | var significand = v / pow(2, e); 195 | if (significand < 1) { 196 | e -= 1; 197 | significand *= 2; 198 | } 199 | if (significand >= 2) { 200 | e += 1; 201 | significand /= 2; 202 | } 203 | var d = pow(2, fbits); 204 | f = roundToEven(significand * d) - d; 205 | e += bias; 206 | if (f / d >= 1) { 207 | e += 1; 208 | f = 0; 209 | } 210 | if (e > 2 * bias) { 211 | // Overflow 212 | e = (1 << ebits) - 1; 213 | f = 0; 214 | } 215 | } else { 216 | // Denormalized 217 | e = 0; 218 | f = roundToEven(v / pow(2, 1 - bias - fbits)); 219 | } 220 | } 221 | 222 | // Pack sign, exponent, fraction 223 | var bits = [], i; 224 | for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } 225 | for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } 226 | bits.push(s ? 1 : 0); 227 | bits.reverse(); 228 | var str = bits.join(''); 229 | 230 | // Bits to bytes 231 | var bytes = []; 232 | while (str.length) { 233 | bytes.unshift(parseInt(str.substring(0, 8), 2)); 234 | str = str.substring(8); 235 | } 236 | return bytes; 237 | } 238 | 239 | function unpackIEEE754(bytes, ebits, fbits) { 240 | // Bytes to bits 241 | var bits = [], i, j, b, str, 242 | bias, s, e, f; 243 | 244 | for (i = 0; i < bytes.length; ++i) { 245 | b = bytes[i]; 246 | for (j = 8; j; j -= 1) { 247 | bits.push(b % 2 ? 1 : 0); b = b >> 1; 248 | } 249 | } 250 | bits.reverse(); 251 | str = bits.join(''); 252 | 253 | // Unpack sign, exponent, fraction 254 | bias = (1 << (ebits - 1)) - 1; 255 | s = parseInt(str.substring(0, 1), 2) ? -1 : 1; 256 | e = parseInt(str.substring(1, 1 + ebits), 2); 257 | f = parseInt(str.substring(1 + ebits), 2); 258 | 259 | // Produce number 260 | if (e === (1 << ebits) - 1) { 261 | return f !== 0 ? NaN : s * Infinity; 262 | } else if (e > 0) { 263 | // Normalized 264 | return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); 265 | } else if (f !== 0) { 266 | // Denormalized 267 | return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); 268 | } else { 269 | return s < 0 ? -0 : 0; 270 | } 271 | } 272 | 273 | function unpackF64(b) { return unpackIEEE754(b, 11, 52); } 274 | function packF64(v) { return packIEEE754(v, 11, 52); } 275 | function unpackF32(b) { return unpackIEEE754(b, 8, 23); } 276 | function packF32(v) { return packIEEE754(v, 8, 23); } 277 | 278 | // 279 | // 3 The ArrayBuffer Type 280 | // 281 | 282 | (function() { 283 | 284 | function ArrayBuffer(length) { 285 | length = ToInt32(length); 286 | if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.'); 287 | Object.defineProperty(this, 'byteLength', {value: length}); 288 | Object.defineProperty(this, '_bytes', {value: Array(length)}); 289 | 290 | for (var i = 0; i < length; i += 1) 291 | this._bytes[i] = 0; 292 | } 293 | 294 | global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer; 295 | 296 | // 297 | // 5 The Typed Array View Types 298 | // 299 | 300 | function $TypedArray$() { 301 | 302 | // %TypedArray% ( length ) 303 | if (!arguments.length || typeof arguments[0] !== 'object') { 304 | return (function(length) { 305 | length = ToInt32(length); 306 | if (length < 0) throw RangeError('length is not a small enough positive integer.'); 307 | Object.defineProperty(this, 'length', {value: length}); 308 | Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT}); 309 | Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)}); 310 | Object.defineProperty(this, 'byteOffset', {value: 0}); 311 | 312 | }).apply(this, arguments); 313 | } 314 | 315 | // %TypedArray% ( typedArray ) 316 | if (arguments.length >= 1 && 317 | Type(arguments[0]) === 'object' && 318 | arguments[0] instanceof $TypedArray$) { 319 | return (function(typedArray){ 320 | if (this.constructor !== typedArray.constructor) throw TypeError(); 321 | 322 | var byteLength = typedArray.length * this.BYTES_PER_ELEMENT; 323 | Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); 324 | Object.defineProperty(this, 'byteLength', {value: byteLength}); 325 | Object.defineProperty(this, 'byteOffset', {value: 0}); 326 | Object.defineProperty(this, 'length', {value: typedArray.length}); 327 | 328 | for (var i = 0; i < this.length; i += 1) 329 | this._setter(i, typedArray._getter(i)); 330 | 331 | }).apply(this, arguments); 332 | } 333 | 334 | // %TypedArray% ( array ) 335 | if (arguments.length >= 1 && 336 | Type(arguments[0]) === 'object' && 337 | !(arguments[0] instanceof $TypedArray$) && 338 | !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { 339 | return (function(array) { 340 | 341 | var byteLength = array.length * this.BYTES_PER_ELEMENT; 342 | Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); 343 | Object.defineProperty(this, 'byteLength', {value: byteLength}); 344 | Object.defineProperty(this, 'byteOffset', {value: 0}); 345 | Object.defineProperty(this, 'length', {value: array.length}); 346 | 347 | for (var i = 0; i < this.length; i += 1) { 348 | var s = array[i]; 349 | this._setter(i, Number(s)); 350 | } 351 | }).apply(this, arguments); 352 | } 353 | 354 | // %TypedArray% ( buffer, byteOffset=0, length=undefined ) 355 | if (arguments.length >= 1 && 356 | Type(arguments[0]) === 'object' && 357 | (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { 358 | return (function(buffer, byteOffset, length) { 359 | 360 | byteOffset = ToUint32(byteOffset); 361 | if (byteOffset > buffer.byteLength) 362 | throw RangeError('byteOffset out of range'); 363 | 364 | // The given byteOffset must be a multiple of the element 365 | // size of the specific type, otherwise an exception is raised. 366 | if (byteOffset % this.BYTES_PER_ELEMENT) 367 | throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.'); 368 | 369 | if (length === undefined) { 370 | var byteLength = buffer.byteLength - byteOffset; 371 | if (byteLength % this.BYTES_PER_ELEMENT) 372 | throw RangeError('length of buffer minus byteOffset not a multiple of the element size'); 373 | length = byteLength / this.BYTES_PER_ELEMENT; 374 | 375 | } else { 376 | length = ToUint32(length); 377 | byteLength = length * this.BYTES_PER_ELEMENT; 378 | } 379 | 380 | if ((byteOffset + byteLength) > buffer.byteLength) 381 | throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); 382 | 383 | Object.defineProperty(this, 'buffer', {value: buffer}); 384 | Object.defineProperty(this, 'byteLength', {value: byteLength}); 385 | Object.defineProperty(this, 'byteOffset', {value: byteOffset}); 386 | Object.defineProperty(this, 'length', {value: length}); 387 | 388 | }).apply(this, arguments); 389 | } 390 | 391 | // %TypedArray% ( all other argument combinations ) 392 | throw TypeError(); 393 | } 394 | 395 | // Properties of the %TypedArray Instrinsic Object 396 | 397 | // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined ) 398 | Object.defineProperty($TypedArray$, 'from', {value: function(iterable) { 399 | return new this(iterable); 400 | }}); 401 | 402 | // %TypedArray%.of ( ...items ) 403 | Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) { 404 | return new this(arguments); 405 | }}); 406 | 407 | // %TypedArray%.prototype 408 | var $TypedArrayPrototype$ = {}; 409 | $TypedArray$.prototype = $TypedArrayPrototype$; 410 | 411 | // WebIDL: getter type (unsigned long index); 412 | Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) { 413 | if (arguments.length < 1) throw SyntaxError('Not enough arguments'); 414 | 415 | index = ToUint32(index); 416 | if (index >= this.length) 417 | return undefined; 418 | 419 | var bytes = [], i, o; 420 | for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; 421 | i < this.BYTES_PER_ELEMENT; 422 | i += 1, o += 1) { 423 | bytes.push(this.buffer._bytes[o]); 424 | } 425 | return this._unpack(bytes); 426 | }}); 427 | 428 | // NONSTANDARD: convenience alias for getter: type get(unsigned long index); 429 | Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter}); 430 | 431 | // WebIDL: setter void (unsigned long index, type value); 432 | Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) { 433 | if (arguments.length < 2) throw SyntaxError('Not enough arguments'); 434 | 435 | index = ToUint32(index); 436 | if (index >= this.length) 437 | return; 438 | 439 | var bytes = this._pack(value), i, o; 440 | for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; 441 | i < this.BYTES_PER_ELEMENT; 442 | i += 1, o += 1) { 443 | this.buffer._bytes[o] = bytes[i]; 444 | } 445 | }}); 446 | 447 | // get %TypedArray%.prototype.buffer 448 | // get %TypedArray%.prototype.byteLength 449 | // get %TypedArray%.prototype.byteOffset 450 | // -- applied directly to the object in the constructor 451 | 452 | // %TypedArray%.prototype.constructor 453 | Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$}); 454 | 455 | // %TypedArray%.prototype.copyWithin (target, start, end = this.length ) 456 | Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) { 457 | var end = arguments[2]; 458 | 459 | var o = ToObject(this); 460 | var lenVal = o.length; 461 | var len = ToUint32(lenVal); 462 | len = max(len, 0); 463 | var relativeTarget = ToInt32(target); 464 | var to; 465 | if (relativeTarget < 0) 466 | to = max(len + relativeTarget, 0); 467 | else 468 | to = min(relativeTarget, len); 469 | var relativeStart = ToInt32(start); 470 | var from; 471 | if (relativeStart < 0) 472 | from = max(len + relativeStart, 0); 473 | else 474 | from = min(relativeStart, len); 475 | var relativeEnd; 476 | if (end === undefined) 477 | relativeEnd = len; 478 | else 479 | relativeEnd = ToInt32(end); 480 | var final; 481 | if (relativeEnd < 0) 482 | final = max(len + relativeEnd, 0); 483 | else 484 | final = min(relativeEnd, len); 485 | var count = min(final - from, len - to); 486 | var direction; 487 | if (from < to && to < from + count) { 488 | direction = -1; 489 | from = from + count - 1; 490 | to = to + count - 1; 491 | } else { 492 | direction = 1; 493 | } 494 | while (count > 0) { 495 | o._setter(to, o._getter(from)); 496 | from = from + direction; 497 | to = to + direction; 498 | count = count - 1; 499 | } 500 | return o; 501 | }}); 502 | 503 | // %TypedArray%.prototype.entries ( ) 504 | // -- defined in es6.js to shim browsers w/ native TypedArrays 505 | 506 | // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined ) 507 | Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) { 508 | if (this === undefined || this === null) throw TypeError(); 509 | var t = Object(this); 510 | var len = ToUint32(t.length); 511 | if (!IsCallable(callbackfn)) throw TypeError(); 512 | var thisArg = arguments[1]; 513 | for (var i = 0; i < len; i++) { 514 | if (!callbackfn.call(thisArg, t._getter(i), i, t)) 515 | return false; 516 | } 517 | return true; 518 | }}); 519 | 520 | // %TypedArray%.prototype.fill (value, start = 0, end = this.length ) 521 | Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) { 522 | var start = arguments[1], 523 | end = arguments[2]; 524 | 525 | var o = ToObject(this); 526 | var lenVal = o.length; 527 | var len = ToUint32(lenVal); 528 | len = max(len, 0); 529 | var relativeStart = ToInt32(start); 530 | var k; 531 | if (relativeStart < 0) 532 | k = max((len + relativeStart), 0); 533 | else 534 | k = min(relativeStart, len); 535 | var relativeEnd; 536 | if (end === undefined) 537 | relativeEnd = len; 538 | else 539 | relativeEnd = ToInt32(end); 540 | var final; 541 | if (relativeEnd < 0) 542 | final = max((len + relativeEnd), 0); 543 | else 544 | final = min(relativeEnd, len); 545 | while (k < final) { 546 | o._setter(k, value); 547 | k += 1; 548 | } 549 | return o; 550 | }}); 551 | 552 | // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined ) 553 | Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) { 554 | if (this === undefined || this === null) throw TypeError(); 555 | var t = Object(this); 556 | var len = ToUint32(t.length); 557 | if (!IsCallable(callbackfn)) throw TypeError(); 558 | var res = []; 559 | var thisp = arguments[1]; 560 | for (var i = 0; i < len; i++) { 561 | var val = t._getter(i); // in case fun mutates this 562 | if (callbackfn.call(thisp, val, i, t)) 563 | res.push(val); 564 | } 565 | return new this.constructor(res); 566 | }}); 567 | 568 | // %TypedArray%.prototype.find (predicate, thisArg = undefined) 569 | Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) { 570 | var o = ToObject(this); 571 | var lenValue = o.length; 572 | var len = ToUint32(lenValue); 573 | if (!IsCallable(predicate)) throw TypeError(); 574 | var t = arguments.length > 1 ? arguments[1] : undefined; 575 | var k = 0; 576 | while (k < len) { 577 | var kValue = o._getter(k); 578 | var testResult = predicate.call(t, kValue, k, o); 579 | if (Boolean(testResult)) 580 | return kValue; 581 | ++k; 582 | } 583 | return undefined; 584 | }}); 585 | 586 | // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined ) 587 | Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) { 588 | var o = ToObject(this); 589 | var lenValue = o.length; 590 | var len = ToUint32(lenValue); 591 | if (!IsCallable(predicate)) throw TypeError(); 592 | var t = arguments.length > 1 ? arguments[1] : undefined; 593 | var k = 0; 594 | while (k < len) { 595 | var kValue = o._getter(k); 596 | var testResult = predicate.call(t, kValue, k, o); 597 | if (Boolean(testResult)) 598 | return k; 599 | ++k; 600 | } 601 | return -1; 602 | }}); 603 | 604 | // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined ) 605 | Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) { 606 | if (this === undefined || this === null) throw TypeError(); 607 | var t = Object(this); 608 | var len = ToUint32(t.length); 609 | if (!IsCallable(callbackfn)) throw TypeError(); 610 | var thisp = arguments[1]; 611 | for (var i = 0; i < len; i++) 612 | callbackfn.call(thisp, t._getter(i), i, t); 613 | }}); 614 | 615 | // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 ) 616 | Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) { 617 | if (this === undefined || this === null) throw TypeError(); 618 | var t = Object(this); 619 | var len = ToUint32(t.length); 620 | if (len === 0) return -1; 621 | var n = 0; 622 | if (arguments.length > 0) { 623 | n = Number(arguments[1]); 624 | if (n !== n) { 625 | n = 0; 626 | } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 627 | n = (n > 0 || -1) * floor(abs(n)); 628 | } 629 | } 630 | if (n >= len) return -1; 631 | var k = n >= 0 ? n : max(len - abs(n), 0); 632 | for (; k < len; k++) { 633 | if (t._getter(k) === searchElement) { 634 | return k; 635 | } 636 | } 637 | return -1; 638 | }}); 639 | 640 | // %TypedArray%.prototype.join ( separator ) 641 | Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) { 642 | if (this === undefined || this === null) throw TypeError(); 643 | var t = Object(this); 644 | var len = ToUint32(t.length); 645 | var tmp = Array(len); 646 | for (var i = 0; i < len; ++i) 647 | tmp[i] = t._getter(i); 648 | return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7 649 | }}); 650 | 651 | // %TypedArray%.prototype.keys ( ) 652 | // -- defined in es6.js to shim browsers w/ native TypedArrays 653 | 654 | // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 ) 655 | Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) { 656 | if (this === undefined || this === null) throw TypeError(); 657 | var t = Object(this); 658 | var len = ToUint32(t.length); 659 | if (len === 0) return -1; 660 | var n = len; 661 | if (arguments.length > 1) { 662 | n = Number(arguments[1]); 663 | if (n !== n) { 664 | n = 0; 665 | } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 666 | n = (n > 0 || -1) * floor(abs(n)); 667 | } 668 | } 669 | var k = n >= 0 ? min(n, len - 1) : len - abs(n); 670 | for (; k >= 0; k--) { 671 | if (t._getter(k) === searchElement) 672 | return k; 673 | } 674 | return -1; 675 | }}); 676 | 677 | // get %TypedArray%.prototype.length 678 | // -- applied directly to the object in the constructor 679 | 680 | // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined ) 681 | Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) { 682 | if (this === undefined || this === null) throw TypeError(); 683 | var t = Object(this); 684 | var len = ToUint32(t.length); 685 | if (!IsCallable(callbackfn)) throw TypeError(); 686 | var res = []; res.length = len; 687 | var thisp = arguments[1]; 688 | for (var i = 0; i < len; i++) 689 | res[i] = callbackfn.call(thisp, t._getter(i), i, t); 690 | return new this.constructor(res); 691 | }}); 692 | 693 | // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] ) 694 | Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) { 695 | if (this === undefined || this === null) throw TypeError(); 696 | var t = Object(this); 697 | var len = ToUint32(t.length); 698 | if (!IsCallable(callbackfn)) throw TypeError(); 699 | // no value to return if no initial value and an empty array 700 | if (len === 0 && arguments.length === 1) throw TypeError(); 701 | var k = 0; 702 | var accumulator; 703 | if (arguments.length >= 2) { 704 | accumulator = arguments[1]; 705 | } else { 706 | accumulator = t._getter(k++); 707 | } 708 | while (k < len) { 709 | accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); 710 | k++; 711 | } 712 | return accumulator; 713 | }}); 714 | 715 | // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] ) 716 | Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) { 717 | if (this === undefined || this === null) throw TypeError(); 718 | var t = Object(this); 719 | var len = ToUint32(t.length); 720 | if (!IsCallable(callbackfn)) throw TypeError(); 721 | // no value to return if no initial value, empty array 722 | if (len === 0 && arguments.length === 1) throw TypeError(); 723 | var k = len - 1; 724 | var accumulator; 725 | if (arguments.length >= 2) { 726 | accumulator = arguments[1]; 727 | } else { 728 | accumulator = t._getter(k--); 729 | } 730 | while (k >= 0) { 731 | accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); 732 | k--; 733 | } 734 | return accumulator; 735 | }}); 736 | 737 | // %TypedArray%.prototype.reverse ( ) 738 | Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() { 739 | if (this === undefined || this === null) throw TypeError(); 740 | var t = Object(this); 741 | var len = ToUint32(t.length); 742 | var half = floor(len / 2); 743 | for (var i = 0, j = len - 1; i < half; ++i, --j) { 744 | var tmp = t._getter(i); 745 | t._setter(i, t._getter(j)); 746 | t._setter(j, tmp); 747 | } 748 | return t; 749 | }}); 750 | 751 | // %TypedArray%.prototype.set(array, offset = 0 ) 752 | // %TypedArray%.prototype.set(typedArray, offset = 0 ) 753 | // WebIDL: void set(TypedArray array, optional unsigned long offset); 754 | // WebIDL: void set(sequence array, optional unsigned long offset); 755 | Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) { 756 | if (arguments.length < 1) throw SyntaxError('Not enough arguments'); 757 | var array, sequence, offset, len, 758 | i, s, d, 759 | byteOffset, byteLength, tmp; 760 | 761 | if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { 762 | // void set(TypedArray array, optional unsigned long offset); 763 | array = arguments[0]; 764 | offset = ToUint32(arguments[1]); 765 | 766 | if (offset + array.length > this.length) { 767 | throw RangeError('Offset plus length of array is out of range'); 768 | } 769 | 770 | byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; 771 | byteLength = array.length * this.BYTES_PER_ELEMENT; 772 | 773 | if (array.buffer === this.buffer) { 774 | tmp = []; 775 | for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { 776 | tmp[i] = array.buffer._bytes[s]; 777 | } 778 | for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { 779 | this.buffer._bytes[d] = tmp[i]; 780 | } 781 | } else { 782 | for (i = 0, s = array.byteOffset, d = byteOffset; 783 | i < byteLength; i += 1, s += 1, d += 1) { 784 | this.buffer._bytes[d] = array.buffer._bytes[s]; 785 | } 786 | } 787 | } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { 788 | // void set(sequence array, optional unsigned long offset); 789 | sequence = arguments[0]; 790 | len = ToUint32(sequence.length); 791 | offset = ToUint32(arguments[1]); 792 | 793 | if (offset + len > this.length) { 794 | throw RangeError('Offset plus length of array is out of range'); 795 | } 796 | 797 | for (i = 0; i < len; i += 1) { 798 | s = sequence[i]; 799 | this._setter(offset + i, Number(s)); 800 | } 801 | } else { 802 | throw TypeError('Unexpected argument type(s)'); 803 | } 804 | }}); 805 | 806 | // %TypedArray%.prototype.slice ( start, end ) 807 | Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) { 808 | var o = ToObject(this); 809 | var lenVal = o.length; 810 | var len = ToUint32(lenVal); 811 | var relativeStart = ToInt32(start); 812 | var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len); 813 | var relativeEnd = (end === undefined) ? len : ToInt32(end); 814 | var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len); 815 | var count = final - k; 816 | var c = o.constructor; 817 | var a = new c(count); 818 | var n = 0; 819 | while (k < final) { 820 | var kValue = o._getter(k); 821 | a._setter(n, kValue); 822 | ++k; 823 | ++n; 824 | } 825 | return a; 826 | }}); 827 | 828 | // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined ) 829 | Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) { 830 | if (this === undefined || this === null) throw TypeError(); 831 | var t = Object(this); 832 | var len = ToUint32(t.length); 833 | if (!IsCallable(callbackfn)) throw TypeError(); 834 | var thisp = arguments[1]; 835 | for (var i = 0; i < len; i++) { 836 | if (callbackfn.call(thisp, t._getter(i), i, t)) { 837 | return true; 838 | } 839 | } 840 | return false; 841 | }}); 842 | 843 | // %TypedArray%.prototype.sort ( comparefn ) 844 | Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) { 845 | if (this === undefined || this === null) throw TypeError(); 846 | var t = Object(this); 847 | var len = ToUint32(t.length); 848 | var tmp = Array(len); 849 | for (var i = 0; i < len; ++i) 850 | tmp[i] = t._getter(i); 851 | if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9 852 | for (i = 0; i < len; ++i) 853 | t._setter(i, tmp[i]); 854 | return t; 855 | }}); 856 | 857 | // %TypedArray%.prototype.subarray(begin = 0, end = this.length ) 858 | // WebIDL: TypedArray subarray(long begin, optional long end); 859 | Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) { 860 | function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } 861 | 862 | start = ToInt32(start); 863 | end = ToInt32(end); 864 | 865 | if (arguments.length < 1) { start = 0; } 866 | if (arguments.length < 2) { end = this.length; } 867 | 868 | if (start < 0) { start = this.length + start; } 869 | if (end < 0) { end = this.length + end; } 870 | 871 | start = clamp(start, 0, this.length); 872 | end = clamp(end, 0, this.length); 873 | 874 | var len = end - start; 875 | if (len < 0) { 876 | len = 0; 877 | } 878 | 879 | return new this.constructor( 880 | this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len); 881 | }}); 882 | 883 | // %TypedArray%.prototype.toLocaleString ( ) 884 | // %TypedArray%.prototype.toString ( ) 885 | // %TypedArray%.prototype.values ( ) 886 | // %TypedArray%.prototype [ @@iterator ] ( ) 887 | // get %TypedArray%.prototype [ @@toStringTag ] 888 | // -- defined in es6.js to shim browsers w/ native TypedArrays 889 | 890 | function makeTypedArray(elementSize, pack, unpack) { 891 | // Each TypedArray type requires a distinct constructor instance with 892 | // identical logic, which this produces. 893 | var TypedArray = function() { 894 | Object.defineProperty(this, 'constructor', {value: TypedArray}); 895 | $TypedArray$.apply(this, arguments); 896 | makeArrayAccessors(this); 897 | }; 898 | if ('__proto__' in TypedArray) { 899 | TypedArray.__proto__ = $TypedArray$; 900 | } else { 901 | TypedArray.from = $TypedArray$.from; 902 | TypedArray.of = $TypedArray$.of; 903 | } 904 | 905 | TypedArray.BYTES_PER_ELEMENT = elementSize; 906 | 907 | var TypedArrayPrototype = function() {}; 908 | TypedArrayPrototype.prototype = $TypedArrayPrototype$; 909 | 910 | TypedArray.prototype = new TypedArrayPrototype(); 911 | 912 | Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize}); 913 | Object.defineProperty(TypedArray.prototype, '_pack', {value: pack}); 914 | Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack}); 915 | 916 | return TypedArray; 917 | } 918 | 919 | var Int8Array = makeTypedArray(1, packI8, unpackI8); 920 | var Uint8Array = makeTypedArray(1, packU8, unpackU8); 921 | var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8); 922 | var Int16Array = makeTypedArray(2, packI16, unpackI16); 923 | var Uint16Array = makeTypedArray(2, packU16, unpackU16); 924 | var Int32Array = makeTypedArray(4, packI32, unpackI32); 925 | var Uint32Array = makeTypedArray(4, packU32, unpackU32); 926 | var Float32Array = makeTypedArray(4, packF32, unpackF32); 927 | var Float64Array = makeTypedArray(8, packF64, unpackF64); 928 | 929 | global.Int8Array = global.Int8Array || Int8Array; 930 | global.Uint8Array = global.Uint8Array || Uint8Array; 931 | global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray; 932 | global.Int16Array = global.Int16Array || Int16Array; 933 | global.Uint16Array = global.Uint16Array || Uint16Array; 934 | global.Int32Array = global.Int32Array || Int32Array; 935 | global.Uint32Array = global.Uint32Array || Uint32Array; 936 | global.Float32Array = global.Float32Array || Float32Array; 937 | global.Float64Array = global.Float64Array || Float64Array; 938 | }()); 939 | 940 | // 941 | // 6 The DataView View Type 942 | // 943 | 944 | (function() { 945 | function r(array, index) { 946 | return IsCallable(array.get) ? array.get(index) : array[index]; 947 | } 948 | 949 | var IS_BIG_ENDIAN = (function() { 950 | var u16array = new Uint16Array([0x1234]), 951 | u8array = new Uint8Array(u16array.buffer); 952 | return r(u8array, 0) === 0x12; 953 | }()); 954 | 955 | // DataView(buffer, byteOffset=0, byteLength=undefined) 956 | // WebIDL: Constructor(ArrayBuffer buffer, 957 | // optional unsigned long byteOffset, 958 | // optional unsigned long byteLength) 959 | function DataView(buffer, byteOffset, byteLength) { 960 | if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError(); 961 | 962 | byteOffset = ToUint32(byteOffset); 963 | if (byteOffset > buffer.byteLength) 964 | throw RangeError('byteOffset out of range'); 965 | 966 | if (byteLength === undefined) 967 | byteLength = buffer.byteLength - byteOffset; 968 | else 969 | byteLength = ToUint32(byteLength); 970 | 971 | if ((byteOffset + byteLength) > buffer.byteLength) 972 | throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); 973 | 974 | Object.defineProperty(this, 'buffer', {value: buffer}); 975 | Object.defineProperty(this, 'byteLength', {value: byteLength}); 976 | Object.defineProperty(this, 'byteOffset', {value: byteOffset}); 977 | }; 978 | 979 | // get DataView.prototype.buffer 980 | // get DataView.prototype.byteLength 981 | // get DataView.prototype.byteOffset 982 | // -- applied directly to instances by the constructor 983 | 984 | function makeGetter(arrayType) { 985 | return function GetViewValue(byteOffset, littleEndian) { 986 | byteOffset = ToUint32(byteOffset); 987 | 988 | if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) 989 | throw RangeError('Array index out of range'); 990 | 991 | byteOffset += this.byteOffset; 992 | 993 | var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), 994 | bytes = []; 995 | for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) 996 | bytes.push(r(uint8Array, i)); 997 | 998 | if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) 999 | bytes.reverse(); 1000 | 1001 | return r(new arrayType(new Uint8Array(bytes).buffer), 0); 1002 | }; 1003 | } 1004 | 1005 | Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)}); 1006 | Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)}); 1007 | Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)}); 1008 | Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)}); 1009 | Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)}); 1010 | Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)}); 1011 | Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)}); 1012 | Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)}); 1013 | 1014 | function makeSetter(arrayType) { 1015 | return function SetViewValue(byteOffset, value, littleEndian) { 1016 | byteOffset = ToUint32(byteOffset); 1017 | if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) 1018 | throw RangeError('Array index out of range'); 1019 | 1020 | // Get bytes 1021 | var typeArray = new arrayType([value]), 1022 | byteArray = new Uint8Array(typeArray.buffer), 1023 | bytes = [], i, byteView; 1024 | 1025 | for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) 1026 | bytes.push(r(byteArray, i)); 1027 | 1028 | // Flip if necessary 1029 | if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) 1030 | bytes.reverse(); 1031 | 1032 | // Write them 1033 | byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); 1034 | byteView.set(bytes); 1035 | }; 1036 | } 1037 | 1038 | Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)}); 1039 | Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)}); 1040 | Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)}); 1041 | Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)}); 1042 | Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)}); 1043 | Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)}); 1044 | Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)}); 1045 | Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)}); 1046 | 1047 | global.DataView = global.DataView || DataView; 1048 | 1049 | }()); 1050 | 1051 | }(self)); 1052 | -------------------------------------------------------------------------------- /tools/webpack/neutrino.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import react from './presets/react' 4 | import typescript from './presets/typescript' 5 | import babel from './presets/babel' 6 | import staticAssets from './presets/staticAssets' 7 | import webWorker from './presets/webWorker' 8 | 9 | export default (): unknown => ({ 10 | // options, 11 | options: { 12 | root: process.cwd(), 13 | source: path.resolve(process.cwd(), 'src/client'), 14 | output: path.resolve(process.cwd(), 'build'), 15 | }, 16 | use: [ 17 | react({ indexHtml: path.resolve(process.cwd(), 'src/client/index.html') }), 18 | typescript(), 19 | babel(), 20 | webWorker(), 21 | staticAssets({ 22 | patterns: [ 23 | { from: path.resolve(process.cwd(), 'src/client/game/resources'), to: './resources' }, 24 | { from: path.resolve(process.cwd(), 'src/client/web/static'), to: './static' }, 25 | ], 26 | }), 27 | ], 28 | }) 29 | -------------------------------------------------------------------------------- /tools/webpack/presets/babel/babel.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import * as Config from 'webpack-chain' 4 | 5 | export default function babelPreset(): unknown { 6 | return ({ config }: { config: Config }): void => { 7 | // require babel config 8 | const babelConfig = require(path.resolve(process.cwd(), 'babel.config.js')) 9 | const { presets, plugins } = babelConfig({ cache: () => {} }) 10 | // extend neutrino babel config 11 | config.module.rule('compile').use('babel').tap((options) => { 12 | return { 13 | ...options, 14 | presets, 15 | plugins, 16 | } 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tools/webpack/presets/babel/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './babel' 2 | -------------------------------------------------------------------------------- /tools/webpack/presets/react/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './react' 2 | -------------------------------------------------------------------------------- /tools/webpack/presets/react/react.ts: -------------------------------------------------------------------------------- 1 | import Config from 'webpack-chain' 2 | 3 | // @ts-ignore 4 | import react from '@neutrinojs/react' 5 | 6 | import { ReactOpts } from './react.types' 7 | 8 | export default ({ indexHtml, htmlTemplateParams = {} }: ReactOpts): Config => { 9 | return react({ 10 | html: { 11 | template: indexHtml, 12 | templateParameters: htmlTemplateParams, 13 | }, 14 | style: { 15 | test: /\.(css|scss)$/, 16 | loaders: [ 17 | { loader: require.resolve('sass-loader'), useId: 'sass' }, 18 | ], 19 | }, 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /tools/webpack/presets/react/react.types.ts: -------------------------------------------------------------------------------- 1 | export interface ReactOpts { 2 | indexHtml: string; 3 | htmlTemplateParams?: { [key: string]: string}; 4 | } 5 | -------------------------------------------------------------------------------- /tools/webpack/presets/staticAssets/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './staticAssets' 2 | -------------------------------------------------------------------------------- /tools/webpack/presets/staticAssets/staticAssets.d.ts: -------------------------------------------------------------------------------- 1 | interface Pattern { 2 | from: string; 3 | to: string; 4 | } 5 | 6 | export interface StaticAssetsOpts { 7 | patterns: Pattern[]; 8 | } 9 | -------------------------------------------------------------------------------- /tools/webpack/presets/staticAssets/staticAssets.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import Config from 'webpack-chain' 4 | 5 | // @ts-ignore 6 | // No types available for @neutrino/* 7 | import copy from '@neutrinojs/copy' 8 | 9 | import { StaticAssetsOpts } from './staticAssets.d' 10 | 11 | /** 12 | * Neutrino assets preset 13 | * - add support for app static folder with microsite prefix for ALB 14 | * eg https://shop.dx-int1-blue.internal.vodafoneaws.co.uk/${prefix}/static/images..... 15 | * - add support for custom assets throug `patterns` ( see weback-copy-plugin ) 16 | * @param opts 17 | * @param opts.patterns 18 | */ 19 | export default ({ patterns }: StaticAssetsOpts): Config => { 20 | return copy({ 21 | patterns: [...patterns], 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /tools/webpack/presets/typescript/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './typescript' 2 | -------------------------------------------------------------------------------- /tools/webpack/presets/typescript/typescript.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import Config from 'webpack-chain' 4 | 5 | /** 6 | * Typescript preset 7 | */ 8 | export default function typescriptPreset(): unknown { 9 | return ({ config }: { config: Config }): void => { 10 | // Add typescript and eslint validation during webpack run 11 | // config.plugin('fork-ts-checker').use(require('fork-ts-checker-webpack-plugin'), [ 12 | // { 13 | // typescript: { 14 | // enabled: true, 15 | // configFile: path.resolve(process.cwd(), 'tsconfig.json'), 16 | // }, 17 | // }, 18 | // ]) 19 | 20 | // Add webpack TS extensions support 21 | const ext = config.resolve.extensions 22 | ext.prepend('.ts') 23 | ext.prepend('.tsx') 24 | 25 | // extend neutrino compile rule ( babel rule ) to include TS extensions 26 | config.module.rule('compile').test(/\.(js|jsx|ts|tsx)$/) 27 | 28 | // extend neutrino babel config 29 | // config.module.rule('compile').use('babel').tap((options) => { 30 | // return { 31 | // ...options, 32 | // presets: babelPresets, 33 | // plugins: babelPlugins({ conditionalCompileFlags }), 34 | // } 35 | // }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tools/webpack/presets/webWorker/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './webWorker' 2 | -------------------------------------------------------------------------------- /tools/webpack/presets/webWorker/webWorker.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import Config from 'webpack-chain' 4 | 5 | export default (): unknown => { 6 | return ({ config }: { config: Config }): void => { 7 | config.module 8 | .rule('compile-worker') 9 | .test(/\.worker.ts$/) 10 | .use('worker-loader') 11 | .loader(require.resolve('worker-loader')) 12 | .loader(require.resolve('babel-loader')) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "outDir": "./=build/", 5 | "jsx": "preserve", 6 | "target": "ESNext", 7 | "declaration": true, 8 | "noImplicitAny": true, 9 | "moduleResolution": "node", 10 | "allowSyntheticDefaultImports":true, 11 | "paths": { 12 | "@web/*":["src/client/web/*"], 13 | "@game/*":["src/client/game/*"], 14 | "@shared/*":["src/client/shared/*"], 15 | "@libraries/*":["src/libraries/*"] 16 | }, 17 | "lib": ["webworker", "es6", "dom", "scripthost"], 18 | "skipLibCheck": true, 19 | "strict": false, 20 | "noEmit": true, 21 | "esModuleInterop": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const neutrino = require('neutrino') 3 | module.exports = neutrino().webpack() 4 | --------------------------------------------------------------------------------