commit d7698684beb132b3f960dca9c5aa1cf21421c912 Author: Evert Prants Date: Fri May 8 19:19:21 2020 +0300 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a0a4de8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.js] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99d9404 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/node_modules/ +/dist/ +/package-lock.json +/**/dna.txt +*.db +*.sqlite3 diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..7114c92 --- /dev/null +++ b/demo.html @@ -0,0 +1,56 @@ + + + + qplanets demo + + + + + + + + + diff --git a/lib/qplanets.js b/lib/qplanets.js new file mode 100644 index 0000000..50738c1 --- /dev/null +++ b/lib/qplanets.js @@ -0,0 +1 @@ +!function(e){var t={};function n(a){if(t[a])return t[a].exports;var r=t[a]={i:a,l:!1,exports:{}};return e[a].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,a){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(n.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(a,r,function(t){return e[t]}.bind(null,r));return a},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){THREE.Extras=THREE.Extras||{},THREE.Extras.Planet={};const a=n(1),r=n(2);class o{static _compose(e,t,n,a,r){const i=new THREE.ShaderMaterial({uniforms:{},vertexShader:n,fragmentShader:a});return o.scatterUniforms(i,e,t,r),i}static newAtmosphere(e,t,n){const r=o._compose(e,t,a.vertexUniforms+a.vertexFunctions+a.vertexAtmosphere,a.fragmentAtmosphere,n);return r.side=THREE.BackSide,r.transparent=!0,r}static newAttenuate(e,t,n){return o._compose(e,t,a.vertexUniforms+a.vertexFunctions+a.vertexGround,a.fragmentGround,n)}static scatterUniforms(e,t,n,a={}){const r=a.Kr||.0025,o=a.Km||.0015,i=a.ESun||15,l=a.scale||1/(n-t),s=a.scaleDepth||.25,f=a.g||-.95,h=a.wavelength||[.65,.57,.475];e.uniforms.invWavelength={value:[1/Math.pow(h[0],4),1/Math.pow(h[1],4),1/Math.pow(h[2],4)],type:"v3"},e.uniforms.outerRadius={value:n,type:"f"},e.uniforms.innerRadius={value:t,type:"f"},e.uniforms.Kr={value:r,type:"f"},e.uniforms.Km={value:o,type:"f"},e.uniforms.ESun={value:i,type:"f"},e.uniforms.scale={value:l,type:"f"},e.uniforms.scaleDepth={value:s,type:"f"},e.uniforms.g={value:f,type:"f"}}}for(const e in r)THREE.Extras.Planet[e]=r[e];THREE.Extras.Planet.ScatterShader=o,e.exports=THREE.Extras.Planet},function(e,t){const n={vertexUniforms:"\n#define M_PI 3.1415926535897932384626433832795\nuniform vec3 planetPosition; // Position of the planet\nuniform vec3 lightDirection; // The direction vector to the light source\nuniform vec3 invWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels\nuniform float outerRadius; // The outer (atmosphere) radius\nuniform float innerRadius; // The inner (planetary) radius\nuniform float ESun; // ESun\nuniform float Km; // Km\nuniform float Kr; // Kr\nuniform float scale; // 1 / (outerRadius - innerRadius)\nuniform float scaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found)\n\nconst int nSamples = 2;\nconst float fSamples = 1.0;\n\nvarying vec3 v3Direction;\nvarying vec3 c0;\nvarying vec3 c1;\n ",vertexFunctions:"\nfloat dscale(float fCos) {\n float x = 1.0 - fCos;\n return scaleDepth * exp(-0.00287 + x * (0.459 + x * (3.83 + x * (-6.80 + x * 5.25))));\n}\n\nfloat calculateNearScatter(vec3 v3CameraPosition, vec3 v3Ray, float fCameraHeight, float fOuterRadius) {\n float B = 2.0 * dot(v3CameraPosition, v3Ray);\n float C = pow(fCameraHeight, 2.0) - pow(fOuterRadius, 2.0);\n float fDet = max(0.0, B * B - 4.0 * C);\n return 0.5 * (-B - sqrt(fDet));\n}\n ",vertexAtmosphere:"\nvoid main(void) {\n // Initialize variables\n float cameraHeight = length(planetPosition - cameraPosition);\n float KmESun = Km * ESun;\n float KrESun = Kr * ESun;\n float Kr4PI = Kr * 4.0 * M_PI;\n float Km4PI = Km * 4.0 * M_PI;\n float scaleOverScaleDepth = scale / scaleDepth;\n\n // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)\n vec3 v3Ray = position - cameraPosition;\n float fFar = length(v3Ray);\n v3Ray /= fFar;\n\n vec3 v3Start;\n float fStartAngle;\n float fStartDepth;\n float fStartOffset;\n\n if (cameraHeight > outerRadius) {\n // Sky from space\n // Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray passing through the atmosphere)\n float fNear = calculateNearScatter(cameraPosition, v3Ray, cameraHeight, outerRadius);\n\n // Calculate the ray's starting position, then calculate its scattering offset\n v3Start = cameraPosition + v3Ray * fNear;\n fFar -= fNear;\n fStartAngle = dot(v3Ray, v3Start) / outerRadius;\n fStartDepth = exp(-1.0 / scaleDepth);\n fStartOffset = fStartDepth * dscale(fStartAngle);\n } else {\n // Sky from within the atmosphere\n v3Start = cameraPosition;\n fStartDepth = exp(scaleOverScaleDepth * (innerRadius - cameraHeight));\n fStartAngle = dot(v3Ray, v3Start) / length(v3Start);\n fStartOffset = fStartDepth * dscale(fStartAngle);\n }\n\n // Initialize the scattering loop variables\n float fSampleLength = fFar / fSamples;\n float scaledLength = fSampleLength * scale;\n vec3 v3SampleRay = v3Ray * fSampleLength;\n vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5;\n\n // Now loop through the sample rays\n vec3 v3FrontColor = vec3(0.0, 0.0, 0.0);\n for(int i=0; i5.5*r&&this.children.length>0)this.merge();else if(this.children.length>0)for(const n in this.children)this.children[n].update(e,t)}}class l extends THREE.Object3D{constructor(e,t){super(),this.position.copy(e),this.generator=t,this.radius=t.radius;const n=t.radius/2;this.add(new i(this,0,0,new THREE.Vector3(0,0,-n),new THREE.Vector3(0,0,-1))),this.add(new i(this,1,0,new THREE.Vector3(0,0,n),new THREE.Vector3(0,0,1))),this.add(new i(this,2,0,new THREE.Vector3(-n,0,0),new THREE.Vector3(-1,0,0))),this.add(new i(this,3,0,new THREE.Vector3(n,0,0),new THREE.Vector3(1,0,0))),this.add(new i(this,4,0,new THREE.Vector3(0,n,0),new THREE.Vector3(0,1,0))),this.add(new i(this,5,0,new THREE.Vector3(0,-n,0),new THREE.Vector3(0,-1,0)))}update(e,t){for(const n in this.children)this.children[n].update(e,t)}setMaterial(e){this.material=e;for(const t in this.children)this.children[t].setMaterial(e)}}e.exports={PlanetGenerator:class{constructor(e){this.radius=e}getHeight(e){return 0}getBiome(e){return 0}},CubePlanet:l,CubePlanetNode:i}}]); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..55a8fe4 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "qplanets.js", + "version": "0.0.1", + "description": "THREE.js extension for procedural planets", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack -p", + "watch": "webpack -w --mode=development" + }, + "keywords": [], + "private": true, + "author": "Evert \"Diamond\" Prants ", + "license": "MIT", + "devDependencies": { + "express": "^4.16.4", + "standard": "^14.3.3", + "three": "^0.116.1", + "webpack": "^4.26.0", + "webpack-command": "^0.5.0" + } +} diff --git a/src/builtin/atmosphere.glsl.js b/src/builtin/atmosphere.glsl.js new file mode 100644 index 0000000..24564b3 --- /dev/null +++ b/src/builtin/atmosphere.glsl.js @@ -0,0 +1,198 @@ + +const ATMOSPHERE = { + vertexUniforms: ` +#define M_PI 3.1415926535897932384626433832795 +uniform vec3 planetPosition; // Position of the planet +uniform vec3 lightDirection; // The direction vector to the light source +uniform vec3 invWavelength; // 1 / pow(wavelength, 4) for the red, green, and blue channels +uniform float outerRadius; // The outer (atmosphere) radius +uniform float innerRadius; // The inner (planetary) radius +uniform float ESun; // ESun +uniform float Km; // Km +uniform float Kr; // Kr +uniform float scale; // 1 / (outerRadius - innerRadius) +uniform float scaleDepth; // The scale depth (i.e. the altitude at which the atmosphere's average density is found) + +const int nSamples = 2; +const float fSamples = 1.0; + +varying vec3 v3Direction; +varying vec3 c0; +varying vec3 c1; + `, + vertexFunctions: ` +float dscale(float fCos) { + float x = 1.0 - fCos; + return scaleDepth * exp(-0.00287 + x * (0.459 + x * (3.83 + x * (-6.80 + x * 5.25)))); +} + +float calculateNearScatter(vec3 v3CameraPosition, vec3 v3Ray, float fCameraHeight, float fOuterRadius) { + float B = 2.0 * dot(v3CameraPosition, v3Ray); + float C = pow(fCameraHeight, 2.0) - pow(fOuterRadius, 2.0); + float fDet = max(0.0, B * B - 4.0 * C); + return 0.5 * (-B - sqrt(fDet)); +} + `, + vertexAtmosphere: ` +void main(void) { + // Initialize variables + float cameraHeight = length(planetPosition - cameraPosition); + float KmESun = Km * ESun; + float KrESun = Kr * ESun; + float Kr4PI = Kr * 4.0 * M_PI; + float Km4PI = Km * 4.0 * M_PI; + float scaleOverScaleDepth = scale / scaleDepth; + + // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) + vec3 v3Ray = position - cameraPosition; + float fFar = length(v3Ray); + v3Ray /= fFar; + + vec3 v3Start; + float fStartAngle; + float fStartDepth; + float fStartOffset; + + if (cameraHeight > outerRadius) { + // Sky from space + // Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray passing through the atmosphere) + float fNear = calculateNearScatter(cameraPosition, v3Ray, cameraHeight, outerRadius); + + // Calculate the ray's starting position, then calculate its scattering offset + v3Start = cameraPosition + v3Ray * fNear; + fFar -= fNear; + fStartAngle = dot(v3Ray, v3Start) / outerRadius; + fStartDepth = exp(-1.0 / scaleDepth); + fStartOffset = fStartDepth * dscale(fStartAngle); + } else { + // Sky from within the atmosphere + v3Start = cameraPosition; + fStartDepth = exp(scaleOverScaleDepth * (innerRadius - cameraHeight)); + fStartAngle = dot(v3Ray, v3Start) / length(v3Start); + fStartOffset = fStartDepth * dscale(fStartAngle); + } + + // Initialize the scattering loop variables + float fSampleLength = fFar / fSamples; + float scaledLength = fSampleLength * scale; + vec3 v3SampleRay = v3Ray * fSampleLength; + vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; + + // Now loop through the sample rays + vec3 v3FrontColor = vec3(0.0, 0.0, 0.0); + for(int i=0; i splitDistance * 5.5 && this.children.length > 0) { + this.merge() + return + } + + if (this.children.length > 0) { + for (const i in this.children) { + this.children[i].update(camera, dt) + } + } + } +} + +class CubePlanet extends THREE.Object3D { + constructor (origin, generator) { + super() + this.position.copy(origin) + this.generator = generator + + this.radius = generator.radius + const hs = generator.radius / 2 + + this.add(new CubePlanetNode(this, 0, 0, new THREE.Vector3(0, 0, -hs), new THREE.Vector3(0, 0, -1))) + this.add(new CubePlanetNode(this, 1, 0, new THREE.Vector3(0, 0, hs), new THREE.Vector3(0, 0, 1))) + + this.add(new CubePlanetNode(this, 2, 0, new THREE.Vector3(-hs, 0, 0), new THREE.Vector3(-1, 0, 0))) + this.add(new CubePlanetNode(this, 3, 0, new THREE.Vector3(hs, 0, 0), new THREE.Vector3(1, 0, 0))) + + this.add(new CubePlanetNode(this, 4, 0, new THREE.Vector3(0, hs, 0), new THREE.Vector3(0, 1, 0))) + this.add(new CubePlanetNode(this, 5, 0, new THREE.Vector3(0, -hs, 0), new THREE.Vector3(0, -1, 0))) + } + + update (camera, dt) { + for (const i in this.children) { + this.children[i].update(camera, dt) + } + } + + setMaterial (mat) { + this.material = mat + for (const i in this.children) { + this.children[i].setMaterial(mat) + } + } +} + +module.exports = { PlanetGenerator, CubePlanet, CubePlanetNode } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..704318f --- /dev/null +++ b/src/index.js @@ -0,0 +1,72 @@ +/* global THREE */ + +THREE.Extras = THREE.Extras || {} +THREE.Extras.Planet = {} + +const atmosShader = require('./builtin/atmosphere.glsl.js') +const cubePlanet = require('./cubeplanet.js') + +class ScatterShader { + static _compose (iR, oR, vS, fS, p) { + const st = new THREE.ShaderMaterial({ + uniforms: {}, + vertexShader: vS, + fragmentShader: fS + }) + ScatterShader.scatterUniforms(st, iR, oR, p) + return st + } + + static newAtmosphere (innerRadius, outerRadius, params) { + const st = ScatterShader._compose( + innerRadius, + outerRadius, + atmosShader.vertexUniforms + atmosShader.vertexFunctions + atmosShader.vertexAtmosphere, + atmosShader.fragmentAtmosphere, + params + ) + st.side = THREE.BackSide + st.transparent = true + return st + } + + static newAttenuate (innerRadius, outerRadius, params) { + return ScatterShader._compose( + innerRadius, + outerRadius, + atmosShader.vertexUniforms + atmosShader.vertexFunctions + atmosShader.vertexGround, + atmosShader.fragmentGround, + params + ) + } + + static scatterUniforms (shader, innerRadius, outerRadius, custom = {}) { + const Kr = custom.Kr || 0.0025 + const Km = custom.Km || 0.0015 + const ESun = custom.ESun || 15.0 + const Scale = custom.scale || (1 / (outerRadius - innerRadius)) + const ScaleDepth = custom.scaleDepth || 0.25 + const G = custom.g || -0.950 + const Wavelength = custom.wavelength || [0.650, 0.570, 0.475] + + shader.uniforms.invWavelength = { + value: [1 / Math.pow(Wavelength[0], 4), 1 / Math.pow(Wavelength[1], 4), 1 / Math.pow(Wavelength[2], 4)], + type: 'v3' + } + shader.uniforms.outerRadius = { value: outerRadius, type: 'f' } + shader.uniforms.innerRadius = { value: innerRadius, type: 'f' } + shader.uniforms.Kr = { value: Kr, type: 'f' } + shader.uniforms.Km = { value: Km, type: 'f' } + shader.uniforms.ESun = { value: ESun, type: 'f' } + shader.uniforms.scale = { value: Scale, type: 'f' } + shader.uniforms.scaleDepth = { value: ScaleDepth, type: 'f' } + shader.uniforms.g = { value: G, type: 'f' } + } +} + +for (const o in cubePlanet) { + THREE.Extras.Planet[o] = cubePlanet[o] +} + +THREE.Extras.Planet.ScatterShader = ScatterShader +module.exports = THREE.Extras.Planet diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..4d359da --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,13 @@ +const path = require('path') + +module.exports = (env) => { + return { + entry: './src/index.js', + output: { + path: path.resolve(__dirname, 'lib'), + filename: 'qplanets.js' + }, + module: {}, + devtool: env.mode === 'development' ? 'inline-source-map' : '' + } +}