[
  {
    "path": ".gitignore",
    "content": "node_modules\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Anderson Mancini\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "bundler/webpack.common.js",
    "content": "const CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst MiniCSSExtractPlugin = require('mini-css-extract-plugin')\nconst path = require('path')\n\nmodule.exports = {\n    entry: path.resolve(__dirname, '../src/script.js'),\n    output:\n    {\n        hashFunction: 'xxhash64',\n        filename: 'bundle.[contenthash].js',\n        path: path.resolve(__dirname, '../dist')\n    },\n    devtool: 'source-map',\n    plugins:\n    [\n        new CopyWebpackPlugin({\n            patterns: [\n                { from: path.resolve(__dirname, '../static') }\n            ]\n        }),\n        new HtmlWebpackPlugin({\n            template: path.resolve(__dirname, '../src/index.html'),\n            minify: true\n        }),\n        new MiniCSSExtractPlugin()\n    ],\n    module:\n    {\n        rules:\n        [\n            // HTML\n            {\n                test: /\\.(html)$/,\n                use:\n                [\n                    'html-loader'\n                ]\n            },\n\n            // JS\n            {\n                test: /\\.js$/,\n                exclude: /node_modules/,\n                use:\n                [\n                    'babel-loader'\n                ]\n            },\n\n            // CSS\n            {\n                test: /\\.css$/,\n                use:\n                [\n                    MiniCSSExtractPlugin.loader,\n                    'css-loader'\n                ]\n            },\n\n            // Images\n            {\n                test: /\\.(jpg|png|gif|svg)$/,\n                type: 'asset/resource',\n                generator:\n                {\n                    filename: 'assets/images/[hash][ext]'\n                }\n            },\n\n            // Fonts\n            {\n                test: /\\.(ttf|eot|woff|woff2)$/,\n                type: 'asset/resource',\n                generator:\n                {\n                    filename: 'assets/fonts/[hash][ext]'\n                }\n            },\n\n            // Shaders\n            {\n                test: /\\.(glsl|vs|fs|vert|frag)$/,\n                type: 'asset/source',\n                generator:\n                {\n                    filename: 'assets/images/[hash][ext]'\n                }\n                }\n        ]\n    }\n}\n"
  },
  {
    "path": "bundler/webpack.dev.js",
    "content": "const path = require('path')\nconst { merge } = require('webpack-merge')\nconst commonConfiguration = require('./webpack.common.js')\nconst ip = require('internal-ip')\nconst portFinderSync = require('portfinder-sync')\n\nconst infoColor = (_message) =>\n{\n    return `\\u001b[1m\\u001b[34m${_message}\\u001b[39m\\u001b[22m`\n}\n\nmodule.exports = merge(\n    commonConfiguration,\n    {\n        stats: 'errors-warnings',\n        mode: 'development',\n        devServer:\n        {\n            host: 'local-ip',\n            port: portFinderSync.getPort(8080),\n            open: true,\n            https: false,\n            allowedHosts: 'all',\n            hot: false,\n            watchFiles: ['src/**', 'static/**'],\n            static:\n            {\n                watch: true,\n                directory: path.join(__dirname, '../static')\n            },\n            client:\n            {\n                logging: 'none',\n                overlay: true,\n                progress: false\n            },\n            onAfterSetupMiddleware: function(devServer)\n            {\n                const port = devServer.options.port\n                const https = devServer.options.https ? 's' : ''\n                const localIp = ip.v4.sync()\n                const domain1 = `http${https}://${localIp}:${port}`\n                const domain2 = `http${https}://localhost:${port}`\n                \n                console.log(`Project running at:\\n  - ${infoColor(domain1)}\\n  - ${infoColor(domain2)}`)\n            }\n        }\n    }\n)\n"
  },
  {
    "path": "bundler/webpack.prod.js",
    "content": "const { merge } = require('webpack-merge')\nconst commonConfiguration = require('./webpack.common.js')\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin')\n\nmodule.exports = merge(\n    commonConfiguration,\n    {\n        mode: 'production',\n        devtool: false,\n        plugins:\n        [\n            new CleanWebpackPlugin()\n        ]\n    }\n)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"repository\": \"#\",\n  \"license\": \"UNLICENSED\",\n  \"author\": \"Anderson Mancini <andersonmancini30@gmail.com> (https://twitter.com/Andersonmancini)\",\n  \"scripts\": {\n    \"build\": \"webpack --config ./bundler/webpack.prod.js\",\n    \"dev\": \"webpack serve --config ./bundler/webpack.dev.js\",\n    \"deploy\": \"vercel --prod\"\n  },\n  \"dependencies\": {\n    \"@babel/core\": \"^7.15.8\",\n    \"@babel/preset-env\": \"^7.15.8\",\n    \"babel-loader\": \"^8.2.3\",\n    \"clean-webpack-plugin\": \"^4.0.0\",\n    \"copy-webpack-plugin\": \"^9.0.1\",\n    \"css-loader\": \"^6.5.0\",\n    \"file-loader\": \"^6.2.0\",\n    \"html-loader\": \"^3.0.0\",\n    \"html-webpack-plugin\": \"^5.5.0\",\n    \"lil-gui\": \"^0.11.0\",\n    \"mini-css-extract-plugin\": \"^2.4.3\",\n    \"portfinder-sync\": \"0.0.2\",\n    \"raw-loader\": \"^4.0.2\",\n    \"style-loader\": \"^3.3.1\",\n    \"three\": \"^0.134.0\",\n    \"webpack\": \"^5.60.0\",\n    \"webpack-cli\": \"^4.9.1\",\n    \"webpack-dev-server\": \"^4.4.0\",\n    \"webpack-merge\": \"^5.8.0\"\n  }\n}\n"
  },
  {
    "path": "readme.md",
    "content": "# Ektogamat ThreeJS Boilerplate\n<h4>by Anderson Mancini</h4>\n\n<p align=\"center\">\n    <img src=\"./static/cover.jpg\" width=\"100%\"/>\n</p>\n\n# Introduction\nThis is a three.js starter project with only 120 lines.\n\nBut why create another threejs boilerplate when there are already so many out there?\n\nWhen I started, I had a hard time finding something simple and written in VanillaJS that would allow, with just a few lines of code, to import a model and have something functional on the screen. Also, most of the examples that exist in the threejs documentation need to be in this framework for them to work just by copying and pasting the code.\n\nSo I decided to share this starter project that tries to make things a little easier. This uses a very basic setup of Node.js to make it easier to install project dependencies and also webpack to make development easier.\n\nI think this would be very useful for web developers, who are trying to get started with threejs. In the source file, which is all documented, you can understand the basic structure of a project in threejs.\n\nResources: [Threejs](https://threejs.org/), [WebGL](https://github.com/KhronosGroup/WebGL), [webpack](https://webpack.js.org/) , [Babel](https://babeljs.io/ ), [ESLint](https://eslint.org/)\n\n# Show, don't tell \nHere you can see a video on how to use this\n<a href=\"https://www.youtube.com/embed/qM6Ih_cC6Gc\" target=\"_blank\"><img src=\"./static/youtube.png\" width=\"100%\"/></a>\n\n# Getting Started\nDownload and install Node.js on your computer (https://nodejs.org/en/download/).\n\nThen, open VSCODE, drag the project folder to it. Open VSCODE terminal and install dependencies (you need to do this only in the first time)\n```\nnpm install\n```\n\nRun this command in your terminal to open a local server at localhost:8080\n```\nnpm run dev\n```\n\n# Debug interface\n<img src=\"./static/debug.jpg\" width=\"100%\"/>\nYou can enable a debug interface by getting the contents of \"debug.js\" file and place it in the end of the main file. This will give you some interface to change things like colors and light position, which can be very useful when you change the model to something else.\n\n# Some projects developed using this\n- https://neotix.com.br/vitrine-virtual/\n- https://rava-cycle-neotix.glitch.me/\n- https://vitamine-se.glitch.me/\n- https://neotix.com.br/promo_havaianas/\n- https://realestate-neotix.vercel.app/\n- https://windland-neotix.vercel.app/\n- https://dna-chain.vercel.app/\n- http://houses-neotix.vercel.app/\n- https://fgr-implantacao.vercel.app/\n- https://metaverse-event.vercel.app/\n\n\n# Notes\nWould be really appreciated if you are willing to give me a star here on GitHub 🎉 or buy me a coffee ☕ https://www.buymeacoffee.com/andersonmancini. The money will be used to produce more content about threejs or to buy new courses.\n\nI plan to add more functionality to this over time, like the ability to use advanced controls, particle systems, etc. If you have any questions or somewhere I can do better, welcome to send an <a href=\"mailto:andersonmancini30@gmail.com\">e-mail</a> to me 🙏"
  },
  {
    "path": "src/debug.js",
    "content": "import { GUI } from 'three/examples/jsm/libs/dat.gui.module.js'\nconst gui = new GUI()\n\n// create parameters for GUI\nvar params = {color: sunLight.color.getHex(), color2: ambient.color.getHex(), color3: scene.background.getHex()}\n\n// create a function to be called by GUI\nconst update = function () {\n\tvar colorObj = new THREE.Color( params.color )\n\tvar colorObj2 = new THREE.Color( params.color2 )\n\tvar colorObj3 = new THREE.Color( params.color3 )\n\tsunLight.color.set(colorObj)\n\tambient.color.set(colorObj2)\n\tscene.background.set(colorObj3)\n}\n\n//////////////////////////////////////////////////\n//// GUI CONFIG\ngui.add(sunLight, 'intensity').min(0).max(10).step(0.0001).name('Dir intensity')\ngui.add(sunLight.position, 'x').min(-100).max(100).step(0.00001).name('Dir X pos')\ngui.add(sunLight.position, 'y').min(0).max(100).step(0.00001).name('Dir Y pos')\ngui.add(sunLight.position, 'z').min(-100).max(100).step(0.00001).name('Dir Z pos')\ngui.addColor(params,'color').name('Dir color').onChange(update)\ngui.addColor(params,'color2').name('Amb color').onChange(update)\ngui.add(ambient, 'intensity').min(0).max(10).step(0.001).name('Amb intensity')\ngui.addColor(params,'color3').name('BG color').onChange(update)\n\n//////////////////////////////////////////////////\n//// ON MOUSE MOVE TO GET CAMERA POSITION\ndocument.addEventListener('mousemove', (event) => {\n    event.preventDefault()\n\n    console.log(camera.position)\n\n}, false)"
  },
  {
    "path": "src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>Ektogamat Threejs Starter Boilerplate</title>\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0\">\n        <meta name=\"description\" content=\"A threejs example using threejs boilerplate - by Anderson Mancini\">\n\n        <meta property=\"og:url\" content=\"https://threejs-andy-boilerplate.vercel.app/\">\n        <meta property=\"og:type\" content=\"website\">\n        <meta property=\"og:title\" content=\"EKTOGAMAT THREEJS BOILERPLATE\">\n        <meta property=\"og:description\" content=\"A threejs example using threejs boilerplate - by Anderson Mancini\">\n        <meta property=\"og:image\" content=\"../static/cover.jpg\">\n\n        <meta name=\"twitter:card\" content=\"summary_large_image\">\n        <meta property=\"twitter:domain\" content=\"https://threejs-andy-boilerplate.vercel.app/\">\n        <meta property=\"twitter:url\" content=\"https://threejs-andy-boilerplate.vercel.app/\">\n        <meta name=\"twitter:title\" content=\"EKTOGAMAT THREEJS BOILERPLATE\">\n        <meta name=\"twitter:description\" content=\"A threejs example using threejs boilerplate - by Anderson Mancini\">\n        <meta name=\"twitter:image\" content=\"../static/cover.jpg\">\n      \n        <meta name=\"keywords\" content=\"ThreeJS, HTML, CSS, JavaScript\">\n        <meta name=\"author\" content=\"Anderson Mancini\">\n        <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"../static/favicon/apple-touch-icon.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"../static/favicon/favicon-32x32.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"../static/favicon/favicon-16x16.png\">\n        <link rel=\"manifest\" href=\"../static/favicon/site.webmanifest\">\n        <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n        <meta name=\"theme-color\" content=\"#ffffff\">\n    </head>\n    <body>\n\n    </body>\n</html>"
  },
  {
    "path": "src/main.css",
    "content": "body, html {\n\tmargin: 0;\n\tpadding: 0;\n\toverscroll-behavior: none;\n\toverflow: hidden;\n}"
  },
  {
    "path": "src/script.js",
    "content": "/////////////////////////////////////////////////////////////////////////\n///// IMPORT\nimport './main.css'\nimport * as THREE from 'three'\nimport { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'\n\n/////////////////////////////////////////////////////////////////////////\n//// DRACO LOADER TO LOAD DRACO COMPRESSED MODELS FROM BLENDER\nconst dracoLoader = new DRACOLoader()\nconst loader = new GLTFLoader()\ndracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')\ndracoLoader.setDecoderConfig({ type: 'js' })\nloader.setDRACOLoader(dracoLoader)\n\n/////////////////////////////////////////////////////////////////////////\n///// DIV CONTAINER CREATION TO HOLD THREEJS EXPERIENCE\nconst container = document.createElement('div')\ndocument.body.appendChild(container)\n\n/////////////////////////////////////////////////////////////////////////\n///// SCENE CREATION\nconst scene = new THREE.Scene()\nscene.background = new THREE.Color('#c8f0f9')\n\n/////////////////////////////////////////////////////////////////////////\n///// RENDERER CONFIG\nconst renderer = new THREE.WebGLRenderer({ antialias: true}) // turn on antialias\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) //set pixel ratio\nrenderer.setSize(window.innerWidth, window.innerHeight) // make it full screen\nrenderer.outputEncoding = THREE.sRGBEncoding // set color encoding\ncontainer.appendChild(renderer.domElement) // add the renderer to html div\n\n/////////////////////////////////////////////////////////////////////////\n///// CAMERAS CONFIG\nconst camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 100)\ncamera.position.set(34,16,-20)\nscene.add(camera)\n\n/////////////////////////////////////////////////////////////////////////\n///// MAKE EXPERIENCE FULL SCREEN\nwindow.addEventListener('resize', () => {\n    const width = window.innerWidth\n    const height = window.innerHeight\n    camera.aspect = width / height\n    camera.updateProjectionMatrix()\n\n    renderer.setSize(width, height)\n    renderer.setPixelRatio(2)\n})\n\n/////////////////////////////////////////////////////////////////////////\n///// CREATE ORBIT CONTROLS\nconst controls = new OrbitControls(camera, renderer.domElement)\n\n/////////////////////////////////////////////////////////////////////////\n///// SCENE LIGHTS\nconst ambient = new THREE.AmbientLight(0xa0a0fc, 0.82)\nscene.add(ambient)\n\nconst sunLight = new THREE.DirectionalLight(0xe8c37b, 1.96)\nsunLight.position.set(-69,44,14)\nscene.add(sunLight)\n\n/////////////////////////////////////////////////////////////////////////\n///// LOADING GLB/GLTF MODEL FROM BLENDER\nloader.load('models/gltf/starter-scene.glb', function (gltf) {\n\n    scene.add(gltf.scene)\n})\n\n/////////////////////////////////////////////////////////////////////////\n//// INTRO CAMERA ANIMATION USING TWEEN\nfunction introAnimation() {\n    controls.enabled = false //disable orbit controls to animate the camera\n    \n    new TWEEN.Tween(camera.position.set(26,4,-35 )).to({ // from camera position\n        x: 16, //desired x position to go\n        y: 50, //desired y position to go\n        z: -0.1 //desired z position to go\n    }, 6500) // time take to animate\n    .delay(1000).easing(TWEEN.Easing.Quartic.InOut).start() // define delay, easing\n    .onComplete(function () { //on finish animation\n        controls.enabled = true //enable orbit controls\n        setOrbitControlsLimits() //enable controls limits\n        TWEEN.remove(this) // remove the animation from memory\n    })\n}\n\nintroAnimation() // call intro animation on start\n\n/////////////////////////////////////////////////////////////////////////\n//// DEFINE ORBIT CONTROLS LIMITS\nfunction setOrbitControlsLimits(){\n    controls.enableDamping = true\n    controls.dampingFactor = 0.04\n    controls.minDistance = 35\n    controls.maxDistance = 60\n    controls.enableRotate = true\n    controls.enableZoom = true\n    controls.maxPolarAngle = Math.PI /2.5\n}\n\n/////////////////////////////////////////////////////////////////////////\n//// RENDER LOOP FUNCTION\nfunction rendeLoop() {\n\n    TWEEN.update() // update animations\n\n    controls.update() // update orbit controls\n\n    renderer.render(scene, camera) // render the scene using the camera\n\n    requestAnimationFrame(rendeLoop) //loop the render function\n    \n}\n\nrendeLoop() //start rendering"
  },
  {
    "path": "static/.gitkeep",
    "content": ""
  },
  {
    "path": "static/favicon/site.webmanifest",
    "content": "{\"name\":\"\",\"short_name\":\"\",\"icons\":[{\"src\":\"/android-chrome-192x192.png\",\"sizes\":\"192x192\",\"type\":\"image/png\"},{\"src\":\"/android-chrome-512x512.png\",\"sizes\":\"512x512\",\"type\":\"image/png\"}],\"theme_color\":\"#ffffff\",\"background_color\":\"#ffffff\",\"display\":\"standalone\"}"
  }
]