JavaScript preprocessors can help make authoring JavaScript easier and more convenient. For instance, CoffeeScript can help prevent easy-to-make mistakes and offer a cleaner syntax and Babel can bring ECMAScript 6 features to browsers that only support ECMAScript 5.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
HTML Settings
Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
<div id="js-app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.20.0/polyfill.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/292951/three.bas.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/292951/OrbitControls_copy.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://rawgit.com/josephg/noisejs/master/perlin.js"></script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script>(function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();document.body.appendChild(stats.dom);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//rawgit.com/mrdoob/stats.js/master/build/stats.min.js';document.head.appendChild(script);})()</script>
body {
margin: 0;
padding: 0;
background-color: #000;
}
canvas {
display: block;
margin: 0 auto;
}
setTimeout(async () => {
const mapTexture = await loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/292951/winter.jpg')
const entities = [
new Plane(mapTexture),
new THREE.AxisHelper(50)
]
ReactDOM.render((
<Simulation
fov={45}
cameraPosition={new THREE.Vector3(40, 18, -82)}
{...{ entities }}
/>
), document.getElementById('js-app'))
}, 0)
const loadTexture = (assetUri) => {
return new Promise((resolve) => {
const textureLoader = new THREE.TextureLoader()
textureLoader.crossOrigin = ''
textureLoader.load(assetUri, image => resolve(image))
})
}
const buildPoints = (edgeCount, rotationOffset = 0) => {
const stepSize = (Math.PI * 2) / edgeCount;
return Array(...(new Array(edgeCount))).map((_, edgeIndex) => [
Math.cos(edgeIndex * stepSize + rotationOffset),
Math.sin(edgeIndex * stepSize + rotationOffset)
]);
}
class Plane extends THREE.Mesh {
uTime = null
constructor (mapTexture) {
const pointsUp = buildPoints(3, Math.PI * 1.5)
const uvs = [
pointsUp[0].map(i => (i + 1) / 2).map(i => 1 - i),
pointsUp[1].map(i => (i + 1) / 2).map(i => 1 - i),
pointsUp[2].map(i => (i + 1) / 2).map(i => 1 - i)
]
const geometry = Plane.createGeometry(uvs)
const material = Plane.createMaterial(mapTexture, uvs)
material.uniforms.uMap.value = mapTexture
super(geometry, material)
}
get time () {
return this.uTime
}
set time (newTime) {
this.uTime = newTime
}
static elevationGenerator (scaling = 20, s = Math.random()) {
const colorLookup = {}
const seed = 5625463739 * s
return (vertex) => {
const key = `${vertex.x}&${vertex.y}&${vertex.z}`
if (colorLookup[key] === undefined) {
const elevation = (noise.simplex3(
(vertex.x + seed) / scaling,
(vertex.y + seed) / scaling,
(vertex.z + seed) / scaling
) + 1) / 2 // this will be a value between 0 and 1
let elevationIndex = 0
if (elevation > 0.3) { // ocean
elevationIndex = 1
}
if (elevation > 0.45) { // sand
elevationIndex = 2
}
if (elevation > 0.62) { // grass
elevationIndex = 3
}
if (elevation > 0.8) { // dense grass
elevationIndex = 4
}
colorLookup[key] = elevationIndex
}
return colorLookup[key]
}
}
static createGeometry (uvs) {
const getElevation = Plane.elevationGenerator()
const model = new THREE.IcosahedronGeometry(20, 4)
THREE.BAS.Utils.separateFaces(model) // this splits each face into its own 3 vertices, allowing us to add elevations to them (i think)
const barycentricCombinations = [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]
]
const geometry = new THREE.BAS.ModelBufferGeometry(model)
const aUv = geometry.createAttribute('aUv', 2)
const aElevation = geometry.createAttribute('aElevation', 4)
let offsetUv = 0
let offsetElevation = 0
;[...new Array(geometry.faceCount)].forEach((_, faceIndex) => {
const face = geometry.modelGeometry.faces[faceIndex]
const {
a,
b,
c
} = face
const {
vertices
} = geometry.modelGeometry
const [
elevationA,
elevationB,
elevationC
] = [a, b, c].map((vertexIndex) => {
const vertex = vertices[vertexIndex]
return getElevation(vertex)
})
const elevationD = Plane.getFaceCenterColor(elevationA, elevationB, elevationC)
for (let i = 0; i < 3; i++) {
aUv.array[offsetUv++] = uvs[i][0]
aUv.array[offsetUv++] = uvs[i][1]
aElevation.array[offsetElevation++] = elevationA
aElevation.array[offsetElevation++] = elevationB
aElevation.array[offsetElevation++] = elevationC
aElevation.array[offsetElevation++] = elevationD
}
})
return geometry
}
static getFaceCenterColor (elevationA, elevationB, elevationC) { // TODO refactor, probably a better way to do this
return (elevationA + elevationB + elevationC) / 3
}
static createMaterial (mapTexture, uvs) {
return new THREE.BAS.PhongAnimationMaterial({
shading: THREE.FlatShading,
side: THREE.FrontSide,
uniforms: {
uTime: { value: 0 },
uMap: { value: null },
uUvA: { value: uvs[0] },
uUvB: { value: uvs[1] },
uUvC: { value: uvs[2] }
},
uniformValues: {
},
vertexParameters: [`
// vertexParamters
attribute vec2 aUv;
attribute vec3 aBarycentric;
attribute vec4 aElevation;
uniform float uTime;
`],
varyingParameters: [`
// varyingParameters
varying vec2 vUv;
varying vec4 vElevation;
`],
vertexFunctions: [`
// vertexFunctions
`],
vertexInit: [`
// vertexInit
`],
vertexNormal: [`
// vertexNormal
`],
vertexPosition: [`
// vertexPosition
`],
vertexColor: [`
// vertexColor
vUv = aUv;
vElevation = aElevation;
`],
fragmentParameters: [`
// fragmentParameters
uniform vec2 uUvA;
uniform vec2 uUvB;
uniform vec2 uUvC;
`],
fragmentFunctions: [`
// fragmentFunctions
float terrainShininess (float elev) {
float landShininess = 10.0;
float waterShininess = 100.0;
if (elev >= 4.0) {
return landShininess; // dense grass
}
if (elev >= 3.0) {
return landShininess; // grass
}
if (elev >= 2.0) {
return landShininess; // sand
}
if (elev >= 1.0) {
return waterShininess; // ocean
}
return waterShininess; // deep ocean
}
vec3 terrainColor (float elev) {
if (elev >= 4.0) {
return vec3(0, 0.533, 0); // dense grass 008800 (0, 136, 0)
}
if (elev >= 3.0) {
return vec3(0, 0.619, 0); // grass 009e00 (0, 158, 0)
}
if (elev >= 2.0) {
return vec3(0.76, 0.698, 0.435); // sand c2b26f (194, 178, 111)
}
if (elev >= 1.0) {
return vec3(0.188, 0.619, 0.752); // ocean 309ec0 (48, 158, 192)
}
return vec3(0.16, 0.533, 0.682); // deep ocean 2988ae (41, 136, 174)
}
vec3 cartesianToBarycentric (vec2 p1, vec2 p2, vec2 p3, vec2 p) { // http://totologic.blogspot.com/2014/01/accurate-point-in-triangle-test.html
float denominator = ((p2.y - p3.y) * (p.x - p3.x) + (p3.x - p2.x) * (p1.y - p3.y));
float a = ((p2.y - p3.y) * (p.x - p3.x) + (p3.x - p2.x) * (p.y - p3.y)) / denominator;
float b = ((p3.y - p1.y) * (p.x - p3.x) + (p1.x - p3.x) * (p.y - p3.y)) / denominator;
float c = 1.0 - a - b;
return vec3(a, b, c);
}
float getPointElevation (vec4 elevation, vec3 barycentric) {
if (barycentric.x > barycentric.y + barycentric.z) {
return elevation.x;
} else if (barycentric.y > barycentric.x + barycentric.z) {
return elevation.y;
} else if (barycentric.z > barycentric.x + barycentric.y) {
return elevation.z;
}
return elevation.w;
}
vec3 perturbNormalArb (vec3 surf_pos, vec3 surf_norm, vec2 dHdxy) {
vec3 vSigmaX = vec3(dFdx(surf_pos.x), dFdx(surf_pos.y), dFdx(surf_pos.z));
vec3 vSigmaY = vec3(dFdy(surf_pos.x), dFdy(surf_pos.y), dFdy(surf_pos.z));
vec3 vN = surf_norm;
vec3 R1 = cross(vSigmaY, vN);
vec3 R2 = cross(vN, vSigmaX);
float fDet = dot(vSigmaX, R1);
vec3 vGrad = sign(fDet) * (dHdxy.x * R1 + dHdxy.y * R2);
return normalize(abs(fDet) * surf_norm - vGrad);
}
vec2 dHdxy_fwd (vec2 uvA, vec2 uvB, vec2 uvC, vec2 uv, vec4 elevation, float bumpScale) {
vec2 dSTdx = dFdx(uv);
vec2 dSTdy = dFdy(uv);
float Hll = bumpScale * getPointElevation(elevation, cartesianToBarycentric(uvA, uvB, uvC, uv));
float dBx = bumpScale * getPointElevation(elevation, cartesianToBarycentric(uvA, uvB, uvC, uv + dSTdx)) - Hll;
float dBy = bumpScale * getPointElevation(elevation, cartesianToBarycentric(uvA, uvB, uvC, uv + dSTdy)) - Hll;
return vec2(dBx, dBy);
}
`],
fragmentInit: [`
// fragmentInit
vec4 elevation = ceil(vElevation + 0.5) - 1.0;
`],
fragmentDiffuse: [`
// fragmentDiffuse
vec3 barycentric = cartesianToBarycentric(uUvA, uUvB, uUvC, vUv);
float pointElevation = getPointElevation(elevation, barycentric);
diffuseColor.rgb = terrainColor(pointElevation);
`],
fragmentMap: [`
// fragmentMap
`],
fragmentEmissive: [`
// fragmentEmissive
if (pointElevation > 1.0) {
normal = perturbNormalArb(-vViewPosition, normal, dHdxy_fwd(uUvA, uUvB, uUvC, vUv, elevation, 0.5));
}
`],
fragmentSpecular: [`
// fragmentSpecular
material.specularShininess = terrainShininess(pointElevation);
`]
})
}
}
class Simulation extends React.Component {
static defaultProps = {
entities: [],
fov: 80,
cameraPosition: new THREE.Vector3(0, 0, 0)
}
shouldComponentUpdate () {
return false
}
componentDidMount () {
const camera = this.createCamera(
this.props.fov,
this.props.cameraPosition,
window.innerWidth,
window.innerHeight
)
camera.target = new THREE.Vector3(0, 0, 0)
camera.lookAt(camera.target)
const scene = new THREE.Scene()
const renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: false,
canvas: this.canvas
})
const handleWindowResize = this.onWindowResize(camera, renderer)
handleWindowResize()
window.addEventListener('resize', handleWindowResize, false)
const controls = this.createControls(camera)
this.props.entities.forEach(e => scene.add(e))
const {
lights,
pointLights
} = this.createLights()
lights.forEach(light => scene.add(light))
this.animate(renderer, scene, camera, controls, this.props.entities, pointLights, +(new Date()))
}
createLights () {
const ambientLight = new THREE.AmbientLight(0x333333)
const pointLightA = new THREE.PointLight(0xffffff, 1, 200)
const pointLightHelperA = new THREE.PointLightHelper(pointLightA, 1)
const pointLightB = new THREE.PointLight(0xffffff, 1, 200)
const pointLightHelperB = new THREE.PointLightHelper(pointLightB, 1)
return {
lights: [
ambientLight,
pointLightA,
pointLightHelperA,
// pointLightB,
// pointLightHelperB
],
pointLights: [
pointLightA,
// pointLightB
]
}
}
createCamera (fov, pos, width, height) {
const camera = new THREE.PerspectiveCamera(fov, width / height, 1, 1000)
camera.position.x = pos.x
camera.position.y = pos.y
camera.position.z = pos.z
return camera
}
createControls (camera) {
const controls = new THREE.OrbitControls(camera)
controls.target = camera.target
controls.enableDamping = true
controls.dampingFactor = 0.1
controls.rotateSpeed = 0.1
return controls
}
onWindowResize (camera, renderer) {
return () => {
const width = window.innerWidth
const height = window.innerHeight
camera.aspect = width / height
camera.updateProjectionMatrix()
renderer.setSize(width, height)
}
}
animate (renderer, scene, camera, controls, entities, pointLights, lastTime) {
const currentTime = +(new Date())
const timeDelta = currentTime - lastTime
entities.forEach(e => e.time += timeDelta / 1000)
requestAnimationFrame(() => {
this.animate(renderer, scene, camera, controls, entities, pointLights, currentTime)
})
pointLights.forEach((pointLight, index) => {
index += 1
const frequency = 1000 + index * 200
const amplitude = 40
const animationTime = Math.PI * frequency + currentTime * index;
const cos = Math.cos(animationTime / frequency) * amplitude
const sin = Math.sin(animationTime / frequency * 1) * amplitude / 1 // turn the 1s to 2s
pointLight.position.set(
sin,
20,
cos
)
})
controls.update()
renderer.render(scene, camera)
}
render () {
return (
<canvas ref={c => this.canvas = c} />
)
}
}
Also see: Tab Triggers