Skip to content

Instantly share code, notes, and snippets.

@erwaller
Last active December 20, 2015 11:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save erwaller/6123608 to your computer and use it in GitHub Desktop.
Save erwaller/6123608 to your computer and use it in GitHub Desktop.
3D Seating Charts
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet/0.6.3/leaflet.css" />
</head>
<body>
<div id="map" style="position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px"></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/zepto/1.0/zepto.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/leaflet/0.6.3/leaflet.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r58/three.min.js"></script>
<script src="tween.min.js"></script>
<script src="three-stats.min.js"></script>
<script>
L.CRS.SeatGeek = L.extend({}, L.CRS, {
projection: L.Projection.LonLat,
transformation: new L.Transformation(0.35, 0, 0.35, 0),
scale: function (zoom) {
return Math.pow(2, zoom);
}
});
var map = L.map("map", {
minZoom: 1,
maxZoom: 4,
crs: L.CRS.SeatGeek
}).setView([500, 500], 1);
L.tileLayer('http://{s}.tiles.seatgeek.com/v3/maps/{mapId}/{z}/{x}/{y}.png', {
mapId: "v1-1-5",
tileSize: 350,
minZoom: 1,
maxZoom: 4,
noWrap: true
}).addTo(map);
L.SG3DLayer = L.Class.extend({
map: null,
container: null,
data: null,
// I don't *think* it matters what we pick here
vFOV: 60,
// Distance of camera from plane of the scene
cameraHeight: 1000,
threeOrigin: null,
// At what coordinates was the sceneOrigin when we first layed everything out
initialSceneOriginLatLng: null,
heightScale: null,
initialize: function (options) {
options = L.Util.setOptions(this, options);
this.data = [];
this.material = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.FlatShading,
opacity: 0.8,
overdraw: false,
wireframe: false
});
},
_move: function () {
},
_initScene: function () {
this.camera = new THREE.PerspectiveCamera(this.vFOV, this.map._size.x / this.map._size.y, 1, 1000);
this.camera.position.x = 0;
this.camera.position.y = 0;
this.camera.position.z = this.cameraHeight;
this.scene = new THREE.Scene();
// Lights
var ambientLight = new THREE.AmbientLight(0x8d8d8d);
this.scene.add(ambientLight);
this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.65);
this.directionalLight.position.x = - 0.5;
this.directionalLight.position.y = - 0.5;
this.directionalLight.position.z = 0.6;
this.scene.add(this.directionalLight);
this.renderer = new THREE.CanvasRenderer();
this._el = L.DomUtil.create('div', 'sg-3d-layer leaflet-zoom-hide');
this._el.appendChild(this.renderer.domElement);
this.map.getPanes().overlayPane.appendChild(this._el);
},
_resetScene: function () {
var that = this;
this.camera.aspect = this.map._size.x / this.map._size.y;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.map._size.x, this.map._size.y);
// Helpers
function degToRad (angle) { return angle * Math.PI / 180; }
function radToDeg (angle) { return angle * 180 / Math.PI; }
// How many vertical scene units can we see? (https://github.com/mrdoob/three.js/issues/1239)
// Useful diagram: http://techpubs.sgi.com/library/dynaweb_docs/0650/SGI_Developer/books/Perf_PG/sgi_html/figures/04.3.frustum.gif
var visibleHeight = 2 * Math.tan(degToRad(this.vFOV) / 2) * this.cameraHeight;
var scale = that.map._size.y / visibleHeight;
this.threeOrigin = new L.Point(this.map._size.x / 2, this.map._size.y / 2);
// ScenePoint: x,y coord in the THREE scene
// LatLng: x,y coord in the reference system
// containerPoint: x,y coord on the screen
this.latLngToScenePoint = function latLngToScenePoint (latLng) {
var containerPoint = that.map.latLngToContainerPoint(latLng);
return new L.Point(
(containerPoint.x - that.threeOrigin.x) * (1 / scale),
// Need to flip the y-axis
-1 * (containerPoint.y - that.threeOrigin.y) * (1 / scale)
);
}
var degreesLatVisible = this.map.containerPointToLatLng([0, this.map._size.y]).lat -
this.map.containerPointToLatLng([0, 0]).lat;
this.heightScale = degreesLatVisible / visibleHeight;
this._drawObjects();
},
_startRenderLoop: function () {
var that = this;
var stats = new Stats();
stats.domElement.style.cssText = 'position: absolute; top: 0px; right: 0px';
document.body.appendChild(stats.domElement);
var lastPos = null
function render() {
requestAnimationFrame(render);
TWEEN.update();
var scenePoint = that.latLngToScenePoint(that.initialSceneOriginLatLng);
that.group.position.x = scenePoint.x;
that.group.position.y = scenePoint.y;
that.renderer.render(that.scene, that.camera);
var curPos = that.map.containerPointToLayerPoint([0, 0]);
if (lastPos === null || !(lastPos.x === curPos.x && lastPos.y === curPos.x)) {
L.DomUtil.setPosition(that._el, lastPos = curPos);
}
stats.update();
}
render();
},
addTo: function (map) {
map.addLayer(this);
return this;
},
onAdd: function (map) {
var that = this;
this.map = map;
if (this.renderer) {
// TODO: re-adding behavior
} else {
this._initScene();
this._resetScene();
this._startRenderLoop();
}
this.map.on({
move: this._move,
viewreset: this._resetScene
}, this);
window.addEventListener('resize', function () { that._resetScene(); }, false);
},
_geoJSONGeometryToShape: function (geometry) {
if (geometry.type !== "Polygon") {
throw "Only Polygons are currently supported";
}
var vertices = geometry.coordinates[0];
var pts = [];
for (var i = 0; i < vertices.length; ++i) {
var ppt = this.latLngToScenePoint(L.latLng(vertices[i][1], vertices[i][0]), 1)
pts.push(new THREE.Vector2(ppt.x, ppt.y));
}
var shape = new THREE.Shape();
shape.fromPoints(pts);
return shape;
},
_drawObjects: function () {
if (this.group) {
this.scene.remove(this.group);
}
this.group = new THREE.Object3D();
var building, geometry;
for (var i = 0; i < this.data.length; ++i) {
building = this.data[i];
geometry = this._geoJSONGeometryToShape(building.geometry)
.extrude({
amount: building.properties.height / this.heightScale,
bevelEnabled: false
});
this.group.add(new THREE.Mesh(geometry, this.material));
}
this.scene.add(this.group)
var sceneOrigin = new L.Point(this.map._size.x / 2, this.map._size.y / 2);
this.initialSceneOriginLatLng = this.map.containerPointToLatLng(sceneOrigin);
},
_animateInObjects: function () {
var that = this;
var tween = new TWEEN.Tween({ z: 0.001 })
.to({ z: 1 }, 600)
.easing(TWEEN.Easing.Cubic.Out)
.onUpdate(function () {
that.group.scale.z = this.z;
})
.start();
},
_initStats: function () {
var container = document.createElement('div');
document.body.appendChild(container);
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
container.appendChild(this.stats.domElement);
},
onRemove: function (map) {
this.map = null;
map.off({
move: this._move,
viewreset: this._resetScene
}, this);
this.container.parentNode.removeChild(this.container);
},
geoJSON: function (x) {
this.data = x;
this._resetScene();
this._animateInObjects();
}
});
var sg3DLayer = new L.SG3DLayer().addTo(map);
setTimeout(function () {
$.get("citi-field.geo.json", function (data) {
sg3DLayer.geoJSON(data.features);
});
}, 500);
</script>
</body>
</html>
// stats.js - http://github.com/mrdoob/stats.js
var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
"block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};
// tween.js - http://github.com/sole/tween.js
'use strict';var TWEEN=TWEEN||function(){var a=[];return{REVISION:"10",getAll:function(){return a},removeAll:function(){a=[]},add:function(c){a.push(c)},remove:function(c){c=a.indexOf(c);-1!==c&&a.splice(c,1)},update:function(c){if(0===a.length)return!1;for(var b=0,d=a.length,c=void 0!==c?c:void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();b<d;)a[b].update(c)?b++:(a.splice(b,1),d--);return!0}}}();
TWEEN.Tween=function(a){var c={},b={},d={},e=1E3,g=0,i=0,k=null,u=TWEEN.Easing.Linear.None,v=TWEEN.Interpolation.Linear,p=[],q=null,r=!1,s=null,t=null,j;for(j in a)c[j]=parseFloat(a[j],10);this.to=function(a,c){void 0!==c&&(e=c);b=a;return this};this.start=function(e){TWEEN.add(this);r=!1;k=void 0!==e?e:void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();k+=i;for(var f in b){if(b[f]instanceof Array){if(0===b[f].length)continue;b[f]=[a[f]].concat(b[f])}c[f]=
a[f];!1===c[f]instanceof Array&&(c[f]*=1);d[f]=c[f]||0}return this};this.stop=function(){TWEEN.remove(this);return this};this.delay=function(a){i=a;return this};this.repeat=function(a){g=a;return this};this.easing=function(a){u=a;return this};this.interpolation=function(a){v=a;return this};this.chain=function(){p=arguments;return this};this.onStart=function(a){q=a;return this};this.onUpdate=function(a){s=a;return this};this.onComplete=function(a){t=a;return this};this.update=function(n){if(n<k)return!0;
!1===r&&(null!==q&&q.call(a),r=!0);var f=(n-k)/e,f=1<f?1:f,m=u(f),h;for(h in b){var j=c[h]||0,l=b[h];l instanceof Array?a[h]=v(l,m):("string"===typeof l&&(l=j+parseFloat(l,10)),a[h]=j+(l-j)*m)}null!==s&&s.call(a,m);if(1==f)if(0<g){isFinite(g)&&g--;for(h in d)"string"===typeof b[h]&&(d[h]+=parseFloat(b[h],10)),c[h]=d[h];k=n+i}else{null!==t&&t.call(a);f=0;for(m=p.length;f<m;f++)p[f].start(n);return!1}return!0}};
TWEEN.Easing={Linear:{None:function(a){return a}},Quadratic:{In:function(a){return a*a},Out:function(a){return a*(2-a)},InOut:function(a){return 1>(a*=2)?0.5*a*a:-0.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a:0.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a*
a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return 0.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?0.5*Math.pow(1024,a-1):0.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1-
Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return-(b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4))},Out:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return b*Math.pow(2,-10*a)*Math.sin((a-c)*
2*Math.PI/0.4)+1},InOut:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return 1>(a*=2)?-0.5*b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4):0.5*b*Math.pow(2,-10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*(3.5949095*a-2.5949095):0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1-
TWEEN.Easing.Bounce.Out(1-a)},Out:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},InOut:function(a){return 0.5>a?0.5*TWEEN.Easing.Bounce.In(2*a):0.5*TWEEN.Easing.Bounce.Out(2*a-1)+0.5}}};
TWEEN.Interpolation={Linear:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.Linear;return 0>c?g(a[0],a[1],d):1<c?g(a[b],a[b-1],b-d):g(a[e],a[e+1>b?b:e+1],d-e)},Bezier:function(a,c){var b=0,d=a.length-1,e=Math.pow,g=TWEEN.Interpolation.Utils.Bernstein,i;for(i=0;i<=d;i++)b+=e(1-c,d-i)*e(c,i)*a[i]*g(d,i);return b},CatmullRom:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.CatmullRom;return a[0]===a[b]?(0>c&&(e=Math.floor(d=b*(1+c))),g(a[(e-
1+b)%b],a[e],a[(e+1)%b],a[(e+2)%b],d-e)):0>c?a[0]-(g(a[0],a[0],a[1],a[1],-d)-a[0]):1<c?a[b]-(g(a[b],a[b],a[b-1],a[b-1],d-b)-a[b]):g(a[e?e-1:0],a[e],a[b<e+1?b:e+1],a[b<e+2?b:e+2],d-e)},Utils:{Linear:function(a,c,b){return(c-a)*b+a},Bernstein:function(a,c){var b=TWEEN.Interpolation.Utils.Factorial;return b(a)/b(c)/b(a-c)},Factorial:function(){var a=[1];return function(c){var b=1,d;if(a[c])return a[c];for(d=c;1<d;d--)b*=d;return a[c]=b}}(),CatmullRom:function(a,c,b,d,e){var a=0.5*(b-a),d=0.5*(d-c),g=
e*e;return(2*c-2*b+a+d)*e*g+(-3*c+3*b-2*a-d)*g+a*e+c}}};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment