├── .gitignore ├── .prettierignore ├── static ├── favicon.ico ├── textures │ └── square.png └── styles │ └── main.css ├── .prettierrc ├── webpack.config.js ├── src ├── modal.js ├── webgl.js └── index.js ├── package.json ├── README.md └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | public/* 2 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aakatev/three-js-webpack/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /static/textures/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aakatev/three-js-webpack/HEAD/static/textures/square.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/index.js', 6 | output: { 7 | path: path.resolve(__dirname, 'public'), 8 | filename: 'bundle.js', 9 | }, 10 | performance: { 11 | maxEntrypointSize: 1024000, 12 | maxAssetSize: 1024000 13 | }, 14 | devServer: { 15 | publicPath: '/public/', 16 | compress: true, 17 | port: 9000, 18 | hot: true, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/modal.js: -------------------------------------------------------------------------------- 1 | var modal = document.getElementById('info-modal') 2 | var btn = document.getElementById('open-modal-btn') 3 | var span = document.getElementsByClassName('close-modal-btn')[0] 4 | 5 | btn.onclick = function () { 6 | modal.style.display = 'block' 7 | } 8 | 9 | span.onclick = function () { 10 | modal.style.display = 'none' 11 | } 12 | 13 | window.onclick = function (event) { 14 | if (event.target == modal) { 15 | modal.style.display = 'none' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-npm", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack-dev-server --mode development", 9 | "format": "prettier --write '**/*.js'" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "three": "^0.115.0" 16 | }, 17 | "devDependencies": { 18 | "prettier": "^2.0.4", 19 | "webpack": "^4.42.1", 20 | "webpack-cli": "^3.3.11", 21 | "webpack-dev-server": "^3.11.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # three-js-webpack 2 | 3 | Starter project for Three.JS. Configured with Webpack 4 as a bundler. 4 | 5 | Great and easy way to bootstrap your Three.JS project. 6 | 7 | ## Development 8 | 9 | Clone the project and install dependencies: 10 | 11 | ```bash 12 | git clone https://github.com/aakatev/three-js-webpack.git 13 | npm i 14 | ``` 15 | 16 | Start webpack development server: 17 | 18 | ```bash 19 | npm run start 20 | ``` 21 | 22 | Webpack configuration is located in [`webpack.config.js`](webpack.config.js). 23 | 24 | ## Deployment on GitHub Pages 25 | 26 | **Works with any other static website hosting too.** 27 | 28 | Bundle your code, and push it in your repo: 29 | 30 | ```bash 31 | npm run build 32 | git add 33 | git commit -m"Deploying on GitHub Pages" 34 | git push 35 | ``` 36 | 37 | ## Extra 38 | 39 | The code can be formated with prettier: 40 | 41 | ```bash 42 | npm run format 43 | ``` -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Three.JS-Webpack Boilerplate 9 | 10 | 11 | 12 | 13 | menu 14 | 15 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/webgl.js: -------------------------------------------------------------------------------- 1 | var WEBGL = { 2 | isWebGLAvailable: function () { 3 | try { 4 | var canvas = document.createElement('canvas') 5 | return !!( 6 | window.WebGLRenderingContext && 7 | (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')) 8 | ) 9 | } catch (e) { 10 | return false 11 | } 12 | }, 13 | 14 | isWebGL2Available: function () { 15 | try { 16 | var canvas = document.createElement('canvas') 17 | return !!(window.WebGL2RenderingContext && canvas.getContext('webgl2')) 18 | } catch (e) { 19 | return false 20 | } 21 | }, 22 | 23 | getWebGLErrorMessage: function () { 24 | return this.getErrorMessage(1) 25 | }, 26 | 27 | getWebGL2ErrorMessage: function () { 28 | return this.getErrorMessage(2) 29 | }, 30 | 31 | getErrorMessage: function (version) { 32 | var names = { 33 | 1: 'WebGL', 34 | 2: 'WebGL 2', 35 | } 36 | 37 | var contexts = { 38 | 1: window.WebGLRenderingContext, 39 | 2: window.WebGL2RenderingContext, 40 | } 41 | 42 | var message = 43 | 'Your $0 does not seem to support $1' 44 | 45 | var element = document.createElement('div') 46 | element.id = 'webgl-error-message' 47 | 48 | if (contexts[version]) { 49 | message = message.replace('$0', 'graphics card') 50 | } else { 51 | message = message.replace('$0', 'browser') 52 | } 53 | 54 | message = message.replace('$1', names[version]) 55 | 56 | element.innerHTML = message 57 | 58 | return element 59 | }, 60 | } 61 | 62 | module.exports = { WEBGL } 63 | -------------------------------------------------------------------------------- /static/styles/main.css: -------------------------------------------------------------------------------- 1 | @import "https://fonts.googleapis.com/icon?family=Material+Icons"; 2 | 3 | body { 4 | font-family: monospace; 5 | margin: 0; 6 | font-size: 1.3rem; 7 | font-weight: normal; 8 | } 9 | 10 | canvas { 11 | display: block; 12 | } 13 | 14 | code { 15 | font-family: Consolas,"courier new"; 16 | color: crimson; 17 | background-color: #f1f1f1; 18 | padding: 2px; 19 | } 20 | 21 | p { 22 | overflow: scroll; 23 | } 24 | 25 | .float { 26 | position:fixed; 27 | width:60px; 28 | height:60px; 29 | bottom:40px; 30 | right:40px; 31 | background-color:#0C9; 32 | color:#FFF; 33 | border-radius:50px; 34 | text-align:center; 35 | box-shadow: 2px 2px 3px #999; 36 | } 37 | 38 | .float:hover { 39 | cursor: pointer; 40 | } 41 | 42 | .icon-float{ 43 | margin-top:18px; 44 | } 45 | 46 | .modal { 47 | display: none; /* Hidden by default */ 48 | position: fixed; /* Stay in place */ 49 | z-index: 1; /* Sit on top */ 50 | left: 0; 51 | top: 0; 52 | width: 100%; /* Full width */ 53 | height: 100%; /* Full height */ 54 | overflow: auto; /* Enable scroll if needed */ 55 | background-color: rgb(0,0,0); /* Fallback color */ 56 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 57 | } 58 | 59 | /* Modal Content/Box */ 60 | .modal-content { 61 | background-color: #fefefe; 62 | margin: 15% auto; /* 15% from the top and centered */ 63 | padding: 20px; 64 | border: 1px solid #888; 65 | width: 80%; /* Could be more or less, depending on screen size */ 66 | } 67 | 68 | .close-modal-btn { 69 | color: #aaa; 70 | float: right; 71 | font-size: 28px; 72 | font-weight: bold; 73 | } 74 | 75 | .close-modal-btn:hover, 76 | .close-modal-btn:focus { 77 | color: black; 78 | text-decoration: none; 79 | cursor: pointer; 80 | } 81 | 82 | #webgl-error-message { 83 | text-align: center; 84 | background: #fff; 85 | color: #000; 86 | padding: 1.5em; 87 | width: 400px; 88 | margin: 5em auto 0; 89 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { WEBGL } from './webgl' 3 | import './modal' 4 | 5 | if (WEBGL.isWebGLAvailable()) { 6 | var camera, scene, renderer 7 | var plane 8 | var mouse, 9 | raycaster, 10 | isShiftDown = false 11 | 12 | var rollOverMesh, rollOverMaterial 13 | var cubeGeo, cubeMaterial 14 | 15 | var objects = [] 16 | 17 | init() 18 | render() 19 | 20 | function init() { 21 | camera = new THREE.PerspectiveCamera( 22 | 45, 23 | window.innerWidth / window.innerHeight, 24 | 1, 25 | 10000 26 | ) 27 | camera.position.set(500, 800, 1300) 28 | camera.lookAt(0, 0, 0) 29 | 30 | scene = new THREE.Scene() 31 | scene.background = new THREE.Color(0xf0f0f0) 32 | 33 | var rollOverGeo = new THREE.BoxBufferGeometry(50, 50, 50) 34 | rollOverMaterial = new THREE.MeshBasicMaterial({ 35 | color: 0xff0000, 36 | opacity: 0.5, 37 | transparent: true, 38 | }) 39 | rollOverMesh = new THREE.Mesh(rollOverGeo, rollOverMaterial) 40 | scene.add(rollOverMesh) 41 | 42 | cubeGeo = new THREE.BoxBufferGeometry(50, 50, 50) 43 | cubeMaterial = new THREE.MeshLambertMaterial({ 44 | color: 0xfeb74c, 45 | map: new THREE.TextureLoader().load('static/textures/square.png'), 46 | }) 47 | 48 | var gridHelper = new THREE.GridHelper(1000, 20) 49 | scene.add(gridHelper) 50 | 51 | raycaster = new THREE.Raycaster() 52 | mouse = new THREE.Vector2() 53 | 54 | var geometry = new THREE.PlaneBufferGeometry(1000, 1000) 55 | geometry.rotateX(-Math.PI / 2) 56 | 57 | plane = new THREE.Mesh( 58 | geometry, 59 | new THREE.MeshBasicMaterial({ visible: false }) 60 | ) 61 | scene.add(plane) 62 | 63 | objects.push(plane) 64 | 65 | var ambientLight = new THREE.AmbientLight(0x606060) 66 | scene.add(ambientLight) 67 | 68 | var directionalLight = new THREE.DirectionalLight(0xffffff) 69 | directionalLight.position.set(1, 0.75, 0.5).normalize() 70 | scene.add(directionalLight) 71 | 72 | renderer = new THREE.WebGLRenderer({ antialias: true }) 73 | renderer.setPixelRatio(window.devicePixelRatio) 74 | renderer.setSize(window.innerWidth, window.innerHeight) 75 | document.body.appendChild(renderer.domElement) 76 | 77 | document.addEventListener('mousemove', onDocumentMouseMove, false) 78 | document.addEventListener('mousedown', onDocumentMouseDown, false) 79 | document.addEventListener('keydown', onDocumentKeyDown, false) 80 | document.addEventListener('keyup', onDocumentKeyUp, false) 81 | window.addEventListener('resize', onWindowResize, false) 82 | } 83 | 84 | function onWindowResize() { 85 | camera.aspect = window.innerWidth / window.innerHeight 86 | camera.updateProjectionMatrix() 87 | 88 | renderer.setSize(window.innerWidth, window.innerHeight) 89 | } 90 | 91 | function onDocumentMouseMove(event) { 92 | event.preventDefault() 93 | 94 | mouse.set( 95 | (event.clientX / window.innerWidth) * 2 - 1, 96 | -(event.clientY / window.innerHeight) * 2 + 1 97 | ) 98 | 99 | raycaster.setFromCamera(mouse, camera) 100 | 101 | var intersects = raycaster.intersectObjects(objects) 102 | 103 | if (intersects.length > 0) { 104 | var intersect = intersects[0] 105 | 106 | rollOverMesh.position.copy(intersect.point).add(intersect.face.normal) 107 | rollOverMesh.position 108 | .divideScalar(50) 109 | .floor() 110 | .multiplyScalar(50) 111 | .addScalar(25) 112 | } 113 | 114 | render() 115 | } 116 | 117 | function onDocumentMouseDown(event) { 118 | event.preventDefault() 119 | 120 | mouse.set( 121 | (event.clientX / window.innerWidth) * 2 - 1, 122 | -(event.clientY / window.innerHeight) * 2 + 1 123 | ) 124 | 125 | raycaster.setFromCamera(mouse, camera) 126 | 127 | var intersects = raycaster.intersectObjects(objects) 128 | 129 | if (intersects.length > 0) { 130 | var intersect = intersects[0] 131 | 132 | if (isShiftDown) { 133 | if (intersect.object !== plane) { 134 | scene.remove(intersect.object) 135 | 136 | objects.splice(objects.indexOf(intersect.object), 1) 137 | } 138 | 139 | } else { 140 | var voxel = new THREE.Mesh(cubeGeo, cubeMaterial) 141 | voxel.position.copy(intersect.point).add(intersect.face.normal) 142 | voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25) 143 | scene.add(voxel) 144 | 145 | objects.push(voxel) 146 | } 147 | 148 | render() 149 | } 150 | } 151 | 152 | function onDocumentKeyDown(event) { 153 | switch (event.keyCode) { 154 | case 16: 155 | isShiftDown = true 156 | break 157 | } 158 | } 159 | 160 | function onDocumentKeyUp(event) { 161 | switch (event.keyCode) { 162 | case 16: 163 | isShiftDown = false 164 | break 165 | } 166 | } 167 | 168 | function render() { 169 | renderer.render(scene, camera) 170 | } 171 | } else { 172 | var warning = WEBGL.getWebGLErrorMessage() 173 | document.body.appendChild(warning) 174 | } 175 | --------------------------------------------------------------------------------