Add earth widget
1
.gitignore
vendored
@ -2,6 +2,7 @@ _site
|
|||||||
.sass-cache
|
.sass-cache
|
||||||
.jekyll-cache
|
.jekyll-cache
|
||||||
.jekyll-metadata
|
.jekyll-metadata
|
||||||
|
.jampack/
|
||||||
.obsidian/
|
.obsidian/
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
vendor
|
vendor
|
||||||
|
@ -9,4 +9,12 @@ excerpt: |
|
|||||||
|
|
||||||
The site is build by a github action based on the [jekyll recommendation](https://jekyllrb.com/docs/continuous-integration/github-actions/). I did this because I wanted to be able to use `npm install` like all the cool kids which lead to adding [jekyll-node-module](https://github.com/mintbit/jekyll-node-module#jekyll-node-module) to the site to be able to copy things in from `node_packages` without committing it all to the repo.
|
The site is build by a github action based on the [jekyll recommendation](https://jekyllrb.com/docs/continuous-integration/github-actions/). I did this because I wanted to be able to use `npm install` like all the cool kids which lead to adding [jekyll-node-module](https://github.com/mintbit/jekyll-node-module#jekyll-node-module) to the site to be able to copy things in from `node_packages` without committing it all to the repo.
|
||||||
|
|
||||||
The 3D outline rendered images on the [projects pages](/projects) are done with code from [Omaha Shehata](https://omar-shehata.medium.com/better-outline-rendering-using-surface-ids-with-webgl-e13cdab1fd94).
|
The 3D outline rendered images on the [projects pages](/projects) are done with code from [Omaha Shehata](https://omar-shehata.medium.com/better-outline-rendering-using-surface-ids-with-webgl-e13cdab1fd94).
|
||||||
|
|
||||||
|
## Future
|
||||||
|
There are a few plugins I'm thinking of adding:
|
||||||
|
- [jekyll-auto-image](https://github.com/merlos/jekyll-auto-image) to simply choosing a representative image for a particular post.
|
||||||
|
- I'd like to write a jekyll plugin for [D2](https://github.com/terrastruct/d2) diagrams. It doesn't [look too difficult](https://jekyllrb.com/docs/plugins/tags/).
|
||||||
|
- I'm wondering if I should switch the rendering to use pandoc, I used pandoc in the past to convert my thesis to multiple output formats and I suspect it is now a more active project than jekyll itself, for example someone has already written a D2 pandoc filter.
|
||||||
|
|
||||||
|
There's a good list of jekyll plugins [here](https://github.com/planetjekyll/awesome-jekyll-plugins).
|
@ -10,6 +10,7 @@ assets: /assets/blog/bookshelf/
|
|||||||
alt:
|
alt:
|
||||||
head: <script type="module" src="/assets/js/model-viewer.js"></script>
|
head: <script type="module" src="/assets/js/model-viewer.js"></script>
|
||||||
---
|
---
|
||||||
|
|
||||||
Here's a little weekend project. We have this mezzanine bed with windows and window shelves around it. I'd like to put books there but sometimes condensation pools so I need something to raise the books a little above the level of the shelf.
|
Here's a little weekend project. We have this mezzanine bed with windows and window shelves around it. I'd like to put books there but sometimes condensation pools so I need something to raise the books a little above the level of the shelf.
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
26
_posts/2024-05-17-threejs-earth.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: ThreeJS Earth
|
||||||
|
layout: post
|
||||||
|
excerpt: A small earth renderer made using ThreeJS
|
||||||
|
|
||||||
|
image:
|
||||||
|
thumbnail:
|
||||||
|
assets:
|
||||||
|
alt:
|
||||||
|
|
||||||
|
head: |
|
||||||
|
<script async src="/node_modules/es-module-shims/dist/es-module-shims.js"></script>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"three": "/node_modules/three/build/three.module.min.js",
|
||||||
|
"three/addons/": "/node_modules/three/examples/jsm/",
|
||||||
|
"three/examples/": "/node_modules/three/examples/jsm/",
|
||||||
|
"lil-gui": "/node_modules/lil-gui/dist/lil-gui.esm.min.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/assets/blog/sphere_geodesics/index.js" type="module"></script>
|
||||||
|
---
|
||||||
|
A small earth renderer made using ThreeJS.
|
||||||
|
<sphere-viewer style="height: 500px; display: block;"></sphere-viewer>
|
BIN
assets/blog/sphere_geodesics/earthbump10k.jpg
Normal file
After Width: | Height: | Size: 5.6 MiB |
BIN
assets/blog/sphere_geodesics/earthbump1k.jpg
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
assets/blog/sphere_geodesics/earthcloudmap.jpg
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
assets/blog/sphere_geodesics/earthcloudmaptrans.jpg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
assets/blog/sphere_geodesics/earthlights10k.jpg
Normal file
After Width: | Height: | Size: 4.8 MiB |
BIN
assets/blog/sphere_geodesics/earthlights1k.jpg
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
assets/blog/sphere_geodesics/earthmap10k.jpg
Normal file
After Width: | Height: | Size: 11 MiB |
BIN
assets/blog/sphere_geodesics/earthmap1k.jpg
Normal file
After Width: | Height: | Size: 336 KiB |
BIN
assets/blog/sphere_geodesics/earthspec10k.jpg
Normal file
After Width: | Height: | Size: 3.2 MiB |
BIN
assets/blog/sphere_geodesics/earthspec1k.jpg
Normal file
After Width: | Height: | Size: 114 KiB |
254
assets/blog/sphere_geodesics/index.js
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
import GUI from 'lil-gui'
|
||||||
|
|
||||||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
import { TransformControls } from 'three/addons/controls/TransformControls.js';
|
||||||
|
import Stats from "three/examples/libs/stats.module.js";
|
||||||
|
// node_modules/three/examples/jsm/libs/stats.module.js
|
||||||
|
|
||||||
|
// previously this feature is .legacyMode = false, see https://www.donmccurdy.com/2020/06/17/color-management-in-threejs/
|
||||||
|
// turning this on has the benefit of doing certain automatic conversions (for hexadecimal and CSS colors from sRGB to linear-sRGB)
|
||||||
|
THREE.ColorManagement.enabled = true
|
||||||
|
|
||||||
|
class SphereViewer extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
let component_rect = this.getBoundingClientRect();
|
||||||
|
console.log("component_rect", component_rect);
|
||||||
|
|
||||||
|
this.shadow = this.attachShadow({ mode: "open" });
|
||||||
|
this.render(component_rect.height);
|
||||||
|
|
||||||
|
const container = this.shadow.querySelector("div#container");
|
||||||
|
const canvas = this.shadow.querySelector("canvas");
|
||||||
|
|
||||||
|
let canvas_rect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
|
// determine the outline and bg colors
|
||||||
|
const body = document.getElementsByTagName("body")[0];
|
||||||
|
const style = window.getComputedStyle(body);
|
||||||
|
const outline_color = style.getPropertyValue("--theme-model-line-color");
|
||||||
|
const model_color = style.getPropertyValue("--theme-model-bg-color");
|
||||||
|
|
||||||
|
// // Init scene
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color( model_color);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera( 70, canvas_rect.width / canvas_rect.height, 0.01, 1000 );
|
||||||
|
camera.position.set( 0, 200, 200 );
|
||||||
|
camera.lookAt(0,0,0);
|
||||||
|
scene.add( camera );
|
||||||
|
|
||||||
|
scene.add( new THREE.AmbientLight( 0xf0f0f0, 3 ) );
|
||||||
|
const light = new THREE.SpotLight( 0xffffff, 4.5 );
|
||||||
|
light.position.set( 0, 1500, 200 );
|
||||||
|
light.angle = Math.PI * 0.2;
|
||||||
|
light.decay = 0;
|
||||||
|
light.castShadow = true;
|
||||||
|
light.shadow.camera.near = 200;
|
||||||
|
light.shadow.camera.far = 2000;
|
||||||
|
light.shadow.bias = - 0.000222;
|
||||||
|
light.shadow.mapSize.width = 1024;
|
||||||
|
light.shadow.mapSize.height = 1024;
|
||||||
|
scene.add( light );
|
||||||
|
|
||||||
|
// Texture loading
|
||||||
|
const loader = new THREE.TextureLoader();
|
||||||
|
|
||||||
|
function load_texture(url) {
|
||||||
|
const texture = loader.load(url);
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sphere
|
||||||
|
const geometry = new THREE.SphereGeometry( 100, 1000, 1000 );
|
||||||
|
const earth_materials = {
|
||||||
|
earth1k: new THREE.MeshPhongMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
map: load_texture("/assets/blog/sphere_geodesics/earthmap1k.jpg"),
|
||||||
|
bumpMap: load_texture("/assets/blog/sphere_geodesics/earthbump1k.jpg"),
|
||||||
|
displacementMap: load_texture("/assets/blog/sphere_geodesics/earthbump1k.jpg"),
|
||||||
|
emissiveMap: load_texture("/assets/blog/sphere_geodesics/earthlights1k.jpg"),
|
||||||
|
specularMap: load_texture("/assets/blog/sphere_geodesics/earthspec1k.jpg"),
|
||||||
|
emissive: 0xffffff,
|
||||||
|
}),
|
||||||
|
earth10k: new THREE.MeshPhongMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
map: load_texture("/assets/blog/sphere_geodesics/earthmap10k.jpg"),
|
||||||
|
bumpMap: load_texture("/assets/blog/sphere_geodesics/earthbump10k.jpg"),
|
||||||
|
displacementMap: load_texture("/assets/blog/sphere_geodesics/earthbump10k.jpg"),
|
||||||
|
emissiveMap: load_texture("/assets/blog/sphere_geodesics/earthlights10k.jpg"),
|
||||||
|
specularMap: load_texture("/assets/blog/sphere_geodesics/earthspec10k.jpg"),
|
||||||
|
emissive: 0xffffff,
|
||||||
|
}),
|
||||||
|
checkerboard: new THREE.MeshBasicMaterial({
|
||||||
|
onBeforeCompile: shader => {
|
||||||
|
shader.fragmentShader = `${shader.fragmentShader}`
|
||||||
|
.replace(
|
||||||
|
`vec4 diffuseColor = vec4( diffuse, opacity );`,
|
||||||
|
`
|
||||||
|
float chCount = 15.;
|
||||||
|
float checker = (1. / chCount);
|
||||||
|
float actualCheckers = 1. - checker;
|
||||||
|
float halfChecker = checker * 0.5;
|
||||||
|
vec2 bUv = (vUv * actualCheckers) - halfChecker;
|
||||||
|
vec2 cUv = fract((bUv) * (chCount * 0.5)) - 0.5;
|
||||||
|
float checkerVal = clamp(step(cUv.x * cUv.y, 0.), 0.5, 1.);
|
||||||
|
vec3 col = vec3(checkerVal);
|
||||||
|
vec4 diffuseColor = vec4( col, opacity );
|
||||||
|
`
|
||||||
|
);},
|
||||||
|
opacity: 0.9,
|
||||||
|
transparent: true,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
earth_materials["checkerboard"].defines = {"USE_UV":""};
|
||||||
|
|
||||||
|
const sphere = new THREE.Mesh(geometry, earth_materials["earth1k"]);
|
||||||
|
scene.add( sphere );
|
||||||
|
|
||||||
|
|
||||||
|
// const planeGeometry = new THREE.PlaneGeometry( 2000, 2000 );
|
||||||
|
// planeGeometry.rotateX( - Math.PI / 2 );
|
||||||
|
// const planeMaterial = new THREE.ShadowMaterial( { color: 0x000000, opacity: 0.2 } );
|
||||||
|
|
||||||
|
// const plane = new THREE.Mesh( planeGeometry, planeMaterial );
|
||||||
|
// plane.position.y = - 200;
|
||||||
|
// plane.receiveShadow = true;
|
||||||
|
// scene.add( plane );
|
||||||
|
|
||||||
|
// const helper = new THREE.GridHelper( 2000, 100 );
|
||||||
|
// helper.position.y = - 199;
|
||||||
|
// helper.material.opacity = 0.25;
|
||||||
|
// helper.material.transparent = true;
|
||||||
|
// scene.add( helper );
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer( {canvas: canvas, antialias: true},
|
||||||
|
(_renderer) => {
|
||||||
|
// best practice: ensure output colorspace is in sRGB, see Color Management documentation:
|
||||||
|
// https://threejs.org/docs/#manual/en/introduction/Color-management
|
||||||
|
_renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||||
|
})
|
||||||
|
renderer.setPixelRatio( window.devicePixelRatio );
|
||||||
|
renderer.setSize(canvas_rect.width, canvas_rect.height, false);
|
||||||
|
renderer.shadowMap.enabled = true;
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
const controls = new OrbitControls(camera, canvas);
|
||||||
|
controls.damping = 0.2;
|
||||||
|
controls.autoRotate = true;
|
||||||
|
controls.autoRotateSpeed = 2;
|
||||||
|
controls.target.set(0, 0, 0);
|
||||||
|
|
||||||
|
// If not using render loop, render on changes
|
||||||
|
// controls.addEventListener( 'change', render);
|
||||||
|
// window.addEventListener('resize', render);
|
||||||
|
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
|
||||||
|
// Render single frame
|
||||||
|
function render() {
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render loop
|
||||||
|
function loop() {
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
controls.update();
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
loop();
|
||||||
|
|
||||||
|
const gui = new GUI({
|
||||||
|
title: "Settings",
|
||||||
|
container: container,
|
||||||
|
injectStyles: false,
|
||||||
|
closeFolders: true,
|
||||||
|
});
|
||||||
|
const params = {
|
||||||
|
Material: "earth1k",
|
||||||
|
rotate: true,
|
||||||
|
emissiveIntensity: 0,
|
||||||
|
emissiveColor: 0xffcd75,
|
||||||
|
specularColor: 0xfbf9c1,
|
||||||
|
displacementScale: 1,
|
||||||
|
bumpScale: 1,
|
||||||
|
}
|
||||||
|
gui.add(params, "Material", earth_materials)
|
||||||
|
.onChange(material => {sphere.material = material});
|
||||||
|
|
||||||
|
gui.add(params, 'rotate')
|
||||||
|
.onChange(value => {controls.autoRotate = value});
|
||||||
|
|
||||||
|
gui.add(params, 'emissiveIntensity', 0, 10)
|
||||||
|
.onChange(value => {sphere.material.emissiveIntensity = value});
|
||||||
|
|
||||||
|
gui.add(params, 'displacementScale', 0, 10)
|
||||||
|
.onChange(value => {sphere.material.displacementScale = value});
|
||||||
|
|
||||||
|
gui.add(params, 'bumpScale', 0, 10)
|
||||||
|
.onChange(value => {sphere.material.bumpScale = value});
|
||||||
|
|
||||||
|
gui.addColor(params, 'emissiveColor')
|
||||||
|
.onChange(color => {sphere.material.emissive = new THREE.Color(color)});
|
||||||
|
|
||||||
|
gui.addColor(params, 'specularColor')
|
||||||
|
.onChange(color => {sphere.material.specular = new THREE.Color(color)});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render(height) {
|
||||||
|
this.shadow.innerHTML = `
|
||||||
|
<div id="container">
|
||||||
|
<canvas class = "object-viewer"></canvas>
|
||||||
|
</div>
|
||||||
|
<link rel="stylesheet" href="/node_modules/lil-gui/dist/lil-gui.min.css">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
#container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lil-gui .title {height: 2em;}
|
||||||
|
.lil-gui.root {
|
||||||
|
margin-top: calc(${height}px - 2em);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
--background-color: none;
|
||||||
|
--text-color: var(--theme-text-color);
|
||||||
|
--title-background-color: none;
|
||||||
|
--title-text-color: var(--theme-text-color);
|
||||||
|
--widget-color: var(--theme-subtle-outline);
|
||||||
|
--hover-color: lightgrey;
|
||||||
|
--focus-color: lightgrey;
|
||||||
|
--number-color: #2cc9ff;
|
||||||
|
--string-color: #a2db3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lil-gui button {
|
||||||
|
border: var(--theme-subtle-outline) 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: ${height}px;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("sphere-viewer", SphereViewer);
|