diff --git a/.gitignore b/.gitignore
index 6521db3..4d24604 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ _site
.sass-cache
.jekyll-cache
.jekyll-metadata
+.jampack/
.obsidian/
Gemfile.lock
vendor
diff --git a/_posts/2023-11-9-this-site.md b/_posts/2023-11-9-this-site.md
index d5d885a..c6786a3 100644
--- a/_posts/2023-11-9-this-site.md
+++ b/_posts/2023-11-9-this-site.md
@@ -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 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).
\ No newline at end of file
+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).
\ No newline at end of file
diff --git a/_posts/2024-03-04-book-shelf.md b/_posts/2024-03-04-book-shelf.md
index ed16a84..fd348eb 100644
--- a/_posts/2024-03-04-book-shelf.md
+++ b/_posts/2024-03-04-book-shelf.md
@@ -10,6 +10,7 @@ assets: /assets/blog/bookshelf/
alt:
head:
---
+
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.
diff --git a/_posts/2024-05-17-threejs-earth.md b/_posts/2024-05-17-threejs-earth.md
new file mode 100644
index 0000000..9e48c7a
--- /dev/null
+++ b/_posts/2024-05-17-threejs-earth.md
@@ -0,0 +1,26 @@
+---
+title: ThreeJS Earth
+layout: post
+excerpt: A small earth renderer made using ThreeJS
+
+image:
+thumbnail:
+assets:
+alt:
+
+head: |
+
+
+
+---
+A small earth renderer made using ThreeJS.
+
\ No newline at end of file
diff --git a/assets/blog/sphere_geodesics/earthbump10k.jpg b/assets/blog/sphere_geodesics/earthbump10k.jpg
new file mode 100644
index 0000000..db38269
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthbump10k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthbump1k.jpg b/assets/blog/sphere_geodesics/earthbump1k.jpg
new file mode 100644
index 0000000..7b1a989
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthbump1k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthcloudmap.jpg b/assets/blog/sphere_geodesics/earthcloudmap.jpg
new file mode 100644
index 0000000..db94d15
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthcloudmap.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthcloudmaptrans.jpg b/assets/blog/sphere_geodesics/earthcloudmaptrans.jpg
new file mode 100644
index 0000000..575ba82
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthcloudmaptrans.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthlights10k.jpg b/assets/blog/sphere_geodesics/earthlights10k.jpg
new file mode 100644
index 0000000..b503993
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthlights10k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthlights1k.jpg b/assets/blog/sphere_geodesics/earthlights1k.jpg
new file mode 100644
index 0000000..ff77ffd
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthlights1k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthmap10k.jpg b/assets/blog/sphere_geodesics/earthmap10k.jpg
new file mode 100644
index 0000000..812458c
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthmap10k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthmap1k.jpg b/assets/blog/sphere_geodesics/earthmap1k.jpg
new file mode 100644
index 0000000..7dcab8a
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthmap1k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthspec10k.jpg b/assets/blog/sphere_geodesics/earthspec10k.jpg
new file mode 100644
index 0000000..c2eea0e
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthspec10k.jpg differ
diff --git a/assets/blog/sphere_geodesics/earthspec1k.jpg b/assets/blog/sphere_geodesics/earthspec1k.jpg
new file mode 100644
index 0000000..1d71646
Binary files /dev/null and b/assets/blog/sphere_geodesics/earthspec1k.jpg differ
diff --git a/assets/blog/sphere_geodesics/index.js b/assets/blog/sphere_geodesics/index.js
new file mode 100644
index 0000000..836c9db
--- /dev/null
+++ b/assets/blog/sphere_geodesics/index.js
@@ -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 = `
+
+
+
+
+
+ `;
+ }
+}
+
+customElements.define("sphere-viewer", SphereViewer);
\ No newline at end of file