BruceSherwood avatar BruceSherwood committed cc20342

Update saved copies of example programs

Comments (0)

Files changed (24)

examples/AtomicSolid.js

-GlowScript 0.3
-/* In GlowScript programs, rotate with right mouse button, 
-
-or drag with Ctrl key;
+GlowScript 1.0
+/* In GlowScript programs, rotate the camera by dragging with the right mouse button, or hold down the Ctrl key and drag.
    
-zoom with left+right mouse buttons, 
-
-or drag with Alt key or mouse wheel.*/
-
-canvas()
-
-var s = "A model of a solid represented as atoms connected by interatomic bonds"
-$("<p/>").text(s).appendTo("#glowscript")
+To zoom, drag with the left+right mouse buttons, or hold down the Alt key and drag, or use the mouse wheel.*/
 
 vec.axes = [vec(1,0,0), vec(0,1,0), vec(0,0,1)]
 
+var k = 1
+var m = 1
+var spacing = 1.0
+var atom_radius = 0.3*spacing
+var L0 = spacing-1.8*atom_radius
+var V0 = pi*pow(0.5*atom_radius,2)*L0 // initial volume of spring
+var N = 3
+var crystal = makeCrystal(N, atom_radius, spacing, 0.1*spacing*sqrt(k/m))
+scene.center = 0.5*(N-1)*vec(1,1,1)
+scene.autoscale = false
+var dt = 0.04*(2*pi*sqrt(m/k))
+
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
+
+// Display text below the 3D graphics:
+scene.title.text("A model of a solid represented as atoms connected by interatomic bonds")
+display_instructions()
+
 function makeCrystal( N, atom_radius, spacing, momentumRange ) {
     var crystal = { atoms:[], springs:[] }
     var atom
                     var neighbor = atomAt(vec(x,y,z)+vec.axes[d])
 
                     if (atom.visible || neighbor.visible) {
-                        var spring = cylinder()
+                        var spring = helix()
                         spring.visible = atom.visible && neighbor.visible
+                        spring.thickness = 0.05
                         spring.size = vec(spacing,atom_radius,atom_radius)
+                        spring.up = vec(1,1,1) // prevent fibrillation of vertical springs
                         spring.atoms = [ atom, neighbor ]
                         spring.color = vec(1,0.5,0)
                         crystal.springs.push(spring)
     return crystal
 }
 
-var k = 1
-var m = 1
-var spacing = 1.0
-var atom_radius = 0.3*spacing
-var L0 = spacing-1.8*atom_radius
-var V0 = pi*pow(0.5*atom_radius,2)*L0 // initial volume of spring
-var N = 4
-var crystal = makeCrystal(N, atom_radius, spacing, 0.1*spacing*sqrt(k/m))
-scene.autocenter = true // get the center of the crystal, to set scene.center
-scene.autocenter = false
-scene.range = spacing*(N-1)/2+atom_radius
-var dt = 0.02*(2*pi*sqrt(m/k))
-
 while (true) {
+    rate(30,wait)
     for(var a=0; a<crystal.atoms.length; a++) {
         var atom = crystal.atoms[a]
         atom.pos = atom.pos + atom.momentum/m*dt
         spring.axis = spring.atoms[1].pos - spring.atoms[0].pos
         var L = mag(spring.axis)
         spring.axis = spring.axis.norm()
-        spring.pos = spring.atoms[0].pos+0.9*atom_radius*spring.axis
-        var Ls = L-1.8*atom_radius
-        var R = sqrt(V0/Ls/pi)
-        spring.size = vec(Ls,R,R)
+        spring.pos = spring.atoms[0].pos+0.5*atom_radius*spring.axis
+        var Ls = L-1*atom_radius
+        spring.size.x = Ls
         var Fdt = spring.axis * (k*dt * (1-spacing/L))
         spring.atoms[0].momentum = spring.atoms[0].momentum + Fdt
         spring.atoms[1].momentum = spring.atoms[1].momentum - Fdt
     }
-    waitfor.redraw(wait)
 }

examples/BinaryStar.js

-GlowScript 0.3
+GlowScript 1.0
 /* Binary star */
 
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
+
+// Display text below the 3D graphics:
+scene.title.text("Binary Star")
+display_instructions()
+
 scene.forward = vec(0,-.3,-1)
 
 var G = 6.7e-11
 var giant = sphere( {pos:vec(-1e11,0,0), size:4e10*vec(1,1,1), color:color.red} )
 giant.mass = 2e30
 giant.p = vec(0, 0, -1e4) * giant.mass
-giant.trail = curve({color:giant.color, radius:1e9})
+attach_trail(giant, {retain:150})
 
 var dwarf = sphere( {pos:vec(1.5e11,0,0), size:2e10*vec(1,1,1), color:color.yellow} )
 dwarf.mass = 1e30
 dwarf.p = -giant.p
-dwarf.trail = curve({color:dwarf.color, radius:1e9})
+attach_trail(dwarf, {type:"spheres", pps:20, retain:40})
 
 var dt = 1e5
-var steps = 0, end = 10
 while (true) { 
     rate(200,wait)
 
     var dist = dwarf.pos - giant.pos
     var force = G * giant.mass * dwarf.mass * dist / pow(mag(dist),3)
-	giant.p = giant.p + force*dt
-	dwarf.p = dwarf.p - force*dt
+    giant.p = giant.p + force*dt
+    dwarf.p = dwarf.p - force*dt
 
-	var stars = [giant, dwarf]
-	for (var _star in stars) {
+    var stars = [giant, dwarf]
+    for (var _star in stars) {
 		var star = stars[_star]
 		star.pos = star.pos + (star.p/star.mass) * dt
-		if (steps == end) {
-			star.trail.push(point(star.pos))
-		}
 	}
-	if (steps == end) steps = 0
-	steps++
 }

examples/Bounce-CoffeeScript.js

+GlowScript 1.0 CoffeeScript
+# The statement above invokes the CoffeeScript language (which generates JavaScript).
+# See coffeescript.org for information about CoffeeScript.
+
+display_instructions = () ->
+    s1 = "In GlowScript programs:\n\n"
+    s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+
+# Display text below the 3D graphics:
+scene.title.text("A ball bounces in a box")
+display_instructions()
+
+side = 4.0
+thk = 0.3
+s2 = 2*side - thk
+s3 = 2*side + thk
+wallR = box ( pos:vec( side, 0, 0), size:vec(thk,s2,s3),  color : color.red )
+wallL = box ( pos:vec(-side, 0, 0), size:vec(thk,s2,s3),  color : color.red )
+wallB = box ( pos:vec(0, -side, 0), size:vec(s3,thk,s3),  color : color.blue )
+wallT = box ( pos:vec(0,  side, 0), size:vec(s3,thk,s3),  color : color.blue )
+wallBK = box( pos:vec(0, 0, -side), size:vec(s2,s2,thk),  color : color.gray(0.7) )
+
+ball = sphere ( color : color.green, size : 0.8*vec(1,1,1) )
+ball.mass = 1.0
+ball.p = vec(-0.15, -0.23, +0.27)
+attach_trail(ball, {pps:200, retain:100})
+
+side = side - thk*0.5 - ball.size.x/2
+
+dt = 0.3
+t=0.0
+while true
+  rate(200, wait)
+  t = t + dt
+  ball.pos = ball.pos + (ball.p/ball.mass)*dt
+  
+  # Note the three-way numerical comparisons here, not available in JavaScript:
+  if not (-side < ball.pos.x < side)  
+    ball.p.x = -ball.p.x
+  if not (-side < ball.pos.y < side)
+    ball.p.y = -ball.p.y
+  if not (-side < ball.pos.z < side)
+    ball.p.z = -ball.p.z

examples/Bumpmaps.js

+GlowScript 1.0
+scene.visible = false
+scene.title.text("Enhanced 3D of surfaces using bump maps")
+scene.caption.text("Drag the single light with the left button, rotate with the right button.\n")
+scene.caption.append("Notice especially the appearance of the left and right faces.")
+var c = box({pos:vec(0,0,0), shininess:0,
+    texture:{file:textures.stones,  
+    bumpmap:bumpmaps.stucco} })
+c.rotate( {angle:pi/2, axis:vec(0,0,1)} )
+c.rotate( {angle:pi, axis:vec(0,1,0)} )
+scene.waitfor("textures",wait)
+scene.visible = true
+
+scene.background = color.gray(.5)
+scene.lights = []
+var L = distant_light( {direction:vec(0,0,1)} )
+
+scene.bind("mousedown", function() {
+    var pos = scene.mouse.pos
+    
+    scene.bind("mousemove", function () {
+        if (pos !== null) {
+            var x = scene.mouse.pos.x
+            var y = scene.mouse.pos.y
+            L.direction.x = x-pos.x
+            L.direction.y = y-pos.y
+        }
+    })
+    
+    scene.bind("mouseup", function () {
+        pos = null
+    })
+})

examples/ButtonsSlidersMenus.js

+GlowScript 1.0
+scene.width = 350
+scene.height = 300
+scene.range = 1.5
+scene.title.text("Buttons, Sliders, and Drop-down Menus\n")
+// The buttons and drop-down menu implemented here use jQuery; see jquery.com
+// The slider uses jQuery UI; see jqueryui.com
+
+var running = true
+
+// Create a button, label it "Pause", append to scene.title,
+//     and bind a click routine to it, using jquery instructions.
+// $('<button/>') adds <button/> to the html web page.
+// .text("Pause") inserts "Pause", creating <button>Pause</button>.
+// .appendTo(scene.title) places the button after the existing text,
+//      which ended with \n, a newline character, so the button
+//      appears below the title.
+// .click(...) specifies what to do when the user clicks the button.
+// $(this) refers to this button, and .text(...) is used to change
+//      the text of the button.
+$('<button/>').text("Pause").appendTo(scene.title)
+    .click( function() {
+        running = !running
+        if (running) $(this).text("Pause") 
+        else $(this).text("Run")
+    })
+
+var box_object = box({visible:true})
+var cone_object = cone({visible:false})
+var pyramid_object = pyramid({visible:false})
+var cylinder_object = cylinder({visible:false})
+
+var col = color.cyan
+var currentobject = box_object
+currentobject.color = col
+
+// Create another button, label it "red" in red color with cyan background,
+// append to scene.title, and bind a click routine to it:
+$('<button/>').text("red").appendTo(scene.title)
+    .css({color:"red", backgroundColor:"cyan", fontWeight:"bold"})
+    .click( function() {
+        if (col == color.cyan) { // change to cyan on a red background
+            currentobject.color = col = color.red
+            $(this).text("cyan").css({color: "cyan", backgroundColor: "red"})
+        } else {                 // change to red on a cyan background
+            currentobject.color = col = color.cyan
+            $(this).text("red").css({color: "red", backgroundColor: "cyan"})
+        }
+    })
+
+scene.caption.text("Vary the rotation rate: ")
+var speed = 150
+
+function setspeed() {
+    speed = $("#speed_slider").slider("value")
+}
+
+// By default, a slider is placed horizontally.
+// If you want a vertical slider, change "width" to "height" in .css(...)
+// and in the function specify orientation:vertical.
+$('<div id="speed_slider"></div>').appendTo(scene.caption)
+    .css({width:"350px"}) // specify height if vertical orientation
+$(function() { $("#speed_slider").slider( {
+        // orientation: vertical, // to make a vertical slider
+        value: 250, 
+        min: 20, 
+        max: 500,
+        range:"min", // color fills left portion of slider
+        slide: function() {setspeed()},
+        change: function() {setspeed()}
+    }) 
+})
+
+// Place text below the slider:
+var choose = $("<p>Change the object: </p>").appendTo(scene.caption)
+
+// Create a drop-down menu (a "select" object). Set up the options to appear:
+var s = ""
+s += "<option select=selected>box</option>"
+s += "<option>cone</option>"
+s += "<option>pyramid</option>"
+s += "<option>cylinder</option>"
+
+$('<select/>').html(s).css({font:"sans"})
+    .change(function() { // come here when a change is made in the menu choice
+            var currentaxis = currentobject.axis
+            currentobject.visible = false
+        switch ($(this).val()) {
+            case "box": 
+                currentobject = box_object
+                break;
+            case "cone": 
+                currentobject = cone_object
+                break;
+            case "pyramid": 
+                currentobject = pyramid_object
+                break;
+            case "cylinder": 
+                currentobject = cylinder_object
+                break;
+        }
+        currentobject.color = col
+        currentobject.axis = currentaxis
+        currentobject.visible = true
+    })
+    .appendTo(choose) // place drop-down menu beside the text, "Change the object: "
+
+while (true) {
+    rate(100,wait)
+    if (running) {
+        currentobject.rotate({angle:speed*1e-4, axis:vec(0,1,0)})
+    }
+}
+GlowScript 1.0 CoffeeScript
+
+scene.range = 1.5
+scene.forward = vec(-1,-.5,-1)
+box
+    size:vec(2,1,1)
+    texture: "https://s3.amazonaws.com/glowscript/textures/cors_test.jpg"
+
+s = 'This illustrates the use of an image from another web site as a texture.\n'
+s += 'This is an example of CORS, "Cross-Origin Resource Sharing".'
+scene.caption.text(s)
+
+display_instructions = () ->
+    s1 = "\n\nIn GlowScript programs:\n\n"
+    s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.append(s1+s2+s3)
+
+# Display text below the 3D graphics:
+display_instructions()

examples/Color-RGB-HSV.js

+GlowScript 1.0
+scene.userzoom = false
+scene.userspin = false
+scene.width = scene.height = 200
+scene.range = 1
+box({pos:vec(10,0,0)}) // Force creation of canvas; box is not seen
+
+var C = ['Red', 'Green', 'Blue', 'Hue', 'Saturation', 'Value']
+
+for (var i=0; i<6; i++) {
+    scene.caption.append(C[i]) // Display slider name
+    scene.caption.append("<p id='"+C[i]+"'></p>") // Make slider container
+        .css({width:"180px", margin:"10px"})
+    if (i == 2) scene.caption.append("\n\n\n") // Separate the RGB and HSV sliders
+}
+
+// set_rgb prevents propagation of changing RGB changing HSV changing RGB ....
+var set_rgb = null // true if RGB changed, false if HSV changed, else null
+
+function set_background() {
+    var s = [], rgb, hsv
+    for (var i=0; i<6; i++) { // Get values for all 6 sliders
+        s[i] = $( "#"+C[i] ).slider( "value" )
+    }
+    if (set_rgb) {
+        rgb = vec(s[0],s[1],s[2])
+        hsv = color.rgb_to_hsv(rgb)
+        $( "#"+C[3] ).slider( "value", hsv.x) // reset HSV slider positions
+        $( "#"+C[4] ).slider( "value", hsv.y)
+        $( "#"+C[5] ).slider( "value", hsv.z)
+    } else {
+        hsv = vec(s[3],s[4],s[5])
+        rgb = color.hsv_to_rgb(hsv)
+        $( "#"+C[0] ).slider( "value", rgb.x) // reset RGB slider positions
+        $( "#"+C[1] ).slider( "value", rgb.y)
+        $( "#"+C[2] ).slider( "value", rgb.z)
+    }
+    scene.background = vec(s[0],s[1],s[2])
+    // For readability, limit precision of display of quantities to 3 figures
+    // The function toFixed() generates a string which must be converted to a floating point quantity
+    rgb = vec(parseFloat(rgb.x.toFixed(3)),parseFloat(rgb.y.toFixed(3)),parseFloat(rgb.z.toFixed(3)))
+    hsv = vec(parseFloat(hsv.x.toFixed(3)),parseFloat(hsv.y.toFixed(3)),parseFloat(hsv.z.toFixed(3)))
+    scene.title.text("RGB = "+rgb.toString()+"\nHSV = "+hsv.toString())
+    set_rgb = null
+}
+
+function which(choose) { // true if RGB slider changed, else HSV slider changed
+    if (set_rgb === null) {
+        set_rgb = choose
+        set_background()
+    }
+}
+
+// Create RGB sliders; the string is "#Red, #Green, #Blue'
+$(function() { $( '#'+C[0]+',#'+C[1]+',#'+C[2] ).slider({
+    orientation: "horizontal",
+    range: "min",
+    min: 0,
+    max: 1,
+    value: 0,
+    step: 0.01,
+	slide: function() {which(true)},
+    change: function() {which(true)}
+})})
+
+// Create HSV sliders; the string is "#Hue, #Saturation, #Value'
+$(function() { $( '#'+C[3]+',#'+C[4]+',#'+C[5] ).slider({
+    orientation: "horizontal",
+	range: "min",
+    min: 0,
+	max: 1,
+	value: 0,
+    step: 0.01,
+    slide: function() {which(false)},
+    change: function() {which(false)}
+})})
+
+$( "#"+C[0] ).slider( "value", 1) // Start with pure red

examples/DancingPendulums.js

-GlowScript 0.3
+GlowScript 1.0
 /* Simulation of dancing pendulums.  
 
 Run the program and follow the link to the video for more explanation. */
     pendulums.push(p)
 }
 
-var text = $("<div/>").appendTo("#glowscript")
-
 var link = $("<a/>").prop("href", "http://sciencedemonstrations.fas.harvard.edu/icb/icb.do?keyword=k16940&pageid=icb.page80863&pageContentId=icb.pagecontent341734&state=maximize&view=view.do&viewParam_name=indepth.html#a_icb_pagecontent341734")
 link.text("Based on this video")
-link.appendTo( $("<div/>").appendTo("#glowscript") )
+link.appendTo( $("<div/>").appendTo( canvas.container ) )
 
 var start = new Date().getTime()
 while (true) {
   var t = (new Date().getTime() - start)*.001
-  text.text("t=" + t.toFixed(1))
+  scene.caption.text("t=" + t.toFixed(1))
   for(var i=0; i<pendulums.length; i++) {
       var p = pendulums[i]
       var theta = p.theta0 * cos( 2*pi*t/p.period )
       p.wire.axis = vec(sin(theta),-cos(theta),0)
       p.pos = p.wire.pos + p.wire.axis*p.wire.size.x
   }
-  waitfor.redraw(wait)
+  scene.waitfor("redraw",wait)
 }

examples/DipoleElectricField.js

-GlowScript 0.3
+GlowScript 1.0
 /* Electric field of a dipole */
 
-canvas() // establish graphics region of screen
-
-var s = "Click or drag to plot an electric field vector produced by the two charges."
-$("<p style='font-size:90%'>"+s+"</p>").appendTo("#glowscript")
-s = "Arrows representing the field are bluer if low magnitude, redder if high."
-$("<p style='font-size:90%'>"+s+"</p>").appendTo("#glowscript")
-
 var scale = 4e-14/1e17
 var ec = 1.6e-19  // electron charge
 scene.range = 2e-13
 var charges = [ sphere( { pos : vec(-1e-13,0,0), Q :  ec, color:color.red, size : 1.2e-14*vec(1,1,1) } ),
                 sphere( { pos : vec( 1e-13,0,0), Q : -ec, color:color.blue, size : 1.2e-14*vec(1,1,1) } )]
 
+var s1 = "Click or drag to plot an electric field vector produced by the two charges."
+var s2 = "Arrows representing the field are bluer if low magnitude, redder if high."
+scene.caption.text(s1+'\n\n'+s2)
+
 function getfield(p) {
     var f = vec(0,0,0)
     for (var _c in charges) {
     var f = getfield(p)
     var m = mag(f)
     var red = Math.max( 1-1e17/m, 0 )
-	var blue = Math.min(   1e17/m, 1 )
-	if (red >= blue) { 
-		blue = blue/red
+    var blue = Math.min(   1e17/m, 1 )
+    if (red >= blue) { 
+        blue = blue/red
 		red = 1.0
 	}
 	else { 
     a. color = vec(red,0,blue)
 }
 
-on.mousedown = function() {
-    var a = arrow( {shaftwidth:6e-15} )
+var drag = false
+var a
+
+scene.bind("mousedown", function() {
+    a = arrow( {shaftwidth:6e-15} )
     mouse_to_field(a)
-    var drag = true
+    drag = true
+})
 
-    on.mousemove = function() {
-        if (!drag) return
-        mouse_to_field(a)
-    }
-    
-    on.mouseup = function() {
-        mouse_to_field(a)
-        drag = false
-    }
-}
+scene.bind("mousemove", function() {
+    if (!drag) return
+    mouse_to_field(a)
+})
+
+scene.bind("mouseup", function() {
+    mouse_to_field(a)
+    drag = false
+})

examples/GlowScriptObjects.js

-GlowScript 0.3
-/* This program displays all existing GlowScript objects other than graphs of functions.
+GlowScript 1.0
+/* This program displays most GlowScript 3D objects.
 
 It also illustrates key features such as mouse handling, rate, and sleep. */
 
 // Bruce Sherwood, August 2011
 
-on.mousedown = function() {
-  var s = sphere({color:color.magenta, visible:false, drag:false})
-  s.pos = scene.mouse.pos
-  s.visible = true
-  
-  on.mousemove = function() {
+var s
+var drag = false
+
+scene.bind("mousedown", function() {
+    s = sphere({color:color.magenta})
     s.pos = scene.mouse.pos
-  }
-  
-  on.mouseup = function() {
-    on.mousemove = null
-    on.mouseup = null
+    drag = true
+})
+
+scene.bind("mousemove", function() {
+    if (!drag) { return }
+    s.pos = scene.mouse.pos
+})
+
+scene.bind("mouseup", function() {
     s.visible = false
-  }
-}
+    drag = false
+})
 
+scene.title.text("A display of most GlowScript 3D objects")
 scene.background = color.gray(0.7)
 scene.center = vec(0,0.5,0)
 scene.forward = vec(-.3,0,-1)
 var ball = sphere( {pos:vec(2,1,0), size:1.2*vec(1,1,1), color:color.cyan} )
 var ptr = arrow( {pos:vec(0,0,2), axis_and_length:vec(2,0,0), color:color.yellow} )
 cone( {pos:vec(-2,0,0), size:vec(3,2,2), color:color.green} )
-ring( {pos:vec(.2,0,0), size:1.2*vec(1,1,1), axis:vec(1,0,0), color:color.gray(0.4)} )
+ring( {pos:vec(.2,0,0), size:1.2*vec(0.2,1,1), axis:vec(1,0,0), color:color.gray(0.4)} )
 sphere( {pos:vec(-.3,2,0), color:color.orange, size:vec(.3,1.5,1.5)} )
 pyramid( {pos:vec(.3,2,0), color:vec(0,0.5,.25), size:vec(0.8,1.2,1.2)} )
 var spring = helix( {pos:vec(2,-1.25,0), size:vec(1.8,.6,.6), axis:vec(0,1,0),
 
 var angle = 0
 var da = .01
+
 var trail = curve({color:color.magenta, radius: .02})
-trail.push(point(vec(1,0,0)))
-trail.push(point(vec(1,0,2)))
-trail.push(point(vec(2,0,2)))
+trail.push(vec(1,0,0))
+trail.push(vec(1,0,2))
+trail.push(vec(2,0,2))
+
 while (angle < 3*pi/4) {
   rate(100,wait)
-  ptr.rotate( da, vec(0,0,1), ptr.pos )
-  trail.push(point(ptr.pos+ptr.axis_and_length))
+  ptr.rotate( {angle:da, axis:vec(0,0,1), origin:ptr.pos} )
+  trail.push(ptr.pos+ptr.axis_and_length)
   angle += da
 }
 
 sleep(1,wait) // sleep for 1 second
 scene.autoscale = false
-var s = "Drag the mouse and you'll drag a sphere."
-$("<p>"+s+"</p>").appendTo("#glowscript")
+scene.caption.text("Drag the mouse and you'll drag a sphere.")
 
 var t = 0
 var dt = .01
   spring.size.x = ball.pos.y-spring.pos.y-ball.size.y/2+0.15
   title.yoffset = 28*sin(-4*t)
   t += dt
-}
+}

examples/Gyroscope.js

-GlowScript 0.3
+GlowScript 1.0
 // Converted from the VPython program gyro2
 
 // Gyroscope sitting on a pedestal
 
 // Bruce Sherwood
 
-canvas({width:800, height:600}) // establish graphics region of screen
+scene.width = 800
+scene.height = 600
+scene.visible = false
+scene.title.text("A precessing, nutating gyroscope")
 
 var Lshaft = 1 // length of gyroscope shaft
 var r = Lshaft/2 // distance from support point to center of mass
 var Rshaft = 0.03 // radius of gyroscope shaft
-var M = 1. // mass of gyroscope (massless shaft)
+var M = 1 // mass of gyroscope (massless shaft)
 var Rrotor = 0.4 // radius of gyroscope rotor
 var Drotor = 0.1 // thickness of gyroscope rotor
-var I = 0.5*M*pow(Rrotor,2.) // moment of inertia of gyroscope
+var I = 0.5*M*pow(Rrotor,2) // moment of inertia of gyroscope
 var hpedestal = Lshaft // height of pedestal
 var wpedestal = 0.1 // width of pedestal
 var tbase = 0.05 // thickness of base
 //else:
 //    phidot = (-alphadot+sqrt(alphadot**2+2*M*g*r*cos(theta)/I))/cos(theta)
 
-var pedestal = box( {pos:pedestal_top-vec(0,hpedestal/2.,0),
+var pedestal = box( {pos:pedestal_top-vec(0,hpedestal/2,0),
                  size:vec(wpedestal,hpedestal,wpedestal),
                  color:vec(0.4,0.4,0.5) } )
-var base = box( {pos:pedestal_top-vec(0,hpedestal+tbase/2.,0),
+var base = box( {pos:pedestal_top-vec(0,hpedestal+tbase/2,0),
                  size:vec(wbase,tbase,wbase),
                  color:pedestal.color } )
 
 var shaft = cylinder( {axis:vec(1,0,0), size:vec(Lshaft,2*Rshaft,2*Rshaft), color:vec(0,1,0)} )
 var rotor = cylinder( {pos:vec(Lshaft/2 - Drotor/2, 0, 0), axis:vec(1,0,0),
-                 size:vec(Drotor,2*Rrotor,2*Rrotor), color:vec(1,0,0)} )
+                 size:vec(Drotor,2*Rrotor,2*Rrotor), color:vec(1,0,0), texture:textures.rough} )
 
-var trail = curve( {radius:Rshaft/8., color:vec(1,1,0)} )
+scene.caption.text("Loading textures...")
+scene.waitfor("textures",wait)
+scene.caption.text("")
+
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
+
+// Display text below the 3D graphics:
+display_instructions()
 
 var dt = 3e-5
 var t = 0
-var Nsteps = 50 // number of calculational steps between graphics updates
+var Nsteps = 200 // number of calculational steps between graphics updates
 
 while (true) { 
-    rate(300,wait)
+    scene.waitfor("redraw",wait)
     for (var step=0; step<Nsteps; step++) { // multiple calculation steps for accuracy
         // Calculate accelerations of the Lagrangian coordinates:
-        var atheta = (pow(phidot,2)*sin(theta)*cos(theta)
-                  -2.*(alphadot+phidot*cos(theta))*phidot*sin(theta)
-                  +2.*M*g*r*sin(theta)/I)
-        var aphi = 2.*thetadot*(alphadot-phidot*cos(theta))/sin(theta)
+        var atheta = (pow(phidot,2)*sin(theta)*cos(theta)-
+                  2*(alphadot+phidot*cos(theta))*phidot*sin(theta)+
+                  2*M*g*r*sin(theta)/I)
+        var aphi = 2*thetadot*(alphadot-phidot*cos(theta))/sin(theta)
         var aalpha = phidot*thetadot*sin(theta)-aphi*cos(theta)
         // Update velocities of the Lagrangian coordinates:
         thetadot = thetadot+atheta*dt
 
     }
     var newaxis = vec(sin(theta)*sin(phi),cos(theta),sin(theta)*cos(phi))
+    // Display approximate rotation of rotor and shaft:
     rotor.axis = shaft.axis = newaxis
-    rotor.size.x = Drotor
-    shaft.size.x = Lshaft
-    // Display approximate rotation of rotor and shaft:
     rotor.pos = shaft.pos + shaft.axis*Lshaft/2
-    rotor.rotate( alphadot*dt*Nsteps, rotor.axis, rotor.pos )
-    trail.push( point(shaft.pos + shaft.axis*Lshaft) )
+    rotor.rotate( {angle:alphadot*dt*Nsteps, axis:rotor.axis, origin:rotor.pos} )
+
+    if (t === 0) {
+        scene.visible = true
+        attach_trail(function() {return (shaft.pos + shaft.axis*Lshaft)}, 
+            {radius:Rshaft/8, color:color.yellow, retain:50})
+    }
     t = t+dt*Nsteps
 }

examples/HardSphereGas.js

+GlowScript 1.0
+
+/* Hard-sphere gas. */
+
+// Bruce Sherwood, sped up with a routine by David Scherer
+
+// VPython gas.py with 100 atoms, dt = 1e-5, rate 500, no graphing: 100 iterations = 0.4 seconds.
+// If gas.py is modified to not use numpy, the time is 3.6 s.
+// This similar Glowsript program with same conditions on same machine: 100 iterations = 1.0 seconds
+// on Chrome, Firefox, or Internet Explorer.
+
+var win=500
+
+var Natoms = 100  // change this to have more or fewer atoms
+
+// Typical values
+var L = 1 // container is a cube L on a side
+var gray = color.gray(0.7) // color of edges of container
+var Matom = 4E-3/6E23 // helium mass
+var Ratom = 0.03 // wildly exaggerated size of helium atom
+var k = 1.4E-23 // Boltzmann constant
+var T = 300 // around room temperature
+var dt = 1E-5
+
+var animation = canvas( {width:win, height:win} )
+animation.range = L
+
+var d = L/2+Ratom
+var boxbottom = curve({color:gray})
+boxbottom.push(vec(-d,-d,-d), vec(-d,-d,d), vec(d,-d,d), vec(d,-d,-d), vec(-d,-d,-d))
+var boxtop = curve({color:gray})
+boxtop.push(vec(-d,d,-d), vec(-d,d,d), vec(d,d,d), vec(d,d,-d), vec(-d,d,-d))
+var vert1 = curve({color:gray})
+var vert2 = curve({color:gray})
+var vert3 = curve({color:gray})
+var vert4 = curve({color:gray})
+vert1.push(vec(-d,-d,-d), vec(-d,d,-d))
+vert2.push(vec(-d,-d,d), vec(-d,d,d))
+vert3.push(vec(d,-d,d), vec(d,d,d))
+vert4.push(vec(d,-d,-d), vec(d,d,-d))
+
+var colors = [color.red, color.green, color.blue,
+              color.yellow, color.cyan, color.magenta]
+
+var Atoms = []
+var apos = []
+var p = []
+var mass = Matom*pow(Ratom,3)/pow(Ratom,3)
+var pavg = sqrt(2*mass*1.5*k*T) // average kinetic energy p**2/(2mass) = (3/2)kT
+    
+for (var i=0; i<Natoms; i++) {
+    var x = L*Math.random()-L/2
+    var y = L*Math.random()-L/2
+    var z = L*Math.random()-L/2
+    Atoms.push(sphere( {pos:vec(x,y,z), color:colors[i % 6], size:2*Ratom*vec(1,1,1)} ) )
+    apos.push(vec(x,y,z))
+    var theta = pi*Math.random()
+    var phi = 2*pi*Math.random()
+    var px = pavg*sin(theta)*cos(phi)
+    var py = pavg*sin(theta)*sin(phi)
+    var pz = pavg*cos(theta)
+    p.push(vec(px,py,pz))
+}
+animation.title.text('A "hard-sphere" gas')
+var s = 'Theoretical and averaged speed distributions (meters/sec).\n'
+s += 'Initially all atoms have the same speed, but collisions\n'
+s += 'change the speeds of the colliding atoms.'
+animation.caption.text(s)
+
+var deltav = 100 // binning for v histogram
+
+function barx(v) {
+    return Math.floor(v/deltav) // index into bars array
+}
+
+var histo = new Array(4000/deltav)
+for (var i=0; i<histo.length; i++) histo[i] = 0
+histo[barx(pavg/mass)] = Natoms
+
+graph( {width:win, height:0.4*win, xmax:3000, ymax:Natoms*deltav/1000} )
+var theory = series( {color:color.cyan} )
+var dv = 10
+for (var v=0; v<3001+dv; v+=dv)  // theoretical prediction
+    theory.plot( v, (deltav/dv)*Natoms*4*pi*pow(Matom/(2*pi*k*T),1.5) *exp(-0.5*Matom*pow(v,2)/(k*T))*pow(v,2)*dv )
+
+
+var accum = new Array(3000/deltav)
+for (var i=0; i<accum.length; i++) accum[i] = [deltav*(i+.5),0]
+var vdist = series( {type:'bar', color:color.red, delta:deltav} )
+
+function interchange(v1, v2) { // remove from v1 bar, add to v2 bar
+    var barx1 = barx(v1)
+    var barx2 = barx(v2)
+    if (barx1 == barx2) { return }
+    histo[barx1] -= 1
+    histo[barx2] += 1
+}
+
+// The following routine by David Scherer increases the speed greatly
+function checkCollisions(apos) {
+    "no overloading"; // no operator overloading invoked here
+    var hitlist = []
+    var Natoms = apos.length;
+    var r2 = 2*Ratom;
+    r2*=r2;
+    for (var i=0; i<Natoms; i++) {
+        var ai = apos[i]
+        var aix = ai.x, aiy = ai.y, aiz = ai.z
+        for (var j=i+1; j<Natoms; j++) {
+            var aj = apos[j]
+            var dx = aix - aj.x, dy = aiy - aj.y, dz = aiz - aj.z
+            if ( dx*dx + dy*dy + dz*dz < r2 ) 
+                hitlist.push([i,j])
+        }
+    }
+    return hitlist;
+}
+
+var nhisto = 0 // number of histogram snapshots to average
+
+while(true) {
+    rate(100,wait)
+    // Accumulate and average histogram snapshots
+    for (var i=0; i<accum.length; i++) accum[i][1] = (nhisto*accum[i][1] + histo[i])/(nhisto+1)
+    if (nhisto % 10 === 0) vdist.data = accum // update graph every 10th iteration
+    nhisto += 1
+
+    // Update all positions
+    for (var i=0; i<Natoms; i++) Atoms[i].pos = apos[i] = apos[i] + (p[i]/mass)*dt
+    
+    // Check for collisions
+    var hitlist = checkCollisions(apos)
+
+    // If any collisions took place, update momenta of the two atoms
+    for (var id1 in hitlist) {
+        var ij = hitlist[id1]
+        var i = ij[0]
+        var j = ij[1]
+        var ptot = p[i]+p[j]
+        var vi = p[i]/mass
+        var vj = p[j]/mass
+        var vrel = vj-vi
+        var a = vrel.mag2()
+        if (a === 0) continue;  // exactly same velocities
+        var rrel = apos[j]-apos[i]
+        var b = 2*rrel.dot(vrel)
+        var c = rrel.mag2()-Ratom*Ratom
+        var d = b*b-4*a*c
+        if (d < 0) continue;  // something wrong; ignore this rare case
+        var deltat = (-b+sqrt(d))/(2*a) // t-deltat is when they made contact
+        apos[i] = apos[i]-vi*deltat // back up to contact configuration
+        apos[j] = apos[j]-vj*deltat
+        var mtot = 2*mass
+        var pcmi = p[i]-ptot*mass/mtot // transform momenta to cm frame
+        var pcmj = p[j]-ptot*mass/mtot
+        rrel = norm(rrel)
+        pcmi = pcmi-2*pcmi.dot(rrel)*rrel // bounce in cm frame
+        pcmj = pcmj-2*pcmj.dot(rrel)*rrel
+        p[i] = pcmi+ptot*mass/mtot // transform momenta back to lab frame
+        p[j] = pcmj+ptot*mass/mtot
+        apos[i] = apos[i]+(p[i]/mass)*deltat // move forward deltat in time
+        apos[j] = apos[j]+(p[j]/mass)*deltat
+        interchange(vi.mag(), p[i].mag()/mass)
+        interchange(vj.mag(), p[j].mag()/mass)
+    }
+    
+    for (var i=0; i<Natoms; i++) {
+        var loc = apos[i]
+        if (abs(loc.x) > L/2) {
+            if (loc.x < 0) p[i].x =  abs(p[i].x)
+            else p[i].x =  -abs(p[i].x)
+        }
+        if (abs(loc.y) > L/2) {
+            if (loc.y < 0) p[i].y =  abs(p[i].y)
+            else p[i].y =  -abs(p[i].y)
+        }
+        if (abs(loc.z) > L/2) {
+            if (loc.z < 0) p[i].z =  abs(p[i].z)
+            else p[i].z =  -abs(p[i].z)
+        }
+    }
+}

examples/MakeGraphs.js

-GlowScript 0.3
+GlowScript 1.0
 /* Making Flot 2D graphs to accompany GlowScript 3D graphics */
 
-canvas({width:300, height:200})
+scene.width = 300
+scene.height = 200
 scene.range = 1
 var b = box({color:color.yellow})
 scene.forward = vec(-1,-0.2,-1)
+scene.caption.text("Make a graph of sine and cosine on the same graph:")
+scene.pause("Click to advance",wait)
 
 /*
 var options = { // specify fixed graphing limits
     xaxis: { min: 4, max: 20, show: true },
     yaxis: { min: 0, max: 6, show: true }
 }
-gdisplay(options) // default width and height
+graph(options) // default width and height
 */
 
 // autoscaled graph is the default
 // Display sine and cosine on the same graph:
 var gd = graph() // default width and height
-var p = line( {color:color.red, label:'sin(x)'} )
-var q = line()
+var p = series( {color:color.red, label:'sin(x)'} )
+var q = series()
 q.color = color.green // can change the color after creating the object
 q.label = 'cos(x)'
 
     q.plot(i+5, 3+2*cos(i))
 }
 
+scene.caption.text("Move the mouse over the graph and see the crosshairs")
+scene.pause(wait)
+
 // Pause, then change cosine curve to dots:
-sleep(1,wait)
-q.gtype = 'scatter'
+scene.caption.text("Change the cosine line graph to a scatter plot:")
+scene.pause(wait)
+q.type = 'scatter'
 
 // Add another graph
-sleep(1,wait)
+scene.caption.text("Add a second graph:")
+scene.pause(wait)
 graph( {width:400, height:150} )
-var p2 = line( {color:color.blue, label:'cos(5x)exp(-.5x)'} )
+var p2 = series( {color:color.blue, label:'cos(5x)exp(-.5x)'} )
 
 for (var i = 0; i < 5; i += 0.1) {
     rate(100,wait)
 }
 
 // Plot vertical bars; note ability to set initial data:
-sleep(1,wait)
+scene.caption.text("Make a bar graph:")
+scene.pause(wait)
 var bargraph = graph( {width:400, height:150} )
-var vb = bar( {delta:0.2, color:color.green, data:[ [1,5], [3,-2], [6,4] ]} )
+var vb = series( {type:'bar', delta:0.2, color:color.green, data:[ [1,5], [3,-2], [6,4] ]} )
+
 // Change the data:
-sleep(1,wait)
+scene.caption.text("Change the bar graph data")
+scene.pause(wait)
 vb.color = color.black
 vb.data = [[1,-2], [3,2], [8,3], [12,4]] // reset all the data
 
 // Transfer cosine curve to bargraph:
-sleep(1,wait)
+scene.caption.text("Transfer the cosine information from the first graph to the new graph:")
+scene.pause(wait)
 q.graph = bargraph
 
 // Add a horizontal line to the first graph:
-sleep(1,wait)
-var N = line( {graph:gd, color:color.blue} )
+scene.caption.text("Add a horizontal line to the first graph:")
+scene.pause(wait)
+
+var N = series( {graph:gd, color:color.blue} )
 N.plot([5+pi/2,5], [5+5*pi/2,5])
 
 // Rotate a cube
-sleep(1,wait)
+scene.caption.text("Flash the line in the first graph while rotating the cube:")
+scene.pause(wait)
 var steps = 0
 while (true) {
     rate(50,wait)
-    b.rotate(.02,vec(0,1,1))
+    b.rotate({angle:.02, axis:vec(0,1,1)})
     steps++
     if (steps == 20) {
         N.visible = !N.visible

examples/MousePicking.js

+GlowScript 1.0
+scene.width = scene.height = 500
+scene.range = 2.2
+//scene.title.text("Picking objects with the mouse")
+scene.caption.text("Click to pick an object and make it red.")
+scene.caption.append("\nNote picking of individual curve points.")
+box({pos:vec(-1,0,0), color:color.cyan, opacity:1})
+box({pos:vec(1,-1,0), color:color.green})
+arrow({pos:vec(-1,-1,0), color:color.orange})
+cone({pos:vec(2,0,0), axis:vec(0,1,-.3), color:color.blue, size:vec(2,1,1)})
+sphere({pos:vec(-1.5,1.5,0), color:color.white, size:.4*vec(3,2,1)})
+var square = curve( {color:color.yellow, radius:.05} )
+square.push( vec(0,0,0), {pos:vec(0,1,0), color:color.cyan, radius:.1},
+             vec(1,1,0), {pos:vec(1,0,0), radius:.1}, vec(0.3,-.3,0) )
+var v0 = vertex({pos:vec(-.5,1.2,0), color:color.green})
+var v1 = vertex({pos:vec(1,1.2,0), color:color.red})
+var v2 = vertex({pos:vec(1,2,0), color:color.blue})
+var v3 = vertex({pos:vec(-.5,2,0), color:color.yellow})
+triangle({v0:v0, v1:v1, v2:v2})
+//quad({v0:v0, v1:v1, v2:v2, v3:v3})
+
+var lasthit = null
+var lastpick = null
+var lastcolor
+scene.bind("mousedown", function() {
+    if (lasthit !== null) {
+        if (lastpick !== null) lasthit.modify(lastpick, {color:lastcolor})
+        else lasthit.color = vec(lastcolor)
+        lasthit = lastpick = null
+    }
+    var hit = scene.mouse.pick()
+    if (hit !== null) {
+        lasthit = hit
+        lastpick = null
+        if (hit.constructor == curve) { // pick individual point of curve
+            lastpick = hit.pick
+            // slice returns an array of points; we want the first (index = 0):
+            lastcolor = hit.slice(lastpick, lastpick+1)[0].color
+            hit.modify(hit.pick, {color:color.red})
+        } else if (hit.constructor == triangle || hit.constructor == quad) {
+            lasthit = hit.v0
+            lastcolor = lasthit.color
+            lasthit.color = color.red
+        } else {
+            lastcolor = vec(hit.color)
+            hit.color = color.red
+            //scene.caption.text(lastcolor.toString()+' '+hit.color.toString())
+        }
+    }
+})

examples/RotatingCubes.js

-GlowScript 0.3
+GlowScript 1.0
 /* 1000 rotating cubes */
 
+scene.title.text("10 by 10 by 10 rotating cubes; fps = frames/sec\n ")
 // Display frames per second and render time:
-$("#glowscript").css("font-size","90%")
-$('<span id="fps"></span>').appendTo('#glowscript')
-
-var s = "Zoom slightly to disable autoscaling, and render time decreases; fps = frames/sec"
-$("<p/>").text(s).appendTo("#glowscript")
+$("<div id='fps'/>").appendTo(scene.title)
+scene.caption.text("Click a box to turn it white")
 
 var boxes = []
 
 var L = 6
 var N = 10
 var b
+scene.range = L
 
 for(var x=0; x<N; x++)
   for(var y=0; y<N; y++)
     for(var z=0; z<N; z++) {
         b = box({color:vec(x/N,y/N,z/N), 
-                    pos:vec(L*(x/(N-1)-.5),L*(y/(N-1)-.5),L*(z/(N-1)-.5)),
-                    size:vec(.6*L/N,.4*L/N,.6*L/N)})
-		boxes.push(b)
-	}
+                pos:vec(L*(x/(N-1)-.5),L*(y/(N-1)-.5),L*(z/(N-1)-.5)), 
+                size:vec(.6*L/N,.4*L/N,.6*L/N)})
+        boxes.push(b)
+    }
 
+var lasthit = null
+var lastcolor = null
+scene.bind("click", function() {
+    var hit = scene.mouse.pick()
+    if (hit) {
+        if (lasthit !== null) lasthit.color = lastcolor
+        lasthit = hit
+        lastcolor = lasthit.color
+        hit.color = color.white
+    }
+})
 
 var t = 0
 var dt = 0.01
 while (true) {
-    waitfor.redraw(wait)
+    scene.waitfor("redraw",wait)
     t += dt
     for(var i=0; i<boxes.length; i++)
         boxes[i].axis = vec( sin(t), 0, cos(t) )
 }
+
+GlowScript 1.0 CoffeeScript
+scene.width = scene.height = 600
+scene.range = 0.6
+
+# A pulse ripples along a rug, demonstrating dynamic changea of shape
+# Bruce Sherwood, May 2012
+
+s = "Click to stop or start\n\nIn GlowScript programs:\n\n"
+s += "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+s += "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+scene.caption.text(s)
+
+# Construct a square WxH divided into little squares
+# There are (w+1)x(h+1) vertices
+# Center of rug is at 0,0,0
+
+H = W = 1
+w = 1
+h = 50
+dx = W/w
+dy = H/h
+
+# Create a grid of vertex objects covering the rug
+verts = []
+for y in [0..h] # from 0 to h inclusive, to include both bottom and top edges
+    verts.push([])
+    for x in [0..w] # from 0 to w inclusive, to include both left and right edges
+        verts[y].push(vertex
+                        pos:vec(-0.5+x*dx,-0.5+y*dy,0)
+                        normal:vec(0,0,1) 
+                        texpos:vec(x/w,y/h,0)
+                        shininess: 0 )
+
+# Create quads (equivalent to two triangles) based on the vertex objects just created.
+# Note that a particular vertex may be shared by as many as 4 neighboring quads, and
+# changing one vertex affects all of the quads that use that vertex.
+for y in [0...h] # from 0 to h, not including h
+    for x in [0...w] # from 0 to w, not including w
+        quad
+            v0: verts[y][x]
+            v1: verts[y][x+1]
+            v2: verts[y+1][x+1]
+            v3: verts[y+1][x]
+            texture:{file: textures.rug}
+
+scene.waitfor('textures',wait)
+
+Lpulse = 0.4 # length of half sine wave
+dy_pulse = Lpulse/50
+k = pi/(0.6*Lpulse)
+A = 0.05
+pulse = (z) -> # return the pulse height and normal
+    if z < 0.2*Lpulse then return 0
+    if z > 0.8*Lpulse then return 0
+    z -= 0.2*Lpulse
+    return A*sin(k*z)
+
+run = true
+scene.bind("mousedown", () ->
+    run = not run
+)
+
+y = -0.5-Lpulse-dy_pulse # bottom of pulse (starts below rug)
+loop
+    while not run
+        scene.waitfor('redraw',wait)
+    y += dy_pulse
+    if y+Lpulse <= -0.5
+        continue
+    if y >= 0.5
+        y = -0.5-Lpulse
+        continue
+        
+    start = Math.floor((y+0.5)/dy)     # lowest row of vertices in pulse
+    end = Math.ceil((y+0.5+Lpulse)/dy) # highest row of vertices in pulse
+    if start < 0
+        if end <= 0 then continue
+        start = 0
+    if end > h 
+        end = h
+    
+    scene.waitfor('redraw',wait) # synchronize updates with graphics card
+    
+    yp = -0.5+start*dy
+    for s in [start...end]
+        z0 = pulse(yp-y-dy_pulse)
+        z1 = pulse(yp-y)
+        z2 = pulse(yp+dy_pulse-y)
+        yp += dy # advance to next row
+        
+        # If slope of a line is dy/dz, normal to the line is in direction < -dz, +dy >
+        n1y0 = -(z1-z0)
+        n2y0 = -(z2-z1)
+        n1y = .5*(n1y0+n2y0) # average adjacent normals to smooth the lighting
+        n1z = dy
+        
+        vy = verts[s]
+        for vx in [0..w]
+            vy[vx].pos.z = z1
+            vy[vx].normal = vec(0,n1y,n1z)
+      

examples/ScrollingText.js

+GlowScript 1.0
+/* Create a simple scrolling text object */
+// A simpler syntax for this may be implemented in the future in GlowScript.
+scene.width = scene.height = 250
+var B = box({pos:vec(0.1,0.2,0)})
+
+// Create a textarea element on the web page, below the box object.
+// $ stands for jQuery, an important web page addition to JavaScript.
+// The string \n represents a carriage return.
+var T = $('<textarea/>').val('Click above.\n').appendTo(scene.caption)
+    .css('width', '250px') // minimum width in pixels
+    .css('height', '80px') // minimum height in pixels
+
+scene.pause(wait)
+// T.val() represents the current contents of the textarea; add to it:
+T.val(T.val()+'B.pos = '+B.pos.toString()+'\n')
+T.val(T.val()+'Type more text here and a scroll bar will appear.\n')

examples/Stonehenge-CoffeeScript.js

+GlowScript 1.0 CoffeeScript
+# The statement above invokes the CoffeeScript language (which generates JavaScript).
+# See coffeescript.org for information about CoffeeScript.
+
+# Fly through Surreal Stonehenge 
+# Bruce Sherwood
+
+# Converted from the JavaScript version of Stonehenge using js2coffee.org
+
+# A surreal scene that illustrates many of the features of GlowScript
+
+hourminute = ->
+  now = new Date()
+  hour = now.getHours()
+  minute = now.getMinutes()
+  [ hour, minute ]
+ 
+class Analog_clock
+  #constructor: (pos: vec(0,0,0), axis: vec(0,0,1), radius: 1) ->
+  constructor: (@pos = vec(0,0,0), @axis = vec(0,0,1), @radius = 1) ->
+      #alert(@pos.toString()+', '+@axis.toString()+', '+@radius)
+      @hour = 0
+      @minute = -1
+      @spheres = []
+      for n in [0...12]
+        @spheres.push sphere(
+            pos: @pos + @radius * scene.up.rotate(
+                angle: -2 * pi * n / 12
+                axis: @axis
+                )
+            size: 0.1 * @radius * vec(1, 1, 1)
+            color: color.hsv_to_rgb(vec(n / 12, 1, 1))
+        )
+      @hand = arrow(
+        pos: @pos
+        axis_and_length: 0.95 * @radius * scene.up
+        shaftwidth: @radius / 10
+        color: color.cyan
+      )
+      @update()
+
+  update: ->
+    [hour, minute] = hourminute()
+    hour -= 12  if hour >= 12
+    return  if @hour is hour and @minute is minute
+    @hand.axis = 0.95 * @radius * scene.up.rotate(
+      angle: -2 * pi * minute / 60
+      axis: @axis
+    )
+    @spheres[@hour].size = 0.1 * @radius * vec(1, 1, 1) # restore normal size
+    @spheres[hour].size = 0.2 * @radius * vec(1, 1, 1) # enalarge the house
+    @hour = hour
+    @minute = minute
+
+scene.width = 800
+scene.height = 400
+roam = false
+
+scene.bind "mousedown", ->
+  roam = true
+
+scene.bind "mouseup", ->
+  roam = false
+
+ycenter = 2
+$(canvas.container).css "font-size", "90%"
+scene.title.html "<b>Surreal Stonehenge</b>"
+
+# Comment this in to see performance (frames per second, render time in milliseconds)
+#$("<div id='fps'/>").appendTo(scene.title)
+
+scene.center = vec(0, ycenter, 0)
+scene.range = 12
+scene.userspin = false
+scene.userzoom = false
+grey = color.gray(0.8)
+Nslabs = 8
+R = 10
+w = 5
+d = 0.5
+h = 5
+photocenter = 0.15 * w
+
+scene.visible = false # make no display until textures are loaded
+
+# The floor, central post, and ball atop the post
+floor = box(
+  pos: vec(0, -0.1, 0)
+  size: vec(0.2, 24, 24)
+  axis: vec(0, 1, 0)
+  texture: textures.wood
+)
+pole = cylinder(
+  pos: vec(0, 0, 0)
+  axis: vec(0, 1, 0)
+  size: vec(h, .4, .4)
+  color: color.red
+)
+sphere
+  pos: vec(0, h, 0)
+  color: vec(1, 0, 0)
+
+# Set up the gray slabs, including a portal
+i = 0
+while i < Nslabs
+  theta = i * 2 * pi / Nslabs
+  c = cos(theta)
+  s = sin(theta)
+  xc = R * c
+  zc = R * s
+  if i is 2
+    box
+      pos: vec(-3 * w / 8, 0.75 * h / 2, R)
+      size: vec(0.5 * w / 2, 0.75 * h, d)
+      color: grey
+
+    box
+      pos: vec(3 * w / 8, 0.75 * h / 2, R)
+      size: vec(0.5 * w / 2, 0.75 * h, d)
+      color: grey
+
+    box
+      pos: vec(0, 0.85 * h, R)
+      size: vec(w, 0.3 * h, d)
+      color: grey
+  else
+    slab = box(
+      pos: vec(R * c, h / 2, R * s)
+      axis: vec(c, 0, s)
+      size: vec(d, h, w)
+      color: grey
+    )
+    unless i is 6
+      T = textures.flower
+      T = textures.rug  if i is 7 or i is 4
+      box
+        pos: slab.pos
+        size: vec(1.1 * d, 0.9 * 4 * photocenter, 0.9 * 4 * photocenter)
+        axis: vec(c, 0, s)
+        texture: T
+  i++
+
+# Decorate back slab with a gold box and a clock
+box
+  pos: vec(0, h / 2, -R + d / 2 + 0.1)
+  size: vec(w / 2, w / 2, 0.2)
+  color: vec(1, 0.8, 0)
+  texture: textures.wood_old
+  
+pos = vec(0, h / 2, -R + d / 2 + 0.2 + 0.2 * h / 10)
+clock = new Analog_clock(pos, vec(0,0,1), 0.2*w)
+clock.update()
+
+# Draw guy wires from the top of the central post
+Nwires = 32
+i = 0
+while i < Nwires
+  theta = i * 2 * pi / Nwires
+  L = vec(R * cos(theta), -h - 0.1, R * sin(theta))
+  cylinder
+    pos: vec(0, h, 0)
+    axis: L
+    size: vec(mag(L), .02, .02)
+    color: vec(1, 0.7, 0)
+  i++
+
+# Display a pyramid
+pyramid
+  pos: vec(-4, 0, -5)
+  size: vec(2, 2, 2)
+  axis: vec(0, 1, 0)
+  color: vec(0, .5, 0)
+  texture: textures.rough
+
+# Display smoke rings rising out of a black tube
+smoke = []
+Nrings = 20
+x0 = -5
+y0 = 1.5
+z0 = -2
+r0 = 0.1
+spacing = 0.2
+thick = r0 / 2
+dr = 0.015
+dthick = thick / Nrings
+gray = 1
+cylinder
+  pos: vec(x0, 0, z0)
+  axis: vec(0, 1, 0)
+  size: vec(y0 + r0, 3 * r0, 3 * r0)
+  color: color.black
+
+# Create the smoke rings
+i = 0
+while i < Nrings
+  D = 2 * (r0 + dr * i)
+  T = thick - dthick * i
+  smoke.push ring(
+    pos: vec(x0, y0 + spacing * i, z0)
+    axis: vec(0, 1, 0)
+    size: vec(T, D, D)
+    color: color.gray(gray)
+  )
+  i++
+
+y = 0
+dy = spacing / 20
+top_of_rings = Nrings - 1
+
+# Roll a log back and forth
+rlog = 1
+wide = 4
+zpos = 2
+zface = 5
+tlogend = 0.2
+v0 = 0.3
+v = v0
+omega = -v0 / rlog
+theta = 0
+dt = 0.1
+tstop = 0.3
+
+# Log rolls back and forth between two stops
+logcyl = cylinder(
+  pos: vec(-wide, rlog, zpos)
+  size: vec(zface - zpos, 2, 2)
+  axis: vec(0, 0, 1)
+  texture: textures.granite
+)
+leftstop = box(
+  pos: vec(-wide - rlog - tstop / 2, 0.6 * rlog, (zpos + zface) / 2)
+  size: vec(tstop, 1.2 * rlog, (zface - zpos))
+  color: color.red
+  emissive: 1
+)
+rightstop = box(
+  pos: vec(wide + rlog + tstop / 2, 0.6 * rlog, (zpos + zface) / 2)
+  size: vec(tstop, 1.2 * rlog, (zface - zpos))
+  color: color.red
+  emissive: 1
+)
+
+# Run a ball up and down the pole
+y1 = 0.2 * h
+y2 = 0.7 * h
+rball = 0.4
+Dband = 1.3 * pole.size.y
+cylinder
+  pos: vec(0, y1 - 0.9 * rball, 0)
+  axis: vec(0, 1, 0)
+  size: vec(0.1, Dband, Dband)
+  color: color.green
+
+cylinder
+  pos: vec(0, y2 + 0.9 * rball, 0)
+  axis: vec(0, 1, 0)
+  size: vec(0.1, Dband, Dband)
+  color: color.green
+
+vball0 = 0.3 * v0
+vball = vball0
+ballangle = 0.05 * pi
+poleball = sphere(
+  pos: vec(0, y1, 0)
+  size: 2 * rball * vec(1, 1, 1)
+  color: color.blue
+)
+polecones = []
+nn = 0
+
+while nn < 4
+  cc = cone(
+    pos: vec(0.8 * rball, y1, 0)
+    size: rball * vec(3, 1, 1)
+    color: color.yellow
+  )
+  cc.rotate
+    angle: 0.5 * nn * pi
+    axis: vec(0, 1, 0)
+    origin: vec(0, y1, 0)
+
+  polecones.push cc
+  nn++
+rbaseball = 0.3
+vbaseball0 = 3 * v0
+
+# A table with a mass-spring object sliding on it
+table = cone(
+  pos: vec(0.4 * R, h / 4, -.3 * R)
+  size: vec(h / 4, 0.6 * R, 0.6 * R)
+  axis: vec(0, -1, 0)
+  texture:
+    file: textures.wood_old
+    turn: 1
+)
+tabletop = table.pos
+rspring = 0.02 * h
+Lspring = .15 * R
+Lspring0 = .1 * R
+hmass = 4 * rspring
+post = cylinder(
+  pos: tabletop
+  axis: vec(0, 1, 0)
+  size: vec(2 * hmass, .4, .4)
+  color: color.gray(.6)
+)
+spring = helix(
+  pos: post.pos + vec(0, hmass / 2, 0)
+  size: vec(Lspring, 2 * rspring, 2 * rspring)
+  color: color.orange
+  thickness: rspring
+)
+mass = cylinder(
+  pos: post.pos + vec(Lspring, 0, 0)
+  axis: vec(0, 1, 0)
+  size: vec(hmass, .04 * R, .04 * R)
+  color: color.orange
+)
+mass.p = vec(10, 0, 5)
+mass.m = 1
+kspring = 200
+deltat = .01
+
+# Display an ellipsoid
+Rcloud = 0.8 * R
+omegacloud = 3 * v0 / Rcloud
+cloud = sphere(
+  pos: vec(0, 0.7 * h, -Rcloud)
+  size: vec(5, 2, 2)
+  color: color.green
+  opacity: 0.3
+)
+rhairs = 0.025
+dhairs = 2
+maxcosine = dhairs / sqrt(pow(rhairs, 2) + pow(dhairs, 2))
+haircolor = color.black
+
+scene.waitfor "textures", wait
+scene.visible = true
+
+# Add instructions below the display
+s1 = "<b>Fly through the scene:</b> With the mouse button down,<br><br>"
+s2 = "    move the mouse above or below the center of the scene to move forward or backward;<br><br>"
+s3 = "    move the mouse right or left to turn your direction of motion.<br><br>"
+s4 = "(Normal GlowScript rotate and zoom are turned off in this program.)"
+scene.caption.html s1 + s2 + s3 + s4
+
+loop
+  if roam
+    ray = scene.mouse.ray
+    if abs(ray.dot(scene.forward)) < maxcosine
+      newray = norm(vec(ray.x, 0, ray.z))
+      angle = asin(scene.forward.cross(newray).dot(scene.up))
+      newforward = scene.forward.rotate(
+        angle: angle / 30
+        axis: scene.up
+      )
+      dist = mag(scene.center - scene.camera.pos)
+      scene.center = scene.camera.pos + newforward * dist
+      scene.forward = newforward
+      scene.center = scene.center + scene.forward * ray.y / 4
+    
+  # Roll the log  theta = theta + omega * dt
+  logcyl.pos.x = logcyl.pos.x + v * dt
+  logcyl.rotate
+    angle: omega * dt
+    axis: vec(0, 0, 1)
+
+  if logcyl.pos.x >= wide
+    v = -v0
+    omega = -v / rlog
+    if rightstop.color.equals(color.red)
+      rightstop.color = color.cyan
+    else
+      rightstop.color = color.red
+  if logcyl.pos.x <= -wide
+    v = +v0
+    omega = -v / rlog
+    if leftstop.color.equals(color.red)
+      leftstop.color = color.cyan
+    else
+      leftstop.color = color.red
+      
+  # Move the cloud
+  cloud.rotate
+    angle: omegacloud * dt
+    axis: vec(0, 1, 0)
+    origin: vec(0, 0, 0)
+
+  # Run the ball up and down
+  poleball.pos.y = poleball.pos.y + vball * dt
+  i = 0
+  while i < 4
+    polecones[i].pos.y = poleball.pos.y
+    polecones[i].rotate
+      angle: ballangle
+      axis: vec(0, 1, 0)
+      origin: vec(0, 0, 0)
+    i++
+  if poleball.pos.y >= y2
+    vball = -vball0
+    ballangle = -ballangle
+  if poleball.pos.y <= y1
+    vball = +vball0
+    ballangle = -ballangle
+    
+  # Move the smoke rings
+  i = 0
+  while i < Nrings
+    smoke[i].pos = smoke[i].pos + vec(0, dy, 0)
+    smoke[i].size.y = smoke[i].size.z = smoke[i].size.y + (dr / spacing) * dy
+    smoke[i].size.x = smoke[i].size.x - (dthick / spacing) * dy
+    i++
+  y = y + dy
+  if y >= spacing
+    y = 0
+    smoke[top_of_rings].pos = vec(x0, y0, z0)
+    smoke[top_of_rings].size = vec(thick, 2 * r0, 2 * r0)
+    top_of_rings--
+  top_of_rings = Nrings - 1  if top_of_rings < 0
+  
+  # Update the mass-spring motion
+  F = -kspring * (spring.size.x - Lspring0) * spring.axis.norm()
+  mass.p = mass.p + F * deltat
+  mass.pos = mass.pos + (mass.p / mass.m) * deltat
+  spring.axis = mass.pos + vec(0, hmass / 2, 0) - spring.pos
+  spring.size.x = mag(spring.axis)
+
+  # Update the analog clock on the back slab
+  clock.update()
+  rate 30, wait

examples/Textures.js

+GlowScript 1.0
+scene.width = 600
+scene.height = 600
+var show = 'box'
+var last_show = show
+ 
+var D = 0.7 // size of box
+var R = .4 // radius of sphere
+
+var names = ['flower', 'granite', 'gravel', 'metal', 'rock', 'rough', 'rug', 'stones', 'stucco', 'wood', 'wood_old']
+var bumps = [ null, null, 'gravel', null, 'rock', null, null, 'stones', 'stucco', null, 'wood_old']
+var labels = []
+
+function erase() {
+    var objects = scene.objects
+    for (var obj in objects) objects[obj].visible = false
+    // At the time of writing, labels were not included in scene.objects:
+    while (labels.length > 0) labels.pop().visible = false
+}
+
+function show_object(index, x, y) {
+    var T = textures[names[index]]
+    var B = null
+    var c
+    // Bump maps aren't very salient unless one moves the light or rotates the object,
+    // so don't bother with bump maps unless there's an option to move the light or object.
+    //if (bumps[index] !== null) B = bumpmaps[bumps[index]]
+    if (show == 'box') {
+        c = box( {pos:vec(x,y,0), size:D*vec(1,1,1)} )
+    } else if (show == 'sphere') {
+        c = sphere( {pos:vec(x,y,0), size:D*vec(1,1,1)} )
+    } else if (show == 'cylinder') { 
+        c = cylinder( {pos:vec(x-D/2,y,0), size:D*vec(1,1,1)} )
+    } else if (show == 'cone') { 
+        c = cone( {pos:vec(x-D/2,y,0), size:D*vec(1,1,1)} )
+    } else if (show == 'pyramid') { 
+        c = pyramid( {pos:vec(x-D/2,y,0), size:D*vec(1,1,1)} )
+    }
+    c.index = index
+    c.shininess = 0
+    c.texture = {file:T, bumpmap:B}
+    labels.push(label( {pos:vec(x,y-.5,0), box:0, text:'textures.'+names[index]} ))
+}
+
+function start_setup() {
+    scene.range = 2.2
+    scene.fov = 0.2
+    scene.center = vec(1.5,2,0)
+    scene.forward = vec(0,0,-1)
+    erase()
+    scene.visible = false
+    var index = 0
+    for (var y=3.3; y>0; y-=1.3) {
+        for (var x=0; x<4; x++) {
+            if (index >= names.length) break; 
+            show_object(index, x, y)
+            index += 1
+        }
+    }
+}
+
+function end_setup() {
+    scene.visible = true
+    $('#message').text('Click an object to enlarge it.')
+}
+
+function setup() {
+    start_setup()
+    end_setup()
+}
+
+start_setup()
+scene.caption.text("Loading textures...")
+scene.waitfor("textures",wait)
+scene.caption.text("")
+
+var choose = $("<div>Change the type of object: </div>").appendTo(scene.caption)
+
+// Create a drop-down menu (a "select" object). Set up the options to appear:
+var s = ""
+s += "<option select=selected>box</option>"
+s += "<option>sphere</option>"
+s += "<option>cylinder</option>"
+s += "<option>cone</option>"
+s += "<option>pyramid</option>"
+
+$('<select/>').html(s).css({font:"sans"})
+    .change(function() { // come here when a change is made in the menu choice
+        show = $(this).val()
+    })
+.appendTo(choose)
+
+$('<p id=message></p>').appendTo(scene.caption)
+end_setup()
+
+var hit = null
+var clicked = false
+scene.bind("click", function () {
+    hit = scene.mouse.pick()
+    clicked = true
+})
+
+function single_object(index) {
+    scene.center = vec(0,-.1*R,0)
+    scene.range = 1.5*R
+    erase()
+    show_object(index, 0, 0)
+    $('#message').text('Click anywhere to see all textures.')
+}
+
+var picked = null
+    
+while (true) {
+    rate(30,wait)
+    if (show != last_show) {
+        last_show = show
+        if (picked !== null) {
+            single_object(picked.index)
+        } else setup()
+    }
+    if (clicked) {
+        clicked = false
+        if (picked !== null) {
+            picked = null
+            setup()
+        } else if (picked === null && hit !== null) {
+            picked = hit
+            hit = null
+            single_object(picked.index)
+        }
+    }
+}

examples/Transparency.js

+GlowScript 1.0
+scene.width = 600
+scene.height = 600
+scene.background = color.gray(0.5)
+scene.range = 1.3
+var s = 'Pixel-level transparency using "depth-peeling."'
+s += "\nNote the correct transparencies of the intersecting slabs."
+s += "\nHere are the <a href='http://glowscript.org/docs/GlowScriptDocs/technical.html' target='_blank'>technical details</a> of the depth-peeling algorithm."
+scene.title.html(s)
+
+// http://www.glowscript.org/docs/GlowScriptDocs/technical.html
+
+box({pos:vec(0,0,0), opacity:1, size:vec(1,1,1), texture:textures.flower})
+sphere({pos:vec(0,0,.9), opacity:0.3, shininess:0, size:0.4*vec(1,1,1), color:color.green})
+var s = sphere({pos:vec(0.1,0,1.2), opacity:0.2, shininess:0, size:0.2*vec(1,1,1), color:color.cyan})
+box({pos:s.pos, size:0.3*s.size, color:color.gray(.2)})
+box({pos:vec(0,.5,1), color:color.red,  opacity:0.2, size:vec(.05,.2,.8), axis:vec(1,0,1) })
+box({pos:vec(0,.5,1), color:color.cyan, opacity:0.2, size:vec(.05,.2,.8), axis:vec(1,0,-1)})
+
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
+
+display_instructions()

examples/bounce.js

-GlowScript 0.3
+GlowScript 1.0
 /* A ball bounces in a box. */
 
 // Converted from the VPython program bounce2
 
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
+
+// Display text below the 3D graphics:
+scene.title.text("A ball bounces in a box")
+display_instructions()
+
 var side = 4.0
 var thk = 0.3
 var s2 = 2*side - thk
 var ball = sphere ( {color : color.green, size : 0.8*vec(1,1,1)} )
 ball.mass = 1.0
 ball.p = vec (-0.15, -0.23, +0.27)
+attach_trail(ball, {pps:200, retain:100})
 
 side = side - thk*0.5 - ball.size.x/2
 
-var dt = 0.5
+var dt = 0.3
 var t=0.0
 while ( true) { 
-  rate(100,wait)
+  rate(200,wait)
   t = t + dt
   ball.pos = ball.pos + (ball.p/ball.mass)*dt
-  if (! (side > ball.pos.x && ball.pos.x > -side)) { 
+  if (! (-side < ball.pos.x && ball.pos.x < side)) { 
     ball.p.x = -ball.p.x
   }
-  if (! (side > ball.pos.y && ball.pos.y > -side)) { 
+  if (! (-side < ball.pos.y && ball.pos.y < side)) { 
     ball.p.y = -ball.p.y
   }
-  if (! (side > ball.pos.z && ball.pos.z > -side)) { 
+  if (! (-side < ball.pos.z && ball.pos.z < side)) { 
     ball.p.z = -ball.p.z
   }
 }
-    

examples/doublependulum.js

-GlowScript 0.3
+GlowScript 1.0
 /* Double pendulum */
 
 //The analysis is in terms of Lagrangian mechanics.
 
 //Bruce Sherwood
 
-canvas({width:600, height:600}) // establish graphics region of screen
+scene.width = scene.height = 600
 scene.range = 1.8
 
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
+
+// Display text below the 3D graphics:
+scene.title.text("A double pendulum")
+display_instructions()
+
+
 var g = 9.8
 var M1 = 2.0
 var M2 = 1.0
                 size:vec(wpedestal,1.1*hpedestal,wpedestal),
                 color:vec(0.4,0.4,0.5)} )
 var base = box( {pos:pedestal_top-vec(0,hpedestal+tbase/2,offset),
-    			 size:vec(wbase,tbase,wbase),
+                 size:vec(wbase,tbase,wbase),
                  color:pedestal.color} )
 var axle1 = cylinder( {pos:pedestal_top-vec(0,0,gap/2-d/4), axis:vec(0,0,-1),
-				  size:vec(offset,d/4,d/4), color:color.yellow} )
+        		  size:vec(offset,d/4,d/4), color:color.yellow} )
 
 var bar1 = box( {pos:pedestal_top+vec(L1display/2-d/2,0,-(gap+d)/2), 
 			 size:vec(L1display,d,d), color:color.red} )
-bar1.rotate( -pi/2, vec(0,0,1), vec(axle1.pos.x, axle1.pos.y, bar1.pos.z) )
-bar1.rotate( theta1, vec(0,0,1), vec(axle1.pos.x, axle1.pos.y, bar1.pos.z) )
+bar1.rotate( {angle:-pi/2, axis:vec(0,0,1), origin:vec(axle1.pos.x, axle1.pos.y, bar1.pos.z)} )
+bar1.rotate( {angle:theta1, axis:vec(0,0,1), origin:vec(axle1.pos.x, axle1.pos.y, bar1.pos.z)} )
 
 var bar1b = box( {pos:pedestal_top+vec(L1display/2-d/2,0,(gap+d)/2), 
 			  size:vec(L1display,d,d), color:bar1.color} )
-bar1b.rotate( -pi/2, vec(0,0,1), vec(axle1.pos.x, axle1.pos.y, bar1b.pos.z) )
-bar1b.rotate( theta1, vec(0,0,1), vec(axle1.pos.x, axle1.pos.y, bar1b.pos.z) )
+bar1b.rotate( {angle:-pi/2, axis:vec(0,0,1), origin:vec(axle1.pos.x, axle1.pos.y, bar1b.pos.z)} )
+bar1b.rotate( {angle:theta1, axis:vec(0,0,1), origin:vec(axle1.pos.x, axle1.pos.y, bar1b.pos.z)} )
 
 var pivot1 = vec(axle1.pos.x, axle1.pos.y, 0)
 
 var axle2 = cylinder( {pos:pedestal_top+vec(L1,0,-(gap+d)/2), axis:vec(0,0,1), 
                 size:vec(gap+d,axle1.size.y/2,axle1.size.y/2), color:axle1.color} )
-axle2.rotate( -pi/2, vec(0,0,1), vec(axle1.pos.x, axle1.pos.y, axle2.pos.z) )
-axle2.rotate( theta1, vec(0,0,1), vec(axle1.pos.x, axle1.pos.y, axle2.pos.z) )
+axle2.rotate( {angle:-pi/2, axis:vec(0,0,1), origin:vec(axle1.pos.x, axle1.pos.y, axle2.pos.z)} )
+axle2.rotate( {angle:theta1, axis:vec(0,0,1), origin:vec(axle1.pos.x, axle1.pos.y, axle2.pos.z)} )
 
 var bar2 = box( {pos:axle2.pos+vec(L2display/2-d/2,0,(gap+d)/2), 
 		size:vec(L2display,d,d), color:color.green} )
 
-bar2.rotate( -pi/2,  vec(0,0,1), vec(axle2.pos.x, axle2.pos.y, bar2.pos.z) )
-bar2.rotate( theta2,  vec(0,0,1), vec(axle2.pos.x, axle2.pos.y, bar2.pos.z) )
+bar2.rotate( {angle:-pi/2,  axis:vec(0,0,1), origin:vec(axle2.pos.x, axle2.pos.y, bar2.pos.z)} )
+bar2.rotate( {angle:theta2,  axis:vec(0,0,1), origin:vec(axle2.pos.x, axle2.pos.y, bar2.pos.z)} )
 
-var dt = 0.001
+var dt = 0.01
 var t = 0
 
 while (true) {
     theta1 = theta1+dtheta1
     theta2 = theta2+dtheta2
 	
-    bar1.rotate( dtheta1, vec(0,0,1), pivot1 )
-    bar1b.rotate( dtheta1, vec(0,0,1), pivot1 )
+    bar1.rotate( {angle:dtheta1, axis:vec(0,0,1), origin:pivot1} )
+    bar1b.rotate( {angle:dtheta1, axis:vec(0,0,1), origin:pivot1} )
     var pivot2 = vec(axle2.pos.x, axle2.pos.y, pivot1.z)
-    axle2.rotate( dtheta1, vec(0,0,1), pivot1 )
-    bar2.rotate( dtheta2, vec(0,0,1), pivot2 )
+    axle2.rotate( {angle:dtheta1, axis:vec(0,0,1), origin:pivot1} )
+    bar2.rotate( {angle:dtheta2, axis:vec(0,0,1), origin:pivot2} )
     pivot2 = vec(axle2.pos.x, axle2.pos.y, pivot1.z)
     bar2.pos = pivot2 + bar2.axis/2
 	

examples/stars.js

-GlowScript 0.3
+GlowScript 1.0
 
 /* Stars interacting gravitationally. */
 
 // Bruce Sherwood; optimized for GlowScript by David Scherer
 
-var win=600
+scene.width = scene.height = 600
 
-canvas({width:win, height:win}) // establish graphics region of screen
+function display_instructions() {
+    var s1 = "In GlowScript programs:\n\n"
+    var s2 = "    Rotate the camera by dragging with the right mouse button,\n        or hold down the Ctrl key and drag.\n\n"
+    var s3 = "    To zoom, drag with the left+right mouse buttons,\n         or hold down the Alt key and drag,\n         or use the mouse wheel."
+    scene.caption.text(s1+s2+s3)
+}
 
-var Nstars = 50  // change this to have more or fewer stars
+// Display text below the 3D graphics:
+scene.title.text("Stars interacting gravitationally")
+display_instructions()
+
+var Nstars = 20  // change this to have more or fewer stars
 
 var G = 6.7e-11 // Universal gravitational constant
 
 var L = 4e10
 var vsun = 0.8*sqrt(G*Msun/Rsun)
 
-scene.range = 4*L
+scene.range = 2*L
 scene.forward = vec(-1,-1,-1)
 
 var xaxis = curve( {color:color.gray(0.5), radius:3e8} )
-xaxis.push(point(vec(0,0,0)))
-xaxis.push(point(vec(L,0,0)))
+xaxis.push(vec(0,0,0))
+xaxis.push(vec(L,0,0))
 var yaxis = curve( {color:color.gray(0.5), radius:3e8} )
-yaxis.push(point(vec(0,0,0)))
-yaxis.push(point(vec(0,L,0)))
+yaxis.push(vec(0,0,0))
+yaxis.push(vec(0,L,0))
 var zaxis = curve( {color:color.gray(0.5), radius:3e8} )
-zaxis.push(point(vec(0,0,0)))
-zaxis.push(point(vec(0,0,L)))
+zaxis.push(vec(0,0,0))
+zaxis.push(vec(0,0,L))
 
 var Stars = []
 var star_colors = [color.red, color.green, color.blue,
 var psum = vec(0,0,0)
 for (var i=0; i<Nstars; i++) {
     var star = sphere()
-    star.pos = 2*L*vec.random()
+    star.pos = L*vec.random()
     var R = Rsun/2+Rsun*Math.random()
     star.size = 2*R*vec(1,1,1)
-    star.mass = Msun*pow(star.size.x/2,3)/pow(Rsun,3)
+    star.mass = Msun*pow(R/Rsun,3)
     star.momentum = vec.random()*vsun*star.mass
     star.color = star_colors[i % 6]
-    star.trail = curve({color:star.color, radius:2e8})
+    attach_trail(star)
     Stars.push( star )
     psum = psum + star.momentum
 }
 var Nhits = 0
 var steps = 0
 
-var before = new Date().getTime()
-
 function computeForces( stars, hitlist ) {
-    // For maximum speed, there is no "overloading" of vector operations.
+    // For maximum speed, there is no operator overloading here.
     "no overloading";
     
     var len = stars.length
         var Fx=0, Fy=0, Fz=0
         var ix = data[i], iy=data[i+1], iz=data[i+2], iradius=data[i+3], imass=data[i+4]
         for(var j=0; j<dlen; j+=6) {
-            if (i===j) continue
+            if (i===j) continue;
             
             var rx=data[j]-ix, ry=data[j+1]-iy, rz=data[j+2]-iz, rad=data[j+3]+iradius, jmass=data[j+4]
             var rmag2 = rx*rx + ry*ry + rz*rz
 }
 
 while (true) {
-    rate(60,wait)
+    rate(100,wait)
     var hitlist = []
     
     // Compute all forces on all stars
     computeForces(Stars, hitlist)
 
     // Having updated all momenta, now update all positions
-    steps += 1
     for (var i=0; i<Stars.length; i++) {
         var star = Stars[i]
         if (star) {
             star.pos = star.pos + star.momentum*(dt/star.mass)
-            if (steps % 5 === 0) {
-        		star.trail.push( point(star.pos) )
-    		}
         }
-	}
+    }
 
     // If any collisions took place, merge those stars
     for(var hit=hitlist.length-1; hit>=0; hit--) {
         snew.momentum = s1.momentum + s2.momentum
         snew.pos = (s1.mass*s1.pos + s2.mass*s2.pos) / snew.mass
         snew.color = (s1.mass*s1.color + s2.mass*s2.color) / snew.mass
-        snew.trail = curve( {color:snew.color} )
+        attach_trail(snew, {color:snew.color, radius:2e8})
         var R = Rsun*pow(snew.mass / Msun, 1/3)
         snew.size = 2*R*vec(1,1,1)
         

examples/stonehenge.js

-GlowScript 0.3
+GlowScript 1.0
 /* Fly through Surreal Stonehenge */
 
 // Bruce Sherwood
 // Display render time:
 //$('<span id="fps"></span>').insertBefore('#glowscript')
 
-canvas({width:800, height:400}) // establish the display region
+scene.width = 800
+scene.height = 400
 
 var roam = false
-
-// Add instructions below the display
-$("#glowscript").css("font-size","90%")
-
-var s1 = "<b>Fly through the scene:</b> With the mouse button down,"
-var s2 = "&nbsp;&nbsp;&nbsp;&nbsp;move the mouse above or below the center of the scene to move forward or backward;"