︠ac8f3cfe-85a6-456d-ba09-1d8aa2ade744s︠
g = tetrahedron(color='purple', mesh=3)
g
︡ed0b40af-5a6f-40ea-b426-7955e6409a10︡{"file":{"uuid":"f7a374f5-4738-4abe-beec-641ab335a710","filename":"f7a374f5-4738-4abe-beec-641ab335a710.sage3d"}}︡
︠850ceaed-a27d-4046-b1bd-de0d580ef180s︠
import graphics
graphics.graphics3d_to_jsonable(g)
︡e8973dc7-b3cf-4e31-a5cb-a4dc8a2dedf2︡{"stdout":"[{'mesh': 3.0, 'material': [{'opacity': 1.0, 'illum': 1.0, 'specular': [0.0, 0.0, 0.0], 'name': 'texture3', 'ambient': [0.250980392157, 5e-06, 0.250980392157], 'color': [0.5019607843137255, 0.0, 0.5019607843137255], 'diffuse': [0.501960784314, 1e-05, 0.501960784314], 'shininess': 1.0}], 'type': 'index_face_set', 'vertex_geometry': [0.0, 0.0, 1.0, 0.942809, 0.0, -0.333333, -0.471405, 0.816497, -0.333333, -0.471405, -0.816497, -0.333333], 'face_geometry': [{'face4': [], 'face5': [], 'material_name': 'texture3', 'face3': [1, 2, 3, 2, 4, 3, 1, 3, 4, 1, 4, 2]}]}]\n"}︡
︠aca929ba-68e2-4a70-801a-30fe335bb661︠
%md
Below I've included the current version of the program that renders the above JSON objects using three.js. It may be of
use in deciding how to use that format.
︠7ca8e01c-d626-4af0-b2e1-07db4b572e3e︠
%coffeescript
###############################################################################
# Copyright (c) 2013, 2014, William Stein
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
###############################################################################
async = require('async')
misc = require('misc')
{defaults, required} = misc
component_to_hex = (c) ->
hex = c.toString(16);
if hex.length == 1
return "0" + hex
else
return hex
rgb_to_hex = (r, g, b) -> "#" + component_to_hex(r) + component_to_hex(g) + component_to_hex(b)
_loading_threejs_callbacks = []
VERSION = '68'
$.ajaxSetup(cache: true) # when using getScript, cache result.
load_threejs = (cb) ->
if Detector? and THREE?
cb(); return
_loading_threejs_callbacks.push(cb)
#console.log("load_threejs")
if _loading_threejs_callbacks.length > 1
#console.log("load_threejs: already loading...")
return # will get called later below
load = (script, name, cb) ->
if typeof(name) != 'string'
cb = name
name = undefined
m = (msg) -> #console.log("load('#{script}'): #{msg}")
m()
if name? and not window.module?
window.module = {exports:{}} # ugly hack around THREE.js now supporting modules
g = $.getScript(script)
g.done (script, textStatus) ->
if name?
window[name] = window.module.exports
delete window.module
# console.log("THREE=", THREE?)
m("done: #{textStatus}")
cb()
g.fail (jqxhr, settings, exception) ->
m("fail: #{exception}")
if name?
delete window.module
cb("error loading -- #{exception}")
async.series([
(cb) -> load("/static/threejs/r#{VERSION}/three.min.js", 'THREE', cb)
(cb) -> load("/static/threejs/r#{VERSION}/OrbitControls.min.js", cb)
(cb) -> load("/static/threejs/r#{VERSION}/Detector.min.js", cb)
(cb) ->
f = () ->
if Detector? and THREE?
cb()
else
#console.log("load_threejs: waiting for THREEJS...")
setTimeout(f, 100)
f()
], (err) ->
#console.log("load_threejs: done loading")
for cb in _loading_threejs_callbacks
cb(err)
_loading_threejs_callbacks = []
)
_scene_using_renderer = undefined
_renderer = {webgl:undefined, canvas:undefined}
dynamic_renderer_type = undefined
get_renderer = (scene, type) ->
# if there is a scene currently using this renderer, tell it to switch to
# the static renderer.
if _scene_using_renderer? and _scene_using_renderer._id != scene._id
_scene_using_renderer.set_static_renderer()
# now scene takes over using this renderer
_scene_using_renderer = scene
if Detector.webgl and (not type? or type == 'webgl')
type = 'webgl'
else
type = 'canvas'
dynamic_renderer_type = type
if not _renderer[type]?
# get the best-possible THREE.js renderer (once and for all)
if type == 'webgl'
_renderer[type] = new THREE.WebGLRenderer
antialias : true
alpha : true
preserveDrawingBuffer : true
else
_renderer[type] = new THREE.CanvasRenderer
antialias : true
alpha : true
$(_renderer[type].domElement).addClass("salvus-3d-dynamic-renderer")
return _renderer[type]
MIN_WIDTH = MIN_HEIGHT = 16
class SalvusThreeJS
constructor: (opts) ->
@opts = defaults opts,
element : required
container : required
width : undefined
height : undefined
renderer : undefined # 'webgl' or 'canvas' or undefined to choose best
background : "#fafafa"
foreground : undefined
spin : false # if true, image spins by itself when mouse is over it.
camera_distance : 10
aspect_ratio : undefined # undefined does nothing or a triple [x,y,z] of length three,
# which scales the x,y,z coordinates of everything by the given values.
stop_when_gone : undefined # if given, animation, etc., stops when this html element (not jquery!) is no longer in the DOM
frame : undefined # if given call set_frame with opts.frame as input when init_done called
cb : undefined # opts.cb(undefined, this object)
@init_eval_note()
load_threejs () =>
opts.cb?(undefined, @)
# client code should call this when start adding objects to the scene
init: () =>
if @_init
return
@_init = true
@_id = misc.uuid()
@init_aspect_ratio_functions()
@scene = new THREE.Scene()
# IMPORTANT: There is a major bug in three.js -- if you make the width below more than .5 of the window
# width, then after 8 3d renders, things get foobared in WebGL mode. This happens even with the simplest
# demo using the basic cube example from their site with R68. It even sometimes happens with this workaround, but
# at least retrying a few times can fix it.
if not @opts.width? or @opts.width < MIN_WIDTH
# ignore width/height less than a cutoff -- some graphics,
# e.g., "Polyhedron([(0,0,0),(0,1,0),(0,2,1),(1,0,0),(1,2,3),(2,1,1)]).plot()"
# weirdly set it very small.
@opts.width = $(window).width()*.5
@opts.height = if @opts.height? and @opts.height >= MIN_HEIGHT then @opts.height else @opts.width*2/3
@opts.container.css(width:"#{@opts.width+50}px")
@set_dynamic_renderer()
@init_orbit_controls()
@init_on_mouseover()
# add a bunch of lights
@init_light()
# set background color
@opts.element.find(".salvus-3d-canvas").css('background':@opts.background)
if not @opts.foreground?
c = @opts.element.find(".salvus-3d-canvas").css('background')
if not c? or c.indexOf(')') == -1
@opts.foreground = "#000" # e.g., on firefox - this is best we can do for now
else
i = c.indexOf(')')
z = []
for a in c.slice(4,i).split(',')
b = parseInt(a)
if b < 128
z.push(255)
else
z.push(0)
@opts.foreground = rgb_to_hex(z[0], z[1], z[2])
# client code should call this when done adding objects to the scene
init_done: () =>
if @opts.frame?
@set_frame(@opts.frame)
if @renderer_type != 'dynamic'
# if we don't have the renderer, swap it in, make a static image, then give it back to whoever had it.
owner = _scene_using_renderer
@set_dynamic_renderer()
@set_static_renderer()
owner?.set_dynamic_renderer()
# possibly show the canvas warning.
if dynamic_renderer_type == 'canvas'
@opts.element.find(".salvus-3d-canvas-warning").show().tooltip()
# show an "eval note" if we don't load the scene within a second.
init_eval_note: () =>
f = () =>
if not @_init
@opts.element.find(".salvus-3d-note").show()
setTimeout(f, 1000)
set_dynamic_renderer: () =>
# console.log "dynamic renderer"
if @renderer_type == 'dynamic'
# already have it
return
@renderer = get_renderer(@, @opts.renderer)
@renderer_type = 'dynamic'
# place renderer in correct place in the DOM
@opts.element.find(".salvus-3d-canvas").empty().append($(@renderer.domElement))
@renderer.setClearColor(@opts.background, 1)
@renderer.setSize(@opts.width, @opts.height)
if @controls?
@controls.enabled = true
if @last_canvas_pos?
@controls.object.position.copy(@last_canvas_pos)
if @last_canvas_target?
@controls.target.copy(@last_canvas_target)
if @opts.spin
@animate(render:false)
@render_scene(true)
set_static_renderer: () =>
# console.log "static renderer"
if @renderer_type == 'static'
# already have it
return
@static_image = @data_url()
@renderer_type = 'static'
if @controls?
@controls.enabled = false
@last_canvas_pos = @controls.object.position
@last_canvas_target = @controls.target
img = $("").attr(src:@static_image).width(@opts.width).height(@opts.height)
@opts.element.find(".salvus-3d-canvas").empty().append(img)
# On mouseover, we switch the renderer out to use webgl, if available, and also enable spin animation.
init_on_mouseover: () =>
@opts.element.mouseenter () =>
@set_dynamic_renderer()
@opts.element.mouseleave () =>
@set_static_renderer()
@opts.element.click () =>
@set_dynamic_renderer()
# initialize functions to create new vectors, which take into account the scene's 3d frame aspect ratio.
init_aspect_ratio_functions: () =>
if @opts.aspect_ratio?
x = @opts.aspect_ratio[0]; y = @opts.aspect_ratio[1]; z = @opts.aspect_ratio[2]
@vector3 = (a,b,c) => new THREE.Vector3(x*a, y*b, z*c)
@vector = (v) => new THREE.Vector3(x*v[0], y*v[1], z*v[2])
@aspect_ratio_scale = (v) => [x*v[0], y*v[1], z*v[2]]
else
@vector3 = (a,b,c) => new THREE.Vector3(a, b, c)
@vector = (v) => new THREE.Vector3(v[0],v[1],v[2])
@aspect_ratio_scale = (v) => v
show_canvas: () =>
@init()
@opts.element.find(".salvus-3d-note").hide()
@opts.element.find(".salvus-3d-canvas").show()
data_url: (opts) =>
opts = defaults opts,
type : 'png' # 'png' or 'jpeg' or 'webp' (the best)
quality : undefined # 1 is best quality; 0 is worst; only applies for jpeg or webp
s = @renderer.domElement.toDataURL("image/#{opts.type}", opts.quality)
# console.log("took #{misc.to_json(opts)} snapshot (length=#{s.length})")
return s
init_orbit_controls: () =>
if not @camera?
@add_camera(distance:@opts.camera_distance)
# console.log 'set_orbit_controls'
# set up camera controls
@controls = new THREE.OrbitControls(@camera, @renderer.domElement)
@controls.damping = 2
@controls.noKeys = true
@controls.zoomSpeed = 0.4
if @_center?
@controls.target = @_center
if @opts.spin
if typeof(@opts.spin) == "boolean"
@controls.autoRotateSpeed = 2.0
else
@controls.autoRotateSpeed = @opts.spin
@controls.autoRotate = true
@controls.addEventListener 'change', () =>
if @renderer_type=='dynamic'
@rescale_objects()
@renderer.render(@scene, @camera)
add_camera: (opts) =>
opts = defaults opts,
distance : 10
if @camera?
return
view_angle = 45
aspect = @opts.width/@opts.height
near = 0.1
far = Math.max(20000, opts.distance*2)
@camera = new THREE.PerspectiveCamera(view_angle, aspect, near, far)
@scene.add(@camera)
@camera.position.set(opts.distance, opts.distance, opts.distance)
@camera.lookAt(@scene.position)
@camera.up = new THREE.Vector3(0,0,1)
init_light: (color= 0xffffff) =>
ambient = new THREE.AmbientLight(0x404040)
@scene.add(ambient)
color = 0xffffff
d = 10000000
intensity = 0.5
for p in [[d,d,d], [d,d,-d], [d,-d,d], [d,-d,-d],[-d,d,d], [-d,d,-d], [-d,-d,d], [-d,-d,-d]]
directionalLight = new THREE.DirectionalLight(color, intensity)
directionalLight.position.set(p[0], p[1], p[2]).normalize()
@scene.add(directionalLight)
@light = new THREE.PointLight(color)
@light.position.set(0,d,0)
add_text: (opts) =>
o = defaults opts,
pos : [0,0,0]
text : required
fontsize : 12
fontface : 'Arial'
color : "#000000" # anything that is valid to canvas context, e.g., "rgba(249,95,95,0.7)" is also valid.
constant_size : true # if true, then text is automatically resized when the camera moves;
# WARNING: if constant_size, don't remove text from scene (or if you do, note that it is slightly inefficient still.)
#console.log("add_text: #{misc.to_json(o)}")
@show_canvas()
# make an HTML5 2d canvas on which to draw text
width = 300 # this determines max text width; beyond this, text is cut off.
height = 150
canvas = document.createElement( 'canvas' )
canvas.width = width
canvas.height = height
context = canvas.getContext("2d") # get the drawing context
# set the fontsize and fix for our text.
context.font = "Normal " + o.fontsize + "px " + o.fontface
context.textAlign = 'center'
# set the color of our text
context.fillStyle = o.color
# actually draw the text -- right in the middle of the canvas.
context.fillText(o.text, width/2, height/2)
# Make THREE.js texture from our canvas.
texture = new THREE.Texture(canvas)
texture.needsUpdate = true
# Make a material out of our texture.
spriteMaterial = new THREE.SpriteMaterial(map: texture)
# Make the sprite itself. (A sprite is a 3d plane that always faces the camera.)
sprite = new THREE.Sprite(spriteMaterial)
# Move the sprite to its position
p = @aspect_ratio_scale(o.pos)
sprite.position.set(p[0],p[1],p[2])
# If the text is supposed to stay constant size, add it to the list of constant size text,
# which gets resized on scene update.
if o.constant_size
if not @_text?
@_text = [sprite]
else
@_text.push(sprite)
# Finally add the sprite to our scene
@scene.add(sprite)
return sprite
add_line : (opts) =>
o = defaults opts,
points : required
thickness : 1
color : "#000000"
arrow_head : false # TODO
@show_canvas()
geometry = new THREE.Geometry()
for a in o.points
geometry.vertices.push(@vector(a))
line = new THREE.Line(geometry, new THREE.LineBasicMaterial(color:o.color, linewidth:o.thickness))
@scene.add(line)
add_point: (opts) =>
o = defaults opts,
loc : [0,0,0]
size : 5
color: "#000000"
@show_canvas()
if not @_points?
@_points = []
# IMPORTANT: Below we use sprites instead of the more natural/faster PointCloudMaterial.
# Why? Because usually people don't plot a huge number of points, and PointCloudMaterial is SQUARE.
# By using sprites, our points are round, which is something people really care about.
switch dynamic_renderer_type
when 'webgl'
width = 50
height = 50
canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height
context = canvas.getContext('2d') # get the drawing context
centerX = width/2
centerY = height/2
radius = 25
context.beginPath()
context.arc(centerX, centerY, radius, 0, 2*Math.PI, false)
context.fillStyle = o.color
context.fill()
texture = new THREE.Texture(canvas)
texture.needsUpdate = true
spriteMaterial = new THREE.SpriteMaterial(map: texture)
particle = new THREE.Sprite(spriteMaterial)
p = @aspect_ratio_scale(o.loc)
particle.position.set(p[0],p[1],p[2])
@_points.push([particle, o.size/200])
when 'canvas'
# inspired by http://mrdoob.github.io/three.js/examples/canvas_particles_random.html
PI2 = Math.PI * 2
program = (context) ->
context.beginPath()
context.arc(0, 0, 0.5, 0, PI2, true)
context.fill()
material = new THREE.SpriteCanvasMaterial
color : new THREE.Color(o.color)
program : program
particle = new THREE.Sprite(material)
p = @aspect_ratio_scale(o.loc)
particle.position.set(p[0],p[1],p[2])
@_points.push([particle, 4*o.size/@opts.width])
else
throw "bug -- unkown dynamic_renderer_type = #{dynamic_renderer_type}"
@scene.add(particle)
add_obj: (myobj)=>
@show_canvas()
vertices = myobj.vertex_geometry
for objects in [0...myobj.face_geometry.length]
#console.log("object=", misc.to_json(myobj))
face3 = myobj.face_geometry[objects].face3
face4 = myobj.face_geometry[objects].face4
face5 = myobj.face_geometry[objects].face5
geometry = new THREE.Geometry()
for k in [0...vertices.length] by 3
geometry.vertices.push(@vector(vertices.slice(k, k+3)))
push_face3 = (a,b,c) =>
geometry.faces.push(new THREE.Face3(a-1,b-1,c-1))
#geometry.faces.push(new THREE.Face3(b-1,a-1,c-1)) # both sides of faces, so material is visible from inside -- but makes some things like look really crappy; disable.
# include all faces defined by 3 vertices (triangles)
for k in [0...face3.length] by 3
push_face3(face3[k], face3[k+1], face3[k+2])
# include all *polyogonal* faces defined by 4 vertices (squares), which for THREE.js we must define using two triangles
push_face4 = (a,b,c,d) =>
push_face3(a,b,c)
push_face3(a,c,d)
for k in [0...face4.length] by 4
push_face4(face4[k], face4[k+1], face4[k+2], face4[k+3])
# include all *polyogonal* faces defined by 6 vertices (see http://people.cs.clemson.edu/~dhouse/courses/405/docs/brief-obj-file-format.html)
for k in [0...face5.length] by 6
[a,b,c,d,e,f] = face5.slice(k, k+6)
push_face3(a, b, c)
push_face3(a, c, d)
push_face3(a, d, e)
push_face3(a, e, f)
geometry.mergeVertices()
#geometry.computeCentroids()
geometry.computeFaceNormals()
#geometry.computeVertexNormals()
geometry.computeBoundingSphere()
#finding material key(mk)
name = myobj.face_geometry[objects].material_name
mk = 0
for item in [0..myobj.material.length-1]
if name == myobj.material[item].name
mk = item
break
if @opts.wireframe or myobj.wireframe
if myobj.color
color = myobj.color
else
c = myobj.material[mk].color
color = "rgb(#{c[0]*255},#{c[1]*255},#{c[2]*255})"
if typeof myobj.wireframe == 'number'
line_width = myobj.wireframe
else if typeof @opts.wireframe == 'number'
line_width = @opts.wireframe
else
line_width = 1
material = new THREE.MeshBasicMaterial
wireframe : true
color : color
wireframeLinewidth : line_width
side : THREE.DoubleSide
else if not myobj.material[mk]?
console.log("BUG -- couldn't get material for ", myobj)
material = new THREE.MeshBasicMaterial
wireframe : false
color : "#000000"
else
m = myobj.material[mk]
material = new THREE.MeshPhongMaterial
shininess : "1"
ambient : 0x0ffff
wireframe : false
transparent : m.opacity < 1
material.color.setRGB(m.color[0], m.color[1], m.color[2])
material.ambient.setRGB(m.ambient[0], m.ambient[1], m.ambient[2])
material.specular.setRGB(m.specular[0], m.specular[1], m.specular[2])
material.opacity = m.opacity
mesh = new THREE.Mesh(geometry, material)
mesh.position.set(0,0,0)
@scene.add(mesh)
# always call this after adding things to the scene to make sure track
# controls are sorted out, etc. Set draw:false, if you don't want to
# actually *see* a frame.
set_frame: (opts) =>
o = defaults opts,
xmin : required
xmax : required
ymin : required
ymax : required
zmin : required
zmax : required
color : @opts.foreground
thickness : .4
labels : true # whether to draw three numerical labels along each of the x, y, and z axes.
fontsize : 14
draw : true
@show_canvas()
@_frame_params = o
eps = 0.1
x0 = o.xmin; x1 = o.xmax; y0 = o.ymin; y1 = o.ymax; z0 = o.zmin; z1 = o.zmax
# console.log("set_frame: #{misc.to_json(o)}")
if Math.abs(x1-x0)
if not b?
z = a
else
z = (a+b)/2
z = z.toFixed(2)
return (z*1).toString()
txt = (x,y,z,text) =>
@_frame_labels.push(@add_text(pos:[x,y,z], text:text, fontsize:o.fontsize, color:o.color, constant_size:false))
offset = 0.075
if o.draw
e = (y1 - y0)*offset
txt(x1,y0-e,z0,l(z0))
txt(x1,y0-e,mz, "z=#{l(z0,z1)}")
txt(x1,y0-e,z1,l(z1))
e = (x1 - x0)*offset
txt(x1+e,y0,z0,l(y0))
txt(x1+e,my,z0, "y=#{l(y0,y1)}")
txt(x1+e,y1,z0,l(y1))
e = (y1 - y0)*offset
txt(x1,y1+e,z0,l(x1))
txt(mx,y1+e,z0, "x=#{l(x0,x1)}")
txt(x0,y1+e,z0,l(x0))
v = @vector3(mx, my, mz)
@camera.lookAt(v)
if @controls?
@controls.target = @_center
@render_scene()
add_3dgraphics_obj: (opts) =>
opts = defaults opts,
obj : required
wireframe : undefined
set_frame : undefined
@show_canvas()
for o in opts.obj
switch o.type
when 'text'
@add_text
pos : o.pos
text : o.text
color : o.color
fontsize : o.fontsize
fontface : o.fontface
constant_size : o.constant_size
when 'index_face_set'
if opts.wireframe?
o.wireframe = opts.wireframe
@add_obj(o)
if o.mesh and not o.wireframe # draw a wireframe mesh on top of the surface we just drew.
o.color='#000000'
o.wireframe = o.mesh
@add_obj(o)
when 'line'
delete o.type
@add_line(o)
when 'point'
delete o.type
@add_point(o)
else
console.log("ERROR: no renderer for model number = #{o.id}")
return
if opts.set_frame?
@set_frame(opts.set_frame)
@render_scene(true)
animate: (opts={}) =>
opts = defaults opts,
fps : undefined
stop : false
mouseover : undefined # ignored now
render : true
#console.log("@animate #{@_animate_started}")
if @_animate_started and not opts.stop
return
@_animate_started = true
@_animate(opts)
_animate: (opts) =>
#console.log("anim?", @opts.element.length, @opts.element.is(":visible"))
if @renderer_type == 'static'
# will try again when we switch to dynamic renderer
@_animate_started = false
return
if not @opts.element.is(":visible")
if @opts.stop_when_gone? and not $.contains(document, @opts.stop_when_gone)
# console.log("stop_when_gone removed from document -- quit animation completely")
@_animate_started = false
else if not $.contains(document, @opts.element[0])
# console.log("element removed from document; wait 5 seconds")
setTimeout((() => @_animate(opts)), 5000)
else
# console.log("check again after a second")
setTimeout((() => @_animate(opts)), 1000)
return
if opts.stop
@_stop_animating = true
# so next time around will start
return
if @_stop_animating
@_stop_animating = false
@_animate_started = false
return
@render_scene(opts.render)
delete opts.render
f = () =>
requestAnimationFrame((()=>@_animate(opts)))
if opts.fps? and opts.fps
setTimeout(f , 1000/opts.fps)
else
f()
render_scene: (force=false) =>
# console.log('render', @opts.element.length)
if @renderer_type == 'static'
console.log 'render static -- todo'
return
if not @camera?
return # nothing to do yet
@controls?.update()
pos = @camera.position
if not @_last_pos?
new_pos = true
@_last_pos = pos.clone()
else if @_last_pos.distanceToSquared(pos) > .05
new_pos = true
@_last_pos.copy(pos)
else
new_pos = false
if not new_pos and not force
return
# rescale all text in scene
@rescale_objects()
@renderer.render(@scene, @camera)
_rescale_factor: () =>
if not @_center?
return undefined
else
return @camera.position.distanceTo(@_center) / 3
rescale_objects: (force=false) =>
s = @_rescale_factor()
if not s? or (Math.abs(@_last_scale - s) < 0.000001 and not force)
return
@_last_scale = s
if @_text?
for sprite in @_text
sprite.scale.set(s,s,s)
if @_frame_labels?
for sprite in @_frame_labels
sprite.scale.set(s,s,s)
if @_points?
for z in @_points
c = z[1]
z[0].scale.set(s*c,s*c,s*c)
exports.render_3d_scene = (opts) ->
opts = defaults opts,
url : undefined # url from which to download (via ajax) a JSON string that parses to {opts:?,obj:?}
scene : undefined # {opts:?, obj:?}
element : required # DOM element
cb : undefined # cb(err, scene object)
# Render a 3-d scene
#console.log("render_3d_scene: url='#{opts.url}'")
if not opts.scene? and not opts.url?
opts.cb?("one of url or scene must be defined")
return
scene_obj = undefined
async.series([
(cb) =>
if opts.scene?
cb()
else
$.ajax(
url : opts.url
timeout : 30000
success : (data) ->
try
opts.scene = misc.from_json(data)
cb()
catch e
cb(e)
).fail () ->
cb("error downloading #{opts.url}")
(cb) =>
# do this initialization *after* we create the 3d renderer
init = (err, s) ->
if err
cb(err)
else
scene_obj = s
s.init()
s.add_3dgraphics_obj
obj : opts.scene.obj
s.init_done()
cb()
# create the 3d renderer
opts.scene.opts.cb = init
opts.element.salvus_threejs(opts.scene.opts)
], (err) ->
opts.cb?(err, scene_obj)
)
# jQuery plugin for making a DOM object into a 3d renderer
$.fn.salvus_threejs = (opts={}) ->
@each () ->
# console.log("applying official .salvus_threejs plugin")
elt = $(this)
e = $(".salvus-3d-templates .salvus-3d-viewer").clone()
elt.empty().append(e)
e.find(".salvus-3d-canvas").hide()
opts.element = e
opts.container = elt
# TODO/NOTE -- this explicit reference is brittle -- it is just an animation efficiency, but still...
opts.stop_when_gone = e.closest(".salvus-editor-codemirror")[0]
f = () ->
obj = new SalvusThreeJS(opts)
elt.data('salvus-threejs', obj)
if not THREE?
load_threejs (err) =>
if not err
f()
else
msg = "Error loading THREE.js -- #{err}"
if not opts.cb?
console.log(msg)
else
opts.cb?(msg)
else
f()
︠77394bc9-79eb-4f4f-b88a-32fbca6a7ad0︠