From c8b3f1902a26e55985f4d564ecb2f4fcda63a133 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Tue, 28 May 2024 17:40:30 +1000 Subject: [PATCH 01/13] Added TaperedLink class, still very broken but I think this approach to properly-scaled links is viable --- fourDShape.js | 26 +++++++++----------------- gui.js | 2 +- main.js | 4 ++++ taperedLink.js | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 taperedLink.js diff --git a/fourDShape.js b/fourDShape.js index 888ebd7..3ccc923 100644 --- a/fourDShape.js +++ b/fourDShape.js @@ -1,9 +1,12 @@ import * as THREE from 'three'; +import { TaperedLink } from './taperedLink.js'; + const HYPERPLANE = 2.0; const W_FORESHORTENING = 0.04; + class FourDShape extends THREE.Group { constructor(node_ms, link_ms, face_ms, structure) { @@ -42,7 +45,7 @@ class FourDShape extends THREE.Group { return sphere; } - makeLink(material, link) { + makeLink(basematerial, link) { const n1 = this.nodes3[link.source]; const n2 = this.nodes3[link.target]; const s1 = n1.scale; @@ -50,17 +53,11 @@ class FourDShape extends THREE.Group { const length = n1.v3.distanceTo(n2.v3); const centre = new THREE.Vector3(); centre.lerpVectors(n1.v3, n2.v3, 0.5); - const geometry = new THREE.CylinderGeometry( - this.link_scale * s2, this.link_scale * s1, 1, - 16, 1, true - ); - const cyl = new THREE.Mesh(geometry, material); - const edge = new THREE.Group(); - edge.add(cyl); + + const edge = new TaperedLink(basematerial); + edge.update(s1, s2, length); edge.position.copy(centre); - edge.scale.copy(new THREE.Vector3(1, 1, length)); edge.lookAt(n2.v3); - cyl.rotation.x = Math.PI / 2.0; this.add(edge); return edge; } @@ -73,14 +70,9 @@ class FourDShape extends THREE.Group { const length = n1.v3.distanceTo(n2.v3); const centre = new THREE.Vector3(); centre.lerpVectors(n1.v3, n2.v3, 0.5); - // take the average of the ends as the thickness - as a workaround, - // because I haven't worked out how to reshape tapered links without - // having to reassign a new geometry to every link - const link_mean = this.link_scale * (s1 + s2) * 0.5; - link.object.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); - link.object.position.copy(centre); + link.object.update(s1, s2, length); + link.object.position.copy(n1.v3); link.object.lookAt(n2.v3); - link.object.children[0].rotation.x = Math.PI / 2.0; link.object.visible = (!links_show || link.label in links_show); } diff --git a/gui.js b/gui.js index baba2ea..57df9d4 100644 --- a/gui.js +++ b/gui.js @@ -7,7 +7,7 @@ const DEFAULTS = { linksize: 0.2, linkopacity: 0.75, link2opacity: 0.75, - shape: '120-cell', + shape: '16-cell', option: 'none', visibility: 5, inscribed: false, diff --git a/main.js b/main.js index 04d8497..b285ae2 100644 --- a/main.js +++ b/main.js @@ -31,6 +31,10 @@ camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize( window.innerWidth, window.innerHeight ); + +renderer.localClippingEnabled = true; + + document.body.appendChild( renderer.domElement ); // set up colours and materials for gui callbacks diff --git a/taperedLink.js b/taperedLink.js new file mode 100644 index 0000000..0ccfcf7 --- /dev/null +++ b/taperedLink.js @@ -0,0 +1,36 @@ +import * as THREE from 'three'; + + +class TaperedLink extends THREE.Group { + + constructor(baseMaterial) { + super(); + const geometry = new THREE.ConeGeometry( 0.75, 1, 32, true ); + const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); + const material = baseMaterial.clone(); + material.clippingPlanes = [ cplane ]; + this.object = new THREE.Mesh( geometry, material ); + this.add( this.object ); + } + + update(r1, r2, l) { + const kraw = ( r1 - r2 ); + const k = ( kraw == 0 ) ? 0.001 : kraw; + if( k > 0 ) { + const h = l * r1 / k; + this.object.scale.copy(new THREE.Vector3(r1, h, r1)); + this.object.material.clippingPlanes[0].normal.y = -1; + this.object.material.clippingPlanes[0].constant = l / 2; + this.object.position.copy(new THREE.Vector3(0, h/2 - l/2, 0)); + } else { + const h = l * r2 / k; + this.object.scale.copy(new THREE.Vector3(r2, h, r2)); + this.object.material.clippingPlanes[0].normal.y = 1; + this.object.material.clippingPlanes[0].constant = l / 2; + this.object.position.copy(new THREE.Vector3(0, h / 2 + l / 2, 0)); + } + } + +} + +export { TaperedLink }; From 6728908d18f1cbd454fbc4f0b2e13bf6442c63fa Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Tue, 28 May 2024 17:57:11 +1000 Subject: [PATCH 02/13] Link scaling --- fourDShape.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fourDShape.js b/fourDShape.js index 3ccc923..bc3bda0 100644 --- a/fourDShape.js +++ b/fourDShape.js @@ -48,8 +48,8 @@ class FourDShape extends THREE.Group { makeLink(basematerial, link) { const n1 = this.nodes3[link.source]; const n2 = this.nodes3[link.target]; - const s1 = n1.scale; - const s2 = n2.scale; + const s1 = this.link_scale * n1.scale; + const s2 = this.link_scale * n2.scale; const length = n1.v3.distanceTo(n2.v3); const centre = new THREE.Vector3(); centre.lerpVectors(n1.v3, n2.v3, 0.5); @@ -65,8 +65,8 @@ class FourDShape extends THREE.Group { updateLink(link, links_show) { const n1 = this.nodes3[link.source]; const n2 = this.nodes3[link.target]; - const s1 = n1.scale; - const s2 = n2.scale; + const s1 = this.link_scale * n1.scale; + const s2 = this.link_scale * n2.scale; const length = n1.v3.distanceTo(n2.v3); const centre = new THREE.Vector3(); centre.lerpVectors(n1.v3, n2.v3, 0.5); From a1fff090fc859ad35b320d370c18f56aa9db6e8e Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 9 Jun 2024 17:21:16 +1000 Subject: [PATCH 03/13] Committing this before trashing it maybe? --- fourDShape.js | 36 ++++++++++++++++++++--------------- gui.js | 8 ++++---- taperedLink.js | 51 ++++++++++++++++++++++++++++++++++---------------- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/fourDShape.js b/fourDShape.js index bc3bda0..609e1cc 100644 --- a/fourDShape.js +++ b/fourDShape.js @@ -50,16 +50,20 @@ class FourDShape extends THREE.Group { const n2 = this.nodes3[link.target]; const s1 = this.link_scale * n1.scale; const s2 = this.link_scale * n2.scale; - const length = n1.v3.distanceTo(n2.v3); - const centre = new THREE.Vector3(); - centre.lerpVectors(n1.v3, n2.v3, 0.5); - - const edge = new TaperedLink(basematerial); - edge.update(s1, s2, length); - edge.position.copy(centre); - edge.lookAt(n2.v3); - this.add(edge); + const edge = new TaperedLink(basematerial, n1, n2, s1, s2); + this.add( edge ); return edge; + // const length = n1.v3.distanceTo(n2.v3); + // const centre = nfew THREE.Vector3(); + // centre.lerpVectors(n1.v3, n2.v3, 0.5); + + // const edge = new TaperedLink(basematerial); + // edge.update(s1, s2, length); + // edge.position.copy(centre); + // edge.lookAt(n2.v3); + // edge.children[0].rotation.x = Math.PI / 2.0; + // this.add(edge); + // return edge; } updateLink(link, links_show) { @@ -67,12 +71,14 @@ class FourDShape extends THREE.Group { const n2 = this.nodes3[link.target]; const s1 = this.link_scale * n1.scale; const s2 = this.link_scale * n2.scale; - const length = n1.v3.distanceTo(n2.v3); - const centre = new THREE.Vector3(); - centre.lerpVectors(n1.v3, n2.v3, 0.5); - link.object.update(s1, s2, length); - link.object.position.copy(n1.v3); - link.object.lookAt(n2.v3); + link.object.update(n1, n2, s1, s2); + // const length = n1.v3.distanceTo(n2.v3); + // const centre = new THREE.Vector3(); + // centre.lerpVectors(n1.v3, n2.v3, 0.5); + // link.object.update(s1, s2, length); + // link.object.position.copy(n1.v3); + // link.object.lookAt(n2.v3); + // link.object.children[0].rotation.x = Math.PI / 2.0; link.object.visible = (!links_show || link.label in links_show); } diff --git a/gui.js b/gui.js index 57df9d4..06d83c9 100644 --- a/gui.js +++ b/gui.js @@ -2,9 +2,9 @@ import { GUI } from 'lil-gui'; const DEFAULTS = { - nodesize: 0.25, + nodesize: 4, nodeopacity: 1, - linksize: 0.2, + linksize: 1.0, linkopacity: 0.75, link2opacity: 0.75, shape: '16-cell', @@ -72,9 +72,9 @@ class FourDGUI { }); this.gui.add(this.params, 'hyperplane', 0.5, 1 / 0.8); this.gui.add(this.params, 'zoom', 0.1, 2.0); - this.gui.add(this.params, 'nodesize', 0, 1); + this.gui.add(this.params, 'nodesize', 0, 5); this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(setNodeOpacity); - this.gui.add(this.params, 'linksize', 0, 1); + this.gui.add(this.params, 'linksize', 0, 5); this.gui.add(this.params, 'linkopacity', 0, 1).onChange( (v) => setLinkOpacity(v, true) ); diff --git a/taperedLink.js b/taperedLink.js index 0ccfcf7..2473430 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -1,35 +1,54 @@ import * as THREE from 'three'; + class TaperedLink extends THREE.Group { - constructor(baseMaterial) { + constructor(baseMaterial, n1, n2, r1, r2) { super(); const geometry = new THREE.ConeGeometry( 0.75, 1, 32, true ); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); - material.clippingPlanes = [ cplane ]; +// material.clippingPlanes = [ cplane ]; this.object = new THREE.Mesh( geometry, material ); this.add( this.object ); + this.update(n1, n2, r1, r2); } - update(r1, r2, l) { - const kraw = ( r1 - r2 ); + update(n1, n2, r1, r2) { + const kraw = r1 - r2; const k = ( kraw == 0 ) ? 0.001 : kraw; - if( k > 0 ) { - const h = l * r1 / k; - this.object.scale.copy(new THREE.Vector3(r1, h, r1)); - this.object.material.clippingPlanes[0].normal.y = -1; - this.object.material.clippingPlanes[0].constant = l / 2; - this.object.position.copy(new THREE.Vector3(0, h/2 - l/2, 0)); - } else { - const h = l * r2 / k; - this.object.scale.copy(new THREE.Vector3(r2, h, r2)); - this.object.material.clippingPlanes[0].normal.y = 1; - this.object.material.clippingPlanes[0].constant = l / 2; - this.object.position.copy(new THREE.Vector3(0, h / 2 + l / 2, 0)); + let nbase = n1; + let napex = n2; + let rbase = r1; + let rapex = r2; + if( k < 0 ) { + nbase = n2; + napex = n1; + rbase = r2; + rapex = r1; } + const l = nbase.v3.distanceTo(napex.v3); + + const h = l * rbase / k; + //const pos = new THREE.Vector3(0, h/2 - l/2, 0); + //pos.add(nbase.v3); + this.object.scale.copy(new THREE.Vector3(rbase, h, rbase)); + this.object.position.copy(nbase.v3); + this.object.lookAt(napex.v3); + this.object.rotation.x = Math.PI / 2.0; } + // this.object.material.clippingPlanes[0].normal.y = -1; + // this.object.material.clippingPlanes[0].constant = l / 2; + //this.object.position.copy(new THREE.Vector3(0, h/2 - l/2, 0)); + // } else { + // const h = l * r2 / k; + // this.object.scale.copy(new THREE.Vector3(r2, h, r2)); + // // this.object.material.clippingPlanes[0].normal.y = 1; + // // this.object.material.clippingPlanes[0].constant = l / 2; + // //this.object.position.copy(new THREE.Vector3(0, h / 2 + l / 2, 0)); + // } + // } } From cc7f77a5a944cac4dd54f78eb76c7a0885cbb0f0 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sat, 8 Nov 2025 17:47:47 +1100 Subject: [PATCH 04/13] Added a shape with a single link to make testing easier --- gui.js | 2 +- polytopes.js | 27 +++++++++++++++++++--- taperedLink.js | 61 +++++++++++++++++++++++++++++--------------------- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/gui.js b/gui.js index 06d83c9..5c0d57e 100644 --- a/gui.js +++ b/gui.js @@ -7,7 +7,7 @@ const DEFAULTS = { linksize: 1.0, linkopacity: 0.75, link2opacity: 0.75, - shape: '16-cell', + shape: 'linky', option: 'none', visibility: 5, inscribed: false, diff --git a/polytopes.js b/polytopes.js index 91816c2..fe69529 100644 --- a/polytopes.js +++ b/polytopes.js @@ -55,8 +55,28 @@ export function auto_detect_edges(nodes, neighbours, debug=false) { return links; } + +export const linkTest = () => { + return { + name: 'linky', + nodes: [ + { id:1, label: 1, x: -1, y: -1, z:-1, w: 0 }, + { id:2, label: 2, x: 1, y: 1, z: 1, w: 0 }, + ], + links: [ + { id: 1, source: 1, target: 2 } + ], + options: [ { name: '--' }], + description: `link`, + } +}; + + + // too small and simple to calculate + + export const cell5 = () => { const c1 = Math.sqrt(5) / 4; return { @@ -844,7 +864,8 @@ export const icosahedron = () => { export const build_all = () => { return [ - tetrahedron(), + linkTest(), +/* tetrahedron(), octahedron(), cube(), icosahedron(), @@ -858,9 +879,9 @@ export const build_all = () => { cell600_layered(), cell120_inscribed(), cell120_layered() - ]; + */ ]; } export const radii = (shape) => { return shape.nodes.map(n => Math.sqrt(n.x * n.x + n.y * n.y + n.z * n.z + n.w * n.w)) -} \ No newline at end of file +} diff --git a/taperedLink.js b/taperedLink.js index 2473430..41d270b 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -9,46 +9,55 @@ class TaperedLink extends THREE.Group { const geometry = new THREE.ConeGeometry( 0.75, 1, 32, true ); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); -// material.clippingPlanes = [ cplane ]; - this.object = new THREE.Mesh( geometry, material ); - this.add( this.object ); + material.clippingPlanes = [ cplane ]; + this.cone = new THREE.Mesh( geometry, material ); + this.add( this.cone ); this.update(n1, n2, r1, r2); } update(n1, n2, r1, r2) { const kraw = r1 - r2; - const k = ( kraw == 0 ) ? 0.001 : kraw; - let nbase = n1; - let napex = n2; + let k = ( kraw == 0 ) ? 0.001 : kraw; + let nbase = n1.v3; + let napex = n2.v3; let rbase = r1; let rapex = r2; if( k < 0 ) { - nbase = n2; - napex = n1; + nbase = n2.v3; + napex = n1.v3; rbase = r2; rapex = r1; + k = -k; } - const l = nbase.v3.distanceTo(napex.v3); + // FIXME - the problem is that when the h_offset > 1, the centroid + // of the cone is on the other side of napex, so it looks the wrong way + + const l = nbase.distanceTo(napex); const h = l * rbase / k; - //const pos = new THREE.Vector3(0, h/2 - l/2, 0); - //pos.add(nbase.v3); - this.object.scale.copy(new THREE.Vector3(rbase, h, rbase)); - this.object.position.copy(nbase.v3); - this.object.lookAt(napex.v3); - this.object.rotation.x = Math.PI / 2.0; + const h_offset = 0.5 * h / l; + const pos = new THREE.Vector3(); + if( l > 0 ) { + pos.lerpVectors(nbase, napex, h_offset); + } + if( h_offset < 1 ) { + this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); + } else { + // you're on the other side of napex so flip the cone + this.cone.scale.copy(new THREE.Vector3(rbase, -h, rbase)); + } + this.lookAt(napex); + this.position.copy(pos); + this.cone.rotation.x = Math.PI / 2.0; + const clipnorm = new THREE.Vector3(); + clipnorm.copy(napex); + clipnorm.sub(nbase); + clipnorm.negate(); + clipnorm.normalize(); + this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + clipnorm, napex + ); } - // this.object.material.clippingPlanes[0].normal.y = -1; - // this.object.material.clippingPlanes[0].constant = l / 2; - //this.object.position.copy(new THREE.Vector3(0, h/2 - l/2, 0)); - // } else { - // const h = l * r2 / k; - // this.object.scale.copy(new THREE.Vector3(r2, h, r2)); - // // this.object.material.clippingPlanes[0].normal.y = 1; - // // this.object.material.clippingPlanes[0].constant = l / 2; - // //this.object.position.copy(new THREE.Vector3(0, h / 2 + l / 2, 0)); - // } - // } } From bf55db9f756d0e1d201bdafee89c46c0304470d6 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 9 Nov 2025 08:43:04 +1100 Subject: [PATCH 05/13] Comment about really silly idea --- taperedLink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taperedLink.js b/taperedLink.js index 41d270b..24cb729 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; - +// really stupid idea: add a cylinder as well! class TaperedLink extends THREE.Group { From 469542393116ebb0bfe5cb7fef95a7aba414efc8 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 9 Nov 2025 11:20:16 +1100 Subject: [PATCH 06/13] Weirdness - the glitchiness is coming and going depending on whether the clipping plane is enabled, or the cylinders added? --- gui.js | 6 +++--- polytopes.js | 4 ++-- taperedLink.js | 40 +++++++++++++++++++++++++++++++--------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/gui.js b/gui.js index 5c0d57e..c7fc05b 100644 --- a/gui.js +++ b/gui.js @@ -2,12 +2,12 @@ import { GUI } from 'lil-gui'; const DEFAULTS = { - nodesize: 4, + nodesize: 1, nodeopacity: 1, linksize: 1.0, - linkopacity: 0.75, + linkopacity: 1, link2opacity: 0.75, - shape: 'linky', + shape: '5-cell', option: 'none', visibility: 5, inscribed: false, diff --git a/polytopes.js b/polytopes.js index fe69529..d39a834 100644 --- a/polytopes.js +++ b/polytopes.js @@ -865,7 +865,7 @@ export const icosahedron = () => { export const build_all = () => { return [ linkTest(), -/* tetrahedron(), + tetrahedron(), octahedron(), cube(), icosahedron(), @@ -879,7 +879,7 @@ export const build_all = () => { cell600_layered(), cell120_inscribed(), cell120_layered() - */ ]; + ]; } export const radii = (shape) => { diff --git a/taperedLink.js b/taperedLink.js index 24cb729..0d6a6f3 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -6,16 +6,28 @@ class TaperedLink extends THREE.Group { constructor(baseMaterial, n1, n2, r1, r2) { super(); - const geometry = new THREE.ConeGeometry( 0.75, 1, 32, true ); + const cone = new THREE.ConeGeometry( 0.75, 1, 32, true ); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); - material.clippingPlanes = [ cplane ]; - this.cone = new THREE.Mesh( geometry, material ); + //material.clippingPlanes = [ cplane ]; + this.cone = new THREE.Mesh( cone, material ); this.add( this.cone ); + const cylinder = new THREE.CylinderGeometry(1, 1, 1, 16, 1, true); + const cyl_material = baseMaterial.clone(); + cyl_material.color = new THREE.Color(0xff0000); + this.cylinder = new THREE.Mesh(cylinder, cyl_material); + this.add( this.cylinder ); this.update(n1, n2, r1, r2); - } + } - update(n1, n2, r1, r2) { +// mystery - commenting out the cylinder stuff makes the glitchiness return! + + update(n1, n2, r1, r2) { + this.update_cone(n1, n2, r1, r2); + this.update_cylinder(n1, n2, r1, r2); + } + + update_cone(n1, n2, r1, r2) { const kraw = r1 - r2; let k = ( kraw == 0 ) ? 0.001 : kraw; let nbase = n1.v3; @@ -54,11 +66,21 @@ class TaperedLink extends THREE.Group { clipnorm.sub(nbase); clipnorm.negate(); clipnorm.normalize(); - this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( - clipnorm, napex - ); + //this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + // clipnorm, napex + // ); } + update_cylinder(n1, n2, r1, r2) { + const length = n1.v3.distanceTo(n2.v3); + const centre = new THREE.Vector3(); + centre.lerpVectors(n1.v3, n2.v3, 0.5); + const link_mean = (r1 + r2) * 0.5; + this.cylinder.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); + this.cylinder.position.copy(centre); + this.lookAt(n2.v3); + this.cylinder.rotation.x = Math.PI / 2.0; + + } } - export { TaperedLink }; From 6c875dbda86d8c58d9d239429442422abb211bf4 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 9 Nov 2025 17:41:37 +1100 Subject: [PATCH 07/13] Not working but it looks good --- taperedLink.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/taperedLink.js b/taperedLink.js index 0d6a6f3..153b9f9 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -9,7 +9,7 @@ class TaperedLink extends THREE.Group { const cone = new THREE.ConeGeometry( 0.75, 1, 32, true ); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); - //material.clippingPlanes = [ cplane ]; + material.clippingPlanes = [ cplane ]; this.cone = new THREE.Mesh( cone, material ); this.add( this.cone ); const cylinder = new THREE.CylinderGeometry(1, 1, 1, 16, 1, true); @@ -18,15 +18,13 @@ class TaperedLink extends THREE.Group { this.cylinder = new THREE.Mesh(cylinder, cyl_material); this.add( this.cylinder ); this.update(n1, n2, r1, r2); - } - -// mystery - commenting out the cylinder stuff makes the glitchiness return! + } update(n1, n2, r1, r2) { this.update_cone(n1, n2, r1, r2); - this.update_cylinder(n1, n2, r1, r2); } + update_cone(n1, n2, r1, r2) { const kraw = r1 - r2; let k = ( kraw == 0 ) ? 0.001 : kraw; @@ -42,9 +40,6 @@ class TaperedLink extends THREE.Group { k = -k; } - // FIXME - the problem is that when the h_offset > 1, the centroid - // of the cone is on the other side of napex, so it looks the wrong way - const l = nbase.distanceTo(napex); const h = l * rbase / k; const h_offset = 0.5 * h / l; @@ -52,33 +47,39 @@ class TaperedLink extends THREE.Group { if( l > 0 ) { pos.lerpVectors(nbase, napex, h_offset); } + const rmean = 0.5 * (r1 + r2); if( h_offset < 1 ) { this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); + this.cylinder.scale.copy(new THREE.Vector3(rmean, l, rmean)); } else { // you're on the other side of napex so flip the cone - this.cone.scale.copy(new THREE.Vector3(rbase, -h, rbase)); + this.cone.scale.copy(new THREE.Vector3(rbase, -h, rbase)); + this.cylinder.scale.copy(new THREE.Vector3(rmean, -l, rmean)); } - this.lookAt(napex); - this.position.copy(pos); + this.lookAt(napex); // the group, not the cone!! + this.position.copy(pos); // the group, not the cone!! this.cone.rotation.x = Math.PI / 2.0; + this.cylinder.rotation.x = Math.PI / 2.0; const clipnorm = new THREE.Vector3(); clipnorm.copy(napex); clipnorm.sub(nbase); clipnorm.negate(); clipnorm.normalize(); - //this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( - // clipnorm, napex - // ); + this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + clipnorm, napex + ); + + } - update_cylinder(n1, n2, r1, r2) { + update_cylinder_busted(n1, n2, r1, r2) { const length = n1.v3.distanceTo(n2.v3); const centre = new THREE.Vector3(); centre.lerpVectors(n1.v3, n2.v3, 0.5); const link_mean = (r1 + r2) * 0.5; this.cylinder.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); this.cylinder.position.copy(centre); - this.lookAt(n2.v3); + //this.cylinder.lookAt(n2.v3); this.cylinder.rotation.x = Math.PI / 2.0; } From 43c85d0084c6712457888504a7f55dfa89921764 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sat, 15 Nov 2025 14:43:51 +1100 Subject: [PATCH 08/13] Added a testbed version of main to fiddle with tapered links --- index.html | 4 +-- linktest.js | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 linktest.js diff --git a/index.html b/index.html index 7791e9a..ccbdcd9 100644 --- a/index.html +++ b/index.html @@ -24,9 +24,9 @@ - +
- \ No newline at end of file + diff --git a/linktest.js b/linktest.js new file mode 100644 index 0000000..756cd37 --- /dev/null +++ b/linktest.js @@ -0,0 +1,88 @@ +import * as THREE from 'three'; + +import { GUI } from 'lil-gui'; + +import { TaperedLink } from './taperedLink.js'; + + + +const FACE_OPACITY = 0.3; +const CAMERA_K = 5; + +// scene, lights and camera + + + + +const scene = new THREE.Scene(); +const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); +const light = new THREE.PointLight(0xffffff, 2); +light.position.set(10, 10, 10); +scene.add(light); +const light2 = new THREE.PointLight(0xffffff, 2); +light2.position.set(-10, 5, 10); +scene.add(light); +const amblight = new THREE.AmbientLight(0xffffff, 0.5); +scene.add(amblight); + +camera.position.set(0, 0, CAMERA_K / 2); + +camera.lookAt(0, 0, 0); +//camera.position.z = 4; + +const renderer = new THREE.WebGLRenderer({antialias: true}); +renderer.setSize( window.innerWidth, window.innerHeight ); + +renderer.localClippingEnabled = true; + + +document.body.appendChild( renderer.domElement ); + +const NODEC = 0x3293a9; +const BACKGROUNDC = 0xd4d4d4; + +scene.background = new THREE.Color(BACKGROUNDC); +const material = new THREE.MeshStandardMaterial({ color: NODEC }); + + +const params = { + r1: 0.5, + r2: 0.6, + l: 3, +}; + +const gui = new GUI(); + +gui.add(params, "r1", 0.01, 1.5); +gui.add(params, "r2", 0.01, 1.5); +gui.add(params, "l", 0, 4); + +function makeNode(material, pos, r) { + const geometry = new THREE.SphereGeometry(1); + const sphere = new THREE.Mesh(geometry, material); + updateNode(sphere, pos, r); + return sphere; +} + +function updateNode(node, pos, r) { + node.scale.copy(new THREE.Vector3(r, r, r)); + node.position.copy(pos); +} + + +const n1 = makeNode(material, new THREE.Vector3(-0.5 * params["l"], 0, 0), params["r1"]); +const n2 = makeNode(material, new THREE.Vector3(0.5 * params["l"], 0, 0), params["r2"]) + +//const tl = new TaperedLink( + +scene.add(n1); +scene.add(n2); + +function animate() { + requestAnimationFrame(animate); + + updateNode(n1, new THREE.Vector3(-0.5 * params["l"], 0, 0), params["r1"]); + updateNode(n2, new THREE.Vector3(0.5 * params["l"], 0, 0), params["r2"]); + renderer.render(scene, camera); +} +animate(); From 67348bce31469d20f734b19f9c6d6a966abd969a Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sat, 15 Nov 2025 16:31:45 +1100 Subject: [PATCH 09/13] The problem is with the flipping logic - I haven't fixed it yet but I'm getting there --- linktest.js | 46 +++++++++++++++++++++++++++----------- taperedLink.js | 60 +++++++++++++++++++++++++------------------------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/linktest.js b/linktest.js index 756cd37..7a9ff86 100644 --- a/linktest.js +++ b/linktest.js @@ -5,7 +5,6 @@ import { GUI } from 'lil-gui'; import { TaperedLink } from './taperedLink.js'; - const FACE_OPACITY = 0.3; const CAMERA_K = 5; @@ -39,15 +38,24 @@ renderer.localClippingEnabled = true; document.body.appendChild( renderer.domElement ); const NODEC = 0x3293a9; +const LINKC = 0x00ff88; const BACKGROUNDC = 0xd4d4d4; scene.background = new THREE.Color(BACKGROUNDC); -const material = new THREE.MeshStandardMaterial({ color: NODEC }); +const material = new THREE.MeshStandardMaterial({ color: LINKC }); +material.transparent = true; +material.opacity = 0.5; + +const node_mat = new THREE.MeshStandardMaterial({ color: NODEC }); + +node_mat.transparent = true; +node_mat.opacity = 0.1; const params = { r1: 0.5, r2: 0.6, + sync: false, l: 3, }; @@ -55,34 +63,46 @@ const gui = new GUI(); gui.add(params, "r1", 0.01, 1.5); gui.add(params, "r2", 0.01, 1.5); +gui.add(params, "sync"); gui.add(params, "l", 0, 4); function makeNode(material, pos, r) { const geometry = new THREE.SphereGeometry(1); const sphere = new THREE.Mesh(geometry, material); - updateNode(sphere, pos, r); - return sphere; + const node = { + v3: pos, + object: sphere + }; + updateNode(node, pos, r); + return node; } function updateNode(node, pos, r) { - node.scale.copy(new THREE.Vector3(r, r, r)); - node.position.copy(pos); + node.v3 = pos; + node.object.scale.copy(new THREE.Vector3(r, r, r)); + node.object.position.copy(pos); } -const n1 = makeNode(material, new THREE.Vector3(-0.5 * params["l"], 0, 0), params["r1"]); -const n2 = makeNode(material, new THREE.Vector3(0.5 * params["l"], 0, 0), params["r2"]) +const n1 = makeNode(node_mat, new THREE.Vector3(-0.5 * params["l"], 0, 0), params["r1"]); +const n2 = makeNode(node_mat, new THREE.Vector3(0.5 * params["l"], 0, 0), params["r2"]); -//const tl = new TaperedLink( +const tl = new TaperedLink(material, n1, n2, params["r1"], params["r2"]); -scene.add(n1); -scene.add(n2); +scene.add(n1.object); +scene.add(n2.object); + +scene.add(tl); function animate() { requestAnimationFrame(animate); - updateNode(n1, new THREE.Vector3(-0.5 * params["l"], 0, 0), params["r1"]); - updateNode(n2, new THREE.Vector3(0.5 * params["l"], 0, 0), params["r2"]); + const r1 = params["r1"]; + const r2 = params["sync"] ? r1 : params["r2"] + + updateNode(n1, new THREE.Vector3(-0.5 * params["l"], 0, 0), r1); + updateNode(n2, new THREE.Vector3(0.5 * params["l"], 0, 0), r2); + tl.update(n1, n2, r1, r2); renderer.render(scene, camera); } animate(); diff --git a/taperedLink.js b/taperedLink.js index 153b9f9..3f5e965 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -2,22 +2,24 @@ import * as THREE from 'three'; // really stupid idea: add a cylinder as well! +const EPSILON = 0.01; + class TaperedLink extends THREE.Group { constructor(baseMaterial, n1, n2, r1, r2) { super(); - const cone = new THREE.ConeGeometry( 0.75, 1, 32, true ); + const cone = new THREE.ConeGeometry( 1, 1, 32, true ); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); - material.clippingPlanes = [ cplane ]; +// material.clippingPlanes = [ cplane ]; this.cone = new THREE.Mesh( cone, material ); this.add( this.cone ); - const cylinder = new THREE.CylinderGeometry(1, 1, 1, 16, 1, true); - const cyl_material = baseMaterial.clone(); - cyl_material.color = new THREE.Color(0xff0000); - this.cylinder = new THREE.Mesh(cylinder, cyl_material); - this.add( this.cylinder ); - this.update(n1, n2, r1, r2); +// const cylinder = new THREE.CylinderGeometry(1, 1, 1, 16, 1, true); +// const cyl_material = baseMaterial.clone(); +// cyl_material.color = new THREE.Color(0xff0000); +// this.cylinder = new THREE.Mesh(cylinder, cyl_material); +// this.add( this.cylinder ); + // this.update(n1, n2, r1, r2); } update(n1, n2, r1, r2) { @@ -41,33 +43,31 @@ class TaperedLink extends THREE.Group { } const l = nbase.distanceTo(napex); - const h = l * rbase / k; + const lapex = l * rapex / k; + const h = l + lapex; + this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); const h_offset = 0.5 * h / l; const pos = new THREE.Vector3(); - if( l > 0 ) { - pos.lerpVectors(nbase, napex, h_offset); - } - const rmean = 0.5 * (r1 + r2); - if( h_offset < 1 ) { - this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); - this.cylinder.scale.copy(new THREE.Vector3(rmean, l, rmean)); - } else { - // you're on the other side of napex so flip the cone - this.cone.scale.copy(new THREE.Vector3(rbase, -h, rbase)); - this.cylinder.scale.copy(new THREE.Vector3(rmean, -l, rmean)); - } + pos.lerpVectors(nbase, napex, h_offset); + + + //if( h_offset > 1 ) { + // this.cone.scale.copy(new THREE.Vector3(rbase, -h, rbase)); + //} { + // this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); + //} this.lookAt(napex); // the group, not the cone!! this.position.copy(pos); // the group, not the cone!! this.cone.rotation.x = Math.PI / 2.0; - this.cylinder.rotation.x = Math.PI / 2.0; - const clipnorm = new THREE.Vector3(); - clipnorm.copy(napex); - clipnorm.sub(nbase); - clipnorm.negate(); - clipnorm.normalize(); - this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( - clipnorm, napex - ); +// this.cylinder.rotation.x = Math.PI / 2.0; +// const clipnorm = new THREE.Vector3(); +// clipnorm.copy(napex); + // clipnorm.sub(nbase); + // clipnorm.negate(); + // clipnorm.normalize(); + // this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + // clipnorm, napex + // ); } From a2581a2f66e69359e740cf622de17ef3348c9e60 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sat, 15 Nov 2025 18:12:06 +1100 Subject: [PATCH 10/13] This is throwing errors in vite and I don't know why --- linktest.js | 23 ++++++++------ taperedLink.js | 84 +++++++++++++++++++++----------------------------- 2 files changed, 49 insertions(+), 58 deletions(-) diff --git a/linktest.js b/linktest.js index 7a9ff86..9b13f39 100644 --- a/linktest.js +++ b/linktest.js @@ -1,10 +1,10 @@ import * as THREE from 'three'; +import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'lil-gui'; import { TaperedLink } from './taperedLink.js'; - const FACE_OPACITY = 0.3; const CAMERA_K = 5; @@ -27,13 +27,17 @@ scene.add(amblight); camera.position.set(0, 0, CAMERA_K / 2); camera.lookAt(0, 0, 0); -//camera.position.z = 4; +camera.position.z = 8; const renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.localClippingEnabled = true; +const controls = new OrbitControls( camera, renderer.domElement ); + + +controls.autoRotate = true; document.body.appendChild( renderer.domElement ); @@ -50,13 +54,13 @@ material.opacity = 0.5; const node_mat = new THREE.MeshStandardMaterial({ color: NODEC }); node_mat.transparent = true; -node_mat.opacity = 0.1; +node_mat.opacity = 0.8; const params = { r1: 0.5, r2: 0.6, sync: false, - l: 3, + l: 9, }; const gui = new GUI(); @@ -64,7 +68,7 @@ const gui = new GUI(); gui.add(params, "r1", 0.01, 1.5); gui.add(params, "r2", 0.01, 1.5); gui.add(params, "sync"); -gui.add(params, "l", 0, 4); +gui.add(params, "l", 0, 10); function makeNode(material, pos, r) { const geometry = new THREE.SphereGeometry(1); @@ -84,8 +88,8 @@ function updateNode(node, pos, r) { } -const n1 = makeNode(node_mat, new THREE.Vector3(-0.5 * params["l"], 0, 0), params["r1"]); -const n2 = makeNode(node_mat, new THREE.Vector3(0.5 * params["l"], 0, 0), params["r2"]); +const n1 = makeNode(node_mat, new THREE.Vector3(-params["l"], -1, -1), params["r1"]); +const n2 = makeNode(node_mat, new THREE.Vector3(params["l"], 1, 1), params["r2"]); const tl = new TaperedLink(material, n1, n2, params["r1"], params["r2"]); @@ -100,9 +104,10 @@ function animate() { const r1 = params["r1"]; const r2 = params["sync"] ? r1 : params["r2"] - updateNode(n1, new THREE.Vector3(-0.5 * params["l"], 0, 0), r1); - updateNode(n2, new THREE.Vector3(0.5 * params["l"], 0, 0), r2); + updateNode(n1, new THREE.Vector3(- params["l"], -1, -1), r1); + updateNode(n2, new THREE.Vector3(params["l"], 1, 1), r2); tl.update(n1, n2, r1, r2); + controls.update(); renderer.render(scene, camera); } animate(); diff --git a/taperedLink.js b/taperedLink.js index 3f5e965..5dd0ad3 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -1,33 +1,22 @@ import * as THREE from 'three'; -// really stupid idea: add a cylinder as well! - -const EPSILON = 0.01; +// wtf dude class TaperedLink extends THREE.Group { constructor(baseMaterial, n1, n2, r1, r2) { super(); - const cone = new THREE.ConeGeometry( 1, 1, 32, true ); + //const geometry = new THREE.ConeGeometry( 1, 1, 32, true ); + const geometry = new THREE.CylinderGeometry( 1, 1, 1, 16, 1, true); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); // material.clippingPlanes = [ cplane ]; - this.cone = new THREE.Mesh( cone, material ); - this.add( this.cone ); -// const cylinder = new THREE.CylinderGeometry(1, 1, 1, 16, 1, true); -// const cyl_material = baseMaterial.clone(); -// cyl_material.color = new THREE.Color(0xff0000); -// this.cylinder = new THREE.Mesh(cylinder, cyl_material); -// this.add( this.cylinder ); - // this.update(n1, n2, r1, r2); + this.object = new THREE.Mesh( geometry, material ); + this.add( this.object ); + this.update(n1, n2, r1, r2); } - update(n1, n2, r1, r2) { - this.update_cone(n1, n2, r1, r2); - } - - - update_cone(n1, n2, r1, r2) { + update_cone_busted(n1, n2, r1, r2) { const kraw = r1 - r2; let k = ( kraw == 0 ) ? 0.001 : kraw; let nbase = n1.v3; @@ -45,43 +34,40 @@ class TaperedLink extends THREE.Group { const l = nbase.distanceTo(napex); const lapex = l * rapex / k; const h = l + lapex; - this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); + this.object.scale.copy(new THREE.Vector3(rbase, h, rbase)); const h_offset = 0.5 * h / l; const pos = new THREE.Vector3(); pos.lerpVectors(nbase, napex, h_offset); - - //if( h_offset > 1 ) { - // this.cone.scale.copy(new THREE.Vector3(rbase, -h, rbase)); - //} { - // this.cone.scale.copy(new THREE.Vector3(rbase, h, rbase)); - //} - this.lookAt(napex); // the group, not the cone!! + + //this.scale.copy(new THREE.Vector3(rbase, h, rbase)); this.position.copy(pos); // the group, not the cone!! - this.cone.rotation.x = Math.PI / 2.0; -// this.cylinder.rotation.x = Math.PI / 2.0; -// const clipnorm = new THREE.Vector3(); -// clipnorm.copy(napex); - // clipnorm.sub(nbase); - // clipnorm.negate(); - // clipnorm.normalize(); - // this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( - // clipnorm, napex - // ); - + this.lookAt(napex); + this.rotation.y = Math.PI / 2.0; + /* const clipnorm = new THREE.Vector3(); + clipnorm.copy(napex); + clipnorm.sub(nbase); + clipnorm.negate(); + clipnorm.normalize(); + this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + clipnorm, napex + ); + +*/ } - update_cylinder_busted(n1, n2, r1, r2) { - const length = n1.v3.distanceTo(n2.v3); - const centre = new THREE.Vector3(); - centre.lerpVectors(n1.v3, n2.v3, 0.5); - const link_mean = (r1 + r2) * 0.5; - this.cylinder.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); - this.cylinder.position.copy(centre); - //this.cylinder.lookAt(n2.v3); - this.cylinder.rotation.x = Math.PI / 2.0; - - } -} + update(n1, n2, r1, r2) { + const length = n1.v3.distanceTo(n2.v3); + const centre = new THREE.Vector3(); + centre.lerpVectors(n1.v3, n2.v3, 0.5); + const link_mean = (r1 + r2) * 0.5; + this.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); + this.position.copy(centre); + this.lookAt(n2.v3); + this.children[0].rotation.x = Math.PI / 2.0; + this.visible = true; +} + + export { TaperedLink }; From 840e46201c5e406e1090dcc00b7a2b5e126ecaec Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 16 Nov 2025 11:04:36 +1100 Subject: [PATCH 11/13] Fixed the weird glitching at r1 = r2 / 2 by making it face the base, not the apex --- linktest.js | 10 ++++++-- taperedLink.js | 63 ++++++++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/linktest.js b/linktest.js index 9b13f39..be36173 100644 --- a/linktest.js +++ b/linktest.js @@ -37,7 +37,7 @@ renderer.localClippingEnabled = true; const controls = new OrbitControls( camera, renderer.domElement ); -controls.autoRotate = true; +controls.autoRotate = false; document.body.appendChild( renderer.domElement ); @@ -61,6 +61,9 @@ const params = { r2: 0.6, sync: false, l: 9, + rotx: 1, + roty: 0, + rotz: 0, }; const gui = new GUI(); @@ -69,6 +72,9 @@ gui.add(params, "r1", 0.01, 1.5); gui.add(params, "r2", 0.01, 1.5); gui.add(params, "sync"); gui.add(params, "l", 0, 10); +gui.add(params, "rotx", 0, 4); +gui.add(params, "roty", 0, 4); +gui.add(params, "rotz", 0, 4); function makeNode(material, pos, r) { const geometry = new THREE.SphereGeometry(1); @@ -106,7 +112,7 @@ function animate() { updateNode(n1, new THREE.Vector3(- params["l"], -1, -1), r1); updateNode(n2, new THREE.Vector3(params["l"], 1, 1), r2); - tl.update(n1, n2, r1, r2); + tl.update(n1, n2, r1, r2, params["rotx"], params["roty"], params["rotz"]); controls.update(); renderer.render(scene, camera); } diff --git a/taperedLink.js b/taperedLink.js index 5dd0ad3..fb93339 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -1,22 +1,38 @@ + + import * as THREE from 'three'; -// wtf dude class TaperedLink extends THREE.Group { constructor(baseMaterial, n1, n2, r1, r2) { super(); - //const geometry = new THREE.ConeGeometry( 1, 1, 32, true ); - const geometry = new THREE.CylinderGeometry( 1, 1, 1, 16, 1, true); + const geometry = new THREE.ConeGeometry( 1, 1, 16, true ); + //const geometry = new THREE.CylinderGeometry( 1, 1, 1, 16, 1, true); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); const material = baseMaterial.clone(); -// material.clippingPlanes = [ cplane ]; + material.clippingPlanes = [ cplane ]; this.object = new THREE.Mesh( geometry, material ); this.add( this.object ); - this.update(n1, n2, r1, r2); + this.update(n1, n2, r1, r2, 1, 0, 0); } - update_cone_busted(n1, n2, r1, r2) { + update_cyl(n1, n2, r1, r2, rotx, roty, rotz) { + const length = n1.v3.distanceTo(n2.v3); + const centre = new THREE.Vector3(); + centre.lerpVectors(n1.v3, n2.v3, 0.5); + const link_mean = (r1 + r2) * 0.5; + this.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); + this.position.copy(centre); + this.lookAt(n2.v3); + this.children[0].rotation.x = rotx * Math.PI / 2.0; + this.children[0].rotation.y = roty * Math.PI / 2.0; + this.children[0].rotation.z = rotz * Math.PI / 2.0; + this.visible = true; + } + + + update(n1, n2, r1, r2, rotx, roty, rotz) { const kraw = r1 - r2; let k = ( kraw == 0 ) ? 0.001 : kraw; let nbase = n1.v3; @@ -34,40 +50,31 @@ class TaperedLink extends THREE.Group { const l = nbase.distanceTo(napex); const lapex = l * rapex / k; const h = l + lapex; - this.object.scale.copy(new THREE.Vector3(rbase, h, rbase)); + this.scale.copy(new THREE.Vector3(rbase, rbase, h)); const h_offset = 0.5 * h / l; const pos = new THREE.Vector3(); pos.lerpVectors(nbase, napex, h_offset); - - - //this.scale.copy(new THREE.Vector3(rbase, h, rbase)); + this.position.copy(pos); // the group, not the cone!! - this.lookAt(napex); - this.rotation.y = Math.PI / 2.0; - /* const clipnorm = new THREE.Vector3(); - clipnorm.copy(napex); + this.lookAt(nbase); + this.children[0].rotation.x = rotx * Math.PI / 2.0; + this.children[0].rotation.y = roty * Math.PI / 2.0; + this.children[0].rotation.z = rotz * Math.PI / 2.0; + this.visible = true; + const clipnorm = new THREE.Vector3(); + clipnorm.copy(napex); clipnorm.sub(nbase); clipnorm.negate(); clipnorm.normalize(); - this.cone.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + this.object.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( clipnorm, napex ); -*/ - } + + } - update(n1, n2, r1, r2) { - const length = n1.v3.distanceTo(n2.v3); - const centre = new THREE.Vector3(); - centre.lerpVectors(n1.v3, n2.v3, 0.5); - const link_mean = (r1 + r2) * 0.5; - this.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); - this.position.copy(centre); - this.lookAt(n2.v3); - this.children[0].rotation.x = Math.PI / 2.0; - this.visible = true; -} +} export { TaperedLink }; From 6019237e3194407754aa62fd7f0c424bd61d0967 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 16 Nov 2025 14:13:02 +1100 Subject: [PATCH 12/13] Fixing a few things before releasing the taperedLink version --- gui.js | 21 ++++++--------------- index.html | 2 +- linktest.js | 6 +++--- main.js | 27 ++++++++++----------------- taperedLink.js | 37 +++++++++---------------------------- 5 files changed, 29 insertions(+), 64 deletions(-) diff --git a/gui.js b/gui.js index c7fc05b..8e4755a 100644 --- a/gui.js +++ b/gui.js @@ -2,12 +2,11 @@ import { GUI } from 'lil-gui'; const DEFAULTS = { - nodesize: 1, + nodesize: 0.6, nodeopacity: 1, linksize: 1.0, - linkopacity: 1, - link2opacity: 0.75, - shape: '5-cell', + linkopacity: 0.75, + shape: '120-cell', option: 'none', visibility: 5, inscribed: false, @@ -41,7 +40,6 @@ class FourDGUI { inscribe_all: this.link['inscribe_all'], linksize: this.link['linksize'], linkopacity: this.link['linkopacity'], - link2opacity: this.link['linkopacity'], nodesize: this.link['nodesize'], nodeopacity: this.link['nodeopacity'], depth: this.link['depth'], @@ -72,15 +70,10 @@ class FourDGUI { }); this.gui.add(this.params, 'hyperplane', 0.5, 1 / 0.8); this.gui.add(this.params, 'zoom', 0.1, 2.0); - this.gui.add(this.params, 'nodesize', 0, 5); + this.gui.add(this.params, 'nodesize', 0, 1.5); this.gui.add(this.params, 'nodeopacity', 0, 1).onChange(setNodeOpacity); - this.gui.add(this.params, 'linksize', 0, 5); - this.gui.add(this.params, 'linkopacity', 0, 1).onChange( - (v) => setLinkOpacity(v, true) - ); - this.gui.add(this.params, 'link2opacity', 0, 1).onChange( - (v) => setLinkOpacity(v, false) - ); + this.gui.add(this.params, 'linksize', 0, 2); + this.gui.add(this.params, 'linkopacity', 0, 1).onChange(setLinkOpacity); this.gui.addColor(this.params, 'color').onChange(setColor); this.gui.addColor(this.params, 'background').onChange(setBackground); this.gui.add(this.params, 'xRotate', [ 'YW', 'YZ', 'ZW' ]); @@ -143,7 +136,6 @@ class FourDGUI { this.link['zoom'] = this.numParam('zoom', parseFloat); this.link['linksize'] = this.numParam('linksize', parseFloat); this.link['linkopacity'] = this.numParam('linkopacity', parseFloat); - this.link['link2opacity'] = this.numParam('link2opacity', parseFloat); this.link['nodesize'] = this.numParam('nodesize', parseFloat); this.link['nodeopacity'] = this.numParam('nodeopacity', parseFloat); this.link['color'] = this.numParam('color', (s) => guiObj.stringToHex(s)); @@ -163,7 +155,6 @@ class FourDGUI { url.searchParams.append("nodesize", this.params.nodesize.toString()); url.searchParams.append("nodeopacity", this.params.nodesize.toString()); url.searchParams.append("linkopacity", this.params.nodeopacity.toString()); - url.searchParams.append("link2opacity", this.params.link2opacity.toString()); url.searchParams.append("color", this.hexToString(this.params.color)); url.searchParams.append("background", this.hexToString(this.params.background)); url.searchParams.append("hyperplane", this.params.hyperplane.toString()); diff --git a/index.html b/index.html index ccbdcd9..9dcf112 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,7 @@ - +
diff --git a/linktest.js b/linktest.js index be36173..dc2157e 100644 --- a/linktest.js +++ b/linktest.js @@ -37,7 +37,7 @@ renderer.localClippingEnabled = true; const controls = new OrbitControls( camera, renderer.domElement ); -controls.autoRotate = false; +controls.autoRotate = true; document.body.appendChild( renderer.domElement ); @@ -49,12 +49,12 @@ scene.background = new THREE.Color(BACKGROUNDC); const material = new THREE.MeshStandardMaterial({ color: LINKC }); material.transparent = true; -material.opacity = 0.5; +material.opacity = 0.7; const node_mat = new THREE.MeshStandardMaterial({ color: NODEC }); node_mat.transparent = true; -node_mat.opacity = 0.8; +node_mat.opacity = 0.5; const params = { r1: 0.5, diff --git a/main.js b/main.js index b285ae2..b728f94 100644 --- a/main.js +++ b/main.js @@ -40,14 +40,8 @@ document.body.appendChild( renderer.domElement ); // set up colours and materials for gui callbacks scene.background = new THREE.Color(DEFAULTS.background); -const material = new THREE.MeshStandardMaterial({ color: DEFAULTS.color }); const node_colours = get_colours(DEFAULTS.color); - -material.transparent = true; -material.opacity = 0.5; - - const node_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c})); const link_ms = node_colours.map((c) => new THREE.MeshStandardMaterial({color: c})); @@ -66,7 +60,7 @@ link_ms.map((m) => { const face_ms = [ - new THREE.MeshLambertMaterial( { color: 0x44ff44 } ) + new THREE.MeshStandardMaterial( { color: 0x44ff44 } ) ]; for( const face_m of face_ms ) { @@ -128,23 +122,22 @@ function setColors(c) { node_ms[i].color = new THREE.Color(nc[i]); link_ms[i].color = new THREE.Color(nc[i]); } - material.color = new THREE.Color(c); + //material.color = new THREE.Color(c); } function setBackground(c) { scene.background = new THREE.Color(c) } +// taperedLinks have their own materials so we have to set opacity +// on them individually. And also set the base materials as they +// will get updated from it when the shape changes + function setLinkOpacity(o, primary) { - if( structure.nolink2opacity ) { - link_ms.map((lm) => lm.opacity = o); - } else { - if( primary ) { - link_ms[0].opacity = o; - } else { - link_ms.slice(1).map((lm) => lm.opacity = o); - } - } + link_ms.map((lm) => lm.opacity = o); + if( shape ) { + shape.links.map((l) => l.object.material.opacity = o); + } } function setNodeOpacity(o) { diff --git a/taperedLink.js b/taperedLink.js index fb93339..d81a5fd 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -1,40 +1,23 @@ - - import * as THREE from 'three'; +const EPSILON = 0.001; class TaperedLink extends THREE.Group { constructor(baseMaterial, n1, n2, r1, r2) { super(); const geometry = new THREE.ConeGeometry( 1, 1, 16, true ); - //const geometry = new THREE.CylinderGeometry( 1, 1, 1, 16, 1, true); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); - const material = baseMaterial.clone(); - material.clippingPlanes = [ cplane ]; - this.object = new THREE.Mesh( geometry, material ); + this.material = baseMaterial.clone(); + this.material.clippingPlanes = [ cplane ]; + this.object = new THREE.Mesh( geometry, this.material ); this.add( this.object ); - this.update(n1, n2, r1, r2, 1, 0, 0); + this.update(n1, n2, r1, r2); } - update_cyl(n1, n2, r1, r2, rotx, roty, rotz) { - const length = n1.v3.distanceTo(n2.v3); - const centre = new THREE.Vector3(); - centre.lerpVectors(n1.v3, n2.v3, 0.5); - const link_mean = (r1 + r2) * 0.5; - this.scale.copy(new THREE.Vector3(link_mean, link_mean, length)); - this.position.copy(centre); - this.lookAt(n2.v3); - this.children[0].rotation.x = rotx * Math.PI / 2.0; - this.children[0].rotation.y = roty * Math.PI / 2.0; - this.children[0].rotation.z = rotz * Math.PI / 2.0; - this.visible = true; - } - - - update(n1, n2, r1, r2, rotx, roty, rotz) { + update(n1, n2, r1, r2) { const kraw = r1 - r2; - let k = ( kraw == 0 ) ? 0.001 : kraw; + let k = ( Math.abs(kraw) < EPSILON ) ? EPSILON : kraw; let nbase = n1.v3; let napex = n2.v3; let rbase = r1; @@ -58,16 +41,14 @@ class TaperedLink extends THREE.Group { this.position.copy(pos); // the group, not the cone!! this.lookAt(nbase); - this.children[0].rotation.x = rotx * Math.PI / 2.0; - this.children[0].rotation.y = roty * Math.PI / 2.0; - this.children[0].rotation.z = rotz * Math.PI / 2.0; + this.children[0].rotation.x = 3 * Math.PI / 2.0; this.visible = true; const clipnorm = new THREE.Vector3(); clipnorm.copy(napex); clipnorm.sub(nbase); clipnorm.negate(); clipnorm.normalize(); - this.object.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( + this.material.clippingPlanes[0].setFromNormalAndCoplanarPoint( clipnorm, napex ); From 9bc23fdeeb881cd4a399066aacd40d76dafc3e27 Mon Sep 17 00:00:00 2001 From: Mike Lynch Date: Sun, 16 Nov 2025 15:33:03 +1100 Subject: [PATCH 13/13] Sorted out a few more interface niggles and added release notes to the interface --- CHANGELOG.md | 12 ++++++++++++ fourDShape.js | 52 +++++++++++++------------------------------------- index.html | 16 +++++++++++++++- main.js | 46 +++++++++++++++++++++++++++++++++++++------- taperedLink.js | 7 ++++++- 5 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1336378 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +## v1.0 - 16/11/2025 + +It's been [two years](https://mikelynch.org/2023/Sep/02/120-cell/) since +I first made this, and I haven't updated it in a while, but I got tapered links to +work without too much performance overhead, so that seemed worth a version. + +The results flicker a bit at low opacities but otherwise I'm pretty happy with +it. +` diff --git a/fourDShape.js b/fourDShape.js index 609e1cc..7cd76db 100644 --- a/fourDShape.js +++ b/fourDShape.js @@ -29,12 +29,12 @@ class FourDShape extends THREE.Group { // if a node/link has no label, use the 0th material - getMaterial(entity, materials) { - if( "label" in entity ) { - return materials[entity.label]; - } else { - return materials[0]; - } + getMaterialLabel(entity) { + if( "label" in entity ) { + return entity.label + } else { + return 0; + } } makeNode(material, v3, scale) { @@ -45,25 +45,15 @@ class FourDShape extends THREE.Group { return sphere; } - makeLink(basematerial, link) { + makeLink(materialLabel, link) { const n1 = this.nodes3[link.source]; const n2 = this.nodes3[link.target]; const s1 = this.link_scale * n1.scale; const s2 = this.link_scale * n2.scale; - const edge = new TaperedLink(basematerial, n1, n2, s1, s2); + const basematerial = this.link_ms[materialLabel]; + const edge = new TaperedLink(basematerial, materialLabel, n1, n2, s1, s2); this.add( edge ); return edge; - // const length = n1.v3.distanceTo(n2.v3); - // const centre = nfew THREE.Vector3(); - // centre.lerpVectors(n1.v3, n2.v3, 0.5); - - // const edge = new TaperedLink(basematerial); - // edge.update(s1, s2, length); - // edge.position.copy(centre); - // edge.lookAt(n2.v3); - // edge.children[0].rotation.x = Math.PI / 2.0; - // this.add(edge); - // return edge; } updateLink(link, links_show) { @@ -72,13 +62,6 @@ class FourDShape extends THREE.Group { const s1 = this.link_scale * n1.scale; const s2 = this.link_scale * n2.scale; link.object.update(n1, n2, s1, s2); - // const length = n1.v3.distanceTo(n2.v3); - // const centre = new THREE.Vector3(); - // centre.lerpVectors(n1.v3, n2.v3, 0.5); - // link.object.update(s1, s2, length); - // link.object.position.copy(n1.v3); - // link.object.lookAt(n2.v3); - // link.object.children[0].rotation.x = Math.PI / 2.0; link.object.visible = (!links_show || link.label in links_show); } @@ -108,15 +91,6 @@ class FourDShape extends THREE.Group { } - fourDtoV3_old(x, y, z, w, rotations) { - const v4 = new THREE.Vector4(x, y, z, w); - for ( const m4 of rotations ) { - v4.applyMatrix4(m4); - } - const k = this.fourDscale(v4.w); - return new THREE.Vector3(v4.x * k, v4.y * k, v4.z * k); - } - fourDscale(w) { return this.hyperplane / ( this.hyperplane + w ); } @@ -138,7 +112,7 @@ class FourDShape extends THREE.Group { for( const n of this.nodes4 ) { const k = this.fourDscale(n.w); const v3 = new THREE.Vector3(n.x * k, n.y * k, n.z * k); - const material = this.getMaterial(n, this.node_ms); + const material = this.node_ms[this.getMaterialLabel(n)]; this.nodes3[n.id] = { v3: v3, scale: k, @@ -147,11 +121,11 @@ class FourDShape extends THREE.Group { }; } for( const l of this.links ) { - const material = this.getMaterial(l, this.link_ms); - l.object = this.makeLink(material, l); + const mLabel = this.getMaterialLabel(l); + l.object = this.makeLink(mLabel, l); } for( const f of this.faces ) { - const material = this.getMaterial(f, this.face_ms); + const material = this.face_ms(this.getMaterialLabel(f)); f.object = this.makeFace(material, f); } } diff --git a/index.html b/index.html index 9dcf112..a96fe8c 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,17 @@ font-family: sans-serif; padding: 1em; } + div#release_notes { + position: fixed; + top: 0; + left: 0; + width: 50%; + z-index: 2; + font-family: sans-serif; + padding: 1em; + visibility: none; + background: #ffffff; + } div#info { position: fixed; bottom:0; @@ -26,7 +37,10 @@
-
by Mike Lynch - +
+ diff --git a/main.js b/main.js index b728f94..45ef86a 100644 --- a/main.js +++ b/main.js @@ -1,5 +1,16 @@ import * as THREE from 'three'; +const RELEASE_NOTES = ` +

v1.0 - 16/11/2025

+ +

It's been two years since +I first made this, and I haven't updated it in a while, but I got tapered links to +work without too much performance overhead, so that seemed worth a version.

+ +

The results flicker a bit at low opacities but otherwise I'm pretty happy with +it.

+`; + import * as POLYTOPES from './polytopes.js'; @@ -102,7 +113,6 @@ function displayDocs(name) { } function showDocs(visible) { - console.log(`showDocs ${visible}`); const docdiv = document.getElementById("description"); if( visible ) { docdiv.style.display = ''; @@ -111,18 +121,40 @@ function showDocs(visible) { } } +function releaseNotes() { + showDocs(false); + const docdiv = document.getElementById("release_notes"); + docdiv.style.display = ''; + docdiv.innerHTML = RELEASE_NOTES + '

[hide]'; + const goaway = document.getElementById("no_notes"); + goaway.addEventListener('click', noNotes); + } + +function noNotes() { + const docdiv = document.getElementById("release_notes"); + docdiv.style.display = 'none'; +} + +const relnotes = document.getElementById('show_notes'); + +relnotes.addEventListener('click', releaseNotes); + + // initialise gui and read params from URL // callbacks to do things which are triggered by controls: reset the shape, // change the colors. Otherwise we just read stuff from gui.params. function setColors(c) { - const nc = get_colours(c); - for( let i = 0; i < node_ms.length; i++ ) { - node_ms[i].color = new THREE.Color(nc[i]); - link_ms[i].color = new THREE.Color(nc[i]); - } - //material.color = new THREE.Color(c); + const nc = get_colours(c); + for( let i = 0; i < node_ms.length; i++ ) { + node_ms[i].color = new THREE.Color(nc[i]); + link_ms[i].color = new THREE.Color(nc[i]); + } + if( shape ) { + // taperedLink.set_color updates according to the link index + shape.links.map((l) => l.object.set_color(nc)); + } } function setBackground(c) { diff --git a/taperedLink.js b/taperedLink.js index d81a5fd..1e88975 100644 --- a/taperedLink.js +++ b/taperedLink.js @@ -4,10 +4,11 @@ const EPSILON = 0.001; class TaperedLink extends THREE.Group { - constructor(baseMaterial, n1, n2, r1, r2) { + constructor(baseMaterial, color_i, n1, n2, r1, r2) { super(); const geometry = new THREE.ConeGeometry( 1, 1, 16, true ); const cplane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0.5); + this.color_i = color_i; this.material = baseMaterial.clone(); this.material.clippingPlanes = [ cplane ]; this.object = new THREE.Mesh( geometry, this.material ); @@ -55,6 +56,10 @@ class TaperedLink extends THREE.Group { } + set_color(colors) { + this.material.color = new THREE.Color(colors[this.color_i]); + } + }