Creating + viewing a html visualisation

I’m hoping to display in Juno’s plot panel a three.js widget consisting of a few particles in space, that the user could manipulate in 3D to get a sense of the geometry.

I have a working example and can produce the relevant particle definitions with the wrapper below, but I don’t know how to go about integrating this with, say, WebIO, to display as a standalone widget in Juno or in Documenter pages. Is there a simple example out there I could use as a starting point? Most examples I’ve found are much more complicated or don’t run anymore.

Code I’m using to generate the particles:


using StaticArrays

struct Cluster{T1,T2,T3}
    positions::Vector{SVector{3,T1}}
    angles::Vector{SVector{3,T2}}
    sizes::Vector{SVector{3,T3}}
end

function cluster_helix(N, a, b, c, R, Λ, δ = π/4, δ_0 = 0, handedness="left")

    sizes = [SVector(a, b, c) for ii in 1:N] 

    s = handedness == "left" ? -1 : +1
    ϕ = collect(1:N) * δ .+ δ_0
    x = R * cos.(ϕ)
    y = R * sin.(ϕ)
    z = s * ϕ * Λ/(2π)
    z .-= s*maximum(s * z)/2 

    # angles calculation
    x′ =  - y
    y′ =   x
    z′ =  s * Λ / (2π)
    n = @. sqrt(x′^2 + y′^2 + z′^2)

    φ =  atan.(y′, x′)
    θ =  acos.(z′ ./ n)
    ψ = 0.0 # don't care for axisymmetric particles

    positions = SVector.(x,y,z)
    angles = SVector.(φ,θ,ψ)

    Cluster(positions, angles, sizes)
end



function visualise(cl)

        s = []

        for i ∈ 1:length(cl.positions)
                x = cl.positions[i][1]
                y = cl.positions[i][2]
                z = cl.positions[i][3]

                a = cl.sizes[i][1]
                b = cl.sizes[i][2]
                c = cl.sizes[i][3]

                α = pi/2+cl.angles[i][1]
                β = cl.angles[i][2]
                γ = cl.angles[i][3]
                push!(s,
                        "var sphere$i = createSphere(radius, segments);
                   sphere$i.position.x = $x;
                   sphere$i.position.y = $y;
                   sphere$i.position.z = $z;
                   sphere$i.scale.set($a,$b,$c);
                   sphere$i.rotation.z = $α;
                   sphere$i.rotation.x = $β;
                   sphere$i.rotation.y = $γ;
                   sphere$i.rotation.order = 'ZXY';
                   scene.add(sphere$i);\n",
                )
        end
        s
end

cl = cluster_helix(10, 10, 10, 20, 50, 200)

p = visualise(cl)

io = open("particles.txt", "w");
write(io, broadcast(*, p...));
close(io);


The full self-contained example would go something like this,

<!doctype html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">

	<style>
		body { margin: 0; overflow: hidden; background-color: #fff; }
		.tm  { position: absolute; top: 10px; right: 10px; }
		.webgl-error { font: 15px/30px monospace; text-align: center; color: #fff; margin: 50px; }
		.webgl-error a { color: #fff; }
    </style>
</head>
<body>
<div id="webgl"></div>

	<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
	<script src="https://unpkg.com/three@0.85.0/examples/js/controls/TrackballControls.js"></script>		
	<script src="https://unpkg.com/three@0.85.0/examples/js/Detector.js"></script>		
		
	
	<script>

(function () {

	var webglEl = document.getElementById('webgl');

	if (!Detector.webgl) {
		Detector.addGetWebGLMessage(webglEl);
		return;
	}

	var width  = 600,
		height = 600;

	// sphere params
	var radius   = 1,
		segments = 36,
		rotation = 6;  

	var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xeeeeee );


	var camera = new THREE.PerspectiveCamera(100, width / height, 0.01, 1000);
	
	camera.position.z = 200;

	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);

const frontSpot = new THREE.SpotLight(0xeeeece);
frontSpot.position.set(1000, 1000, 1000);
scene.add(frontSpot);

const frontSpot2 = new THREE.SpotLight(0xddddce);
frontSpot2.position.set(-500, -500, -500);
scene.add(frontSpot2);


	scene.add(new THREE.AmbientLight(0xffffff, 1));

     	var xDistance = 2;
       var yDistance = 2;
         //initial offset so does not start in middle.
         var xOffset = 0;
         var yOffset = 1;
         var sphere1 = createSphere(radius, segments);
                   sphere1.position.x = 35.35533905932738;
                   sphere1.position.y = 35.35533905932737;
                   sphere1.position.z = 99.99999999999999;
                   sphere1.scale.set(10,10,20);
                   sphere1.rotation.z = 3.9269908169872414;
                   sphere1.rotation.x = 2.137707831735906;
                   sphere1.rotation.y = 0.0;
                   sphere1.rotation.order = 'ZXY';
                   scene.add(sphere1);
var sphere2 = createSphere(radius, segments);
                   sphere2.position.x = 3.061616997868383e-15;
                   sphere2.position.y = 50.0;
                   sphere2.position.z = 74.99999999999999;
                   sphere2.scale.set(10,10,20);
                   sphere2.rotation.z = 4.71238898038469;
                   sphere2.rotation.x = 2.137707831735906;
                   sphere2.rotation.y = 0.0;
                   sphere2.rotation.order = 'ZXY';
                   scene.add(sphere2);
var sphere3 = createSphere(radius, segments);
                   sphere3.position.x = -35.35533905932737;
                   sphere3.position.y = 35.35533905932738;
                   sphere3.position.z = 49.999999999999986;
                   sphere3.scale.set(10,10,20);
                   sphere3.rotation.z = -0.7853981633974483;
                   sphere3.rotation.x = 2.137707831735906;
                   sphere3.rotation.y = 0.0;
                   sphere3.rotation.order = 'ZXY';
                   scene.add(sphere3);
var sphere4 = createSphere(radius, segments);
                   sphere4.position.x = -50.0;
                   sphere4.position.y = 6.123233995736766e-15;
                   sphere4.position.z = 24.999999999999986;
                   sphere4.scale.set(10,10,20);
                   sphere4.rotation.z = -2.220446049250313e-16;
                   sphere4.rotation.x = 2.137707831735906;
                   sphere4.rotation.y = 0.0;
                   sphere4.rotation.order = 'ZXY';
                   scene.add(sphere4);
var sphere5 = createSphere(radius, segments);
                   sphere5.position.x = -35.355339059327385;
                   sphere5.position.y = -35.35533905932737;
                   sphere5.position.z = 0.0;
                   sphere5.scale.set(10,10,20);
                   sphere5.rotation.z = 0.7853981633974481;
                   sphere5.rotation.x = 2.137707831735906;
                   sphere5.rotation.y = 0.0;
                   sphere5.rotation.order = 'ZXY';
                   scene.add(sphere5);
var sphere6 = createSphere(radius, segments);
                   sphere6.position.x = -9.184850993605149e-15;
                   sphere6.position.y = -50.0;
                   sphere6.position.z = -25.000000000000014;
                   sphere6.scale.set(10,10,20);
                   sphere6.rotation.z = 1.5707963267948963;
                   sphere6.rotation.x = 2.137707831735906;
                   sphere6.rotation.y = 0.0;
                   sphere6.rotation.order = 'ZXY';
                   scene.add(sphere6);
var sphere7 = createSphere(radius, segments);
                   sphere7.position.x = 35.35533905932737;
                   sphere7.position.y = -35.355339059327385;
                   sphere7.position.z = -49.999999999999986;
                   sphere7.scale.set(10,10,20);
                   sphere7.rotation.z = 2.356194490192345;
                   sphere7.rotation.x = 2.137707831735906;
                   sphere7.rotation.y = 0.0;
                   sphere7.rotation.order = 'ZXY';
                   scene.add(sphere7);
var sphere8 = createSphere(radius, segments);
                   sphere8.position.x = 50.0;
                   sphere8.position.y = -1.2246467991473532e-14;
                   sphere8.position.z = -75.00000000000001;
                   sphere8.scale.set(10,10,20);
                   sphere8.rotation.z = 3.141592653589793;
                   sphere8.rotation.x = 2.137707831735906;
                   sphere8.rotation.y = 0.0;
                   sphere8.rotation.order = 'ZXY';
                   scene.add(sphere8);
var sphere9 = createSphere(radius, segments);
                   sphere9.position.x = 35.355339059327385;
                   sphere9.position.y = 35.35533905932737;
                   sphere9.position.z = -100.00000000000001;
                   sphere9.scale.set(10,10,20);
                   sphere9.rotation.z = 3.9269908169872414;
                   sphere9.rotation.x = 2.137707831735906;
                   sphere9.rotation.y = 0.0;
                   sphere9.rotation.order = 'ZXY';
                   scene.add(sphere9);
var sphere10 = createSphere(radius, segments);
                   sphere10.position.x = 1.5308084989341916e-14;
                   sphere10.position.y = 50.0;
                   sphere10.position.z = -124.99999999999999;
                   sphere10.scale.set(10,10,20);
                   sphere10.rotation.z = 4.71238898038469;
                   sphere10.rotation.x = 2.137707831735906;
                   sphere10.rotation.y = 0.0;
                   sphere10.rotation.order = 'ZXY';
                   scene.add(sphere10);


        		
//var axesHelper = new THREE.AxesHelper( 5 );
//scene.add( axesHelper );

				scene.add( new THREE.AxesHelper( 1 ) );
	var controls = new THREE.TrackballControls(camera);

	webglEl.appendChild(renderer.domElement);

	render();

	function render() {
		controls.update();
		requestAnimationFrame(render);
		renderer.render(scene, camera);
	}


 
	function createSphere(radius, segments) {
const material = new THREE.MeshStandardMaterial({
  color: 0xfcc742,
  emissive: 0x111111,
  specular: 0xffffff,
  metalness: 0.8,
  roughness: 0.6,
});
		return new THREE.Mesh(
			new THREE.SphereGeometry(radius, segments, segments),
			material
		);
	}


}());
</script>
</body>
</html>
1 Like