https://github.com/jampekka/webtrajsim
Tip revision: 61b0c3047004e8dfa7ee181975b13b94a3cf9668 authored by Jami Pekkanen on 28 December 2016, 19:53:27 UTC
Texture filtering tweaking
Texture filtering tweaking
Tip revision: 61b0c30
assets.ls
THREE = require 'three'
Cannon = require 'cannon'
$ = require 'jquery'
P = require 'bluebird'
seqr = require './seqr.ls'
require './node_modules/three/examples/js/SkyShader.js'
{loadCollada, mergeObject} = require './utils.ls'
svgToCanvas = seqr.bind (el, width, height, pw=0, ph=0) ->*
img = new Image
data = new Blob [el.outerHTML], type: 'image/svg+xml;charset=utf-8'
p = new P (accept, reject) ->
img.onload = accept
img.onerror = reject
DOMURL = window.URL ? window.webkitURL ? window;
img.src = DOMURL.createObjectURL data
yield p
canvas = document.createElement 'canvas'
canvas.width = width + pw
canvas.height = height + ph
ctx = canvas.getContext '2d'
ctx.drawImage img, 0, 0, width, height
DOMURL.revokeObjectURL img.src
return canvas
svgToSign = seqr.bind (img, {pixelsPerMeter=100}={}) ->*
texSize = (v) ->
v = Math.round v*pixelsPerMeter
return 0 if v < 1
for i from 0 to Infinity
pow2 = 2**i
break if pow2 > v
return [pow2, v - pow2]
meters = (v) ->
v = v.baseVal
v.convertToSpecifiedUnits v.SVG_LENGTHTYPE_CM
v.valueInSpecifiedUnits/100
faceWidth = meters img.prop 'width'
faceHeight = meters img.prop 'height'
[w, pw] = texSize faceWidth
[h, ph] = texSize faceHeight
raster = yield svgToCanvas img[0], w, h, 0, 0 # TODO
texture = new THREE.Texture raster
texture.needsUpdate = true
face = new THREE.Mesh do
new THREE.PlaneGeometry faceWidth, faceHeight
new THREE.MeshLambertMaterial do
map: texture
side: THREE.DoubleSide
transparent: true
face.width = faceWidth
face.height = faceHeight
return face
export SineGratingBitmap = ({resolution=1024, cycles=8}={}) ->
canvas = document.createElement 'canvas'
canvas.width = resolution
canvas.height = resolution
ctx = canvas.getContext '2d'
img = ctx.createImageData resolution, resolution
setPixel = (x, y, value) ->
i = (y*resolution + x)*4
c = value*255
img.data[i] = 255
img.data[i + 1] = 255
img.data[i + 2] = 255
img.data[i + 3] = c
for x til resolution
for y til resolution
rx = x/(resolution/2) - 1
ry = y/(resolution/2) - 1
d = Math.sqrt (rx**2 + ry**2)
v = (Math.cos(rx*cycles*Math.PI) + 1)/2
v *= Math.cos d*Math.PI/2
v *= d < 1.0
setPixel x, y, v
ctx.putImageData img, 0, 0
return canvas
SineGrating = (opts={}) ->
canvas = SineGratingBitmap opts
texture = new THREE.Texture canvas
texture.needsUpdate = true
face = new THREE.Mesh do
new THREE.PlaneGeometry 1, 1
new THREE.MeshLambertMaterial do
map: texture
side: THREE.DoubleSide
transparent: true
return face
export ArrowMarker = seqr.bind (opts={}) ->*
#doc = $ yield $.ajax "./res/signs/arrow.svg", dataType: 'xml'
marker = new THREE.Object3D
marker.signs = signs = {}
load = seqr.bind (name, file) ->*
doc = $ yield $.ajax file, dataType: 'xml'
img = $ doc.find "svg"
obj = yield svgToSign img
obj.visible = false
marker.add obj
signs[name] = obj
marker.setSign = (name) ->
for n, o of signs
o.visible = false
return if name not of signs
signs[name].visible = true
target = SineGrating opts
target.visible = false
target.transparent = true
signs.target = target
marker.add target
marker.setFrequency = (frequency) ->
return if frequency == target._frequency
target.material.map = new THREE.Texture(SineGratingBitmap cycles: frequency)
target.material.map.needsUpdate = true
target.material.needsUpdate = true
target._frequency = frequency
yield load 'cue', './res/signs/arrow-circle.svg'
yield load 'mask', './res/signs/arrow-mask.svg'
yield load 'wait', './res/signs/arrow-wait.svg'
yield load 'query', './res/signs/arrow-query.svg'
yield load 'success', './res/signs/arrow-success.svg'
yield load 'failure', './res/signs/arrow-failure.svg'
return marker
export TrackingMarker = seqr.bind ->*
#doc = $ yield $.ajax "./res/signs/arrow.svg", dataType: 'xml'
doc = $ yield $.ajax "./res/signs/trackerbr.svg", dataType: 'xml'
img = $ doc.find "svg"
target = yield svgToSign img, pixelsPerMeter: 2000
target.position.z = -2
doc = $ yield $.ajax "./res/signs/trackerbar.svg", dataType: 'xml'
img = $ doc.find "svg"
crosshair = yield svgToSign img, pixelsPerMeter: 2000
marker = new THREE.Object3D
marker.add crosshair
marker.add target
marker.crosshair = crosshair
marker.target = target
return marker
export SpeedSign = seqr.bind (limit, {height=2, poleRadius=0.07/2}=opts={}) ->*
doc = $ yield $.ajax "./res/signs/speedsign.svg", dataType: 'xml'
img = $ doc.find "svg"
(img.find '#limit')[0].textContent = limit
sign = new THREE.Object3D
face = yield svgToSign img, opts
face.position.y = height
face.position.z = -poleRadius - 0.01
face.rotation.y = Math.PI
sign.add face
pole = new THREE.Mesh do
new THREE.CylinderGeometry poleRadius, poleRadius, height, 32
new THREE.MeshLambertMaterial color: 0xdddddd
pole.position.y = height/2
sign.add pole
sign.traverse (o) ->
o.castShadow = true
o.receiveShadow = false
return sign
export FinishSign = seqr.bind ({height=4, texSize=[256,256], poleRadius=0.07/2}=opts={}) ->*
doc = $ yield $.ajax "./res/signs/finish.svg", dataType: 'xml'
img = $ doc.find "svg"
face = yield svgToSign img, opts
sign = new THREE.Object3D
face.position.y = height
face.position.z = -poleRadius - 0.01
face.rotation.y = Math.PI
sign.add face
pole = new THREE.Mesh do
new THREE.CylinderGeometry poleRadius, poleRadius, height, 32
new THREE.MeshLambertMaterial color: 0xdddddd
pole.position.y = height/2
pole.position.x = face.width/2
sign.add pole
pole = pole.clone()
pole.position.x = -face.width/2
sign.add pole
sign.traverse (o) ->
o.castShadow = true
o.receiveShadow = false
watcherWidth = face.width
watcherHeight = height
halfExtent = new Cannon.Vec3 watcherWidth/2, watcherHeight/2, 0.1
watcherShape = new Cannon.Box halfExtent
watcher = new Cannon.Body mass: 0, type: Cannon.Body.STATIC
..addShape watcherShape, new Cannon.Vec3 0, watcherHeight/2, 0
..objectClass = "finish-line"
..collisionResponse = false
..preventCollisionEvent = -> true
self =
onPassed: Signal!
bodyPassed: (body) -> new Promise (accept) ->
watcher.addEventListener "collide", (e) ->
if e.body == body
accept(e)
return false
visual: sign
position: watcher.position
addTo: (scene) ->
scene.visual.add sign
scene.physics.add watcher
scene.bindPhys watcher, sign
watcher.addEventListener "collide", (e) ->
self.onPassed.dispatch e
return self
export StopSign = seqr.bind ({height=2, poleRadius=0.07/2}=opts={}) ->*
doc = $ yield $.ajax "./res/signs/stopsign.svg", dataType: 'xml'
img = $ doc.find "svg"
sign = new THREE.Object3D
face = yield svgToSign img, opts
face.position.y = height
face.position.z = -poleRadius - 0.01
face.rotation.y = Math.PI
sign.add face
pole = new THREE.Mesh do
new THREE.CylinderGeometry poleRadius, poleRadius, height, 32
new THREE.MeshLambertMaterial color: 0xdddddd
pole.position.y = height/2
sign.add pole
watcherWidth = 5
watcherHeight = 10
halfExtent = new Cannon.Vec3 watcherWidth/2, watcherHeight/2, 0.1
watcherShape = new Cannon.Box halfExtent
watcher = new Cannon.Body mass: 0, type: Cannon.Body.STATIC
..addShape watcherShape, halfExtent
..objectClass = "stop-sign"
..collisionResponse = false
self =
visual: sign
position: watcher.position
addTo: (scene) ->
scene.visual.add sign
scene.physics.add watcher
scene.bindPhys watcher, sign
return self
export BallBoard = seqr.bind ->*
doc = $ yield $.ajax "./res/items/circle.svg", dataType: 'xml'
img = $ doc.find "svg"
ball = yield svgToSign img
ball.scale.set 0.05, 0.05, 0.05
ball.position.y = 0.05/2
doc = $ yield $.ajax "./res/items/level.svg", dataType: 'xml'
img = $ doc.find "svg"
board = yield svgToSign img, pixelsPerMeter: 1000
doc = $ yield $.ajax "./res/items/balancepoint.svg", dataType: 'xml'
img = $ doc.find "svg"
midpoint = yield svgToSign img, pixelsPerMeter: 1000
obj = new THREE.Object3D
obj.add midpoint
turnable = new THREE.Object3D
turnable.add ball
obj.ball = ball
turnable.add board
obj.turnable = turnable
obj.add turnable
return obj
export TrafficLight = seqr.bind ->*
data = yield loadCollada 'res/signs/TrafficLight.dae'
mod = data.scene.children[0]
mod.scale.set 0.03, 0.03, 0.03
mod.rotation.y = Math.PI
mod.position.y -= 1.0
model = new THREE.Object3D
..add mod
lights =
red: model.getObjectByName 'Red'
yellow: model.getObjectByName 'Yellow'
green: model.getObjectByName 'Green'
watcherWidth = 5
watcherHeight = 10
halfExtent = new Cannon.Vec3 5/2, 10/2, 0.1
watcherShape = new Cannon.Box halfExtent
watcher = new Cannon.Body mass: 0, type: Cannon.Body.STATIC
..addShape watcherShape, halfExtent
..objectClass = "traffic-light"
..collisionResponse = false
for let name, light of lights
materials = light.children[0].material.materials
materials = for material in materials
material = material.clone()
hsl = material.color.getHSL()
material.color.setHSL hsl.h, hsl.s, 0.1
material
light.children[0].material.materials = materials
light.on = ->
for material in materials
hsl = material.color.getHSL()
material.emissive.setHSL hsl.h, 0.9, 0.5
light.isOn = true
light.off = ->
for material in materials
hsl = material.color.getHSL()
material.emissive.setHSL hsl.h, 0.0, 0.0
light.isOn = false
onGreen = Signal!
lights.red.on()
#lights.yellow.on()
#lights.green.on()
getState: ->
red: lights.red.isOn
yellow: lights.yellow.isOn
green: lights.green.isOn
switchToGreen: seqr.bind ->*
if lights.green.isOn
lights.red.off()
lights.yellow.off()
return
lights.red.on()
lights.yellow.on()
yield P.delay 1*1000
lights.red.off()
lights.yellow.off()
lights.green.on()
onGreen.dispatch()
visual: model
position: watcher.position
addTo: (scene) ->
scene.visual.add model
scene.physics.add watcher
scene.bindPhys watcher, model
onGreen ->
scene.physics.removeBody watcher
SunCalc = require 'suncalc'
export addSky = (scene, {location=[60, 0], date}={}) ->
if not date?
date = new Date 1970, 5, 24, 12
distance = 4500
dome = new THREE.Object3D
scene.visual.add dome
sky = new THREE.Sky
dome.add sky.mesh
sunlight = new THREE.DirectionalLight 0xffffff, 0.5
..castShadow = true
..shadowCameraNear = distance/2
..shadowCameraFar = distance*2
..shadowCameraLeft = -distance
..shadowCameraRight = distance
..shadowCameraTop = distance
..shadowCameraBottom = -distance
..shadowMapWidth = 2048
..shadowMapHeight = 2048
..shadowBias = 0.0001
..shadowDarkness = 1.0
..target = dome
#..shadowCameraVisible = true
#
dome.add sunlight
#hemiLight = new THREE.HemisphereLight 0xffffff, 0xffffff, 0.5
# ..position.set 0, 4500, 0
#scene.visual.add hemiLight
scene.visual.add new THREE.AmbientLight 0xa0a0a0
position = new THREE.Vector3
scene.beforeRender.add ->
#if sunlight.shadowCamera
# scene.camera = sunlight.shadowCamera
position.setFromMatrixPosition scene.camera.matrixWorld
dome.position.z = position.z
dome.position.x = position.x
updatePosition = ->
degs = SunCalc.getPosition date, ...location
position = new THREE.Vector3 0, 0, distance
position.applyEuler new THREE.Euler -degs.altitude, degs.azimuth, 0, "YXZ"
#position = new THREE.Vector3 0, distance, 0
sky.uniforms.sunPosition.value.copy position
sunlight.position.copy position
updatePosition()
setDate: (newDate) ->
date := new Date newDate.getTime()
updatePosition()
getDate: -> new Date date.getTime()
export SceneDisplay = seqr.bind ({width=1024, height=1024}={}) ->*
rtTexture = new THREE.WebGLRenderTarget width, height,
minFilter: THREE.LinearFilter
magFilter: THREE.NearestFilter
format: THREE.RGBFormat
geo = new THREE.PlaneGeometry 1, 1
mat = new THREE.MeshBasicMaterial color: 0xffffff, map: rtTexture
object = new THREE.Mesh geo, mat
object: object
renderTarget: rtTexture
export addMarkerScreen = (scene) ->
aspect = screen.width / screen.height
t = scene.camera.top
b = scene.camera.bottom
l = -aspect
r = aspect
s = 0.2
pos = [[l + s, t - s], [r - s, t - s], [l + s, -t + s], [r - s, -t + s]]
for i from 0 til 4
path = 'res/markers/' + i + '_marker.png'
texture = THREE.ImageUtils.loadTexture path
marker = new THREE.Mesh do
new THREE.PlaneGeometry s, s
new THREE.MeshBasicMaterial map:texture
marker.position.x = pos[i][0]
marker.position.y = pos[i][1]
scene.visual.add marker