Skip to content

Commit d43883e

Browse files
committed
Add new examples
* kangaroo example with sidebar ui-style * multi-file example (rnd_lattice + rnd_node)
1 parent 51bf5e6 commit d43883e

File tree

3 files changed

+855
-0
lines changed

3 files changed

+855
-0
lines changed

src/examples/bendy/index.html

Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<link rel="icon" href="data:,">
6+
<title>Bendy</title>
7+
<style>
8+
body {
9+
margin: 0;
10+
font-family: monospace;
11+
}
12+
canvas { width: 100%; height: 100%; }
13+
input[type=range] { width: 100%; }
14+
#container { position: relative; }
15+
#container canvas, #overlay { position: absolute; }
16+
/*///////////////////////////////////////////////////////////////////////////*/
17+
#overlay {
18+
z-index: 1;
19+
width: 250px;
20+
height: 100vh;
21+
background-color: rgba(0,0,0,0.1);
22+
}
23+
#overlay div { padding: 5px 10px; }
24+
/*///////////////////////////////////////////////////////////////////////////*/
25+
#loader {
26+
border: 5px solid #f3f3f3; /* Light grey */
27+
border-top: 5px solid #3d3d3d; /* Grey */
28+
border-radius: 50%;
29+
width: 40px;
30+
height: 40px;
31+
animation: spin 1s linear infinite;
32+
position: absolute;
33+
top: 50%;
34+
left: 50%;
35+
z-index: 2;
36+
}
37+
@keyframes spin {
38+
0% { transform: rotate(0deg); }
39+
100% { transform: rotate(360deg); }
40+
}
41+
</style>
42+
</head>
43+
<body>
44+
<div id="loader"></div>
45+
<div id="container">
46+
<div id="overlay">
47+
<!-- /////////////////////////////////////////////////////////////////////////// -->
48+
<div><h2>Bendy</h2></div>
49+
<div>
50+
<label for="RH_IN:iterations">RH_IN:iterations</label>
51+
<input type="range" id="RH_IN:iterations" min="0" max="1000" value="1000" step="1">
52+
</div>
53+
<div><button id="downloadButton" disabled>Download</button></div>
54+
<!-- <div><a href="_Bending_gridshell.gh" download="_Bending_gridshell.gh.html">Save source code</a></div> -->
55+
<!-- /////////////////////////////////////////////////////////////////////////// -->
56+
</div>
57+
</div>
58+
59+
<script type="module">
60+
import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js'
61+
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js'
62+
import { Rhino3dmLoader } from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/3DMLoader.js'
63+
import rhino3dm from 'https://cdn.jsdelivr.net/npm/[email protected]/rhino3dm.module.js'
64+
65+
// set up loader for converting the results to threejs
66+
const loader = new Rhino3dmLoader()
67+
loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/[email protected]/' )
68+
69+
// initialise 'data' object that will be used by compute()
70+
const data = {
71+
definition: 'Bending_gridshell.gh',
72+
inputs: getInputs()
73+
}
74+
75+
// globals
76+
let rhino, doc
77+
78+
rhino3dm().then(async m => {
79+
rhino = m
80+
81+
init()
82+
compute()
83+
})
84+
85+
const downloadButton = document.getElementById("downloadButton")
86+
downloadButton.onclick = download
87+
88+
/////////////////////////////////////////////////////////////////////////////
89+
// HELPER FUNCTIONS //
90+
/////////////////////////////////////////////////////////////////////////////
91+
92+
/**
93+
* Gets <input> elements from html and sets handlers
94+
* (html is generated from the grasshopper definition)
95+
*/
96+
function getInputs() {
97+
const inputs = {}
98+
for (const input of document.getElementsByTagName('input')) {
99+
switch (input.type) {
100+
case 'number':
101+
inputs[input.id] = input.valueAsNumber
102+
input.onchange = onSliderChange
103+
break
104+
case 'range':
105+
inputs[input.id] = input.valueAsNumber
106+
input.onmouseup = onSliderChange
107+
input.ontouchend = onSliderChange
108+
break
109+
case 'checkbox':
110+
inputs[input.id] = input.checked
111+
input.onclick = onSliderChange
112+
break
113+
default:
114+
break
115+
}
116+
}
117+
return inputs
118+
}
119+
120+
// more globals
121+
let scene, camera, renderer, controls
122+
123+
/**
124+
* Sets up the scene, camera, renderer, lights and controls and starts the animation
125+
*/
126+
function init() {
127+
128+
// Rhino models are z-up, so set this as the default
129+
THREE.Object3D.DefaultUp = new THREE.Vector3( 0, 0, 1 );
130+
131+
// create a scene and a camera
132+
scene = new THREE.Scene()
133+
scene.background = new THREE.Color(1, 1, 1)
134+
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
135+
camera.position.set(1, -1, 1) // like perspective view
136+
137+
// very light grey for background, like rhino
138+
scene.background = new THREE.Color('whitesmoke')
139+
140+
// create the renderer and add it to the html
141+
renderer = new THREE.WebGLRenderer({ antialias: true })
142+
renderer.setPixelRatio( window.devicePixelRatio )
143+
renderer.setSize(window.innerWidth, window.innerHeight)
144+
document.body.appendChild(renderer.domElement)
145+
146+
// add some controls to orbit the camera
147+
controls = new OrbitControls(camera, renderer.domElement)
148+
149+
// add a directional light
150+
const directionalLight = new THREE.DirectionalLight( 0xffffff )
151+
directionalLight.intensity = 2
152+
scene.add( directionalLight )
153+
154+
const ambientLight = new THREE.AmbientLight()
155+
scene.add( ambientLight )
156+
157+
// handle changes in the window size
158+
window.addEventListener( 'resize', onWindowResize, false )
159+
160+
animate()
161+
}
162+
163+
/**
164+
* Call appserver
165+
*/
166+
async function compute() {
167+
// construct url for GET /solve/definition.gh?name=value(&...)
168+
const url = new URL('/solve/' + data.definition, window.location.origin)
169+
Object.keys(data.inputs).forEach(key => url.searchParams.append(key, data.inputs[key]))
170+
console.log(url.toString())
171+
172+
try {
173+
const response = await fetch(url)
174+
175+
if(!response.ok) {
176+
// TODO: check for errors in response json
177+
throw new Error(response.statusText)
178+
}
179+
180+
const responseJson = await response.json()
181+
182+
collectResults(responseJson)
183+
184+
} catch(error) {
185+
console.error(error)
186+
}
187+
}
188+
189+
/**
190+
* Parse response
191+
*/
192+
function collectResults(responseJson) {
193+
194+
const values = responseJson.values
195+
196+
// clear doc
197+
if( doc !== undefined)
198+
doc.delete()
199+
200+
//console.log(values)
201+
doc = new rhino.File3dm()
202+
203+
// for each output (RH_OUT:*)...
204+
for ( let i = 0; i < values.length; i ++ ) {
205+
// ...iterate through data tree structure...
206+
for (const path in values[i].InnerTree) {
207+
const branch = values[i].InnerTree[path]
208+
// ...and for each branch...
209+
for( let j = 0; j < branch.length; j ++) {
210+
// ...load rhino geometry into doc
211+
const rhinoObject = decodeItem(branch[j])
212+
if (rhinoObject !== null) {
213+
doc.objects().add(rhinoObject, null)
214+
}
215+
}
216+
}
217+
}
218+
219+
if (doc.objects().count < 1) {
220+
console.error('No rhino objects to load!')
221+
showSpinner(false)
222+
return
223+
}
224+
225+
// hack (https://github.com/mcneel/rhino3dm/issues/353)
226+
const sphereAttrs = new rhino.ObjectAttributes()
227+
sphereAttrs.mode = rhino.ObjectMode.Hidden
228+
doc.objects().addSphere(new rhino.Sphere([0,0,0], 0.001), sphereAttrs)
229+
230+
// load rhino doc into three.js scene
231+
const buffer = new Uint8Array(doc.toByteArray()).buffer
232+
loader.parse( buffer, function ( object )
233+
{
234+
// debug
235+
/*
236+
object.traverse(child => {
237+
if (child.material !== undefined)
238+
child.material = new THREE.MeshNormalMaterial()
239+
}, false)
240+
*/
241+
242+
// clear objects from scene. do this here to avoid blink
243+
scene.traverse(child => {
244+
if (!child.isLight) {
245+
scene.remove(child)
246+
}
247+
})
248+
249+
// add object graph from rhino model to three.js scene
250+
scene.add( object )
251+
252+
// hide spinner and enable download button
253+
showSpinner(false)
254+
downloadButton.disabled = false
255+
256+
// zoom to extents
257+
zoomCameraToSelection(camera, controls, scene.children)
258+
})
259+
}
260+
261+
/**
262+
* Attempt to decode data tree item to rhino geometry
263+
*/
264+
function decodeItem(item) {
265+
const data = JSON.parse(item.data)
266+
if (item.type === 'System.String') {
267+
// hack for draco meshes
268+
try {
269+
return rhino.DracoCompression.decompressBase64String(data)
270+
} catch {} // ignore errors (maybe the string was just a string...)
271+
} else if (typeof data === 'object') {
272+
return rhino.CommonObject.decode(data)
273+
}
274+
return null
275+
}
276+
277+
/**
278+
* Called when a slider value changes in the UI. Collect all of the
279+
* slider values and call compute to solve for a new scene
280+
*/
281+
function onSliderChange () {
282+
showSpinner(true)
283+
// get slider values
284+
let inputs = {}
285+
for (const input of document.getElementsByTagName('input')) {
286+
switch (input.type) {
287+
case 'number':
288+
inputs[input.id] = input.valueAsNumber
289+
break
290+
case 'range':
291+
inputs[input.id] = input.valueAsNumber
292+
break
293+
case 'checkbox':
294+
inputs[input.id] = input.checked
295+
break
296+
}
297+
}
298+
299+
data.inputs = inputs
300+
301+
compute()
302+
}
303+
304+
/**
305+
* The animation loop!
306+
*/
307+
function animate() {
308+
requestAnimationFrame( animate )
309+
controls.update()
310+
renderer.render(scene, camera)
311+
}
312+
313+
/**
314+
* Helper function for window resizes (resets the camera pov and renderer size)
315+
*/
316+
function onWindowResize() {
317+
camera.aspect = window.innerWidth / window.innerHeight
318+
camera.updateProjectionMatrix()
319+
renderer.setSize( window.innerWidth, window.innerHeight )
320+
animate()
321+
}
322+
323+
/**
324+
* Helper function that behaves like rhino's "zoom to selection", but for three.js!
325+
*/
326+
function zoomCameraToSelection( camera, controls, selection, fitOffset = 1.2 ) {
327+
328+
const box = new THREE.Box3();
329+
330+
for( const object of selection ) {
331+
if (object.isLight) continue
332+
box.expandByObject( object );
333+
}
334+
335+
const size = box.getSize( new THREE.Vector3() );
336+
const center = box.getCenter( new THREE.Vector3() );
337+
338+
const maxSize = Math.max( size.x, size.y, size.z );
339+
const fitHeightDistance = maxSize / ( 2 * Math.atan( Math.PI * camera.fov / 360 ) );
340+
const fitWidthDistance = fitHeightDistance / camera.aspect;
341+
const distance = fitOffset * Math.max( fitHeightDistance, fitWidthDistance );
342+
343+
const direction = controls.target.clone()
344+
.sub( camera.position )
345+
.normalize()
346+
.multiplyScalar( distance );
347+
controls.maxDistance = distance * 10;
348+
controls.target.copy( center );
349+
350+
camera.near = distance / 100;
351+
camera.far = distance * 100;
352+
camera.updateProjectionMatrix();
353+
camera.position.copy( controls.target ).sub(direction);
354+
355+
controls.update();
356+
357+
}
358+
359+
/**
360+
* This function is called when the download button is clicked
361+
*/
362+
function download () {
363+
// write rhino doc to "blob"
364+
const bytes = doc.toByteArray()
365+
const blob = new Blob([bytes], {type: "application/octect-stream"})
366+
367+
// use "hidden link" trick to get the browser to download the blob
368+
const filename = data.definition.replace(/\.gh$/, '') + '.3dm'
369+
const link = document.createElement('a')
370+
link.href = window.URL.createObjectURL(blob)
371+
link.download = filename
372+
link.click()
373+
}
374+
375+
/**
376+
* Shows or hides the loading spinner
377+
*/
378+
function showSpinner(enable) {
379+
if (enable)
380+
document.getElementById('loader').style.display = 'block'
381+
else
382+
document.getElementById('loader').style.display = 'none'
383+
}
384+
</script>
385+
</body>
386+
</html>

0 commit comments

Comments
 (0)