update projects

This commit is contained in:
Tom 2023-11-28 13:48:17 +00:00
parent d5bd1a236e
commit 256c398c04
13 changed files with 196 additions and 141 deletions

View File

@ -18,17 +18,37 @@ head: |
{ {
"imports": { "imports": {
"three": "/node_modules/three/build/three.module.min.js", "three": "/node_modules/three/build/three.module.min.js",
"three/addons/": "/node_modules//three/examples/jsm/", "three/addons/": "/node_modules/three/examples/jsm/",
"dat.gui": "https://unpkg.com/dat.gui@0.7.9/build/dat.gui.module.js" "lil-gui": "/node_modules/lil-gui/dist/lil-gui.esm.min.js"
} }
} }
</script> </script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script> <script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
--- ---
<outline-model-viewer model = "/assets/projects/bike_lights/models/bigger.glb" zoom=37> <outline-model-viewer model = "/assets/projects/bike_lights/models/bigger.glb" zoom=37>
<img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg"> <img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg">
<p class="has-wc">Loading model...</p> <p class="has-wc">Loading model...</p>
</outline-model-viewer> </outline-model-viewer>
<img src = "/assets/projects/bike_lights/bike_light.jpg"> I've been playing around with making dynamo bike lights for my bike for a while now.
The first iteration, the imperfect but actually on my bike and works version... is a rectifier and dc-dc converter soldered to some perfboard covered in a ziplock bag with some holes pocked in it. The DC-DC converter is actually a lipo battery charger with potentiometers for setting the maximum current and voltage. I just hooked it up to a power resistor and fiddled until I got it in current limited mode pushing about 2-3W through one of these 3W white LEDs you can get for peanuts on ebay.
Running those LEDs bare has upsides and downsides, they act like a point source and are pretty bright, so anyone within a 180 degree arc ahead of you is going to be able to see you very well. However you're sending a lot of the light up towards the sky where it probably bounces off a starlink satellite directly into the telescope of some poor astronomer.
This light distribution also isn't that good for seeing the road. Normally in London there's enough street lighting that this isn't a concern (for anyone except the aforementioned astronomers). But it has become more relevant to me recently because I've started commuting along an unlit canal in the winter and this canal has lots of complex bits of pacing stones and other stuff I need to be able to see in order to avoid taking an unplanned ice bath.
Cue the CAD model at the top of the page, it's a thing to hold an LED and a cheap lens (again off ebay) and attach them to some bike handlebars. Here it is printed out.
<img src = "/assets/projects/bike_lights/bike_light.jpg" alt="An image of an LED and a lense mounted to a tube in a 3D printed case. The case is split in half so you can see what's inside.">
I hope it's obvious that there are actually two mirrored pieces and I'm just excluding one so that you can see inside.
The lenses come in different nominal spread angles from 90 to 5 degrees. So far 90 seems to work well, being wide enough that I'm still visible to others but concentrating the light enough that I can now see the canal towpath ahead of me pretty well in the pitch black.
## Future
This project is one of those ones I've had far too many ideas for and haven't done enough implementation.
- I'd like to add a battery pack for some energy storage
- I'd like to add a microcontroller to add some smarts like pulsing the light or displaying batter level.
- I'd like to do the above while still driving the LEDs efficiently from a varying input voltage. I wish I could buy an i2c controllable DC-DC converter off the shelf for that. Ah just found this [buck converting LED driver][buck] that might work.
[buck]: https://protofusion.org/wordpress/2013/03/picobuck-rgb-led-driver/

View File

@ -19,7 +19,7 @@ head: |
"imports": { "imports": {
"three": "/node_modules/three/build/three.module.min.js", "three": "/node_modules/three/build/three.module.min.js",
"three/addons/": "/node_modules//three/examples/jsm/", "three/addons/": "/node_modules//three/examples/jsm/",
"dat.gui": "https://unpkg.com/dat.gui@0.7.9/build/dat.gui.module.js" "lil-gui": "/node_modules/lil-gui/dist/lil-gui.esm.min.js"
} }
} }
</script> </script>
@ -30,3 +30,7 @@ head: |
<img class="outline-model-poster no-wc" src = "{{page.img.src}}"> <img class="outline-model-poster no-wc" src = "{{page.img.src}}">
<p class="has-wc">Loading model...</p> <p class="has-wc">Loading model...</p>
</outline-model-viewer> </outline-model-viewer>
Similar to the [bike lights](/projects/bike_lights) project, I've made a few adapters to attach lights to helmets over time. Here's the latest iteration. The light is an off the shelf model that you can slip out the side to recharge it. The two pieces fit snugly in the front air hole in the helmet.
<img src = "/assets/projects/helmet_lights/with_helmet_model.png" alt="A cad model of the mounts attached to a kinda poor quality 3D scan of the helmet">

View File

@ -11,3 +11,15 @@ img:
social_image: /assets/projects/lego_adapters/thumbnail.png social_image: /assets/projects/lego_adapters/thumbnail.png
--- ---
I've been looking at using Lego as a way to quickly prototype ideas.
## Magnetic Encoder
![The pieces of the mount for encoder board split. One half holds the magnet with an adapter for lego shafts and the other holds the PCB with the endcoder on it.](/assets/projects/lego_adapters/encoder/encoder_two_parts.jpg)
![The encoder and a custom lego motor mounted to a beam with a gear train linking them together and to a lego wheel.](/assets/projects/lego_adapters/encoder/wheel_motor_and_encoder.jpg)
## Servo Mount
![An adapter for a 9g servo to lego. It has holes for studs to click into and I'm working on designing a lego compatible gear that can mount ](/assets/projects/lego_adapters/thumbnail.svg)

View File

@ -10,5 +10,41 @@ img:
class: invertable class: invertable
social_image: /assets/projects/vector_magnet/thumbnail.png social_image: /assets/projects/vector_magnet/thumbnail.png
---
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/",
"lil-gui": "/node_modules/lil-gui/dist/lil-gui.esm.min.js"
}
}
</script>
<script src="/assets/js/outline-model-viewer/index.js" type="module"></script>
---
<outline-model-viewer model = "/assets/blog/vector_magnet/vector_magnet.glb" zoom=500 camera='{"position":[-4.187,2.613,-9.927],"rotation":[-2.895,-0.3904,-3.046],"zoom":715.9863905262143,"target":[0.02078,0.1128,-0.01309]}'>
<img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>
The project centered around the use of a Nitrogen-Vancancy defect in a nanoscale diamond to detect magnetic fields with ultra high resolution. We experimented with mounting such a nano-diamond to the tip of an atomic force microscope in order to produce field images. I built a 3d vector magnetometer in order to determine the axis of a defect in a nano-diamond.
Check out a little interactive model of the magnetometer below. The device has three pairs of copper Helmholtz coils that generate controlled, linear, magnetic fields in all three directions.
Here's a cutaway view of the interior.
<outline-model-viewer model = "/assets/blog/vector_magnet/vector_magnet_section.glb" zoom=500 spin=false camera='{"position":[-3.078,3.353,10.11],"rotation":[-0.309,-0.2822,-0.08866],"zoom":9794.920097823839,"target":[0.0006876,0.1232,-0.005368]}' true-color=true>
<img class="outline-model-poster no-wc" src = "/assets/projects/bike_lights/thumbnail.svg">
<p class="has-wc">Loading model...</p>
</outline-model-viewer>
I should add some color to this but roughly you have:
**AFM Tip**: The atomic force microscope tip (dark blue) with a nano-diamond attached to the very tip. We want to figure out which was the axis the NV defect in this nano-diamond is pointing. To do that we need to expose it to different directions of magnetic field while also blasting it with light and radio waves.
**PCB coil** For the radio wave blasting we have a single turn coil made on a PCB (green). I haven't cut the coil away so that you can see it's whole shape. We'll pump RF power into this tuned to the electronic transitions in the NV defect that we want to probe.
**Microscope Objective** The microscope objective (lower with blue strip) allows us to optically pump the transitions in the NV defect (much like a laser) in order to keep electrons in excited quantum states that they wouldn't normally sit in.

View File

@ -21,7 +21,6 @@
box-sizing: border-box; box-sizing: border-box;
font-family: $font_stack; font-family: $font_stack;
text-rendering: geometricPrecision; text-rendering: geometricPrecision;
} }
html { html {
@ -30,13 +29,18 @@ html {
} }
body { body {
background: #fcfcfc; --theme-text-color: #222;
color: #222; --theme-bg-color: #fcfcfc;
--theme-model-line-color: #222;
--theme-model-bg-color: #fcfcfc;
--theme-subtle-outline: oklch(90% 0.0 50);
background: var(--theme-bg-color);
color: var(--theme-text-color);
// constrain width and center // constrain width and center
max-width: 900px; max-width: 900px;
margin: auto; margin: auto;
} }
// Padding to keep the keep the content to the right of the header // Padding to keep the keep the content to the right of the header
@ -56,7 +60,11 @@ main {
h1 { h1 {
font-size: 2em; font-size: 2em;
} }
// img that are direct children of p are usually img tags in markdown
p > img { p > img {
margin-top: 2em;
margin-bottom: 1em;
width: 90%; width: 90%;
//hack to center images in p tags //hack to center images in p tags
@ -79,7 +87,7 @@ a {
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
text-decoration-thickness: 0.5px; text-decoration-thickness: 0.5px;
color: #222; color: var(--theme-text-color);
} }
header a, nav a { header a, nav a {
@ -113,7 +121,7 @@ figure > img, figure > svg, figure > canvas {
} }
// For fallback images inside custom outline-model-viewer elements // For fallback images inside custom outline-model-viewer elements
outline-model-viewer, .outline-model-poster { .outline-model-poster {
width: 100%; width: 100%;
height: 300px; height: 300px;
} }
@ -122,6 +130,11 @@ outline-model-viewer {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%;
border: var(--theme-subtle-outline) 1px solid;
border-radius: 10px;
margin-top: 1em;
margin-bottom: 1em;
} }
// If the browser doesn't support web components, hide anything with has-wc class // If the browser doesn't support web components, hide anything with has-wc class
@ -164,9 +177,10 @@ model-viewer {
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body, a{ body {
background: #222; --theme-text-color: #fcfcfc;
color: #fcfcfc; --theme-bg-color: #222;
--theme-subtle-outline: oklch(50% 0.0 50);
} }
img { img {
opacity: .75; opacity: .75;

View File

@ -166,6 +166,18 @@ class CustomOutlinePass extends Pass {
return surfaceIdDiff; return surfaceIdDiff;
} }
const uint k = 1103515245U; // GLIB C
vec3 hash( vec3 f )
{
uvec3 x = floatBitsToUint(f);
x = ((x>>8U)^x.yzx)*k;
x = ((x>>8U)^x.yzx)*k;
x = ((x>>8U)^x.yzx)*k;
return vec3(x)/float(0xffffffffU);
}
void main() { void main() {
vec4 sceneColor = texture2D(sceneColorBuffer, vUv); vec4 sceneColor = texture2D(sceneColorBuffer, vUv);
float depth = getPixelDepth(0, 0); float depth = getPixelDepth(0, 0);
@ -207,7 +219,7 @@ class CustomOutlinePass extends Pass {
} }
if (debugVisualize == 4) { if (debugVisualize == 4) {
// 4 visualizes the surfaceID buffer // 4 visualizes the surfaceID buffer
gl_FragColor = vec4(surfaceValue, 1.0); gl_FragColor = vec4(hash(surfaceValue), 1.0);
} }
if (debugVisualize == 5) { if (debugVisualize == 5) {
// Outlines only // Outlines only

View File

@ -1,6 +1,6 @@
import * as THREE from "three"; import * as THREE from "three";
import * as dat from 'dat.gui'; // import * as dat from 'dat.gui';
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@ -9,6 +9,7 @@ import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js"; import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
import { FXAAShader } from "three/addons/shaders/FXAAShader.js"; import { FXAAShader } from "three/addons/shaders/FXAAShader.js";
import GUI from 'lil-gui'
import { CustomOutlinePass } from "./CustomOutlinePass.js"; import { CustomOutlinePass } from "./CustomOutlinePass.js";
import FindSurfaces from "./FindSurfaces.js"; import FindSurfaces from "./FindSurfaces.js";
@ -19,81 +20,24 @@ import FindSurfaces from "./FindSurfaces.js";
// Fix the cetnering and scaling // Fix the cetnering and scaling
// Switch to an angled isometric camera to match the style from the main page. // Switch to an angled isometric camera to match the style from the main page.
const fitCameraToCenteredObject = function (camera, object, offset, orbitControls ) { const serialiseCamera = (camera, controls) => {
const boundingBox = new THREE.Box3(); const position = Object.values(camera.position);
boundingBox.setFromObject( object ); const extractXYZ = ({_x, _y, _z}) => [_x, _y, _z];
const rotation = extractXYZ(camera.rotation);
var middle = new THREE.Vector3(); const fixed = (l) => l.map( x => parseFloat(x.toPrecision(4)))
var size = new THREE.Vector3(); return JSON.stringify({
boundingBox.getSize(size); position: fixed(position),
rotation: fixed(rotation),
// figure out how to fit the box in the view: zoom: camera.zoom,
// 1. figure out horizontal FOV (on non-1.0 aspects) target: fixed(Object.values(controls.target)),
// 2. figure out distance from the object in X and Y planes });
// 3. select the max distance (to fit both sides in)
//
// The reason is as follows:
//
// Imagine a bounding box (BB) is centered at (0,0,0).
// Camera has vertical FOV (camera.fov) and horizontal FOV
// (camera.fov scaled by aspect, see fovh below)
//
// Therefore if you want to put the entire object into the field of view,
// you have to compute the distance as: z/2 (half of Z size of the BB
// protruding towards us) plus for both X and Y size of BB you have to
// figure out the distance created by the appropriate FOV.
//
// The FOV is always a triangle:
//
// (size/2)
// +--------+
// | /
// | /
// | /
// | F° /
// | /
// | /
// | /
// |/
//
// F° is half of respective FOV, so to compute the distance (the length
// of the straight line) one has to: `size/2 / Math.tan(F)`.
//
// FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
// the camera.fov is the vertical FOV.
const fov = camera.fov * ( Math.PI / 180 );
const fovh = 2*Math.atan(Math.tan(fov/2) * camera.aspect);
let dx = size.z / 2 + Math.abs( size.x / 2 / Math.tan( fovh / 2 ) );
let dy = size.z / 2 + Math.abs( size.y / 2 / Math.tan( fov / 2 ) );
let cameraZ = Math.max(dx, dy);
// offset the camera, if desired (to avoid filling the whole canvas)
if( offset !== undefined && offset !== 0 ) cameraZ *= offset;
camera.position.set( 0, 0, cameraZ );
// set the far plane of the camera so that it easily encompasses the whole object
const minZ = boundingBox.min.z;
const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;
camera.far = cameraToFarEdge * 3;
camera.updateProjectionMatrix();
if ( orbitControls !== undefined ) {
// set camera to rotate around the center
orbitControls.target = new THREE.Vector3(0, 0, 0);
// prevent camera from zooming out far enough to create far plane cutoff
orbitControls.maxDistance = cameraToFarEdge * 2;
}
}; };
const setupDebug = () => { const setupDebug = (container, customOutline, camera, controls) => {
// Set up GUI controls const gui = new GUI({
const GUI = dat.GUI; container: container,
const gui = new GUI({ width: 300, autoPlace: true}); width: "100%",
// container.append(gui.domElement); });
const uniforms = customOutline.fsQuad.material.uniforms; const uniforms = customOutline.fsQuad.material.uniforms;
const params = { const params = {
@ -102,8 +46,11 @@ const setupDebug = () => {
depthMult: uniforms.multiplierParameters.value.y, depthMult: uniforms.multiplierParameters.value.y,
normalBias: uniforms.multiplierParameters.value.z, normalBias: uniforms.multiplierParameters.value.z,
normalMult: uniforms.multiplierParameters.value.w, normalMult: uniforms.multiplierParameters.value.w,
printCamera: () => console.log(serialiseCamera(camera, controls)),
}; };
gui.add(params, 'printCamera' );
gui gui
.add(params.mode, "Mode", { .add(params.mode, "Mode", {
"Outlines": 0, "Outlines": 0,
@ -122,12 +69,12 @@ const setupDebug = () => {
gui.add(params, "depthMult", 0.0, 20).onChange(function (value) { gui.add(params, "depthMult", 0.0, 20).onChange(function (value) {
uniforms.multiplierParameters.value.y = value; uniforms.multiplierParameters.value.y = value;
}); });
gui.add(params, "normalBias", 0.0, 20).onChange(function (value) { // gui.add(params, "normalBias", 0.0, 20).onChange(function (value) {
uniforms.multiplierParameters.value.z = value; // uniforms.multiplierParameters.value.z = value;
}); // });
gui.add(params, "normalMult", 0.0, 10).onChange(function (value) { // gui.add(params, "normalMult", 0.0, 10).onChange(function (value) {
uniforms.multiplierParameters.value.w = value; // uniforms.multiplierParameters.value.w = value;
}); // });
} }
class OutlineModelViewer extends HTMLElement { class OutlineModelViewer extends HTMLElement {
@ -138,6 +85,8 @@ class OutlineModelViewer extends HTMLElement {
this.render(); this.render();
const model_path = this.getAttribute("model") || "/assets/projects/bike_lights/models/bigger.glb"; const model_path = this.getAttribute("model") || "/assets/projects/bike_lights/models/bigger.glb";
const spin = (this.getAttribute("spin") || 'true') === 'true'
const container = this.shadow.querySelector("div#container"); const container = this.shadow.querySelector("div#container");
console.log(container); console.log(container);
const canvas = this.shadow.querySelector("canvas"); const canvas = this.shadow.querySelector("canvas");
@ -145,32 +94,21 @@ class OutlineModelViewer extends HTMLElement {
let canvas_rect = canvas.getBoundingClientRect(); let canvas_rect = canvas.getBoundingClientRect();
console.log(canvas_rect); console.log(canvas_rect);
// determine the outline and bg colors
const body = document.getElementsByTagName("body")[0]; const body = document.getElementsByTagName("body")[0];
const style = window.getComputedStyle(body); const style = window.getComputedStyle(body);
const background_color = style.getPropertyValue("background-color"); const outline_color = style.getPropertyValue("--theme-model-line-color");
const model_color = style.getPropertyValue("--theme-model-bg-color");
const isDark = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
const outline_color = isDark ? 0xffffff : 0x000000;
const model_color = background_color;
// // Init scene // // Init scene
// const camera = new THREE.PerspectiveCamera( // const camera = new THREE.PerspectiveCamera(70, canvas_rect.width / canvas_rect.height, 0.1, 100);
// 70,
// canvas_rect.width / canvas_rect.height,
// 0.1,
// 100
// );
const camera = new THREE.OrthographicCamera( canvas_rect.width / - 2, canvas_rect.width / 2, canvas_rect.height / 2, canvas_rect.height / - 2, 1, 1000 ); const camera = new THREE.OrthographicCamera( canvas_rect.width / - 2, canvas_rect.width / 2, canvas_rect.height / 2, canvas_rect.height / - 2, 1, 1000 );
camera.zoom = parseFloat(this.getAttribute("zoom") || "1") camera.zoom = parseFloat(this.getAttribute("zoom") || "1")
console.log(camera.zoom)
camera.position.set(10, 2.5, 4); camera.position.set(10, 2.5, 4);
// create the scene and the camera // create the scene and the camera
const scene = new THREE.Scene(); const scene = new THREE.Scene();
// override the model material to be a flat color
// scene.overrideMaterial = new THREE.MeshBasicMaterial({color: model_color});
const renderer = new THREE.WebGLRenderer({ const renderer = new THREE.WebGLRenderer({
canvas: canvas, canvas: canvas,
alpha: true, alpha: true,
@ -178,10 +116,13 @@ class OutlineModelViewer extends HTMLElement {
// renderer.setPixelRatio( window.devicePixelRatio ); // renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(canvas_rect.width, canvas_rect.height, false); renderer.setSize(canvas_rect.width, canvas_rect.height, false);
const light = new THREE.DirectionalLight(0xffffff, 1);
const light = new THREE.DirectionalLight(0xffffff, 2);
scene.add(light); scene.add(light);
light.position.set(1.7, 1, -1); light.position.set(1.7, 1, -1);
scene.add(new THREE.AmbientLight(0xffffff, 5));
// Set up post processing // Set up post processing
// Create a render target that holds a depthTexture so we can use it in the outline pass // Create a render target that holds a depthTexture so we can use it in the outline pass
// See: https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderTarget.depthBuffer // See: https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderTarget.depthBuffer
@ -232,11 +173,9 @@ class OutlineModelViewer extends HTMLElement {
new THREE.BufferAttribute(colorsTypedArray, 4) new THREE.BufferAttribute(colorsTypedArray, 4)
); );
//override materials const material_params = this.getAttribute("true-color") ? {color: node.material.color} : {emissive: model_color};
node.material = new THREE.MeshStandardMaterial({ // override materials
emissive: model_color, node.material = new THREE.MeshStandardMaterial(material_params);
// opacity: node.material.transparent ? 0 : 1
});
} }
}); });
@ -245,17 +184,17 @@ class OutlineModelViewer extends HTMLElement {
// Set up orbital camera controls. // Set up orbital camera controls.
let controls = new OrbitControls(camera, renderer.domElement); let controls = new OrbitControls(camera, renderer.domElement);
controls.autoRotate = true; controls.autoRotate = spin;
let bbox = new THREE.Box3().setFromObject(scene);
let middle = new THREE.Vector3();
bbox.getCenter(middle);
// // Center it
scene.applyMatrix4(new THREE.Matrix4().makeTranslation( -middle.x, -middle.y, -middle.z ) );
// fitCameraToCenteredObject(camera, scene, 1, controls );
controls.update(); controls.update();
if(this.getAttribute("camera")) {
const cameraState = JSON.parse(this.getAttribute("camera"));
camera.zoom = cameraState.zoom;
camera.position.set(...cameraState.position);
camera.rotation.set(...cameraState.rotation);
controls.target.set(...cameraState.target);
}
// Render loop // Render loop
function update() { function update() {
requestAnimationFrame(update); requestAnimationFrame(update);
@ -278,15 +217,14 @@ class OutlineModelViewer extends HTMLElement {
.5 / canvas_rect.height .5 / canvas_rect.height
); );
} }
window.addEventListener("resize", onWindowResize, false); window.addEventListener("resize", onWindowResize, false);
if(this.getAttribute("debug")) setupDebug();
document.addEventListener("keydown", onDocumentKeyDown, false); if(this.hasAttribute("debug")) {
function onDocumentKeyDown(event) { setupDebug(
if (event.key == " ") { this.shadow.querySelector("div#container"),
console.log(camera.toJSON()); customOutline,
} camera,
controls);
} }
} }
@ -295,10 +233,6 @@ class OutlineModelViewer extends HTMLElement {
this.shadow.innerHTML = ` this.shadow.innerHTML = `
<div id="container"> <div id="container">
<canvas class = "object-viewer"></canvas> <canvas class = "object-viewer"></canvas>
<details>
<summary>Debug</summary>
<input id="outline" type="range" min="0.5" max="4" step="0.1" value="2" />
</details>
</div> </div>
<style> <style>
@ -307,7 +241,7 @@ class OutlineModelViewer extends HTMLElement {
} }
#container { #container {
width: 90%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -319,6 +253,7 @@ class OutlineModelViewer extends HTMLElement {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
</style> </style>
<link rel="stylesheet" href="/node_modules/lil-gui/dist/lil-gui.min.css">
`; `;
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 KiB

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

15
credits.md Normal file
View File

@ -0,0 +1,15 @@
---
title: Credits
layout: default
permalink: /credits
excerpt: |
This page serves as a narrative list of influences and stolen ideas for this site.
---
## Credits
The layout and css for this site was *heavily* inspired by [Tim Holman's](https://tholman.com/).
The code that generates the 3D wireframe renders on the [project pages](/projects) was written by [Omar Shehata][lines], he helpfully packaged the code up so others could ~~steal~~ use it [here][lines_github].
[lines]: https://omar-shehata.medium.com/better-outline-rendering-using-surface-ids-with-webgl-e13cdab1fd94
[lines_github]: https://github.com/OmarShehata/webgl-outlines/

6
package-lock.json generated
View File

@ -6,6 +6,7 @@
"": { "": {
"dependencies": { "dependencies": {
"es-module-shims": "^1.8.1", "es-module-shims": "^1.8.1",
"lil-gui": "^0.19.1",
"mathjax": "^3.2.2", "mathjax": "^3.2.2",
"three": "^0.158.0" "three": "^0.158.0"
} }
@ -15,6 +16,11 @@
"resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.8.1.tgz", "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.8.1.tgz",
"integrity": "sha512-egouQrkryAJpKHVDZICQq5+fW4z1RomzVJpicA+8yqUHzKkTuMeoHaNIZ7PsWDnRl0ImCEVJEpW/aVb6JYKVJg==" "integrity": "sha512-egouQrkryAJpKHVDZICQq5+fW4z1RomzVJpicA+8yqUHzKkTuMeoHaNIZ7PsWDnRl0ImCEVJEpW/aVb6JYKVJg=="
}, },
"node_modules/lil-gui": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.19.1.tgz",
"integrity": "sha512-9dbIg+UxS8RIROI6OH5gV2KrVE0Cn37bcLOQGF2GKN8ibTxDrUSLzzZfkQR82LnZsgs7DEZOOGfn3zhtd6hk0Q=="
},
"node_modules/mathjax": { "node_modules/mathjax": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.2.tgz", "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.2.tgz",

View File

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"es-module-shims": "^1.8.1", "es-module-shims": "^1.8.1",
"lil-gui": "^0.19.1",
"mathjax": "^3.2.2", "mathjax": "^3.2.2",
"three": "^0.158.0" "three": "^0.158.0"
} }