├── .gitignore ├── package.json ├── README.md ├── src ├── Observer.coffee ├── Info.coffee ├── Utility.coffee ├── Subject.coffee ├── Panel.coffee ├── Palette.coffee ├── Storage.coffee ├── Menu.coffee ├── Toolbar.coffee ├── DataProjector.coffee ├── Selector.coffee └── Projector.coffee ├── DataProjector.css ├── LICENSE ├── index.html ├── TrackballControls.js ├── TweenLite.min.js └── DataProjector.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datacratic-data-projector", 3 | "version": "0.1.0", 4 | "author": "Datacratic ", 5 | "description": "3-D scatterplotting", 6 | "dependencies" : 7 | { 8 | "browserify": "2.25.0", 9 | "coffeeify": "0.5.1" 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | data-projector 2 | ============== 3 | 4 | See http://datacratic.com/site/blog/visualizing-high-dimensional-data-browser-svd-t-sne-and-threejs for details on this project. 5 | 6 | A live demo of this code is available here: http://opensource.datacratic.com/data-projector/ 7 | 8 | To rebuild from coffeescript source: 9 | 10 | $ npm install 11 | $ node_modules/.bin/browserify -t coffeeify src/DataProjector.coffee > DataProjector.js 12 | -------------------------------------------------------------------------------- /src/Observer.coffee: -------------------------------------------------------------------------------- 1 | # Observer.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Abstract base class for all observers in the Observer pattern for passing events. 6 | # Observer objects attach themselves to Subjects and listen for updates. 7 | 8 | class Observer 9 | 10 | # M E T H O D S 11 | 12 | # Concrete observer classes should implement and use this method. 13 | update: (subject, type, data) -> 14 | 15 | 16 | module.exports = Observer 17 | -------------------------------------------------------------------------------- /src/Info.coffee: -------------------------------------------------------------------------------- 1 | # Info.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Controls application info console on the right side of the application window. 6 | 7 | Panel = require('./Panel.coffee') 8 | 9 | class Info extends Panel 10 | 11 | 12 | # C O N S T R U C T O R 13 | 14 | # Create info console panel. 15 | constructor: (id) -> 16 | 17 | super(id) 18 | 19 | 20 | # M E T H O D S 21 | 22 | # Display given message keeping the existing text intact. 23 | display: (message) -> 24 | 25 | $('#message').append(message + "
") 26 | 27 | 28 | # Clear the info console. 29 | clear: -> 30 | 31 | $('#message').text("") 32 | 33 | 34 | module.exports = Info -------------------------------------------------------------------------------- /src/Utility.coffee: -------------------------------------------------------------------------------- 1 | # Utility.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Collection of static helper methods of various sorts and other constants. 6 | 7 | class Utility 8 | 9 | # C O N S T A N T S 10 | 11 | # three orthographic view directions (ALL is used when all three need to be considered) 12 | @DIRECTION : { ALL: 0, TOP: 1, FRONT: 2, SIDE: 3 } 13 | 14 | @DEGREE : Math.PI / 180 # one degree 15 | 16 | @SECOND : 1000 # second in milliseconds 17 | 18 | # modifier keys used in shortcuts 19 | @NO_KEY : "NO_KEY" 20 | @SHIFT_KEY : "SHIFT_KEY" 21 | @CTRL_KEY : "CTRL_KEY" 22 | @ALT_KEY : "ALT_KEY" 23 | 24 | 25 | # S T A T I C M E T H O D S 26 | 27 | # Debug utility. Prints out THREE.Vector3 component values. 28 | @printVector3: (vector) -> 29 | 30 | console.log vector.x.toFixed(1) + " : " + vector.y.toFixed(1) + " : " + vector.z.toFixed(1) 31 | 32 | module.exports = Utility -------------------------------------------------------------------------------- /src/Subject.coffee: -------------------------------------------------------------------------------- 1 | # Subject.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Abstract base class for all subjects in the Observer pattern for passing events. 6 | # Subject provides interface for attaching and detaching Observer objects. 7 | 8 | class Subject 9 | 10 | # M E M B E R S 11 | 12 | observers : null # list of listeners 13 | 14 | 15 | # C O N S T R U C T O R 16 | 17 | constructor: () -> 18 | 19 | @observers = new Array() 20 | 21 | 22 | # M E T H O D S 23 | 24 | # Attach observer to this subject. 25 | attach: (o) -> 26 | 27 | @observers.push(o) 28 | 29 | 30 | # Remove observer from this subject. 31 | detach: (o) -> 32 | 33 | index = @observers.indexOf(o) 34 | if index >= 0 then @observers.splice(index, 1) 35 | 36 | 37 | # Notify all observers. 38 | notify: (type, data = null) -> 39 | 40 | for o in @observers 41 | o.update(@, type, data) 42 | 43 | 44 | module.exports = Subject 45 | -------------------------------------------------------------------------------- /DataProjector.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #909090; 3 | font-family: Monospace; 4 | font-size: 13px; 5 | text-align: center; 6 | 7 | background-color: #000; 8 | margin: 0px; 9 | overflow: hidden; 10 | } 11 | 12 | #toolbar { 13 | position: absolute; 14 | top: 10px; 15 | left: 10px; 16 | width: 100%; 17 | text-align: left; 18 | z-index: 100; 19 | display:block; 20 | } 21 | #toolbar .button { color: #CCCCCC; font-weight: bold; cursor: pointer } 22 | 23 | #menu { 24 | position: absolute; 25 | top: 100px; 26 | left: 10px; 27 | width: 25%; 28 | text-align: left; 29 | z-index: 101; 30 | display:block; 31 | } 32 | #menu .toggle { color: #CCCCCC; font-weight: bold; cursor: pointer } 33 | #menu .button { color: #CCCCCC; font-weight: bold; cursor: pointer } 34 | #menu .color { color: #CCCCCC; font-weight: bold; cursor: pointer } 35 | 36 | #info { 37 | position: absolute; 38 | top: 25px; 39 | right: 10px; 40 | width: 20%; 41 | text-align: left; 42 | z-index: 102; 43 | display:block; 44 | } 45 | #info .button { color: #CCCCCC; font-weight: bold; cursor: pointer } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Datacratic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/Panel.coffee: -------------------------------------------------------------------------------- 1 | # Panel.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Base abstract class for all UI panels. There are four classes derived from Panel: 6 | # Menu - main application menu (left) 7 | # Console - main output/info area (right) 8 | # ToolBar - top window control 9 | # StatusBar - bottom window helper 10 | 11 | Subject = require('./Subject.coffee') 12 | 13 | class Panel extends Subject 14 | 15 | # E V E N T S 16 | 17 | @EVENT_PANEL_SHOWN : "EVENT_PANEL_SHOWN" 18 | @EVENT_PANEL_HIDDEN : "EVENT_PANEL_HIDDEN" 19 | 20 | visible: true # default 21 | 22 | # C O N S T R U C T O R 23 | 24 | # Create panel. The id parameter is the id of the element in HTML (example: "#Menu") 25 | constructor: (@id) -> 26 | 27 | super() 28 | 29 | 30 | # Show this panel. 31 | show: -> 32 | 33 | $(@id).show() 34 | @visible = true 35 | @notify(Panel.EVENT_PANEL_SHOWN) 36 | 37 | 38 | # Hide this panel. 39 | hide: -> 40 | 41 | $(@id).hide() 42 | @visible = false 43 | @notify(Panel.EVENT_PANEL_HIDDEN) 44 | 45 | 46 | # Toggle visibility. 47 | toggle: -> 48 | 49 | if @visible then @hide() else @show() 50 | return @visible 51 | 52 | 53 | module.exports = Panel -------------------------------------------------------------------------------- /src/Palette.coffee: -------------------------------------------------------------------------------- 1 | # Palette.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Experimental color palette manager and auto generator. 6 | # Also stores interface color constants. 7 | 8 | class Palette 9 | 10 | # C O N S T A N T S 11 | 12 | @BACKGROUND : new THREE.Color( 0x202020 ) # used to display and clear background 13 | @HIGHLIGHT : new THREE.Color( 0xFFFFFF ) # used to highlight selected data points 14 | @SELECTOR : new THREE.Color( 0xCC0000 ) # color used for the rubber band/box outline 15 | @BUTTON : new THREE.Color( 0xCCCCCC ) # button normal color 16 | @BUTTON_SELECTED : new THREE.Color( 0xFF9C00 ) # button selected color 17 | 18 | # M E M B E R S 19 | 20 | # automatically generated palette 21 | 22 | colors : null # array of THREE.Color 23 | 24 | # C O N S T R U C T O R 25 | 26 | # Create color palette of given size. 27 | constructor: (size) -> 28 | 29 | @colors = new Array() 30 | @generate(size) 31 | 32 | # M E T H O D S 33 | 34 | # Automatically generate a palette. 35 | generate: (size) -> 36 | 37 | hue = 0 38 | saturation = 0.7 39 | lightness = 0.45 40 | 41 | step = 1 / size 42 | 43 | for i in [0...size] 44 | hue = (i + 1) * step 45 | color = new THREE.Color() 46 | color.setHSL(hue, saturation, lightness) 47 | @colors.push(color) 48 | 49 | 50 | # Return generated array. 51 | getColors: -> return @colors 52 | 53 | 54 | # Debug utility. Prints out all palette colors. 55 | print: -> 56 | 57 | i = 0 58 | for c in @colors 59 | css = c.getStyle() 60 | hsl = c.getHSL() 61 | hue = hsl.h.toFixed(1) 62 | saturation = hsl.s.toFixed(1) 63 | lightness = hsl.l.toFixed(1) 64 | console.log i++ + " > " + hue + " : " + saturation + " : " + lightness + " | " + css 65 | 66 | 67 | module.exports = Palette -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Datacratic : DataProjector 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | Display: 14 | [M]enu 15 | [I]nfo
16 | Camera: 17 | [P]erspective 18 | [O]rthographic 19 | [D]ual 20 | [R]eset
21 | Tools: 22 | [C]lear 23 | [B]ox 24 | [S]elect 25 | [V]iewport 26 | [1]Top 27 | [2]Front 28 | [3]Side 29 | [<]Left 30 | [ ]Stop 31 | [>]Right 32 | [A]nimate 33 |
34 | 35 | 40 | 41 |
42 | --- Info Console ---

43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/Storage.coffee: -------------------------------------------------------------------------------- 1 | # Storage.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Class that provides access to application storage. Technically this is server 6 | # based storage accessed asynchronously, but with custom Node based server this 7 | # can be run in the context of the single machine with local storage. 8 | 9 | # Storage provides services to retrieve the data file for visualization and to 10 | # save the screenshots of the rendered images. It also does the initial scan of 11 | # the data to extract some basic info about it like the number of clusters present. 12 | 13 | # SEE server.coffee 14 | 15 | Subject = require('./Subject.coffee') 16 | 17 | class Storage extends Subject 18 | 19 | # E V E N T S 20 | 21 | @EVENT_DATAFILE_READY : "EVENT_DATAFILE_READY" # fired when data file name was received 22 | @EVENT_JSON_READY : "EVENT_JSON_READY" # fired when JSON data source was downloaded 23 | @EVENT_DATA_READY : "EVENT_DATA_READY" # fired when JSON data was processed and ready for use 24 | @EVENT_SCREENSHOT_OK : "EVENT_SCREENSHOT_OK" # fired when screenshot was saved OK 25 | 26 | # M E M B E R S 27 | 28 | datafile : null # name of the data file to be used 29 | data : null # JSON data 30 | 31 | points : 0 # counts data points loaded 32 | clusterIDs : null # array of cluster IDs loaded 33 | clusters : 0 # number of unique clusters 34 | 35 | saved : 0 # save screenshot counter 36 | 37 | 38 | # C O N S T R U C T O R 39 | 40 | constructor : -> 41 | 42 | super() 43 | 44 | @clusterIDs = new Array() 45 | 46 | 47 | # E V E N T H A N D L E R S 48 | 49 | 50 | # Server response to filename request. 51 | onDatafile : (@datafile) => 52 | 53 | @notify(Storage.EVENT_DATAFILE_READY) 54 | @requestJSON(@datafile) 55 | 56 | 57 | # Called when data arrives. 58 | onJSON : (@data) => 59 | 60 | @notify(Storage.EVENT_JSON_READY) 61 | # process JSON data 62 | $.each(@data.points, @processPoint) 63 | @notify(Storage.EVENT_DATA_READY) 64 | 65 | 66 | 67 | # Server response to saving image on disk. 68 | onSaveResponse : (message) => 69 | 70 | console.log "DataProjector.onSaveResponse " + message 71 | @notify(Storage.EVENT_SCREENSHOT_OK) 72 | 73 | 74 | # M E T H O D S 75 | 76 | 77 | # This is a two step process. First data file name is retrieved. Then data itself. 78 | requestData : -> 79 | 80 | @requestDatafile() 81 | 82 | 83 | # Get the name of the data file to use in visualization. 84 | requestDatafile : -> @onDatafile "data.json" 85 | 86 | 87 | # Use jQuery JSON loader to fetch data. 88 | requestJSON : (@datafile) -> 89 | 90 | # attach random value to avoid browser cache problem 91 | file = @datafile + "?" + String(Math.round(Math.random() * 99999)) 92 | $.getJSON(file, @onJSON) 93 | 94 | 95 | # Save copy of the given image to storage. 96 | saveImage : (base64Image) -> 97 | 98 | $.post('/upload', { id : ++@saved, image : base64Image }, @onSaveResponse) 99 | 100 | 101 | # Called for each data point loaded in JSON file. 102 | # Initial scan of loaded data to extract some info about it. 103 | processPoint : (nodeName, nodeData) => 104 | 105 | unless nodeData.cid in @clusterIDs 106 | @clusterIDs.push(nodeData.cid) 107 | @clusters = @clusterIDs.length 108 | 109 | @points++ 110 | 111 | 112 | # Get the data file name. 113 | getDatafile : -> return @datafile 114 | 115 | 116 | # Get the JSON data. 117 | getData : -> return @data 118 | 119 | 120 | # Get number of unique clusters found in data. 121 | getClusters : -> return @clusters 122 | 123 | 124 | # Get number of points found in data. 125 | getPoints : -> return @points 126 | 127 | 128 | # Get number of saved screenshots. 129 | getSaved : -> return @saved 130 | 131 | 132 | module.exports = Storage 133 | -------------------------------------------------------------------------------- /src/Menu.coffee: -------------------------------------------------------------------------------- 1 | # Menu.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Main application menu on the left side of the application window. 6 | 7 | Panel = require('./Panel.coffee') 8 | 9 | class Menu extends Panel 10 | 11 | # E V E N T S 12 | 13 | @EVENT_TOGGLE_ALL_ON : "EVENT_TOGGLE_ALL_ON" 14 | @EVENT_TOGGLE_ALL_OFF : "EVENT_TOGGLE_ALL_OFF" 15 | 16 | @EVENT_TOGGLE_ID : "EVENT_TOGGLE_ID" 17 | @EVENT_CLUSTER_ID : "EVENT_CLUSTER_ID" 18 | 19 | # C O N S T A N T S 20 | 21 | @TOGGLE_ON : "[+]" 22 | @TOGGLE_OFF : "[-]" 23 | @TOGGLE_MIX : "[/]" 24 | 25 | # M E M B E R S 26 | 27 | clusters : 0 # total number of clusters 28 | 29 | selected : -1 # currently selected cluster 30 | 31 | colors : null # set of colors to use 32 | 33 | 34 | # C O N S T R U C T O R 35 | 36 | constructor : (id) -> 37 | 38 | super(id) 39 | 40 | 41 | # E V E N T H A N D L E R S 42 | 43 | # Toggle visibility of all clusters at once. 44 | onToggleAll : (event) => 45 | 46 | state = $("#toggleAll").text() 47 | 48 | switch state 49 | 50 | when Menu.TOGGLE_OFF, Menu.TOGGLE_MIX # turn all on 51 | 52 | $("#toggleAll").text(Menu.TOGGLE_ON) 53 | 54 | for i in [0...@clusters] 55 | $("#t" + String(i)).text(Menu.TOGGLE_ON) 56 | 57 | @notify(Menu.EVENT_TOGGLE_ALL_ON) 58 | 59 | when Menu.TOGGLE_ON # turn all off 60 | 61 | $("#toggleAll").text(Menu.TOGGLE_OFF) 62 | 63 | for i in [0...@clusters] 64 | $("#t" + String(i)).text(Menu.TOGGLE_OFF) 65 | 66 | @notify(Menu.EVENT_TOGGLE_ALL_OFF) 67 | 68 | 69 | onToggle : (event) => 70 | 71 | identifier = event.target.id 72 | id = identifier.replace("t", "") 73 | index = parseInt(id) 74 | 75 | @doToggle(index) 76 | @notify(Menu.EVENT_TOGGLE_ID, { id : index }) 77 | 78 | 79 | onCluster : (event) => 80 | 81 | # retrieve clicked cluster number 82 | index = parseInt(event.target.id.replace("b", "")) 83 | 84 | if @selected is index then @selected = -1 # unselect 85 | else @selected = index # select 86 | 87 | @updateSwatches() 88 | @updateButtons() 89 | 90 | @notify(Menu.EVENT_CLUSTER_ID, { id : index }) 91 | 92 | 93 | # Flip toggle given by its index. 94 | doToggle : (index) -> 95 | 96 | tag = "#t" + String(index) 97 | state = $(tag).text() 98 | 99 | switch state 100 | 101 | when Menu.TOGGLE_ON 102 | $(tag).text(Menu.TOGGLE_OFF) 103 | 104 | when Menu.TOGGLE_OFF 105 | $(tag).text(Menu.TOGGLE_ON) 106 | 107 | @updateMasterToggle() 108 | 109 | 110 | # M E T H O D S 111 | 112 | # Create dynamically menu for given number of clusters. 113 | # Use given set of colors for color coding to match visualization. 114 | create : (@clusters, @colors) -> 115 | 116 | # button IDs are b0, b1, b2... 117 | # toggle IDs are t0, t1, t2... 118 | # swatch IDs are c0, c1, c2... 119 | 120 | for i in [0...@clusters] 121 | html = "[+] Cluster #{i}
" 122 | $("#menu").append(html) 123 | 124 | $("#toggleAll").click(@onToggleAll) 125 | 126 | for i in [0...@clusters] 127 | $("#t" + String(i)).click( @onToggle ) 128 | $("#b" + String(i)).click( @onCluster ) 129 | 130 | @updateSwatches() 131 | 132 | 133 | # Count how many toggles are on. 134 | togglesOn : -> 135 | 136 | result = 0 137 | 138 | for i in [0...@clusters] 139 | tag = "#t" + String(i) 140 | state = $(tag).text() 141 | if state is Menu.TOGGLE_ON then result++ 142 | 143 | return result 144 | 145 | 146 | 147 | # Based on the state of all cluster toggles, set the master toggle. 148 | updateMasterToggle : () -> 149 | 150 | shown = @togglesOn() 151 | 152 | switch shown 153 | when 0 then $("#toggleAll").text(Menu.TOGGLE_OFF) 154 | when @clusters then $("#toggleAll").text(Menu.TOGGLE_ON) 155 | else $("#toggleAll").text(Menu.TOGGLE_MIX) 156 | 157 | 158 | # Swatches have IDs: c0, c1, c2... 159 | updateSwatches : -> 160 | 161 | for i in [0...@clusters] 162 | if i is @selected 163 | $("#c" + String(i)).css( 'color', Palette.HIGHLIGHT.getStyle() ) 164 | else 165 | $("#c" + String(i)).css( 'color', @colors[i].getStyle() ) 166 | 167 | 168 | # Cluster buttons have IDs: b0, b1, b2... 169 | updateButtons : -> 170 | 171 | for i in [0...@clusters] 172 | if i is @selected 173 | $("#b" + String(i)).css( 'color', Palette.HIGHLIGHT.getStyle() ) 174 | else 175 | $("#b" + String(i)).css( 'color', Palette.BUTTON.getStyle() ) 176 | 177 | 178 | module.exports = Menu 179 | -------------------------------------------------------------------------------- /src/Toolbar.coffee: -------------------------------------------------------------------------------- 1 | # Toolbar.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Application top of the window menu toolbar. 6 | 7 | Utility = require('./Utility.coffee') 8 | Panel = require('./Panel.coffee') 9 | Palette = require('./Palette.coffee') 10 | 11 | class Toolbar extends Panel 12 | 13 | # E V E N T S 14 | 15 | @EVENT_MENU : "EVENT_MENU" 16 | @EVENT_INFO : "EVENT_INFO" 17 | @EVENT_PERSPECTIVE : "EVENT_PERSPECTIVE" 18 | @EVENT_ORTHOGRAPHIC : "EVENT_ORTHOGRAPHIC" 19 | @EVENT_DUAL : "EVENT_DUAL" 20 | @EVENT_RESET : "EVENT_RESET" 21 | @EVENT_CLEAR : "EVENT_CLEAR" 22 | @EVENT_BOX : "EVENT_BOX" 23 | @EVENT_VIEWPORT : "EVENT_VIEWPORT" 24 | @EVENT_SELECT : "EVENT_SELECT" 25 | @EVENT_VIEW_TOP : "EVENT_VIEW_TOP" 26 | @EVENT_VIEW_FRONT : "EVENT_VIEW_FRONT" 27 | @EVENT_VIEW_SIDE : "EVENT_VIEW_SIDE" 28 | @EVENT_SPIN_LEFT : "EVENT_SPIN_LEFT" 29 | @EVENT_SPIN_STOP : "EVENT_SPIN_STOP" 30 | @EVENT_SPIN_RIGHT : "EVENT_SPIN_RIGHT" 31 | @EVENT_ANIMATE : "EVENT_ANIMATE" 32 | 33 | 34 | # M E M B E R S 35 | 36 | dispatcher : null # map of IDs and event handlers 37 | 38 | # C O N S T R U C T O R 39 | 40 | constructor : (id) -> 41 | 42 | super(id) 43 | 44 | @createDispatcher() 45 | 46 | for item in @dispatcher 47 | $(item.id).click({ type : item.type }, @onClick) 48 | 49 | document.addEventListener('keydown', @onKeyDown, false) 50 | 51 | @initialize() 52 | 53 | 54 | # E V E N T H A N D L E R S 55 | 56 | # Called when key pressed. 57 | onKeyDown : (event) => 58 | 59 | # console.log event.keyCode + " : " + event.shiftKey 60 | 61 | modifier = Utility.NO_KEY # default 62 | 63 | if event.shiftKey then modifier = Utility.SHIFT_KEY 64 | 65 | for item in @dispatcher 66 | if (item.key is event.keyCode) and (item.modifier is modifier) then @notify(item.type) 67 | 68 | 69 | 70 | onClick : (event) => 71 | @notify(event.data.type) 72 | 73 | 74 | # M E T H O D S 75 | 76 | # Create centralized event registration and dispatch map 77 | createDispatcher : => 78 | 79 | # NOTE key == 0 means no shortcut assigned 80 | 81 | @dispatcher = [ { id : "#menuButton", key : 77, modifier : Utility.NO_KEY, type : Toolbar.EVENT_MENU }, 82 | { id : "#infoButton", key : 73, modifier : Utility.NO_KEY, type : Toolbar.EVENT_INFO }, 83 | { id : "#perspectiveButton", key : 80, modifier : Utility.NO_KEY, type : Toolbar.EVENT_PERSPECTIVE }, 84 | { id : "#orthographicButton", key : 79, modifier : Utility.NO_KEY, type : Toolbar.EVENT_ORTHOGRAPHIC }, 85 | { id : "#dualButton", key : 68, modifier : Utility.NO_KEY, type : Toolbar.EVENT_DUAL }, 86 | { id : "#resetButton", key : 82, modifier : Utility.NO_KEY, type : Toolbar.EVENT_RESET }, 87 | { id : "#clearButton", key : 67, modifier : Utility.NO_KEY, type : Toolbar.EVENT_CLEAR }, 88 | { id : "#boxButton", key : 66, modifier : Utility.NO_KEY, type : Toolbar.EVENT_BOX }, 89 | { id : "#viewportButton", key : 86, modifier : Utility.NO_KEY, type : Toolbar.EVENT_VIEWPORT }, 90 | { id : "#selectButton", key : 83, modifier : Utility.NO_KEY, type : Toolbar.EVENT_SELECT }, 91 | { id : "#viewTopButton", key : 49, modifier : Utility.NO_KEY, type : Toolbar.EVENT_VIEW_TOP }, 92 | { id : "#viewFrontButton", key : 50, modifier : Utility.NO_KEY, type : Toolbar.EVENT_VIEW_FRONT }, 93 | { id : "#viewSideButton", key : 51, modifier : Utility.NO_KEY, type : Toolbar.EVENT_VIEW_SIDE }, 94 | { id : "#spinLeftButton", key : 37, modifier : Utility.NO_KEY, type : Toolbar.EVENT_SPIN_LEFT }, 95 | { id : "#spinStopButton", key : 32, modifier : Utility.NO_KEY, type : Toolbar.EVENT_SPIN_STOP }, 96 | { id : "#spinRightButton", key : 39, modifier : Utility.NO_KEY, type : Toolbar.EVENT_SPIN_RIGHT }, 97 | { id : "#animateButton", key : 65, modifier : Utility.NO_KEY, type : Toolbar.EVENT_ANIMATE }, 98 | ] 99 | 100 | initialize : => 101 | 102 | @setButtonSelected("#menuButton", true) 103 | @setButtonSelected("#infoButton", true) 104 | 105 | @setButtonSelected("#perspectiveButton", false) 106 | @setButtonSelected("#orthographicButton", false) 107 | @setButtonSelected("#dualButton", true) 108 | 109 | @setButtonSelected("#boxButton", true) 110 | @setButtonSelected("#viewportButton", true) 111 | 112 | @setButtonSelected("#selectButton", false) 113 | 114 | @setButtonSelected("#viewTopButton", true) 115 | @setButtonSelected("#viewFrontButton", false) 116 | @setButtonSelected("#viewSideButton", false) 117 | 118 | @setButtonSelected("#spinLeftButton", false) 119 | @setButtonSelected("#spinStopButton", true) 120 | @setButtonSelected("#spinRightButton", false) 121 | 122 | @setButtonSelected("#animateButton", false) 123 | 124 | 125 | setButtonSelected : (id, selected) => 126 | 127 | color = Palette.BUTTON.getStyle() 128 | if selected then color = Palette.BUTTON_SELECTED.getStyle() 129 | 130 | $(id).css('color', color) 131 | 132 | 133 | blinkButton : (id) => 134 | 135 | @setButtonSelected(id, true) 136 | window.setTimeout(@unblinkButton, 200, id) 137 | 138 | 139 | unblinkButton : (id) => 140 | 141 | console.log "Toolbar.unblinkButton " + id 142 | @setButtonSelected(id, false) 143 | 144 | 145 | setMenuButtonSelected : (selected) => 146 | 147 | @setButtonSelected("#menuButton", selected) 148 | 149 | 150 | setInfoButtonSelected : (selected) => 151 | 152 | @setButtonSelected("#infoButton", selected) 153 | 154 | 155 | setCameraButtonSelected : (selected1, selected2, selected3) => 156 | 157 | @setButtonSelected("#perspectiveButton", selected1) 158 | @setButtonSelected("#orthographicButton", selected2) 159 | @setButtonSelected("#dualButton", selected3) 160 | 161 | 162 | blinkResetButton : => 163 | 164 | @blinkButton("#resetButton") 165 | 166 | 167 | blinkClearButton : => 168 | 169 | @blinkButton("#clearButton") 170 | 171 | 172 | setBoxButtonSelected : (selected) => 173 | 174 | @setButtonSelected("#boxButton", selected) 175 | 176 | 177 | setViewportButtonSelected : (selected) => 178 | 179 | @setButtonSelected("#viewportButton", selected) 180 | 181 | 182 | setSelectButtonSelected : (selected) => 183 | 184 | @setButtonSelected("#selectButton", selected) 185 | 186 | 187 | setViewButtonSelected : (selected1, selected2, selected3) => 188 | 189 | @setButtonSelected("#viewTopButton", selected1) 190 | @setButtonSelected("#viewFrontButton", selected2) 191 | @setButtonSelected("#viewSideButton", selected3) 192 | 193 | 194 | setSpinButtonSelected : (selected1, selected2, selected3) => 195 | 196 | @setButtonSelected("#spinLeftButton", selected1) 197 | @setButtonSelected("#spinStopButton", selected2) 198 | @setButtonSelected("#spinRightButton", selected3) 199 | 200 | 201 | setAnimateButtonSelected : (selected) => 202 | 203 | @setButtonSelected("#animateButton", selected) 204 | 205 | 206 | 207 | 208 | module.exports = Toolbar 209 | -------------------------------------------------------------------------------- /src/DataProjector.coffee: -------------------------------------------------------------------------------- 1 | # DataProjector.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Main class of the data visualization application. 6 | 7 | Subject = require('./Subject.coffee') 8 | Observer = require('./Observer.coffee') 9 | Utility = require('./Utility.coffee') 10 | Palette = require('./Palette.coffee') 11 | Storage = require('./Storage.coffee') 12 | Toolbar = require('./Toolbar.coffee') 13 | Menu = require('./Menu.coffee') 14 | Info = require('./Info.coffee') 15 | Projector = require('./Projector.coffee') 16 | 17 | class DataProjector extends Observer 18 | 19 | # M E M B E R S 20 | 21 | # main modules 22 | 23 | storage : null # read/write 24 | 25 | toolbar : null # main application menu 26 | menu : null # main data menu - left side panel 27 | info : null # information console - right side panel 28 | 29 | projector : null # WebGL visualization canvas 30 | 31 | palette : null # stores both fixed UI colors and autogenerated ones for visualization 32 | colors : null # Array generated color values for visualization 33 | 34 | # C O N S T R U C T O R 35 | 36 | # Create data projector and display/visualize given data file. 37 | constructor : -> 38 | 39 | # data read/write access 40 | 41 | @storage = new Storage() 42 | @storage.attach(@) 43 | @storage.requestData() 44 | 45 | # user interface - panels 46 | 47 | @toolbar = new Toolbar('#toolbar') 48 | @toolbar.attach(@) 49 | 50 | @menu = new Menu('#menu') 51 | @menu.attach(@) 52 | 53 | @info = new Info('#info') 54 | @info.attach(@) 55 | 56 | # user interface - visualization 57 | 58 | @projector = new Projector() 59 | @projector.attach(@) 60 | 61 | # throw new Error() 62 | 63 | 64 | # E V E N T H A N D L E R S 65 | 66 | # Derived from Observer, update is fired whenever event notifications from subjects come in. 67 | update: (subject, type, data) -> 68 | 69 | if subject instanceof Storage then @onStorageEvent(type, data) 70 | if subject instanceof Toolbar then @onToolbarEvent(type, data) 71 | if subject instanceof Menu then @onMenuEvent(type, data) 72 | if subject instanceof Projector then @onProjectorEvent(type, data) 73 | 74 | 75 | # Handle storage events. 76 | onStorageEvent : (type, data) -> 77 | 78 | switch type 79 | 80 | when Storage.EVENT_DATA_READY 81 | @info.display "Processed #{@storage.getPoints()} points." 82 | @info.display "Found #{@storage.getClusters()} clusters." 83 | @initialize() # get going! 84 | 85 | when Storage.EVENT_SCREENSHOT_OK 86 | @info.display "Screenshot #{@storage.getSaved()} saved." 87 | 88 | 89 | # Handle toolbar events. 90 | onToolbarEvent : (type, data) -> 91 | 92 | switch type 93 | 94 | when Toolbar.EVENT_MENU 95 | state = @menu.toggle() 96 | @toolbar.setMenuButtonSelected(state) 97 | 98 | when Toolbar.EVENT_INFO 99 | state = @info.toggle() 100 | @toolbar.setInfoButtonSelected(state) 101 | 102 | when Toolbar.EVENT_PERSPECTIVE 103 | @projector.setMode(Projector.VIEW.PERSPECTIVE) 104 | @toolbar.setCameraButtonSelected(true, false, false) 105 | 106 | when Toolbar.EVENT_ORTHOGRAPHIC 107 | @projector.setMode(Projector.VIEW.ORTHOGRAPHIC) 108 | @toolbar.setCameraButtonSelected(false, true, false) 109 | 110 | when Toolbar.EVENT_DUAL 111 | @projector.setMode(Projector.VIEW.DUAL) 112 | @toolbar.setCameraButtonSelected(false, false, true) 113 | 114 | when Toolbar.EVENT_RESET 115 | @projector.resetCamera(true) 116 | @toolbar.blinkResetButton() 117 | 118 | when Toolbar.EVENT_CLEAR 119 | @info.clear() 120 | @toolbar.blinkClearButton() 121 | 122 | when Toolbar.EVENT_BOX 123 | state = @projector.toggleBox() 124 | @toolbar.setBoxButtonSelected(state) 125 | 126 | when Toolbar.EVENT_VIEWPORT 127 | state = @projector.toggleViewport() 128 | @toolbar.setViewportButtonSelected(state) 129 | 130 | when Toolbar.EVENT_SELECT 131 | state = @projector.toggleSelector() 132 | @toolbar.setSelectButtonSelected(state) 133 | 134 | when Toolbar.EVENT_VIEW_TOP 135 | @projector.changeView(Utility.DIRECTION.TOP) 136 | @toolbar.setViewButtonSelected(true, false, false) 137 | 138 | when Toolbar.EVENT_VIEW_FRONT 139 | @projector.changeView(Utility.DIRECTION.FRONT) 140 | @toolbar.setViewButtonSelected(false, true, false) 141 | 142 | when Toolbar.EVENT_VIEW_SIDE 143 | @projector.changeView(Utility.DIRECTION.SIDE) 144 | @toolbar.setViewButtonSelected(false, false, true) 145 | 146 | when Toolbar.EVENT_SPIN_LEFT 147 | @projector.setSpin(Projector.SPIN.LEFT) 148 | @toolbar.setSpinButtonSelected(true, false, false) 149 | 150 | when Toolbar.EVENT_SPIN_STOP 151 | @projector.setSpin(Projector.SPIN.NONE) 152 | @toolbar.setSpinButtonSelected(false, true, false) 153 | 154 | when Toolbar.EVENT_SPIN_RIGHT 155 | @projector.setSpin(Projector.SPIN.RIGHT) 156 | @toolbar.setSpinButtonSelected(false, false, true) 157 | 158 | when Toolbar.EVENT_ANIMATE 159 | state = @projector.toggleAnimation() 160 | @toolbar.setAnimateButtonSelected(state) 161 | 162 | when Toolbar.EVENT_PRINT 163 | @storage.saveImage(@projector.getImage()) 164 | @toolbar.blinkPrintButton() 165 | 166 | 167 | # Handle menu events. 168 | onMenuEvent : (type, data) -> 169 | 170 | switch type 171 | 172 | when Menu.EVENT_TOGGLE_ALL_ON 173 | @projector.setAllClustersVisible(true) 174 | 175 | when Menu.EVENT_TOGGLE_ALL_OFF 176 | @projector.setAllClustersVisible(false) 177 | 178 | when Menu.EVENT_TOGGLE_ID 179 | @projector.toggleClusterVisibility(data.id) 180 | 181 | when Menu.EVENT_CLUSTER_ID 182 | @projector.toggleClusterSelection(data.id) 183 | 184 | 185 | # Handle projector events. 186 | onProjectorEvent : (type, data) -> 187 | 188 | console.log "DataProjector.onProjectorEvent " + type + " : " + data 189 | 190 | switch type 191 | 192 | when Projector.EVENT_DATA_LOADED 193 | console.log "DataProjector.onProjectorEvent " + type 194 | 195 | when Projector.EVENT_POINTS_SELECTED 196 | @info.display "Selected #{data.points} points." 197 | 198 | when Projector.EVENT_CLUSTER_SELECTED 199 | if data.id > -1 200 | @info.display("Cluster #{data.id} selected") 201 | else 202 | @info.display("No cluster selected") 203 | 204 | 205 | 206 | # M E T H O D S 207 | 208 | 209 | # Initialize the application with the loaded data. 210 | initialize : -> 211 | 212 | # autogenerate palette for the visualization and menus 213 | 214 | @palette = new Palette(@storage.getClusters()) 215 | @colors = @palette.getColors() 216 | 217 | # dynamically build menu for all clusters 218 | 219 | @menu.create(@storage.getClusters(), @palette.getColors()) 220 | 221 | @projector.setColors(@colors) # use generated palette 222 | @projector.load(@storage) # load data for visualization 223 | @onToolbarEvent(Toolbar.EVENT_SPIN_RIGHT) 224 | 225 | 226 | dataProjector = new DataProjector() # run! 227 | -------------------------------------------------------------------------------- /TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | */ 4 | 5 | THREE.TrackballControls = function ( object, domElement ) { 6 | 7 | var _this = this; 8 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; 9 | 10 | this.object = object; 11 | this.domElement = ( domElement !== undefined ) ? domElement : document; 12 | 13 | // API 14 | 15 | this.enabled = true; 16 | 17 | this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 }; 18 | this.radius = ( this.screen.width + this.screen.height ) / 4; 19 | 20 | this.rotateSpeed = 1.0; 21 | this.zoomSpeed = 1.2; 22 | this.panSpeed = 0.3; 23 | 24 | this.noRotate = false; 25 | this.noZoom = false; 26 | this.noPan = false; 27 | 28 | this.staticMoving = false; 29 | this.dynamicDampingFactor = 0.2; 30 | 31 | this.minDistance = 0; 32 | this.maxDistance = Infinity; 33 | 34 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 35 | 36 | // internals 37 | 38 | this.target = new THREE.Vector3(); 39 | 40 | var lastPosition = new THREE.Vector3(); 41 | 42 | var _state = STATE.NONE, 43 | _prevState = STATE.NONE, 44 | 45 | _eye = new THREE.Vector3(), 46 | 47 | _rotateStart = new THREE.Vector3(), 48 | _rotateEnd = new THREE.Vector3(), 49 | 50 | _zoomStart = new THREE.Vector2(), 51 | _zoomEnd = new THREE.Vector2(), 52 | 53 | _touchZoomDistanceStart = 0, 54 | _touchZoomDistanceEnd = 0, 55 | 56 | _panStart = new THREE.Vector2(), 57 | _panEnd = new THREE.Vector2(); 58 | 59 | // for reset 60 | 61 | this.target0 = this.target.clone(); 62 | this.position0 = this.object.position.clone(); 63 | this.up0 = this.object.up.clone(); 64 | 65 | // events 66 | 67 | var changeEvent = { type: 'change' }; 68 | 69 | 70 | // methods 71 | 72 | this.handleResize = function () { 73 | 74 | this.screen.width = window.innerWidth; 75 | this.screen.height = window.innerHeight; 76 | 77 | this.screen.offsetLeft = 0; 78 | this.screen.offsetTop = 0; 79 | 80 | this.radius = ( this.screen.width + this.screen.height ) / 4; 81 | 82 | }; 83 | 84 | this.handleEvent = function ( event ) { 85 | 86 | if ( typeof this[ event.type ] == 'function' ) { 87 | 88 | this[ event.type ]( event ); 89 | 90 | } 91 | 92 | }; 93 | 94 | this.getMouseOnScreen = function ( clientX, clientY ) { 95 | 96 | return new THREE.Vector2( 97 | ( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5, 98 | ( clientY - _this.screen.offsetTop ) / _this.radius * 0.5 99 | ); 100 | 101 | }; 102 | 103 | this.getMouseProjectionOnBall = function ( clientX, clientY ) { 104 | 105 | var mouseOnBall = new THREE.Vector3( 106 | ( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius, 107 | ( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius, 108 | 0.0 109 | ); 110 | 111 | var length = mouseOnBall.length(); 112 | 113 | if ( length > 1.0 ) { 114 | 115 | mouseOnBall.normalize(); 116 | 117 | } else { 118 | 119 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 120 | 121 | } 122 | 123 | _eye.copy( _this.object.position ).sub( _this.target ); 124 | 125 | var projection = _this.object.up.clone().setLength( mouseOnBall.y ); 126 | projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) ); 127 | projection.add( _eye.setLength( mouseOnBall.z ) ); 128 | 129 | return projection; 130 | 131 | }; 132 | 133 | this.rotateCamera = function () { 134 | 135 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); 136 | 137 | if ( angle ) { 138 | 139 | var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(), 140 | quaternion = new THREE.Quaternion(); 141 | 142 | angle *= _this.rotateSpeed; 143 | 144 | quaternion.setFromAxisAngle( axis, -angle ); 145 | 146 | _eye.applyQuaternion( quaternion ); 147 | _this.object.up.applyQuaternion( quaternion ); 148 | 149 | _rotateEnd.applyQuaternion( quaternion ); 150 | 151 | if ( _this.staticMoving ) { 152 | 153 | _rotateStart.copy( _rotateEnd ); 154 | 155 | } else { 156 | 157 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); 158 | _rotateStart.applyQuaternion( quaternion ); 159 | 160 | } 161 | 162 | } 163 | 164 | }; 165 | 166 | this.zoomCamera = function () { 167 | 168 | if ( _state === STATE.TOUCH_ZOOM ) { 169 | 170 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 171 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 172 | _eye.multiplyScalar( factor ); 173 | 174 | } else { 175 | 176 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 177 | 178 | if ( factor !== 1.0 && factor > 0.0 ) { 179 | 180 | _eye.multiplyScalar( factor ); 181 | 182 | if ( _this.staticMoving ) { 183 | 184 | _zoomStart.copy( _zoomEnd ); 185 | 186 | } else { 187 | 188 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 189 | 190 | } 191 | 192 | } 193 | 194 | } 195 | 196 | }; 197 | 198 | this.panCamera = function () { 199 | 200 | var mouseChange = _panEnd.clone().sub( _panStart ); 201 | 202 | if ( mouseChange.lengthSq() ) { 203 | 204 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 205 | 206 | var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x ); 207 | pan.add( _this.object.up.clone().setLength( mouseChange.y ) ); 208 | 209 | _this.object.position.add( pan ); 210 | _this.target.add( pan ); 211 | 212 | if ( _this.staticMoving ) { 213 | 214 | _panStart = _panEnd; 215 | 216 | } else { 217 | 218 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 219 | 220 | } 221 | 222 | } 223 | 224 | }; 225 | 226 | this.checkDistances = function () { 227 | 228 | if ( !_this.noZoom || !_this.noPan ) { 229 | 230 | if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) { 231 | 232 | _this.object.position.setLength( _this.maxDistance ); 233 | 234 | } 235 | 236 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 237 | 238 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 239 | 240 | } 241 | 242 | } 243 | 244 | }; 245 | 246 | this.update = function () { 247 | 248 | _eye.subVectors( _this.object.position, _this.target ); 249 | 250 | if ( !_this.noRotate ) { 251 | 252 | _this.rotateCamera(); 253 | 254 | } 255 | 256 | if ( !_this.noZoom ) { 257 | 258 | _this.zoomCamera(); 259 | 260 | } 261 | 262 | if ( !_this.noPan ) { 263 | 264 | _this.panCamera(); 265 | 266 | } 267 | 268 | _this.object.position.addVectors( _this.target, _eye ); 269 | 270 | _this.checkDistances(); 271 | 272 | _this.object.lookAt( _this.target ); 273 | 274 | if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) { 275 | 276 | _this.dispatchEvent( changeEvent ); 277 | 278 | lastPosition.copy( _this.object.position ); 279 | 280 | } 281 | 282 | }; 283 | 284 | this.reset = function () { 285 | 286 | _state = STATE.NONE; 287 | _prevState = STATE.NONE; 288 | 289 | _this.target.copy( _this.target0 ); 290 | _this.object.position.copy( _this.position0 ); 291 | _this.object.up.copy( _this.up0 ); 292 | 293 | _eye.subVectors( _this.object.position, _this.target ); 294 | 295 | _this.object.lookAt( _this.target ); 296 | 297 | _this.dispatchEvent( changeEvent ); 298 | 299 | lastPosition.copy( _this.object.position ); 300 | 301 | }; 302 | 303 | // listeners 304 | 305 | function keydown( event ) { 306 | 307 | if ( _this.enabled === false ) return; 308 | 309 | window.removeEventListener( 'keydown', keydown ); 310 | 311 | _prevState = _state; 312 | 313 | if ( _state !== STATE.NONE ) { 314 | 315 | return; 316 | 317 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { 318 | 319 | _state = STATE.ROTATE; 320 | 321 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { 322 | 323 | _state = STATE.ZOOM; 324 | 325 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { 326 | 327 | _state = STATE.PAN; 328 | 329 | } 330 | 331 | } 332 | 333 | function keyup( event ) { 334 | 335 | if ( _this.enabled === false ) return; 336 | 337 | _state = _prevState; 338 | 339 | window.addEventListener( 'keydown', keydown, false ); 340 | 341 | } 342 | 343 | function mousedown( event ) { 344 | 345 | if ( _this.enabled === false ) return; 346 | 347 | event.preventDefault(); 348 | event.stopPropagation(); 349 | 350 | if ( _state === STATE.NONE ) { 351 | 352 | _state = event.button; 353 | 354 | } 355 | 356 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 357 | 358 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); 359 | 360 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 361 | 362 | _zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 363 | 364 | } else if ( _state === STATE.PAN && !_this.noPan ) { 365 | 366 | _panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 367 | 368 | } 369 | 370 | document.addEventListener( 'mousemove', mousemove, false ); 371 | document.addEventListener( 'mouseup', mouseup, false ); 372 | 373 | } 374 | 375 | function mousemove( event ) { 376 | 377 | if ( _this.enabled === false ) return; 378 | 379 | event.preventDefault(); 380 | event.stopPropagation(); 381 | 382 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 383 | 384 | _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); 385 | 386 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 387 | 388 | _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 389 | 390 | } else if ( _state === STATE.PAN && !_this.noPan ) { 391 | 392 | _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 393 | 394 | } 395 | 396 | } 397 | 398 | function mouseup( event ) { 399 | 400 | if ( _this.enabled === false ) return; 401 | 402 | event.preventDefault(); 403 | event.stopPropagation(); 404 | 405 | _state = STATE.NONE; 406 | 407 | document.removeEventListener( 'mousemove', mousemove ); 408 | document.removeEventListener( 'mouseup', mouseup ); 409 | 410 | } 411 | 412 | function mousewheel( event ) { 413 | 414 | if ( _this.enabled === false ) return; 415 | 416 | event.preventDefault(); 417 | event.stopPropagation(); 418 | 419 | var delta = 0; 420 | 421 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 422 | 423 | delta = event.wheelDelta / 40; 424 | 425 | } else if ( event.detail ) { // Firefox 426 | 427 | delta = - event.detail / 3; 428 | 429 | } 430 | 431 | _zoomStart.y += delta * 0.01; 432 | 433 | } 434 | 435 | function touchstart( event ) { 436 | 437 | if ( _this.enabled === false ) return; 438 | 439 | switch ( event.touches.length ) { 440 | 441 | case 1: 442 | _state = STATE.TOUCH_ROTATE; 443 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 444 | break; 445 | 446 | case 2: 447 | _state = STATE.TOUCH_ZOOM; 448 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 449 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 450 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 451 | break; 452 | 453 | case 3: 454 | _state = STATE.TOUCH_PAN; 455 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 456 | break; 457 | 458 | default: 459 | _state = STATE.NONE; 460 | 461 | } 462 | 463 | } 464 | 465 | function touchmove( event ) { 466 | 467 | if ( _this.enabled === false ) return; 468 | 469 | event.preventDefault(); 470 | event.stopPropagation(); 471 | 472 | switch ( event.touches.length ) { 473 | 474 | case 1: 475 | _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 476 | break; 477 | 478 | case 2: 479 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 480 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 481 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ) 482 | break; 483 | 484 | case 3: 485 | _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 486 | break; 487 | 488 | default: 489 | _state = STATE.NONE; 490 | 491 | } 492 | 493 | } 494 | 495 | function touchend( event ) { 496 | 497 | if ( _this.enabled === false ) return; 498 | 499 | switch ( event.touches.length ) { 500 | 501 | case 1: 502 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 503 | break; 504 | 505 | case 2: 506 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; 507 | break; 508 | 509 | case 3: 510 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 511 | break; 512 | 513 | } 514 | 515 | _state = STATE.NONE; 516 | 517 | } 518 | 519 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 520 | 521 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 522 | 523 | this.domElement.addEventListener( 'mousewheel', mousewheel, false ); 524 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox 525 | 526 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 527 | this.domElement.addEventListener( 'touchend', touchend, false ); 528 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 529 | 530 | window.addEventListener( 'keydown', keydown, false ); 531 | window.addEventListener( 'keyup', keyup, false ); 532 | 533 | this.handleResize(); 534 | 535 | }; 536 | 537 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 538 | -------------------------------------------------------------------------------- /src/Selector.coffee: -------------------------------------------------------------------------------- 1 | # Selector.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Rubber band style selector machanism that works in 3D. 6 | # The 3D selector is actually composed from three 2D selectors on each side of the cube. 7 | 8 | Utility = require('./Utility.coffee') 9 | Palette = require('./Palette.coffee') 10 | 11 | class Selector 12 | 13 | # M E M B E R S 14 | 15 | active : false # visible and active when true 16 | 17 | direction : Utility.DIRECTION.TOP # default 2D view is from top 18 | 19 | selectorTop : null # THREE.Line - top (along y axis) view selector 20 | selectorFront : null # THREE.Line - front (along z axis) view selector 21 | selectorSide : null # THREE.Line - top (along x axis) view selector 22 | 23 | mouseStart : null # mouse touch down 24 | mouse : null # mouse moving updates 25 | mouseEnd : null # mouse take off 26 | 27 | min : null # 3D selection bounds - minimum 28 | max : null # 3D selection bounds - maximum 29 | 30 | 31 | # C O N S T R U C T O R 32 | 33 | # Create selector and add it to the parent in 3D world. 34 | constructor : (parent) -> 35 | 36 | @mouseStart = new THREE.Vector3() 37 | @mouse = new THREE.Vector3() 38 | @mouseEnd = new THREE.Vector3() 39 | 40 | @min = new THREE.Vector3() 41 | @max = new THREE.Vector3() 42 | 43 | # top view 44 | @selectorTop = @createSelector(Utility.DIRECTION.TOP) 45 | parent.add(@selectorTop) 46 | 47 | # front view 48 | @selectorFront = @createSelector(Utility.DIRECTION.FRONT) 49 | parent.add(@selectorFront) 50 | 51 | # side view 52 | @selectorSide = @createSelector(Utility.DIRECTION.SIDE) 53 | parent.add(@selectorSide) 54 | 55 | @setActive(false) 56 | 57 | 58 | # M E T H D O S 59 | 60 | # Set selector active/visible on/off. 61 | # All side components work together. 62 | setActive : (@active) -> 63 | 64 | @selectorTop.visible = @active 65 | @selectorFront.visible = @active 66 | @selectorSide.visible = @active 67 | 68 | return @active 69 | 70 | 71 | # Set direction. 72 | setDirection : (@direction) -> 73 | 74 | console.log "Selector.setDirection " + @direction 75 | 76 | 77 | # Check if currently active/visible. 78 | isActive : => return @active 79 | 80 | 81 | # Toggle selector on/off 82 | toggle : => return @setActive(not @active) 83 | 84 | 85 | # Called at the start of the selection. 86 | start : (@mouse) -> 87 | 88 | @setActive(true) # automatically enable 89 | 90 | # two options: start new selection or adjust existing... 91 | 92 | if not @contains(mouse, @direction) 93 | 94 | # mouse outside the selector - restart anew 95 | @mouseStart = mouse 96 | 97 | else 98 | 99 | # mouse inside the selector - make adjustment 100 | 101 | # determine which corner is closest to the mouse 102 | 103 | switch @direction 104 | when Utility.DIRECTION.TOP 105 | @mouseStart = @getStart(mouse, @selectorTop) 106 | when Utility.DIRECTION.FRONT 107 | @mouseStart = @getStart(mouse, @selectorFront) 108 | when Utility.DIRECTION.SIDE 109 | @mouseStart = @getStart(mouse, @selectorSide) 110 | 111 | 112 | getStart : (mouse, selector) -> 113 | 114 | # determine which corner is closest to the mouse 115 | 116 | # TODO Set up array + loop... 117 | distanceTo0 = mouse.distanceTo(selector.geometry.vertices[0]) 118 | distanceTo1 = mouse.distanceTo(selector.geometry.vertices[1]) 119 | distanceTo2 = mouse.distanceTo(selector.geometry.vertices[2]) 120 | distanceTo3 = mouse.distanceTo(selector.geometry.vertices[3]) 121 | 122 | shortest = Math.min(distanceTo0, distanceTo1, distanceTo2, distanceTo3) 123 | 124 | # make the closest corner the end point and the opposite one the start point 125 | 126 | if shortest is distanceTo0 then start = selector.geometry.vertices[2].clone() 127 | if shortest is distanceTo1 then start = selector.geometry.vertices[3].clone() 128 | if shortest is distanceTo2 then start = selector.geometry.vertices[0].clone() 129 | if shortest is distanceTo3 then start = selector.geometry.vertices[1].clone() 130 | 131 | return start 132 | 133 | 134 | # Called when selection in progress to update mouse position. 135 | update : (@mouse) -> 136 | 137 | switch @direction 138 | when Utility.DIRECTION.TOP 139 | 140 | # Modifying : Top 141 | 142 | @selectorTop.geometry.vertices[0].x = @mouseStart.x 143 | @selectorTop.geometry.vertices[0].y = 100 144 | @selectorTop.geometry.vertices[0].z = @mouseStart.z 145 | 146 | @selectorTop.geometry.vertices[1].x = @mouse.x 147 | @selectorTop.geometry.vertices[1].y = 100 148 | @selectorTop.geometry.vertices[1].z = @mouseStart.z 149 | 150 | @selectorTop.geometry.vertices[2].x = @mouse.x 151 | @selectorTop.geometry.vertices[2].y = 100 152 | @selectorTop.geometry.vertices[2].z = @mouse.z 153 | 154 | @selectorTop.geometry.vertices[3].x = @mouseStart.x 155 | @selectorTop.geometry.vertices[3].y = 100 156 | @selectorTop.geometry.vertices[3].z = @mouse.z 157 | 158 | @selectorTop.geometry.vertices[4].x = @mouseStart.x 159 | @selectorTop.geometry.vertices[4].y = 100 160 | @selectorTop.geometry.vertices[4].z = @mouseStart.z 161 | 162 | # Adjusting : Front 163 | 164 | @selectorFront.geometry.vertices[0].x = @mouseStart.x 165 | @selectorFront.geometry.vertices[0].z = 100 166 | 167 | @selectorFront.geometry.vertices[1].x = @mouse.x 168 | @selectorFront.geometry.vertices[1].z = 100 169 | 170 | @selectorFront.geometry.vertices[2].x = @mouse.x 171 | @selectorFront.geometry.vertices[2].z = 100 172 | 173 | @selectorFront.geometry.vertices[3].x = @mouseStart.x 174 | @selectorFront.geometry.vertices[3].z = 100 175 | 176 | @selectorFront.geometry.vertices[4].x = @mouseStart.x 177 | @selectorFront.geometry.vertices[4].z = 100 178 | 179 | # Adjusting : Side 180 | 181 | @selectorSide.geometry.vertices[0].x = 100 182 | @selectorSide.geometry.vertices[0].z = @mouseStart.z 183 | 184 | @selectorSide.geometry.vertices[1].x = 100 185 | @selectorSide.geometry.vertices[1].z = @mouseStart.z 186 | 187 | @selectorSide.geometry.vertices[2].x = 100 188 | @selectorSide.geometry.vertices[2].z = @mouse.z 189 | 190 | @selectorSide.geometry.vertices[3].x = 100 191 | @selectorSide.geometry.vertices[3].z = @mouse.z 192 | 193 | @selectorSide.geometry.vertices[4].x = 100 194 | @selectorSide.geometry.vertices[4].z = @mouseStart.z 195 | 196 | when Utility.DIRECTION.FRONT 197 | 198 | # Modifying : FRONT 199 | 200 | @selectorFront.geometry.vertices[0].x = @mouseStart.x 201 | @selectorFront.geometry.vertices[0].y = @mouseStart.y 202 | @selectorFront.geometry.vertices[0].z = 100 203 | 204 | @selectorFront.geometry.vertices[1].x = @mouse.x 205 | @selectorFront.geometry.vertices[1].y = @mouseStart.y 206 | @selectorFront.geometry.vertices[1].z = 100 207 | 208 | @selectorFront.geometry.vertices[2].x = @mouse.x 209 | @selectorFront.geometry.vertices[2].y = @mouse.y 210 | @selectorFront.geometry.vertices[2].z = 100 211 | 212 | @selectorFront.geometry.vertices[3].x = @mouseStart.x 213 | @selectorFront.geometry.vertices[3].y = @mouse.y 214 | @selectorFront.geometry.vertices[3].z = 100 215 | 216 | @selectorFront.geometry.vertices[4].x = @mouseStart.x 217 | @selectorFront.geometry.vertices[4].y = @mouseStart.y 218 | @selectorFront.geometry.vertices[4].z = 100 219 | 220 | # Adjusting : TOP 221 | 222 | @selectorTop.geometry.vertices[0].x = @mouseStart.x 223 | @selectorTop.geometry.vertices[0].y = 100 224 | 225 | @selectorTop.geometry.vertices[1].x = @mouse.x 226 | @selectorTop.geometry.vertices[1].y = 100 227 | 228 | @selectorTop.geometry.vertices[2].x = @mouse.x 229 | @selectorTop.geometry.vertices[2].y = 100 230 | 231 | @selectorTop.geometry.vertices[3].x = @mouseStart.x 232 | @selectorTop.geometry.vertices[3].y = 100 233 | 234 | @selectorTop.geometry.vertices[4].x = @mouseStart.x 235 | @selectorTop.geometry.vertices[4].y = 100 236 | 237 | # Adjusting : SIDE 238 | 239 | @selectorSide.geometry.vertices[0].x = 100 240 | @selectorSide.geometry.vertices[0].y = @mouseStart.y 241 | 242 | @selectorSide.geometry.vertices[1].x = 100 243 | @selectorSide.geometry.vertices[1].y = @mouse.y 244 | 245 | @selectorSide.geometry.vertices[2].x = 100 246 | @selectorSide.geometry.vertices[2].y = @mouse.y 247 | 248 | @selectorSide.geometry.vertices[3].x = 100 249 | @selectorSide.geometry.vertices[3].y = @mouseStart.y 250 | 251 | @selectorSide.geometry.vertices[4].x = 100 252 | @selectorSide.geometry.vertices[4].y = @mouseStart.y 253 | 254 | 255 | when Utility.DIRECTION.SIDE 256 | 257 | # Modifying : SIDE 258 | 259 | @selectorSide.geometry.vertices[0].x = 100 260 | @selectorSide.geometry.vertices[0].y = @mouseStart.y 261 | @selectorSide.geometry.vertices[0].z = @mouseStart.z 262 | 263 | @selectorSide.geometry.vertices[1].x = 100 264 | @selectorSide.geometry.vertices[1].y = @mouse.y 265 | @selectorSide.geometry.vertices[1].z = @mouseStart.z 266 | 267 | @selectorSide.geometry.vertices[2].x = 100 268 | @selectorSide.geometry.vertices[2].y = @mouse.y 269 | @selectorSide.geometry.vertices[2].z = @mouse.z 270 | 271 | @selectorSide.geometry.vertices[3].x = 100 272 | @selectorSide.geometry.vertices[3].y = @mouseStart.y 273 | @selectorSide.geometry.vertices[3].z = @mouse.z 274 | 275 | @selectorSide.geometry.vertices[4].x = 100 276 | @selectorSide.geometry.vertices[4].y = @mouseStart.y 277 | @selectorSide.geometry.vertices[4].z = @mouseStart.z 278 | 279 | # Adjusting : TOP 280 | 281 | @selectorTop.geometry.vertices[0].y = 100 282 | @selectorTop.geometry.vertices[0].z = @mouseStart.z 283 | 284 | @selectorTop.geometry.vertices[1].y = 100 285 | @selectorTop.geometry.vertices[1].z = @mouseStart.z 286 | 287 | @selectorTop.geometry.vertices[2].y = 100 288 | @selectorTop.geometry.vertices[2].z = @mouse.z 289 | 290 | @selectorTop.geometry.vertices[3].y = 100 291 | @selectorTop.geometry.vertices[3].z = @mouse.z 292 | 293 | @selectorTop.geometry.vertices[4].y = 100 294 | @selectorTop.geometry.vertices[4].z = @mouseStart.z 295 | 296 | # Adjusting : FRONT 297 | 298 | @selectorFront.geometry.vertices[0].y = @mouseStart.y 299 | @selectorFront.geometry.vertices[0].z = 100 300 | 301 | @selectorFront.geometry.vertices[1].y = @mouseStart.y 302 | @selectorFront.geometry.vertices[1].z = 100 303 | 304 | @selectorFront.geometry.vertices[2].y = @mouse.y 305 | @selectorFront.geometry.vertices[2].z = 100 306 | 307 | @selectorFront.geometry.vertices[3].y = @mouse.y 308 | @selectorFront.geometry.vertices[3].z = 100 309 | 310 | @selectorFront.geometry.vertices[4].y = @mouseStart.y 311 | @selectorFront.geometry.vertices[4].z = 100 312 | 313 | 314 | @selectorTop.geometry.verticesNeedUpdate = true 315 | @selectorFront.geometry.verticesNeedUpdate = true 316 | @selectorSide.geometry.verticesNeedUpdate = true 317 | 318 | 319 | # Called at the end of the selection. 320 | end : (mouseEnd) -> 321 | 322 | @mouseEnd = mouseEnd 323 | @updateBounds() 324 | 325 | 326 | updateBounds : -> 327 | 328 | @min.x = Math.min( @getMinX(@selectorTop), @getMinX(@selectorFront) ) 329 | @max.x = Math.max( @getMaxX(@selectorTop), @getMaxX(@selectorFront) ) 330 | 331 | @min.y = Math.min( @getMinY(@selectorFront), @getMinY(@selectorSide) ) 332 | @max.y = Math.max( @getMaxY(@selectorFront), @getMaxY(@selectorSide) ) 333 | 334 | @min.z = Math.min( @getMinZ(@selectorTop), @getMinZ(@selectorSide) ) 335 | @max.z = Math.max( @getMaxZ(@selectorTop), @getMaxZ(@selectorSide) ) 336 | 337 | # DEBUG 338 | # Utility.printVector3(@min) 339 | # Utility.printVector3(@max) 340 | 341 | 342 | # Return true if given point is within the selector, false otherwise. 343 | # NOTE For each individual direction only two coordinates are checked. 344 | # NOTE In case of direction ALL, all three coordinates are tested. 345 | contains : (point, direction) -> 346 | 347 | inside = true 348 | 349 | switch direction 350 | when Utility.DIRECTION.ALL 351 | if point.x < @min.x or point.x > @max.x then inside = false 352 | if point.y < @min.y or point.y > @max.y then inside = false 353 | if point.z < @min.z or point.z > @max.z then inside = false 354 | when Utility.DIRECTION.TOP 355 | if point.x < @min.x or point.x > @max.x then inside = false 356 | if point.z < @min.z or point.z > @max.z then inside = false 357 | when Utility.DIRECTION.FRONT 358 | if point.x < @min.x or point.x > @max.x then inside = false 359 | if point.y < @min.y or point.y > @max.y then inside = false 360 | when Utility.DIRECTION.SIDE 361 | if point.z < @min.z or point.z > @max.z then inside = false 362 | if point.y < @min.y or point.y > @max.y then inside = false 363 | 364 | return inside 365 | 366 | 367 | getMinX : (selector) -> 368 | 369 | vertices = selector.geometry.vertices 370 | minX = vertices[0].x 371 | 372 | for i in [1..4] 373 | if vertices[i].x < minX then minX = vertices[i].x 374 | 375 | return minX 376 | 377 | 378 | getMaxX : (selector) -> 379 | 380 | vertices = selector.geometry.vertices 381 | maxX = vertices[0].x 382 | 383 | for i in [1..4] 384 | if vertices[i].x > maxX then maxX = vertices[i].x 385 | 386 | return maxX 387 | 388 | 389 | getMinY : (selector) -> 390 | 391 | vertices = selector.geometry.vertices 392 | minY = vertices[0].y 393 | 394 | for i in [1..4] 395 | if vertices[i].y < minY then minY = vertices[i].y 396 | 397 | return minY 398 | 399 | 400 | getMaxY : (selector) -> 401 | 402 | vertices = selector.geometry.vertices 403 | maxY = vertices[0].y 404 | 405 | for i in [1..4] 406 | if vertices[i].y > maxY then maxY = vertices[i].y 407 | 408 | return maxY 409 | 410 | 411 | getMinZ : (selector) -> 412 | 413 | vertices = selector.geometry.vertices 414 | minZ = vertices[0].z 415 | 416 | for i in [1..4] 417 | if vertices[i].z < minZ then minZ = vertices[i].z 418 | 419 | return minZ 420 | 421 | 422 | getMaxZ : (selector) -> 423 | 424 | vertices = selector.geometry.vertices 425 | maxZ = vertices[0].z 426 | 427 | for i in [1..4] 428 | if vertices[i].z > maxZ then maxZ = vertices[i].z 429 | 430 | return maxZ 431 | 432 | 433 | # Create selector rectangle line for given direction. 434 | createSelector : (direction) -> 435 | 436 | SIZE = 100 437 | 438 | geometry = new THREE.Geometry() 439 | 440 | # five points in each case, last one is the first one 441 | 442 | switch direction 443 | when Utility.DIRECTION.TOP 444 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, +SIZE ) ) 445 | geometry.vertices.push( new THREE.Vector3( -SIZE, +SIZE, +SIZE ) ) 446 | geometry.vertices.push( new THREE.Vector3( -SIZE, +SIZE, -SIZE ) ) 447 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, -SIZE ) ) 448 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, +SIZE ) ) 449 | when Utility.DIRECTION.FRONT 450 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, +SIZE ) ) 451 | geometry.vertices.push( new THREE.Vector3( -SIZE, +SIZE, +SIZE ) ) 452 | geometry.vertices.push( new THREE.Vector3( -SIZE, -SIZE, +SIZE ) ) 453 | geometry.vertices.push( new THREE.Vector3( +SIZE, -SIZE, +SIZE ) ) 454 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, +SIZE ) ) 455 | when Utility.DIRECTION.SIDE 456 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, +SIZE ) ) 457 | geometry.vertices.push( new THREE.Vector3( +SIZE, -SIZE, +SIZE ) ) 458 | geometry.vertices.push( new THREE.Vector3( +SIZE, -SIZE, -SIZE ) ) 459 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, -SIZE ) ) 460 | geometry.vertices.push( new THREE.Vector3( +SIZE, +SIZE, +SIZE ) ) 461 | 462 | selector = new THREE.Line(geometry, 463 | new THREE.LineBasicMaterial( { color : Palette.SELECTOR.getHex() } ), 464 | THREE.LineStrip) 465 | 466 | module.exports = Selector 467 | -------------------------------------------------------------------------------- /TweenLite.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: beta 1.10.1 3 | * DATE: 2013-07-10 4 | * UPDATES AND DOCS AT: http://www.greensock.com 5 | * 6 | * @license Copyright (c) 2008-2013, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://www.greensock.com/terms_of_use.html or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | (function(t){"use strict";var e,i,s,n,r,a=t.GreenSockGlobals||t,o=function(t){var e,i=t.split("."),s=a;for(e=0;i.length>e;e++)s[i[e]]=s=s[i[e]]||{};return s},h=o("com.greensock"),l=[].slice,_=function(){},u={},m=function(e,i,s,n){this.sc=u[e]?u[e].sc:[],u[e]=this,this.gsClass=null,this.func=s;var r=[];this.check=function(h){for(var l,_,f,p,c=i.length,d=c;--c>-1;)(l=u[i[c]]||new m(i[c],[])).gsClass?(r[c]=l.gsClass,d--):h&&l.sc.push(this);if(0===d&&s)for(_=("com.greensock."+e).split("."),f=_.pop(),p=o(_.join("."))[f]=this.gsClass=s.apply(s,r),n&&(a[f]=p,"function"==typeof define&&define.amd?define((t.GreenSockAMDPath?t.GreenSockAMDPath+"/":"")+e.split(".").join("/"),[],function(){return p}):"undefined"!=typeof module&&module.exports&&(module.exports=p)),c=0;this.sc.length>c;c++)this.sc[c].check()},this.check(!0)},f=t._gsDefine=function(t,e,i,s){return new m(t,e,i,s)},p=h._class=function(t,e,i){return e=e||function(){},f(t,[],function(){return e},i),e};f.globals=a;var c=[0,0,1,1],d=[],v=p("easing.Ease",function(t,e,i,s){this._func=t,this._type=i||0,this._power=s||0,this._params=e?c.concat(e):c},!0),g=v.map={},T=v.register=function(t,e,i,s){for(var n,r,a,o,l=e.split(","),_=l.length,u=(i||"easeIn,easeOut,easeInOut").split(",");--_>-1;)for(r=l[_],n=s?p("easing."+r,null,!0):h.easing[r]||{},a=u.length;--a>-1;)o=u[a],g[r+"."+o]=g[o+r]=n[o]=t.getRatio?t:t[o]||new t};for(s=v.prototype,s._calcEnd=!1,s.getRatio=function(t){if(this._func)return this._params[0]=t,this._func.apply(null,this._params);var e=this._type,i=this._power,s=1===e?1-t:2===e?t:.5>t?2*t:2*(1-t);return 1===i?s*=s:2===i?s*=s*s:3===i?s*=s*s*s:4===i&&(s*=s*s*s*s),1===e?1-s:2===e?s:.5>t?s/2:1-s/2},e=["Linear","Quad","Cubic","Quart","Quint,Strong"],i=e.length;--i>-1;)s=e[i]+",Power"+i,T(new v(null,null,1,i),s,"easeOut",!0),T(new v(null,null,2,i),s,"easeIn"+(0===i?",easeNone":"")),T(new v(null,null,3,i),s,"easeInOut");g.linear=h.easing.Linear.easeIn,g.swing=h.easing.Quad.easeInOut;var w=p("events.EventDispatcher",function(t){this._listeners={},this._eventTarget=t||this});s=w.prototype,s.addEventListener=function(t,e,i,s,a){a=a||0;var o,h,l=this._listeners[t],_=0;for(null==l&&(this._listeners[t]=l=[]),h=l.length;--h>-1;)o=l[h],o.c===e&&o.s===i?l.splice(h,1):0===_&&a>o.pr&&(_=h+1);l.splice(_,0,{c:e,s:i,up:s,pr:a}),this!==n||r||n.wake()},s.removeEventListener=function(t,e){var i,s=this._listeners[t];if(s)for(i=s.length;--i>-1;)if(s[i].c===e)return s.splice(i,1),void 0},s.dispatchEvent=function(t){var e,i,s,n=this._listeners[t];if(n)for(e=n.length,i=this._eventTarget;--e>-1;)s=n[e],s.up?s.c.call(s.s||i,{type:t,target:i}):s.c.call(s.s||i)};var P=t.requestAnimationFrame,y=t.cancelAnimationFrame,k=Date.now||function(){return(new Date).getTime()};for(e=["ms","moz","webkit","o"],i=e.length;--i>-1&&!P;)P=t[e[i]+"RequestAnimationFrame"],y=t[e[i]+"CancelAnimationFrame"]||t[e[i]+"CancelRequestAnimationFrame"];p("Ticker",function(t,e){var i,s,a,o,h,l=this,u=k(),m=e!==!1&&P,f=function(t){l.time=(k()-u)/1e3;var e=a,n=l.time-h;(!i||n>0||t===!0)&&(l.frame++,h+=n+(n>=o?.004:o-n),l.dispatchEvent("tick")),t!==!0&&e===a&&(a=s(f))};w.call(l),this.time=this.frame=0,this.tick=function(){f(!0)},this.sleep=function(){null!=a&&(m&&y?y(a):clearTimeout(a),s=_,a=null,l===n&&(r=!1))},this.wake=function(){null!==a&&l.sleep(),s=0===i?_:m&&P?P:function(t){return setTimeout(t,0|1e3*(h-l.time)+1)},l===n&&(r=!0),f(2)},this.fps=function(t){return arguments.length?(i=t,o=1/(i||60),h=this.time+o,l.wake(),void 0):i},this.useRAF=function(t){return arguments.length?(l.sleep(),m=t,l.fps(i),void 0):m},l.fps(t),setTimeout(function(){m&&(!a||5>l.frame)&&l.useRAF(!1)},1500)}),s=h.Ticker.prototype=new h.events.EventDispatcher,s.constructor=h.Ticker;var b=p("core.Animation",function(t,e){if(this.vars=e||{},this._duration=this._totalDuration=t||0,this._delay=Number(this.vars.delay)||0,this._timeScale=1,this._active=this.vars.immediateRender===!0,this.data=this.vars.data,this._reversed=this.vars.reversed===!0,L){r||n.wake();var i=this.vars.useFrames?U:L;i.add(this,i._time),this.vars.paused&&this.paused(!0)}});n=b.ticker=new h.Ticker,s=b.prototype,s._dirty=s._gc=s._initted=s._paused=!1,s._totalTime=s._time=0,s._rawPrevTime=-1,s._next=s._last=s._onUpdate=s._timeline=s.timeline=null,s._paused=!1,s.play=function(t,e){return arguments.length&&this.seek(t,e),this.reversed(!1).paused(!1)},s.pause=function(t,e){return arguments.length&&this.seek(t,e),this.paused(!0)},s.resume=function(t,e){return arguments.length&&this.seek(t,e),this.paused(!1)},s.seek=function(t,e){return this.totalTime(Number(t),e!==!1)},s.restart=function(t,e){return this.reversed(!1).paused(!1).totalTime(t?-this._delay:0,e!==!1,!0)},s.reverse=function(t,e){return arguments.length&&this.seek(t||this.totalDuration(),e),this.reversed(!0).paused(!1)},s.render=function(){},s.invalidate=function(){return this},s._enabled=function(t,e){return r||n.wake(),this._gc=!t,this._active=t&&!this._paused&&this._totalTime>0&&this._totalTime-1;)"{self}"===t[e]&&(i[e]=this);return i},s.eventCallback=function(t,e,i,s){if("on"===(t||"").substr(0,2)){var n=this.vars;if(1===arguments.length)return n[t];null==e?delete n[t]:(n[t]=e,n[t+"Params"]=i instanceof Array&&-1!==i.join("").indexOf("{self}")?this._swapSelfInParams(i):i,n[t+"Scope"]=s),"onUpdate"===t&&(this._onUpdate=e)}return this},s.delay=function(t){return arguments.length?(this._timeline.smoothChildTiming&&this.startTime(this._startTime+t-this._delay),this._delay=t,this):this._delay},s.duration=function(t){return arguments.length?(this._duration=this._totalDuration=t,this._uncache(!0),this._timeline.smoothChildTiming&&this._time>0&&this._timethis._duration?this._duration:t,e)):this._time},s.totalTime=function(t,e,i){if(r||n.wake(),!arguments.length)return this._totalTime;if(this._timeline){if(0>t&&!i&&(t+=this.totalDuration()),this._timeline.smoothChildTiming){this._dirty&&this.totalDuration();var s=this._totalDuration,a=this._timeline;if(t>s&&!i&&(t=s),this._startTime=(this._paused?this._pauseTime:a._time)-(this._reversed?s-t:t)/this._timeScale,a._dirty||this._uncache(!1),a._timeline)for(;a._timeline;)a._timeline._time!==(a._startTime+a._totalTime)/a._timeScale&&a.totalTime(a._totalTime,!0),a=a._timeline}this._gc&&this._enabled(!0,!1),this._totalTime!==t&&this.render(t,e,!1)}return this},s.startTime=function(t){return arguments.length?(t!==this._startTime&&(this._startTime=t,this.timeline&&this.timeline._sortChildren&&this.timeline.add(this,t-this._delay)),this):this._startTime},s.timeScale=function(t){if(!arguments.length)return this._timeScale;if(t=t||1e-6,this._timeline&&this._timeline.smoothChildTiming){var e=this._pauseTime,i=e||0===e?e:this._timeline.totalTime();this._startTime=i-(i-this._startTime)*this._timeScale/t}return this._timeScale=t,this._uncache(!1)},s.reversed=function(t){return arguments.length?(t!=this._reversed&&(this._reversed=t,this.totalTime(this._totalTime,!0)),this):this._reversed},s.paused=function(t){if(!arguments.length)return this._paused;if(t!=this._paused&&this._timeline){r||t||n.wake();var e=this._timeline,i=e.rawTime(),s=i-this._pauseTime;!t&&e.smoothChildTiming&&(this._startTime+=s,this._uncache(!1)),this._pauseTime=t?i:null,this._paused=t,this._active=!t&&this._totalTime>0&&this._totalTimes;)i=i._prev;return i?(t._next=i._next,i._next=t):(t._next=this._first,this._first=t),t._next?t._next._prev=t:this._last=t,t._prev=i,this._timeline&&this._uncache(!0),this},s._remove=function(t,e){return t.timeline===this&&(e||t._enabled(!1,!0),t.timeline=null,t._prev?t._prev._next=t._next:this._first===t&&(this._first=t._next),t._next?t._next._prev=t._prev:this._last===t&&(this._last=t._prev),this._timeline&&this._uncache(!0)),this},s.render=function(t,e,i){var s,n=this._first;for(this._totalTime=this._time=this._rawPrevTime=t;n;)s=n._next,(n._active||t>=n._startTime&&!n._paused)&&(n._reversed?n.render((n._dirty?n.totalDuration():n._totalDuration)-(t-n._startTime)*n._timeScale,e,i):n.render((t-n._startTime)*n._timeScale,e,i)),n=s},s.rawTime=function(){return r||n.wake(),this._totalTime};var A=p("TweenLite",function(e,i,s){if(b.call(this,i,s),null==e)throw"Cannot tween a null target.";this.target=e="string"!=typeof e?e:A.selector(e)||e;var n,r,a,o=e.jquery||e.length&&e!==t&&e[0]&&(e[0]===t||e[0].nodeType&&e[0].style&&!e.nodeType),h=this.vars.overwrite;if(this._overwrite=h=null==h?N[A.defaultOverwrite]:"number"==typeof h?h>>0:N[h],(o||e instanceof Array)&&"number"!=typeof e[0])for(this._targets=a=l.call(e,0),this._propLookup=[],this._siblings=[],n=0;a.length>n;n++)r=a[n],r?"string"!=typeof r?r.length&&r!==t&&r[0]&&(r[0]===t||r[0].nodeType&&r[0].style&&!r.nodeType)?(a.splice(n--,1),this._targets=a=a.concat(l.call(r,0))):(this._siblings[n]=F(r,this,!1),1===h&&this._siblings[n].length>1&&j(r,this,null,1,this._siblings[n])):(r=a[n--]=A.selector(r),"string"==typeof r&&a.splice(n+1,1)):a.splice(n--,1);else this._propLookup={},this._siblings=F(e,this,!1),1===h&&this._siblings.length>1&&j(e,this,null,1,this._siblings);(this.vars.immediateRender||0===i&&0===this._delay&&this.vars.immediateRender!==!1)&&this.render(-this._delay,!1,!0)},!0),x=function(e){return e.length&&e!==t&&e[0]&&(e[0]===t||e[0].nodeType&&e[0].style&&!e.nodeType)},C=function(t,e){var i,s={};for(i in t)O[i]||i in e&&"x"!==i&&"y"!==i&&"width"!==i&&"height"!==i&&"className"!==i&&"border"!==i||!(!D[i]||D[i]&&D[i]._autoCSS)||(s[i]=t[i],delete t[i]);t.css=s};s=A.prototype=new b,s.constructor=A,s.kill()._gc=!1,s.ratio=0,s._firstPT=s._targets=s._overwrittenProps=s._startAt=null,s._notifyPluginsOfEnabled=!1,A.version="1.10.1",A.defaultEase=s._ease=new v(null,null,1,1),A.defaultOverwrite="auto",A.ticker=n,A.autoSleep=!0,A.selector=t.$||t.jQuery||function(e){return t.$?(A.selector=t.$,t.$(e)):t.document?t.document.getElementById("#"===e.charAt(0)?e.substr(1):e):e};var R=A._internals={},D=A._plugins={},E=A._tweenLookup={},I=0,O=R.reservedProps={ease:1,delay:1,overwrite:1,onComplete:1,onCompleteParams:1,onCompleteScope:1,useFrames:1,runBackwards:1,startAt:1,onUpdate:1,onUpdateParams:1,onUpdateScope:1,onStart:1,onStartParams:1,onStartScope:1,onReverseComplete:1,onReverseCompleteParams:1,onReverseCompleteScope:1,onRepeat:1,onRepeatParams:1,onRepeatScope:1,easeParams:1,yoyo:1,immediateRender:1,repeat:1,repeatDelay:1,data:1,paused:1,reversed:1,autoCSS:1},N={none:0,all:1,auto:2,concurrent:3,allOnStart:4,preexisting:5,"true":1,"false":0},U=b._rootFramesTimeline=new S,L=b._rootTimeline=new S;L._startTime=n.time,U._startTime=n.frame,L._active=U._active=!0,b._updateRoot=function(){if(L.render((n.time-L._startTime)*L._timeScale,!1,!1),U.render((n.frame-U._startTime)*U._timeScale,!1,!1),!(n.frame%120)){var t,e,i;for(i in E){for(e=E[i].tweens,t=e.length;--t>-1;)e[t]._gc&&e.splice(t,1);0===e.length&&delete E[i]}if(i=L._first,(!i||i._paused)&&A.autoSleep&&!U._first&&1===n._listeners.tick.length){for(;i&&i._paused;)i=i._next;i||n.sleep()}}},n.addEventListener("tick",b._updateRoot);var F=function(t,e,i){var s,n,r=t._gsTweenID;if(E[r||(t._gsTweenID=r="t"+I++)]||(E[r]={target:t,tweens:[]}),e&&(s=E[r].tweens,s[n=s.length]=e,i))for(;--n>-1;)s[n]===e&&s.splice(n,1);return E[r].tweens},j=function(t,e,i,s,n){var r,a,o,h;if(1===s||s>=4){for(h=n.length,r=0;h>r;r++)if((o=n[r])!==e)o._gc||o._enabled(!1,!1)&&(a=!0);else if(5===s)break;return a}var l,_=e._startTime+1e-10,u=[],m=0,f=0===e._duration;for(r=n.length;--r>-1;)(o=n[r])===e||o._gc||o._paused||(o._timeline!==e._timeline?(l=l||G(e,0,f),0===G(o,l,f)&&(u[m++]=o)):_>=o._startTime&&o._startTime+o.totalDuration()/o._timeScale+1e-10>_&&((f||!o._initted)&&2e-10>=_-o._startTime||(u[m++]=o)));for(r=m;--r>-1;)o=u[r],2===s&&o._kill(i,t)&&(a=!0),(2!==s||!o._firstPT&&o._initted)&&o._enabled(!1,!1)&&(a=!0);return a},G=function(t,e,i){for(var s=t._timeline,n=s._timeScale,r=t._startTime,a=1e-10;s._timeline;){if(r+=s._startTime,n*=s._timeScale,s._paused)return-100;s=s._timeline}return r/=n,r>e?r-e:i&&r===e||!t._initted&&2*a>r-e?a:(r+=t.totalDuration()/t._timeScale/n)>e+a?0:r-e-a};s._init=function(){var t,e,i,s,n=this.vars,r=this._overwrittenProps,a=this._duration,o=n.ease;if(n.startAt){if(n.startAt.overwrite=0,n.startAt.immediateRender=!0,this._startAt=A.to(this.target,0,n.startAt),n.immediateRender&&(this._startAt=null,0===this._time&&0!==a))return}else if(n.runBackwards&&n.immediateRender&&0!==a)if(this._startAt)this._startAt.render(-1,!0),this._startAt=null;else if(0===this._time){i={};for(s in n)O[s]&&"autoCSS"!==s||(i[s]=n[s]);return i.overwrite=0,this._startAt=A.to(this.target,0,i),void 0}if(this._ease=o?o instanceof v?n.easeParams instanceof Array?o.config.apply(o,n.easeParams):o:"function"==typeof o?new v(o,n.easeParams):g[o]||A.defaultEase:A.defaultEase,this._easeType=this._ease._type,this._easePower=this._ease._power,this._firstPT=null,this._targets)for(t=this._targets.length;--t>-1;)this._initProps(this._targets[t],this._propLookup[t]={},this._siblings[t],r?r[t]:null)&&(e=!0);else e=this._initProps(this.target,this._propLookup,this._siblings,r);if(e&&A._onPluginEvent("_onInitAllProps",this),r&&(this._firstPT||"function"!=typeof this.target&&this._enabled(!1,!1)),n.runBackwards)for(i=this._firstPT;i;)i.s+=i.c,i.c=-i.c,i=i._next;this._onUpdate=n.onUpdate,this._initted=!0},s._initProps=function(e,i,s,n){var r,a,o,h,l,_;if(null==e)return!1;this.vars.css||e.style&&e!==t&&e.nodeType&&D.css&&this.vars.autoCSS!==!1&&C(this.vars,e);for(r in this.vars){if(_=this.vars[r],O[r])_ instanceof Array&&-1!==_.join("").indexOf("{self}")&&(this.vars[r]=_=this._swapSelfInParams(_,this));else if(D[r]&&(h=new D[r])._onInitTween(e,this.vars[r],this)){for(this._firstPT=l={_next:this._firstPT,t:h,p:"setRatio",s:0,c:1,f:!0,n:r,pg:!0,pr:h._priority},a=h._overwriteProps.length;--a>-1;)i[h._overwriteProps[a]]=this._firstPT;(h._priority||h._onInitAllProps)&&(o=!0),(h._onDisable||h._onEnable)&&(this._notifyPluginsOfEnabled=!0)}else this._firstPT=i[r]=l={_next:this._firstPT,t:e,p:r,f:"function"==typeof e[r],n:r,pg:!1,pr:0},l.s=l.f?e[r.indexOf("set")||"function"!=typeof e["get"+r.substr(3)]?r:"get"+r.substr(3)]():parseFloat(e[r]),l.c="string"==typeof _&&"="===_.charAt(1)?parseInt(_.charAt(0)+"1",10)*Number(_.substr(2)):Number(_)-l.s||0;l&&l._next&&(l._next._prev=l)}return n&&this._kill(n,e)?this._initProps(e,i,s,n):this._overwrite>1&&this._firstPT&&s.length>1&&j(e,this,i,this._overwrite,s)?(this._kill(i,e),this._initProps(e,i,s,n)):o},s.render=function(t,e,i){var s,n,r,a=this._time;if(t>=this._duration)this._totalTime=this._time=this._duration,this.ratio=this._ease._calcEnd?this._ease.getRatio(1):1,this._reversed||(s=!0,n="onComplete"),0===this._duration&&((0===t||0>this._rawPrevTime)&&this._rawPrevTime!==t&&(i=!0,this._rawPrevTime>0&&(n="onReverseComplete",e&&(t=-1))),this._rawPrevTime=t);else if(1e-7>t)this._totalTime=this._time=0,this.ratio=this._ease._calcEnd?this._ease.getRatio(0):0,(0!==a||0===this._duration&&this._rawPrevTime>0)&&(n="onReverseComplete",s=this._reversed),0>t?(this._active=!1,0===this._duration&&(this._rawPrevTime>=0&&(i=!0),this._rawPrevTime=t)):this._initted||(i=!0);else if(this._totalTime=this._time=t,this._easeType){var o=t/this._duration,h=this._easeType,l=this._easePower;(1===h||3===h&&o>=.5)&&(o=1-o),3===h&&(o*=2),1===l?o*=o:2===l?o*=o*o:3===l?o*=o*o*o:4===l&&(o*=o*o*o*o),this.ratio=1===h?1-o:2===h?o:.5>t/this._duration?o/2:1-o/2}else this.ratio=this._ease.getRatio(t/this._duration);if(this._time!==a||i){if(!this._initted){if(this._init(),!this._initted)return;this._time&&!s?this.ratio=this._ease.getRatio(this._time/this._duration):s&&this._ease._calcEnd&&(this.ratio=this._ease.getRatio(0===this._time?0:1))}for(this._active||!this._paused&&this._time!==a&&t>=0&&(this._active=!0),0===a&&(this._startAt&&(t>=0?this._startAt.render(t,e,i):n||(n="_dummyGS")),this.vars.onStart&&(0!==this._time||0===this._duration)&&(e||this.vars.onStart.apply(this.vars.onStartScope||this,this.vars.onStartParams||d))),r=this._firstPT;r;)r.f?r.t[r.p](r.c*this.ratio+r.s):r.t[r.p]=r.c*this.ratio+r.s,r=r._next;this._onUpdate&&(0>t&&this._startAt&&this._startAt.render(t,e,i),e||this._onUpdate.apply(this.vars.onUpdateScope||this,this.vars.onUpdateParams||d)),n&&(this._gc||(0>t&&this._startAt&&!this._onUpdate&&this._startAt.render(t,e,i),s&&(this._timeline.autoRemoveChildren&&this._enabled(!1,!1),this._active=!1),!e&&this.vars[n]&&this.vars[n].apply(this.vars[n+"Scope"]||this,this.vars[n+"Params"]||d)))}},s._kill=function(t,e){if("all"===t&&(t=null),null==t&&(null==e||e===this.target))return this._enabled(!1,!1);e="string"!=typeof e?e||this._targets||this.target:A.selector(e)||e;var i,s,n,r,a,o,h,l;if((e instanceof Array||x(e))&&"number"!=typeof e[0])for(i=e.length;--i>-1;)this._kill(t,e[i])&&(o=!0);else{if(this._targets){for(i=this._targets.length;--i>-1;)if(e===this._targets[i]){a=this._propLookup[i]||{},this._overwrittenProps=this._overwrittenProps||[],s=this._overwrittenProps[i]=t?this._overwrittenProps[i]||{}:"all";break}}else{if(e!==this.target)return!1;a=this._propLookup,s=this._overwrittenProps=t?this._overwrittenProps||{}:"all"}if(a){h=t||a,l=t!==s&&"all"!==s&&t!==a&&(null==t||t._tempKill!==!0);for(n in h)(r=a[n])&&(r.pg&&r.t._kill(h)&&(o=!0),r.pg&&0!==r.t._overwriteProps.length||(r._prev?r._prev._next=r._next:r===this._firstPT&&(this._firstPT=r._next),r._next&&(r._next._prev=r._prev),r._next=r._prev=null),delete a[n]),l&&(s[n]=1);!this._firstPT&&this._initted&&this._enabled(!1,!1)}}return o},s.invalidate=function(){return this._notifyPluginsOfEnabled&&A._onPluginEvent("_onDisable",this),this._firstPT=null,this._overwrittenProps=null,this._onUpdate=null,this._startAt=null,this._initted=this._active=this._notifyPluginsOfEnabled=!1,this._propLookup=this._targets?{}:[],this},s._enabled=function(t,e){if(r||n.wake(),t&&this._gc){var i,s=this._targets;if(s)for(i=s.length;--i>-1;)this._siblings[i]=F(s[i],this,!0);else this._siblings=F(this.target,this,!0)}return b.prototype._enabled.call(this,t,e),this._notifyPluginsOfEnabled&&this._firstPT?A._onPluginEvent(t?"_onEnable":"_onDisable",this):!1},A.to=function(t,e,i){return new A(t,e,i)},A.from=function(t,e,i){return i.runBackwards=!0,i.immediateRender=0!=i.immediateRender,new A(t,e,i)},A.fromTo=function(t,e,i,s){return s.startAt=i,s.immediateRender=0!=s.immediateRender&&0!=i.immediateRender,new A(t,e,s)},A.delayedCall=function(t,e,i,s,n){return new A(e,0,{delay:t,onComplete:e,onCompleteParams:i,onCompleteScope:s,onReverseComplete:e,onReverseCompleteParams:i,onReverseCompleteScope:s,immediateRender:!1,useFrames:n,overwrite:0})},A.set=function(t,e){return new A(t,0,e)},A.killTweensOf=A.killDelayedCallsTo=function(t,e){for(var i=A.getTweensOf(t),s=i.length;--s>-1;)i[s]._kill(e,t)},A.getTweensOf=function(t){if(null==t)return[];t="string"!=typeof t?t:A.selector(t)||t;var e,i,s,n;if((t instanceof Array||x(t))&&"number"!=typeof t[0]){for(e=t.length,i=[];--e>-1;)i=i.concat(A.getTweensOf(t[e]));for(e=i.length;--e>-1;)for(n=i[e],s=e;--s>-1;)n===i[s]&&i.splice(e,1)}else for(i=F(t).concat(),e=i.length;--e>-1;)i[e]._gc&&i.splice(e,1);return i};var Q=p("plugins.TweenPlugin",function(t,e){this._overwriteProps=(t||"").split(","),this._propName=this._overwriteProps[0],this._priority=e||0,this._super=Q.prototype},!0);if(s=Q.prototype,Q.version="1.10.1",Q.API=2,s._firstPT=null,s._addTween=function(t,e,i,s,n,r){var a,o;return null!=s&&(a="number"==typeof s||"="!==s.charAt(1)?Number(s)-i:parseInt(s.charAt(0)+"1",10)*Number(s.substr(2)))?(this._firstPT=o={_next:this._firstPT,t:t,p:e,s:i,c:a,f:"function"==typeof t[e],n:n||e,r:r},o._next&&(o._next._prev=o),o):void 0},s.setRatio=function(t){for(var e,i=this._firstPT,s=1e-6;i;)e=i.c*t+i.s,i.r?e=0|e+(e>0?.5:-.5):s>e&&e>-s&&(e=0),i.f?i.t[i.p](e):i.t[i.p]=e,i=i._next},s._kill=function(t){var e,i=this._overwriteProps,s=this._firstPT;if(null!=t[this._propName])this._overwriteProps=[];else for(e=i.length;--e>-1;)null!=t[i[e]]&&i.splice(e,1);for(;s;)null!=t[s.n]&&(s._next&&(s._next._prev=s._prev),s._prev?(s._prev._next=s._next,s._prev=null):this._firstPT===s&&(this._firstPT=s._next)),s=s._next;return!1},s._roundProps=function(t,e){for(var i=this._firstPT;i;)(t[this._propName]||null!=i.n&&t[i.n.split(this._propName+"_").join("")])&&(i.r=e),i=i._next},A._onPluginEvent=function(t,e){var i,s,n,r,a,o=e._firstPT;if("_onInitAllProps"===t){for(;o;){for(a=o._next,s=n;s&&s.pr>o.pr;)s=s._next;(o._prev=s?s._prev:r)?o._prev._next=o:n=o,(o._next=s)?s._prev=o:r=o,o=a}o=e._firstPT=n}for(;o;)o.pg&&"function"==typeof o.t[t]&&o.t[t]()&&(i=!0),o=o._next;return i},Q.activate=function(t){for(var e=t.length;--e>-1;)t[e].API===Q.API&&(D[(new t[e])._propName]=t[e]);return!0},f.plugin=function(t){if(!(t&&t.propName&&t.init&&t.API))throw"illegal plugin definition.";var e,i=t.propName,s=t.priority||0,n=t.overwriteProps,r={init:"_onInitTween",set:"setRatio",kill:"_kill",round:"_roundProps",initAll:"_onInitAllProps"},a=p("plugins."+i.charAt(0).toUpperCase()+i.substr(1)+"Plugin",function(){Q.call(this,i,s),this._overwriteProps=n||[]},t.global===!0),o=a.prototype=new Q(i);o.constructor=a,a.API=t.API;for(e in r)"function"==typeof t[e]&&(o[r[e]]=t[e]);return a.version=t.version,Q.activate([a]),a},e=t._gsQueue){for(i=0;e.length>i;i++)e[i]();for(s in u)u[s].func||t.console.log("GSAP encountered missing dependency: com.greensock."+s)}r=!1})(window); -------------------------------------------------------------------------------- /src/Projector.coffee: -------------------------------------------------------------------------------- 1 | # Projector.coffee 2 | # Tomasz (Tomek) Zemla 3 | # tomek@datacratic.com 4 | 5 | # Projector class displays the data visualization. 6 | # Images are rendered in WebGL on HTML5 Canvas using Three.js library. 7 | 8 | # TODO Extend selection to work in ORTHOGRAPHIC and PERSPECTIVE, not only DUAL mode. 9 | 10 | Subject = require('./Subject.coffee') 11 | Utility = require('./Utility.coffee') 12 | Palette = require('./Palette.coffee') 13 | Selector = require('./Selector.coffee') 14 | 15 | class Projector extends Subject 16 | 17 | # E V E N T S 18 | 19 | @EVENT_DATA_LOADED : "EVENT_DATA_LOADED" 20 | @EVENT_POINTS_SELECTED : "EVENT_POINTS_SELECTED" 21 | @EVENT_CLUSTER_SELECTED : "EVENT_CLUSTER_SELECTED" 22 | 23 | # C O N S T A N T S 24 | 25 | # three view/display modes 26 | @VIEW : { NONE: -1, PERSPECTIVE: 0, ORTHOGRAPHIC: 1, DUAL: 2 } 27 | 28 | # spin clock or counter clockwise 29 | @SPIN : { LEFT: -1, NONE: 0, RIGHT: +1 } 30 | 31 | @SPIN_STEP : Utility.DEGREE / 10 # 0.1 degree - default step 32 | 33 | # M E M B E R S 34 | 35 | # these are pseudo constants which are redefined when browser resizes 36 | SCREEN_WIDTH : window.innerWidth 37 | SCREEN_HEIGHT : window.innerHeight 38 | 39 | mode : Projector.VIEW.DUAL # starting default 40 | 41 | storage : null # reference to the data storage 42 | 43 | colors : null # Array generated color values for visualization 44 | 45 | scene : null # THREE.Scene 46 | 47 | # perspective (3D) and orthographic (2D) projection cameras 48 | 49 | cameraPerspective : null # THREE.PerspectiveCamera 50 | cameraOrthographic : null # THREE.OrthographicCamera 51 | 52 | renderer : null # THREE.WebGLRenderer 53 | 54 | # mouse tracking variables 55 | mouse : new THREE.Vector3() # current mouse coordinates when selecting 56 | mouseStart : new THREE.Vector3() # mouse down coordinates when selecting 57 | mouseEnd : new THREE.Vector3() # mouse up coordinates when selecting 58 | dragging : false # true when rubber banding... 59 | 60 | selector : null # Selector 61 | 62 | # visual helpers for data display 63 | box : null # THREE.Mesh - data cage 64 | viewport : null # parent of selectable view rectangles 65 | direction : Utility.DIRECTION.TOP # default 2D view is from top 66 | view1 : null # THREE.Line - 2D orthographic view box - top 67 | view2 : null # THREE.Line - 2D orthographic view box - front 68 | view3 : null # THREE.Line - 2D orthographic view box - side 69 | 70 | # visual representations of loaded data 71 | points : null # Array 72 | particles : null # Array 73 | clusters : null # array of particle systems one per cluster 74 | 75 | selected : -1 # currently selected cluster 76 | 77 | controls : null # THREE.TrackballControls 78 | 79 | timeStamp : 0 80 | 81 | # C O N S T R U C T O R 82 | 83 | # Create projector. 84 | # Constructor creates all initial setup to make projector ready for data. 85 | constructor: -> 86 | 87 | super() 88 | 89 | @addUIListeners() # listen for UI events 90 | 91 | @scene = new THREE.Scene() # 3D world 92 | 93 | @createPerspectiveCamera() # left side (dual mode): 3D perspective camera 94 | @createOrthographicCamera() # right side (dual mode): 2D ortographic projection camera 95 | 96 | @createControls() # trackball simulation controls 97 | 98 | @createBox() # bounding box for the data 99 | 100 | @cameraPerspective.lookAt( @box.position ) 101 | @cameraOrthographic.lookAt( @box.position ) 102 | 103 | @createViews() 104 | @updateView(true) 105 | 106 | @selector = new Selector(@box) # 3D rubber band selector 107 | 108 | @createRenderingEngine() # set up WebGL renderer on canvas 109 | 110 | @onWindowResize(null) 111 | 112 | @animate() # start rendering loop! 113 | 114 | 115 | # E V E N T H A N D L E R S 116 | 117 | # Make updates related to window size changes. 118 | # Also used when view configuration is switched. 119 | onWindowResize : (event) => 120 | 121 | @SCREEN_WIDTH = window.innerWidth 122 | @SCREEN_HEIGHT = window.innerHeight 123 | 124 | console.log "Screen #{@SCREEN_WIDTH} x #{@SCREEN_HEIGHT}" 125 | 126 | if @renderer? 127 | 128 | @renderer.setSize( @SCREEN_WIDTH, @SCREEN_HEIGHT ) 129 | 130 | switch @mode 131 | 132 | when Projector.VIEW.PERSPECTIVE 133 | @cameraPerspective.aspect = @SCREEN_WIDTH / @SCREEN_HEIGHT 134 | @cameraPerspective.updateProjectionMatrix() 135 | 136 | when Projector.VIEW.ORTHOGRAPHIC 137 | @cameraOrthographic.left = - (@SCREEN_WIDTH / 8) 138 | @cameraOrthographic.right = + (@SCREEN_WIDTH / 8) 139 | @cameraOrthographic.top = + (@SCREEN_HEIGHT / 8) 140 | @cameraOrthographic.bottom = - (@SCREEN_HEIGHT / 8) 141 | @cameraOrthographic.updateProjectionMatrix() 142 | 143 | when Projector.VIEW.DUAL 144 | # left side 145 | @cameraPerspective.aspect = 0.5 * @SCREEN_WIDTH / @SCREEN_HEIGHT 146 | @cameraPerspective.updateProjectionMatrix() 147 | # right side 148 | @cameraOrthographic.left = - (@SCREEN_WIDTH / 10) 149 | @cameraOrthographic.right = + (@SCREEN_WIDTH / 10) 150 | @cameraOrthographic.top = + (@SCREEN_HEIGHT / 5) 151 | @cameraOrthographic.bottom = - (@SCREEN_HEIGHT / 5) 152 | @cameraOrthographic.updateProjectionMatrix() 153 | 154 | @controls.handleResize() 155 | 156 | 157 | onMouseDown : (event) => 158 | 159 | if @mode is Projector.VIEW.DUAL 160 | 161 | event.preventDefault() 162 | 163 | if event.shiftKey 164 | 165 | @dragging = true 166 | @updateMouse3D() 167 | @mouseStart.copy(@mouse) 168 | @selector.start(@mouseStart.clone()) 169 | 170 | event.stopPropagation() 171 | 172 | 173 | onMouseMove : (event) => 174 | 175 | if @mode is Projector.VIEW.DUAL 176 | 177 | event.preventDefault() 178 | 179 | if @dragging 180 | 181 | @updateMouse3D() 182 | @selector.update(@mouse) 183 | 184 | event.stopPropagation() 185 | 186 | 187 | onMouseUp : (event) => 188 | 189 | if @mode is Projector.VIEW.DUAL 190 | 191 | event.preventDefault() 192 | 193 | if @dragging 194 | 195 | @dragging = false 196 | @updateMouse3D() 197 | @mouseEnd.copy(@mouse) 198 | @selector.end(@mouseEnd.clone()) 199 | @updateSelection() 200 | 201 | event.stopPropagation() 202 | 203 | 204 | # Toggle next cluster during the animated walk through. 205 | onTimer : (index) => 206 | 207 | @toggleClusterVisibility(index) 208 | if ++index is @storage.getClusters() then index = 0 209 | if @animateOn then @startTimer(index) 210 | 211 | 212 | # M E T H O D S 213 | 214 | # Set the current mode. 215 | setMode : (@mode) => 216 | 217 | @onWindowResize(null) 218 | 219 | 220 | # Use given color set for visualization. 221 | setColors : (@colors) => 222 | 223 | 224 | # Toggle box visibility. Return current state. 225 | toggleBox : => return (@box.visible = not @box.visible) 226 | 227 | 228 | # Toggle viewport visibility. Return current state. 229 | toggleViewport : => return @updateView(not @viewport.visible) 230 | 231 | 232 | toggleSelector : => 233 | 234 | state = @selector.toggle() 235 | @updateSelection() 236 | return state 237 | 238 | 239 | # Get the base 64 encoded image of the current state of the projector. 240 | getImage : => 241 | 242 | return document.getElementById("renderer").toDataURL("image/png") 243 | 244 | 245 | # Hook up to browser and mouse events. 246 | addUIListeners : => 247 | 248 | window.addEventListener('resize', @onWindowResize, false) 249 | 250 | # container will hold WebGL canvas 251 | 252 | $('#container').mousedown(@onMouseDown) 253 | $('#container').mousemove(@onMouseMove) 254 | $('#container').mouseup(@onMouseUp) 255 | 256 | 257 | # Proper 3D camera. 258 | createPerspectiveCamera : => 259 | 260 | # NOTE Cameras aspect ratio setup matches the half screen viewports for initial dual mode 261 | 262 | @cameraPerspective = new THREE.PerspectiveCamera( 50, 0.5 * @SCREEN_WIDTH / @SCREEN_HEIGHT, 150, 1000 ) 263 | @cameraPerspective.position.set(0, 0, 550) 264 | @scene.add( @cameraPerspective ) 265 | 266 | 267 | # Flat, 2D, no perspective camera. 268 | createOrthographicCamera : => 269 | 270 | @cameraOrthographic = new THREE.OrthographicCamera( - (@SCREEN_WIDTH / 8), 271 | + (@SCREEN_WIDTH / 8), 272 | + (@SCREEN_HEIGHT / 4), 273 | - (@SCREEN_HEIGHT / 4), 274 | 250, 750 ) 275 | @cameraOrthographic.position.set(0, 500, 0) 276 | @scene.add( @cameraOrthographic ) 277 | 278 | 279 | # Initialize simulated trackball navigation controls 280 | createControls : => 281 | 282 | @controls = new THREE.TrackballControls( @cameraPerspective ) 283 | 284 | @controls.rotateSpeed = 1.0 285 | @controls.zoomSpeed = 1.0 286 | @controls.panSpeed = 0.8 287 | 288 | @controls.noZoom = false 289 | @controls.noPan = false 290 | 291 | @controls.staticMoving = true 292 | @controls.dynamicDampingFactor = 0.3 293 | 294 | @controls.addEventListener('change', @render) 295 | 296 | 297 | # Bounding box where the data is displayed. 298 | createBox : => 299 | 300 | @box = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), 301 | new THREE.MeshBasicMaterial({ color: 0x404040, wireframe: true })) 302 | @scene.add(@box) 303 | 304 | 305 | # Create a set of highlights that indicate ortographic projection in perspective view. 306 | # Each rectangle simply indicates where 2D view is within the 3D space. 307 | createViews : => 308 | 309 | @viewport = new THREE.Object3D() 310 | 311 | # top view 312 | geometry1 = new THREE.Geometry() 313 | geometry1.vertices.push( new THREE.Vector3( +100, +101, +100 ) ) 314 | geometry1.vertices.push( new THREE.Vector3( -100, +101, +100 ) ) 315 | geometry1.vertices.push( new THREE.Vector3( -100, +101, -100 ) ) 316 | geometry1.vertices.push( new THREE.Vector3( +100, +101, -100 ) ) 317 | geometry1.vertices.push( new THREE.Vector3( +100, +101, +100 ) ) 318 | 319 | @view1 = new THREE.Line(geometry1, new THREE.LineBasicMaterial(), THREE.LineStrip) 320 | 321 | # front view 322 | geometry2 = new THREE.Geometry() 323 | geometry2.vertices.push( new THREE.Vector3( +100, +100, +101 ) ) 324 | geometry2.vertices.push( new THREE.Vector3( -100, +100, +101 ) ) 325 | geometry2.vertices.push( new THREE.Vector3( -100, -100, +101 ) ) 326 | geometry2.vertices.push( new THREE.Vector3( +100, -100, +101 ) ) 327 | geometry2.vertices.push( new THREE.Vector3( +100, +100, +101 ) ) 328 | 329 | @view2 = new THREE.Line(geometry2, new THREE.LineBasicMaterial(), THREE.LineStrip) 330 | 331 | # side view 332 | geometry3 = new THREE.Geometry() 333 | geometry3.vertices.push( new THREE.Vector3( +101, +100, +100 ) ) 334 | geometry3.vertices.push( new THREE.Vector3( +101, -100, +100 ) ) 335 | geometry3.vertices.push( new THREE.Vector3( +101, -100, -100 ) ) 336 | geometry3.vertices.push( new THREE.Vector3( +101, +100, -100 ) ) 337 | geometry3.vertices.push( new THREE.Vector3( +101, +100, +100 ) ) 338 | 339 | @view3 = new THREE.Line(geometry3, new THREE.LineBasicMaterial(), THREE.LineStrip) 340 | 341 | @viewport.add(@view1) # top 342 | @viewport.add(@view2) # front 343 | @viewport.add(@view3) # side 344 | 345 | @box.add(@viewport) 346 | 347 | 348 | createRenderingEngine : => 349 | 350 | # basically canvas in WebGL mode 351 | @renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true } ) 352 | @renderer.setSize( @SCREEN_WIDTH, @SCREEN_HEIGHT ) 353 | @renderer.setClearColor( Palette.BACKGROUND.getHex(), 1 ) 354 | @renderer.domElement.style.position = "relative" 355 | @renderer.domElement.id = "renderer" 356 | @renderer.autoClear = false 357 | 358 | # container is the display area placeholder in HTML 359 | container = $('#container').get(0) 360 | container.appendChild( @renderer.domElement ) 361 | 362 | 363 | # Load JSON data to visualize. 364 | load : (@storage) => 365 | 366 | data = @storage.getData() # JSON 367 | clusters = @storage.getClusters() # number of clusters 368 | 369 | # create point clouds first for each cluster 370 | 371 | @points = new Array() 372 | 373 | for c in [0...clusters] 374 | @points[c] = new THREE.Geometry() 375 | @points[c].colorsNeedUpdate = true 376 | 377 | 378 | # process JSON data 379 | $.each(data.points, @processPoint) 380 | 381 | # create particle systems for each cluster (with point clouds within) 382 | 383 | @particles = new Array() 384 | 385 | for p in [0...clusters] 386 | material = new THREE.ParticleBasicMaterial( { size: 1.0, sizeAttenuation: false, vertexColors: true } ) 387 | @particles[p] = new THREE.ParticleSystem( @points[p], material ) 388 | @box.add( @particles[p] ) # put them in the data cage 389 | 390 | @notify(Projector.EVENT_DATA_LOADED) 391 | 392 | 393 | 394 | # Called for each data point loaded in JSON file. 395 | processPoint : (nodeName, nodeData) => 396 | 397 | # cluster index 398 | index = parseInt(nodeData.cid) 399 | 400 | vertex = new THREE.Vector3() 401 | vertex.x = parseFloat( nodeData.x ) 402 | vertex.y = parseFloat( nodeData.y ) 403 | vertex.z = parseFloat( nodeData.z ) 404 | @points[index].vertices.push( vertex ) 405 | 406 | # NOTE Although initially all points in the same cluster have the same color 407 | # they do take individual colors during the selection interactions therefore 408 | # each point needs its own individual color object instead of shared one... 409 | 410 | color = @colors[index].clone() 411 | @points[index].colors.push( color ) 412 | 413 | 414 | # Rendering loop - animate calls itself forever. 415 | animate : => 416 | 417 | requestAnimationFrame( @animate ) 418 | @controls.update() 419 | @render() 420 | 421 | 422 | # Rendering done on each frame. 423 | # Rendering configuration depends on the current view mode. 424 | render : => 425 | 426 | @renderer.clear() 427 | 428 | switch @mode 429 | 430 | # one viewport: perspective camera only 431 | when Projector.VIEW.PERSPECTIVE 432 | if @spin isnt Projector.SPIN.NONE then @spinCamera() 433 | @cameraPerspective.lookAt( @box.position ) 434 | # RENDERING 435 | @renderer.setViewport( 0, 0, @SCREEN_WIDTH, @SCREEN_HEIGHT ) 436 | @renderer.render( @scene, @cameraPerspective ) 437 | 438 | # one viewport: orthographic camera only 439 | when Projector.VIEW.ORTHOGRAPHIC 440 | # RENDERING 441 | @cameraOrthographic.rotation.z = 0 442 | @renderer.setViewport( 0, 0, @SCREEN_WIDTH, @SCREEN_HEIGHT ) 443 | @renderer.render( @scene, @cameraOrthographic ) 444 | 445 | # dual perspective and orthographic cameras view 446 | when Projector.VIEW.DUAL 447 | # synchronize camera with rotation 448 | if @spin isnt Projector.SPIN.NONE then @spinCamera() 449 | @cameraPerspective.lookAt( @box.position ) 450 | # RENDERING 451 | # left side viewport: perspective camera 452 | @renderer.setViewport( 0, 0, @SCREEN_WIDTH/2, @SCREEN_HEIGHT ) 453 | @renderer.render( @scene, @cameraPerspective ) 454 | # right side viewport: orthographic camera 455 | @cameraOrthographic.rotation.z = 0 456 | @renderer.setViewport( @SCREEN_WIDTH/2, 0, @SCREEN_WIDTH/2, @SCREEN_HEIGHT ) 457 | @renderer.render( @scene, @cameraOrthographic ) 458 | 459 | 460 | updateSelection : => 461 | 462 | # algorithm: 463 | # loop through all clusters 464 | # if cluster is visible then process it 465 | # for each point check if it's inside selection 466 | # if inside (and selector is active) set color to highlight 467 | # else set color to original cluster color 468 | 469 | counter = 0 470 | 471 | for i in [0...@storage.getClusters()] 472 | if @particles[i].visible 473 | cloud = @points[i] 474 | all = cloud.vertices.length 475 | for j in [0...all] 476 | vertex = cloud.vertices[j] 477 | color = cloud.colors[j] 478 | if @selector.isActive() and @selector.contains(vertex, Utility.DIRECTION.ALL) 479 | color.setHex(Palette.HIGHLIGHT.getHex()) 480 | counter++ 481 | # Utility.printVector3(vertex) 482 | else 483 | color.setHex(@colors[i].getHex()) 484 | 485 | cloud.colorsNeedUpdate = true; 486 | 487 | @notify(Projector.EVENT_POINTS_SELECTED, { points : counter }) 488 | 489 | 490 | updateMouse3D : => 491 | 492 | # NOTE This works only in DUAL mode 493 | # TODO Extend this to other modes 494 | 495 | ratio = 100 / 250 # ? 496 | 497 | switch @direction 498 | when Utility.DIRECTION.TOP 499 | @mouse.x = (event.pageX - (3 * @SCREEN_WIDTH / 4)) * ratio 500 | @mouse.y = 100 501 | @mouse.z = (event.pageY - (@SCREEN_HEIGHT / 2)) * ratio 502 | when Utility.DIRECTION.FRONT 503 | @mouse.x = (event.pageX - (3 * @SCREEN_WIDTH / 4)) * ratio 504 | @mouse.y = - (event.pageY - (@SCREEN_HEIGHT / 2)) * ratio 505 | @mouse.z = 100 506 | when Utility.DIRECTION.SIDE 507 | @mouse.x = 100 508 | @mouse.y = - (event.pageY - (@SCREEN_HEIGHT / 2)) * ratio 509 | @mouse.z = - (event.pageX - (3 * @SCREEN_WIDTH / 4)) * ratio 510 | 511 | 512 | # Returns 3D camera to its starting orientation and optionally position. 513 | # Position is only reset if location argument is true. 514 | resetCamera : (location) => 515 | 516 | if location then TweenLite.to( @cameraPerspective.position, 1, {x:0, y:0, z:550} ) 517 | TweenLite.to( @cameraPerspective.rotation, 1, {x:0, y:0, z:0} ) 518 | TweenLite.to( @cameraPerspective.up, 1, {x:0, y:1, z:0} ) 519 | 520 | 521 | # Set the visibility of orthographic view (top, front, side) indicator. 522 | updateView : (visible) => 523 | 524 | @viewport.visible = visible 525 | 526 | # NOTE Changing visibility of the viewport alone does not work as the change 527 | # of visibility of the parent is an ongoing bug/issue of the ThreeJS library... 528 | # ...so toggle all three separately 529 | 530 | if @viewport.visible 531 | switch @direction 532 | when Utility.DIRECTION.TOP 533 | @setViewsVisible(true, false, false) 534 | @cameraOrthographic.position.set(0, 500, 0) 535 | when Utility.DIRECTION.FRONT 536 | @setViewsVisible(false, true, false) 537 | @cameraOrthographic.position.set(0, 0, 500) 538 | when Utility.DIRECTION.SIDE 539 | @setViewsVisible(false, false, true) 540 | @cameraOrthographic.position.set(500, 0, 0) 541 | @cameraOrthographic.lookAt(@box.position) 542 | else 543 | @setViewsVisible(false, false, false) 544 | 545 | return @viewport.visible 546 | 547 | 548 | # Set visibility of view indicators. 549 | setViewsVisible : (top, front, side) => 550 | 551 | @view1.visible = top 552 | @view2.visible = front 553 | @view3.visible = side 554 | 555 | 556 | changeView : (@direction) => 557 | 558 | @updateView(@viewport.visible) 559 | @selector.setDirection(@direction) 560 | 561 | 562 | toggleAnimation : => 563 | 564 | @animateOn = not @animateOn 565 | 566 | if @animateOn 567 | @setAllClustersVisible(false) 568 | @startTimer(0) 569 | else 570 | @setAllClustersVisible(true) 571 | 572 | return @animateOn 573 | 574 | 575 | setSpin : (@spin) => 576 | 577 | switch @spin 578 | 579 | when Projector.SPIN.LEFT 580 | @resetCamera(false) 581 | 582 | when Projector.SPIN.NONE 583 | @timeStamp = 0 584 | 585 | when Projector.SPIN.RIGHT 586 | @resetCamera(false) 587 | 588 | 589 | # Spin camera in a circle around the center. 590 | spinCamera : => 591 | 592 | STEP = @getSpinStep() 593 | 594 | cx = @cameraPerspective.position.x 595 | cy = -1 * @cameraPerspective.position.z 596 | radians = Math.atan2(cy, cx) 597 | radius = Math.sqrt(cx * cx + cy * cy) 598 | 599 | switch @spin 600 | 601 | when Projector.SPIN.LEFT 602 | radians += STEP 603 | if radians > Math.PI then radians = radians - (2 * Math.PI) 604 | 605 | when Projector.SPIN.RIGHT 606 | radians -= STEP 607 | if radians < -Math.PI then radians = (2 * Math.PI) + radians 608 | 609 | x = radius * Math.cos(radians) 610 | y = radius * Math.sin(radians) 611 | 612 | @cameraPerspective.position.x = x 613 | @cameraPerspective.position.z = -1 * y 614 | 615 | 616 | # Adjust the rotation step depending on time elapsed between the frames. 617 | getSpinStep : => 618 | 619 | step = Projector.SPIN_STEP # default 620 | 621 | if @timeStamp isnt 0 622 | date = new Date() 623 | timeNow = date.getTime() 624 | delta = timeNow - @timeStamp 625 | @timeStamp = timeNow 626 | step = delta * step / 10 627 | 628 | return step 629 | 630 | 631 | # Toggle visibility of the cluster given by its index. 632 | toggleClusterVisibility : (index) => 633 | 634 | @particles[index].visible = not @particles[index].visible 635 | 636 | 637 | setAllClustersVisible : (visible) => 638 | 639 | p.visible = visible for p in @particles 640 | 641 | 642 | # Select or unselect cluster of given index. 643 | toggleClusterSelection : (index) => 644 | 645 | # clear old selected 646 | if @selected > -1 647 | # restore color coding on previous selection 648 | hexColor = @colors[@selected].getHex() 649 | @updatePoints(@selected, hexColor) 650 | 651 | if @selected is index 652 | # unselecting 653 | @selected = -1 654 | else 655 | # selecting 656 | @selected = index 657 | # highlight new selected 658 | @updatePoints(@selected, Palette.HIGHLIGHT.getHex()) 659 | 660 | if @selected > -1 661 | @notify(Projector.EVENT_CLUSTER_SELECTED, { id : index }) 662 | else 663 | @notify(Projector.EVENT_CLUSTER_SELECTED, { id : -1 }) 664 | 665 | 666 | # Color code given points cloud (cluster). 667 | updatePoints : (index, color) => 668 | 669 | cloud = @points[index] 670 | all = cloud.vertices.length 671 | 672 | for i in [0...all] 673 | cloud.colors[i].setHex(color) 674 | 675 | @points[index].colorsNeedUpdate = true 676 | 677 | 678 | startTimer : (index) => 679 | 680 | @toggleClusterVisibility(index) 681 | window.setTimeout(@onTimer, 2 * Utility.SECOND, index) 682 | 683 | 684 | # Count visible clusters. 685 | clustersVisible : => 686 | 687 | result = 0 688 | 689 | result++ for cloud in @particles when cloud.visible 690 | 691 | return result 692 | 693 | 694 | module.exports = Projector -------------------------------------------------------------------------------- /DataProjector.js: -------------------------------------------------------------------------------- 1 | ;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s -1) { 163 | return this.info.display("Cluster " + data.id + " selected"); 164 | } else { 165 | return this.info.display("No cluster selected"); 166 | } 167 | } 168 | }; 169 | 170 | DataProjector.prototype.initialize = function() { 171 | this.palette = new Palette(this.storage.getClusters()); 172 | this.colors = this.palette.getColors(); 173 | this.menu.create(this.storage.getClusters(), this.palette.getColors()); 174 | this.projector.setColors(this.colors); 175 | this.projector.load(this.storage); 176 | return this.onToolbarEvent(Toolbar.EVENT_SPIN_RIGHT); 177 | }; 178 | 179 | return DataProjector; 180 | 181 | })(Observer); 182 | 183 | dataProjector = new DataProjector(); 184 | 185 | 186 | },{"./Info.coffee":2,"./Menu.coffee":3,"./Observer.coffee":4,"./Palette.coffee":5,"./Projector.coffee":7,"./Storage.coffee":9,"./Subject.coffee":10,"./Toolbar.coffee":11,"./Utility.coffee":12}],2:[function(require,module,exports){ 187 | var Info, Panel, 188 | __hasProp = {}.hasOwnProperty, 189 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 190 | 191 | Panel = require('./Panel.coffee'); 192 | 193 | Info = (function(_super) { 194 | __extends(Info, _super); 195 | 196 | function Info(id) { 197 | Info.__super__.constructor.call(this, id); 198 | } 199 | 200 | Info.prototype.display = function(message) { 201 | return $('#message').append(message + "
"); 202 | }; 203 | 204 | Info.prototype.clear = function() { 205 | return $('#message').text(""); 206 | }; 207 | 208 | return Info; 209 | 210 | })(Panel); 211 | 212 | module.exports = Info; 213 | 214 | 215 | },{"./Panel.coffee":6}],3:[function(require,module,exports){ 216 | var Menu, Panel, 217 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 218 | __hasProp = {}.hasOwnProperty, 219 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 220 | 221 | Panel = require('./Panel.coffee'); 222 | 223 | Menu = (function(_super) { 224 | __extends(Menu, _super); 225 | 226 | Menu.EVENT_TOGGLE_ALL_ON = "EVENT_TOGGLE_ALL_ON"; 227 | 228 | Menu.EVENT_TOGGLE_ALL_OFF = "EVENT_TOGGLE_ALL_OFF"; 229 | 230 | Menu.EVENT_TOGGLE_ID = "EVENT_TOGGLE_ID"; 231 | 232 | Menu.EVENT_CLUSTER_ID = "EVENT_CLUSTER_ID"; 233 | 234 | Menu.TOGGLE_ON = "[+]"; 235 | 236 | Menu.TOGGLE_OFF = "[-]"; 237 | 238 | Menu.TOGGLE_MIX = "[/]"; 239 | 240 | Menu.prototype.clusters = 0; 241 | 242 | Menu.prototype.selected = -1; 243 | 244 | Menu.prototype.colors = null; 245 | 246 | function Menu(id) { 247 | this.onCluster = __bind(this.onCluster, this); 248 | this.onToggle = __bind(this.onToggle, this); 249 | this.onToggleAll = __bind(this.onToggleAll, this); 250 | Menu.__super__.constructor.call(this, id); 251 | } 252 | 253 | Menu.prototype.onToggleAll = function(event) { 254 | var i, state, _i, _j, _ref, _ref1; 255 | state = $("#toggleAll").text(); 256 | switch (state) { 257 | case Menu.TOGGLE_OFF: 258 | case Menu.TOGGLE_MIX: 259 | $("#toggleAll").text(Menu.TOGGLE_ON); 260 | for (i = _i = 0, _ref = this.clusters; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 261 | $("#t" + String(i)).text(Menu.TOGGLE_ON); 262 | } 263 | return this.notify(Menu.EVENT_TOGGLE_ALL_ON); 264 | case Menu.TOGGLE_ON: 265 | $("#toggleAll").text(Menu.TOGGLE_OFF); 266 | for (i = _j = 0, _ref1 = this.clusters; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { 267 | $("#t" + String(i)).text(Menu.TOGGLE_OFF); 268 | } 269 | return this.notify(Menu.EVENT_TOGGLE_ALL_OFF); 270 | } 271 | }; 272 | 273 | Menu.prototype.onToggle = function(event) { 274 | var id, identifier, index; 275 | identifier = event.target.id; 276 | id = identifier.replace("t", ""); 277 | index = parseInt(id); 278 | this.doToggle(index); 279 | return this.notify(Menu.EVENT_TOGGLE_ID, { 280 | id: index 281 | }); 282 | }; 283 | 284 | Menu.prototype.onCluster = function(event) { 285 | var index; 286 | index = parseInt(event.target.id.replace("b", "")); 287 | if (this.selected === index) { 288 | this.selected = -1; 289 | } else { 290 | this.selected = index; 291 | } 292 | this.updateSwatches(); 293 | this.updateButtons(); 294 | return this.notify(Menu.EVENT_CLUSTER_ID, { 295 | id: index 296 | }); 297 | }; 298 | 299 | Menu.prototype.doToggle = function(index) { 300 | var state, tag; 301 | tag = "#t" + String(index); 302 | state = $(tag).text(); 303 | switch (state) { 304 | case Menu.TOGGLE_ON: 305 | $(tag).text(Menu.TOGGLE_OFF); 306 | break; 307 | case Menu.TOGGLE_OFF: 308 | $(tag).text(Menu.TOGGLE_ON); 309 | } 310 | return this.updateMasterToggle(); 311 | }; 312 | 313 | Menu.prototype.create = function(clusters, colors) { 314 | var html, i, _i, _j, _ref, _ref1; 315 | this.clusters = clusters; 316 | this.colors = colors; 317 | for (i = _i = 0, _ref = this.clusters; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 318 | html = "[+] Cluster " + i + "
"; 319 | $("#menu").append(html); 320 | } 321 | $("#toggleAll").click(this.onToggleAll); 322 | for (i = _j = 0, _ref1 = this.clusters; 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { 323 | $("#t" + String(i)).click(this.onToggle); 324 | $("#b" + String(i)).click(this.onCluster); 325 | } 326 | return this.updateSwatches(); 327 | }; 328 | 329 | Menu.prototype.togglesOn = function() { 330 | var i, result, state, tag, _i, _ref; 331 | result = 0; 332 | for (i = _i = 0, _ref = this.clusters; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 333 | tag = "#t" + String(i); 334 | state = $(tag).text(); 335 | if (state === Menu.TOGGLE_ON) { 336 | result++; 337 | } 338 | } 339 | return result; 340 | }; 341 | 342 | Menu.prototype.updateMasterToggle = function() { 343 | var shown; 344 | shown = this.togglesOn(); 345 | switch (shown) { 346 | case 0: 347 | return $("#toggleAll").text(Menu.TOGGLE_OFF); 348 | case this.clusters: 349 | return $("#toggleAll").text(Menu.TOGGLE_ON); 350 | default: 351 | return $("#toggleAll").text(Menu.TOGGLE_MIX); 352 | } 353 | }; 354 | 355 | Menu.prototype.updateSwatches = function() { 356 | var i, _i, _ref, _results; 357 | _results = []; 358 | for (i = _i = 0, _ref = this.clusters; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 359 | if (i === this.selected) { 360 | _results.push($("#c" + String(i)).css('color', Palette.HIGHLIGHT.getStyle())); 361 | } else { 362 | _results.push($("#c" + String(i)).css('color', this.colors[i].getStyle())); 363 | } 364 | } 365 | return _results; 366 | }; 367 | 368 | Menu.prototype.updateButtons = function() { 369 | var i, _i, _ref, _results; 370 | _results = []; 371 | for (i = _i = 0, _ref = this.clusters; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 372 | if (i === this.selected) { 373 | _results.push($("#b" + String(i)).css('color', Palette.HIGHLIGHT.getStyle())); 374 | } else { 375 | _results.push($("#b" + String(i)).css('color', Palette.BUTTON.getStyle())); 376 | } 377 | } 378 | return _results; 379 | }; 380 | 381 | return Menu; 382 | 383 | })(Panel); 384 | 385 | module.exports = Menu; 386 | 387 | 388 | },{"./Panel.coffee":6}],4:[function(require,module,exports){ 389 | var Observer; 390 | 391 | Observer = (function() { 392 | function Observer() {} 393 | 394 | Observer.prototype.update = function(subject, type, data) {}; 395 | 396 | return Observer; 397 | 398 | })(); 399 | 400 | module.exports = Observer; 401 | 402 | 403 | },{}],5:[function(require,module,exports){ 404 | var Palette; 405 | 406 | Palette = (function() { 407 | Palette.BACKGROUND = new THREE.Color(0x202020); 408 | 409 | Palette.HIGHLIGHT = new THREE.Color(0xFFFFFF); 410 | 411 | Palette.SELECTOR = new THREE.Color(0xCC0000); 412 | 413 | Palette.BUTTON = new THREE.Color(0xCCCCCC); 414 | 415 | Palette.BUTTON_SELECTED = new THREE.Color(0xFF9C00); 416 | 417 | Palette.prototype.colors = null; 418 | 419 | function Palette(size) { 420 | this.colors = new Array(); 421 | this.generate(size); 422 | } 423 | 424 | Palette.prototype.generate = function(size) { 425 | var color, hue, i, lightness, saturation, step, _i, _results; 426 | hue = 0; 427 | saturation = 0.7; 428 | lightness = 0.45; 429 | step = 1 / size; 430 | _results = []; 431 | for (i = _i = 0; 0 <= size ? _i < size : _i > size; i = 0 <= size ? ++_i : --_i) { 432 | hue = (i + 1) * step; 433 | color = new THREE.Color(); 434 | color.setHSL(hue, saturation, lightness); 435 | _results.push(this.colors.push(color)); 436 | } 437 | return _results; 438 | }; 439 | 440 | Palette.prototype.getColors = function() { 441 | return this.colors; 442 | }; 443 | 444 | Palette.prototype.print = function() { 445 | var c, css, hsl, hue, i, lightness, saturation, _i, _len, _ref, _results; 446 | i = 0; 447 | _ref = this.colors; 448 | _results = []; 449 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 450 | c = _ref[_i]; 451 | css = c.getStyle(); 452 | hsl = c.getHSL(); 453 | hue = hsl.h.toFixed(1); 454 | saturation = hsl.s.toFixed(1); 455 | lightness = hsl.l.toFixed(1); 456 | _results.push(console.log(i++ + " > " + hue + " : " + saturation + " : " + lightness + " | " + css)); 457 | } 458 | return _results; 459 | }; 460 | 461 | return Palette; 462 | 463 | })(); 464 | 465 | module.exports = Palette; 466 | 467 | 468 | },{}],6:[function(require,module,exports){ 469 | var Panel, Subject, 470 | __hasProp = {}.hasOwnProperty, 471 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 472 | 473 | Subject = require('./Subject.coffee'); 474 | 475 | Panel = (function(_super) { 476 | __extends(Panel, _super); 477 | 478 | Panel.EVENT_PANEL_SHOWN = "EVENT_PANEL_SHOWN"; 479 | 480 | Panel.EVENT_PANEL_HIDDEN = "EVENT_PANEL_HIDDEN"; 481 | 482 | Panel.prototype.visible = true; 483 | 484 | function Panel(id) { 485 | this.id = id; 486 | Panel.__super__.constructor.call(this); 487 | } 488 | 489 | Panel.prototype.show = function() { 490 | $(this.id).show(); 491 | this.visible = true; 492 | return this.notify(Panel.EVENT_PANEL_SHOWN); 493 | }; 494 | 495 | Panel.prototype.hide = function() { 496 | $(this.id).hide(); 497 | this.visible = false; 498 | return this.notify(Panel.EVENT_PANEL_HIDDEN); 499 | }; 500 | 501 | Panel.prototype.toggle = function() { 502 | if (this.visible) { 503 | this.hide(); 504 | } else { 505 | this.show(); 506 | } 507 | return this.visible; 508 | }; 509 | 510 | return Panel; 511 | 512 | })(Subject); 513 | 514 | module.exports = Panel; 515 | 516 | 517 | },{"./Subject.coffee":10}],7:[function(require,module,exports){ 518 | var Palette, Projector, Selector, Subject, Utility, 519 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 520 | __hasProp = {}.hasOwnProperty, 521 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 522 | 523 | Subject = require('./Subject.coffee'); 524 | 525 | Utility = require('./Utility.coffee'); 526 | 527 | Palette = require('./Palette.coffee'); 528 | 529 | Selector = require('./Selector.coffee'); 530 | 531 | Projector = (function(_super) { 532 | __extends(Projector, _super); 533 | 534 | Projector.EVENT_DATA_LOADED = "EVENT_DATA_LOADED"; 535 | 536 | Projector.EVENT_POINTS_SELECTED = "EVENT_POINTS_SELECTED"; 537 | 538 | Projector.EVENT_CLUSTER_SELECTED = "EVENT_CLUSTER_SELECTED"; 539 | 540 | Projector.VIEW = { 541 | NONE: -1, 542 | PERSPECTIVE: 0, 543 | ORTHOGRAPHIC: 1, 544 | DUAL: 2 545 | }; 546 | 547 | Projector.SPIN = { 548 | LEFT: -1, 549 | NONE: 0, 550 | RIGHT: +1 551 | }; 552 | 553 | Projector.SPIN_STEP = Utility.DEGREE / 10; 554 | 555 | Projector.prototype.SCREEN_WIDTH = window.innerWidth; 556 | 557 | Projector.prototype.SCREEN_HEIGHT = window.innerHeight; 558 | 559 | Projector.prototype.mode = Projector.VIEW.DUAL; 560 | 561 | Projector.prototype.storage = null; 562 | 563 | Projector.prototype.colors = null; 564 | 565 | Projector.prototype.scene = null; 566 | 567 | Projector.prototype.cameraPerspective = null; 568 | 569 | Projector.prototype.cameraOrthographic = null; 570 | 571 | Projector.prototype.renderer = null; 572 | 573 | Projector.prototype.mouse = new THREE.Vector3(); 574 | 575 | Projector.prototype.mouseStart = new THREE.Vector3(); 576 | 577 | Projector.prototype.mouseEnd = new THREE.Vector3(); 578 | 579 | Projector.prototype.dragging = false; 580 | 581 | Projector.prototype.selector = null; 582 | 583 | Projector.prototype.box = null; 584 | 585 | Projector.prototype.viewport = null; 586 | 587 | Projector.prototype.direction = Utility.DIRECTION.TOP; 588 | 589 | Projector.prototype.view1 = null; 590 | 591 | Projector.prototype.view2 = null; 592 | 593 | Projector.prototype.view3 = null; 594 | 595 | Projector.prototype.points = null; 596 | 597 | Projector.prototype.particles = null; 598 | 599 | Projector.prototype.clusters = null; 600 | 601 | Projector.prototype.selected = -1; 602 | 603 | Projector.prototype.controls = null; 604 | 605 | Projector.prototype.timeStamp = 0; 606 | 607 | function Projector() { 608 | this.clustersVisible = __bind(this.clustersVisible, this); 609 | this.startTimer = __bind(this.startTimer, this); 610 | this.updatePoints = __bind(this.updatePoints, this); 611 | this.toggleClusterSelection = __bind(this.toggleClusterSelection, this); 612 | this.setAllClustersVisible = __bind(this.setAllClustersVisible, this); 613 | this.toggleClusterVisibility = __bind(this.toggleClusterVisibility, this); 614 | this.getSpinStep = __bind(this.getSpinStep, this); 615 | this.spinCamera = __bind(this.spinCamera, this); 616 | this.setSpin = __bind(this.setSpin, this); 617 | this.toggleAnimation = __bind(this.toggleAnimation, this); 618 | this.changeView = __bind(this.changeView, this); 619 | this.setViewsVisible = __bind(this.setViewsVisible, this); 620 | this.updateView = __bind(this.updateView, this); 621 | this.resetCamera = __bind(this.resetCamera, this); 622 | this.updateMouse3D = __bind(this.updateMouse3D, this); 623 | this.updateSelection = __bind(this.updateSelection, this); 624 | this.render = __bind(this.render, this); 625 | this.animate = __bind(this.animate, this); 626 | this.processPoint = __bind(this.processPoint, this); 627 | this.load = __bind(this.load, this); 628 | this.createRenderingEngine = __bind(this.createRenderingEngine, this); 629 | this.createViews = __bind(this.createViews, this); 630 | this.createBox = __bind(this.createBox, this); 631 | this.createControls = __bind(this.createControls, this); 632 | this.createOrthographicCamera = __bind(this.createOrthographicCamera, this); 633 | this.createPerspectiveCamera = __bind(this.createPerspectiveCamera, this); 634 | this.addUIListeners = __bind(this.addUIListeners, this); 635 | this.getImage = __bind(this.getImage, this); 636 | this.toggleSelector = __bind(this.toggleSelector, this); 637 | this.toggleViewport = __bind(this.toggleViewport, this); 638 | this.toggleBox = __bind(this.toggleBox, this); 639 | this.setColors = __bind(this.setColors, this); 640 | this.setMode = __bind(this.setMode, this); 641 | this.onTimer = __bind(this.onTimer, this); 642 | this.onMouseUp = __bind(this.onMouseUp, this); 643 | this.onMouseMove = __bind(this.onMouseMove, this); 644 | this.onMouseDown = __bind(this.onMouseDown, this); 645 | this.onWindowResize = __bind(this.onWindowResize, this); 646 | Projector.__super__.constructor.call(this); 647 | this.addUIListeners(); 648 | this.scene = new THREE.Scene(); 649 | this.createPerspectiveCamera(); 650 | this.createOrthographicCamera(); 651 | this.createControls(); 652 | this.createBox(); 653 | this.cameraPerspective.lookAt(this.box.position); 654 | this.cameraOrthographic.lookAt(this.box.position); 655 | this.createViews(); 656 | this.updateView(true); 657 | this.selector = new Selector(this.box); 658 | this.createRenderingEngine(); 659 | this.onWindowResize(null); 660 | this.animate(); 661 | } 662 | 663 | Projector.prototype.onWindowResize = function(event) { 664 | this.SCREEN_WIDTH = window.innerWidth; 665 | this.SCREEN_HEIGHT = window.innerHeight; 666 | console.log("Screen " + this.SCREEN_WIDTH + " x " + this.SCREEN_HEIGHT); 667 | if (this.renderer != null) { 668 | this.renderer.setSize(this.SCREEN_WIDTH, this.SCREEN_HEIGHT); 669 | switch (this.mode) { 670 | case Projector.VIEW.PERSPECTIVE: 671 | this.cameraPerspective.aspect = this.SCREEN_WIDTH / this.SCREEN_HEIGHT; 672 | this.cameraPerspective.updateProjectionMatrix(); 673 | break; 674 | case Projector.VIEW.ORTHOGRAPHIC: 675 | this.cameraOrthographic.left = -(this.SCREEN_WIDTH / 8); 676 | this.cameraOrthographic.right = +(this.SCREEN_WIDTH / 8); 677 | this.cameraOrthographic.top = +(this.SCREEN_HEIGHT / 8); 678 | this.cameraOrthographic.bottom = -(this.SCREEN_HEIGHT / 8); 679 | this.cameraOrthographic.updateProjectionMatrix(); 680 | break; 681 | case Projector.VIEW.DUAL: 682 | this.cameraPerspective.aspect = 0.5 * this.SCREEN_WIDTH / this.SCREEN_HEIGHT; 683 | this.cameraPerspective.updateProjectionMatrix(); 684 | this.cameraOrthographic.left = -(this.SCREEN_WIDTH / 10); 685 | this.cameraOrthographic.right = +(this.SCREEN_WIDTH / 10); 686 | this.cameraOrthographic.top = +(this.SCREEN_HEIGHT / 5); 687 | this.cameraOrthographic.bottom = -(this.SCREEN_HEIGHT / 5); 688 | this.cameraOrthographic.updateProjectionMatrix(); 689 | } 690 | } 691 | return this.controls.handleResize(); 692 | }; 693 | 694 | Projector.prototype.onMouseDown = function(event) { 695 | if (this.mode === Projector.VIEW.DUAL) { 696 | event.preventDefault(); 697 | if (event.shiftKey) { 698 | this.dragging = true; 699 | this.updateMouse3D(); 700 | this.mouseStart.copy(this.mouse); 701 | this.selector.start(this.mouseStart.clone()); 702 | return event.stopPropagation(); 703 | } 704 | } 705 | }; 706 | 707 | Projector.prototype.onMouseMove = function(event) { 708 | if (this.mode === Projector.VIEW.DUAL) { 709 | event.preventDefault(); 710 | if (this.dragging) { 711 | this.updateMouse3D(); 712 | this.selector.update(this.mouse); 713 | return event.stopPropagation(); 714 | } 715 | } 716 | }; 717 | 718 | Projector.prototype.onMouseUp = function(event) { 719 | if (this.mode === Projector.VIEW.DUAL) { 720 | event.preventDefault(); 721 | if (this.dragging) { 722 | this.dragging = false; 723 | this.updateMouse3D(); 724 | this.mouseEnd.copy(this.mouse); 725 | this.selector.end(this.mouseEnd.clone()); 726 | this.updateSelection(); 727 | return event.stopPropagation(); 728 | } 729 | } 730 | }; 731 | 732 | Projector.prototype.onTimer = function(index) { 733 | this.toggleClusterVisibility(index); 734 | if (++index === this.storage.getClusters()) { 735 | index = 0; 736 | } 737 | if (this.animateOn) { 738 | return this.startTimer(index); 739 | } 740 | }; 741 | 742 | Projector.prototype.setMode = function(mode) { 743 | this.mode = mode; 744 | return this.onWindowResize(null); 745 | }; 746 | 747 | Projector.prototype.setColors = function(colors) { 748 | this.colors = colors; 749 | }; 750 | 751 | Projector.prototype.toggleBox = function() { 752 | return (this.box.visible = !this.box.visible); 753 | }; 754 | 755 | Projector.prototype.toggleViewport = function() { 756 | return this.updateView(!this.viewport.visible); 757 | }; 758 | 759 | Projector.prototype.toggleSelector = function() { 760 | var state; 761 | state = this.selector.toggle(); 762 | this.updateSelection(); 763 | return state; 764 | }; 765 | 766 | Projector.prototype.getImage = function() { 767 | return document.getElementById("renderer").toDataURL("image/png"); 768 | }; 769 | 770 | Projector.prototype.addUIListeners = function() { 771 | window.addEventListener('resize', this.onWindowResize, false); 772 | $('#container').mousedown(this.onMouseDown); 773 | $('#container').mousemove(this.onMouseMove); 774 | return $('#container').mouseup(this.onMouseUp); 775 | }; 776 | 777 | Projector.prototype.createPerspectiveCamera = function() { 778 | this.cameraPerspective = new THREE.PerspectiveCamera(50, 0.5 * this.SCREEN_WIDTH / this.SCREEN_HEIGHT, 150, 1000); 779 | this.cameraPerspective.position.set(0, 0, 550); 780 | return this.scene.add(this.cameraPerspective); 781 | }; 782 | 783 | Projector.prototype.createOrthographicCamera = function() { 784 | this.cameraOrthographic = new THREE.OrthographicCamera(-(this.SCREEN_WIDTH / 8), +(this.SCREEN_WIDTH / 8), +(this.SCREEN_HEIGHT / 4), -(this.SCREEN_HEIGHT / 4), 250, 750); 785 | this.cameraOrthographic.position.set(0, 500, 0); 786 | return this.scene.add(this.cameraOrthographic); 787 | }; 788 | 789 | Projector.prototype.createControls = function() { 790 | this.controls = new THREE.TrackballControls(this.cameraPerspective); 791 | this.controls.rotateSpeed = 1.0; 792 | this.controls.zoomSpeed = 1.0; 793 | this.controls.panSpeed = 0.8; 794 | this.controls.noZoom = false; 795 | this.controls.noPan = false; 796 | this.controls.staticMoving = true; 797 | this.controls.dynamicDampingFactor = 0.3; 798 | return this.controls.addEventListener('change', this.render); 799 | }; 800 | 801 | Projector.prototype.createBox = function() { 802 | this.box = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), new THREE.MeshBasicMaterial({ 803 | color: 0x404040, 804 | wireframe: true 805 | })); 806 | return this.scene.add(this.box); 807 | }; 808 | 809 | Projector.prototype.createViews = function() { 810 | var geometry1, geometry2, geometry3; 811 | this.viewport = new THREE.Object3D(); 812 | geometry1 = new THREE.Geometry(); 813 | geometry1.vertices.push(new THREE.Vector3(+100, +101, +100)); 814 | geometry1.vertices.push(new THREE.Vector3(-100, +101, +100)); 815 | geometry1.vertices.push(new THREE.Vector3(-100, +101, -100)); 816 | geometry1.vertices.push(new THREE.Vector3(+100, +101, -100)); 817 | geometry1.vertices.push(new THREE.Vector3(+100, +101, +100)); 818 | this.view1 = new THREE.Line(geometry1, new THREE.LineBasicMaterial(), THREE.LineStrip); 819 | geometry2 = new THREE.Geometry(); 820 | geometry2.vertices.push(new THREE.Vector3(+100, +100, +101)); 821 | geometry2.vertices.push(new THREE.Vector3(-100, +100, +101)); 822 | geometry2.vertices.push(new THREE.Vector3(-100, -100, +101)); 823 | geometry2.vertices.push(new THREE.Vector3(+100, -100, +101)); 824 | geometry2.vertices.push(new THREE.Vector3(+100, +100, +101)); 825 | this.view2 = new THREE.Line(geometry2, new THREE.LineBasicMaterial(), THREE.LineStrip); 826 | geometry3 = new THREE.Geometry(); 827 | geometry3.vertices.push(new THREE.Vector3(+101, +100, +100)); 828 | geometry3.vertices.push(new THREE.Vector3(+101, -100, +100)); 829 | geometry3.vertices.push(new THREE.Vector3(+101, -100, -100)); 830 | geometry3.vertices.push(new THREE.Vector3(+101, +100, -100)); 831 | geometry3.vertices.push(new THREE.Vector3(+101, +100, +100)); 832 | this.view3 = new THREE.Line(geometry3, new THREE.LineBasicMaterial(), THREE.LineStrip); 833 | this.viewport.add(this.view1); 834 | this.viewport.add(this.view2); 835 | this.viewport.add(this.view3); 836 | return this.box.add(this.viewport); 837 | }; 838 | 839 | Projector.prototype.createRenderingEngine = function() { 840 | var container; 841 | this.renderer = new THREE.WebGLRenderer({ 842 | antialias: true, 843 | preserveDrawingBuffer: true 844 | }); 845 | this.renderer.setSize(this.SCREEN_WIDTH, this.SCREEN_HEIGHT); 846 | this.renderer.setClearColor(Palette.BACKGROUND.getHex(), 1); 847 | this.renderer.domElement.style.position = "relative"; 848 | this.renderer.domElement.id = "renderer"; 849 | this.renderer.autoClear = false; 850 | container = $('#container').get(0); 851 | return container.appendChild(this.renderer.domElement); 852 | }; 853 | 854 | Projector.prototype.load = function(storage) { 855 | var c, clusters, data, material, p, _i, _j; 856 | this.storage = storage; 857 | data = this.storage.getData(); 858 | clusters = this.storage.getClusters(); 859 | this.points = new Array(); 860 | for (c = _i = 0; 0 <= clusters ? _i < clusters : _i > clusters; c = 0 <= clusters ? ++_i : --_i) { 861 | this.points[c] = new THREE.Geometry(); 862 | this.points[c].colorsNeedUpdate = true; 863 | } 864 | $.each(data.points, this.processPoint); 865 | this.particles = new Array(); 866 | for (p = _j = 0; 0 <= clusters ? _j < clusters : _j > clusters; p = 0 <= clusters ? ++_j : --_j) { 867 | material = new THREE.ParticleBasicMaterial({ 868 | size: 1.0, 869 | sizeAttenuation: false, 870 | vertexColors: true 871 | }); 872 | this.particles[p] = new THREE.ParticleSystem(this.points[p], material); 873 | this.box.add(this.particles[p]); 874 | } 875 | return this.notify(Projector.EVENT_DATA_LOADED); 876 | }; 877 | 878 | Projector.prototype.processPoint = function(nodeName, nodeData) { 879 | var color, index, vertex; 880 | index = parseInt(nodeData.cid); 881 | vertex = new THREE.Vector3(); 882 | vertex.x = parseFloat(nodeData.x); 883 | vertex.y = parseFloat(nodeData.y); 884 | vertex.z = parseFloat(nodeData.z); 885 | this.points[index].vertices.push(vertex); 886 | color = this.colors[index].clone(); 887 | return this.points[index].colors.push(color); 888 | }; 889 | 890 | Projector.prototype.animate = function() { 891 | requestAnimationFrame(this.animate); 892 | this.controls.update(); 893 | return this.render(); 894 | }; 895 | 896 | Projector.prototype.render = function() { 897 | this.renderer.clear(); 898 | switch (this.mode) { 899 | case Projector.VIEW.PERSPECTIVE: 900 | if (this.spin !== Projector.SPIN.NONE) { 901 | this.spinCamera(); 902 | } 903 | this.cameraPerspective.lookAt(this.box.position); 904 | this.renderer.setViewport(0, 0, this.SCREEN_WIDTH, this.SCREEN_HEIGHT); 905 | return this.renderer.render(this.scene, this.cameraPerspective); 906 | case Projector.VIEW.ORTHOGRAPHIC: 907 | this.cameraOrthographic.rotation.z = 0; 908 | this.renderer.setViewport(0, 0, this.SCREEN_WIDTH, this.SCREEN_HEIGHT); 909 | return this.renderer.render(this.scene, this.cameraOrthographic); 910 | case Projector.VIEW.DUAL: 911 | if (this.spin !== Projector.SPIN.NONE) { 912 | this.spinCamera(); 913 | } 914 | this.cameraPerspective.lookAt(this.box.position); 915 | this.renderer.setViewport(0, 0, this.SCREEN_WIDTH / 2, this.SCREEN_HEIGHT); 916 | this.renderer.render(this.scene, this.cameraPerspective); 917 | this.cameraOrthographic.rotation.z = 0; 918 | this.renderer.setViewport(this.SCREEN_WIDTH / 2, 0, this.SCREEN_WIDTH / 2, this.SCREEN_HEIGHT); 919 | return this.renderer.render(this.scene, this.cameraOrthographic); 920 | } 921 | }; 922 | 923 | Projector.prototype.updateSelection = function() { 924 | var all, cloud, color, counter, i, j, vertex, _i, _j, _ref; 925 | counter = 0; 926 | for (i = _i = 0, _ref = this.storage.getClusters(); 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { 927 | if (this.particles[i].visible) { 928 | cloud = this.points[i]; 929 | all = cloud.vertices.length; 930 | for (j = _j = 0; 0 <= all ? _j < all : _j > all; j = 0 <= all ? ++_j : --_j) { 931 | vertex = cloud.vertices[j]; 932 | color = cloud.colors[j]; 933 | if (this.selector.isActive() && this.selector.contains(vertex, Utility.DIRECTION.ALL)) { 934 | color.setHex(Palette.HIGHLIGHT.getHex()); 935 | counter++; 936 | } else { 937 | color.setHex(this.colors[i].getHex()); 938 | } 939 | } 940 | cloud.colorsNeedUpdate = true; 941 | } 942 | } 943 | return this.notify(Projector.EVENT_POINTS_SELECTED, { 944 | points: counter 945 | }); 946 | }; 947 | 948 | Projector.prototype.updateMouse3D = function() { 949 | var ratio; 950 | ratio = 100 / 250; 951 | switch (this.direction) { 952 | case Utility.DIRECTION.TOP: 953 | this.mouse.x = (event.pageX - (3 * this.SCREEN_WIDTH / 4)) * ratio; 954 | this.mouse.y = 100; 955 | return this.mouse.z = (event.pageY - (this.SCREEN_HEIGHT / 2)) * ratio; 956 | case Utility.DIRECTION.FRONT: 957 | this.mouse.x = (event.pageX - (3 * this.SCREEN_WIDTH / 4)) * ratio; 958 | this.mouse.y = -(event.pageY - (this.SCREEN_HEIGHT / 2)) * ratio; 959 | return this.mouse.z = 100; 960 | case Utility.DIRECTION.SIDE: 961 | this.mouse.x = 100; 962 | this.mouse.y = -(event.pageY - (this.SCREEN_HEIGHT / 2)) * ratio; 963 | return this.mouse.z = -(event.pageX - (3 * this.SCREEN_WIDTH / 4)) * ratio; 964 | } 965 | }; 966 | 967 | Projector.prototype.resetCamera = function(location) { 968 | if (location) { 969 | TweenLite.to(this.cameraPerspective.position, 1, { 970 | x: 0, 971 | y: 0, 972 | z: 550 973 | }); 974 | } 975 | TweenLite.to(this.cameraPerspective.rotation, 1, { 976 | x: 0, 977 | y: 0, 978 | z: 0 979 | }); 980 | return TweenLite.to(this.cameraPerspective.up, 1, { 981 | x: 0, 982 | y: 1, 983 | z: 0 984 | }); 985 | }; 986 | 987 | Projector.prototype.updateView = function(visible) { 988 | this.viewport.visible = visible; 989 | if (this.viewport.visible) { 990 | switch (this.direction) { 991 | case Utility.DIRECTION.TOP: 992 | this.setViewsVisible(true, false, false); 993 | this.cameraOrthographic.position.set(0, 500, 0); 994 | break; 995 | case Utility.DIRECTION.FRONT: 996 | this.setViewsVisible(false, true, false); 997 | this.cameraOrthographic.position.set(0, 0, 500); 998 | break; 999 | case Utility.DIRECTION.SIDE: 1000 | this.setViewsVisible(false, false, true); 1001 | this.cameraOrthographic.position.set(500, 0, 0); 1002 | } 1003 | this.cameraOrthographic.lookAt(this.box.position); 1004 | } else { 1005 | this.setViewsVisible(false, false, false); 1006 | } 1007 | return this.viewport.visible; 1008 | }; 1009 | 1010 | Projector.prototype.setViewsVisible = function(top, front, side) { 1011 | this.view1.visible = top; 1012 | this.view2.visible = front; 1013 | return this.view3.visible = side; 1014 | }; 1015 | 1016 | Projector.prototype.changeView = function(direction) { 1017 | this.direction = direction; 1018 | this.updateView(this.viewport.visible); 1019 | return this.selector.setDirection(this.direction); 1020 | }; 1021 | 1022 | Projector.prototype.toggleAnimation = function() { 1023 | this.animateOn = !this.animateOn; 1024 | if (this.animateOn) { 1025 | this.setAllClustersVisible(false); 1026 | this.startTimer(0); 1027 | } else { 1028 | this.setAllClustersVisible(true); 1029 | } 1030 | return this.animateOn; 1031 | }; 1032 | 1033 | Projector.prototype.setSpin = function(spin) { 1034 | this.spin = spin; 1035 | switch (this.spin) { 1036 | case Projector.SPIN.LEFT: 1037 | return this.resetCamera(false); 1038 | case Projector.SPIN.NONE: 1039 | return this.timeStamp = 0; 1040 | case Projector.SPIN.RIGHT: 1041 | return this.resetCamera(false); 1042 | } 1043 | }; 1044 | 1045 | Projector.prototype.spinCamera = function() { 1046 | var STEP, cx, cy, radians, radius, x, y; 1047 | STEP = this.getSpinStep(); 1048 | cx = this.cameraPerspective.position.x; 1049 | cy = -1 * this.cameraPerspective.position.z; 1050 | radians = Math.atan2(cy, cx); 1051 | radius = Math.sqrt(cx * cx + cy * cy); 1052 | switch (this.spin) { 1053 | case Projector.SPIN.LEFT: 1054 | radians += STEP; 1055 | if (radians > Math.PI) { 1056 | radians = radians - (2 * Math.PI); 1057 | } 1058 | break; 1059 | case Projector.SPIN.RIGHT: 1060 | radians -= STEP; 1061 | if (radians < -Math.PI) { 1062 | radians = (2 * Math.PI) + radians; 1063 | } 1064 | } 1065 | x = radius * Math.cos(radians); 1066 | y = radius * Math.sin(radians); 1067 | this.cameraPerspective.position.x = x; 1068 | return this.cameraPerspective.position.z = -1 * y; 1069 | }; 1070 | 1071 | Projector.prototype.getSpinStep = function() { 1072 | var date, delta, step, timeNow; 1073 | step = Projector.SPIN_STEP; 1074 | if (this.timeStamp !== 0) { 1075 | date = new Date(); 1076 | timeNow = date.getTime(); 1077 | delta = timeNow - this.timeStamp; 1078 | this.timeStamp = timeNow; 1079 | step = delta * step / 10; 1080 | } 1081 | return step; 1082 | }; 1083 | 1084 | Projector.prototype.toggleClusterVisibility = function(index) { 1085 | return this.particles[index].visible = !this.particles[index].visible; 1086 | }; 1087 | 1088 | Projector.prototype.setAllClustersVisible = function(visible) { 1089 | var p, _i, _len, _ref, _results; 1090 | _ref = this.particles; 1091 | _results = []; 1092 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1093 | p = _ref[_i]; 1094 | _results.push(p.visible = visible); 1095 | } 1096 | return _results; 1097 | }; 1098 | 1099 | Projector.prototype.toggleClusterSelection = function(index) { 1100 | var hexColor; 1101 | if (this.selected > -1) { 1102 | hexColor = this.colors[this.selected].getHex(); 1103 | this.updatePoints(this.selected, hexColor); 1104 | } 1105 | if (this.selected === index) { 1106 | this.selected = -1; 1107 | } else { 1108 | this.selected = index; 1109 | this.updatePoints(this.selected, Palette.HIGHLIGHT.getHex()); 1110 | } 1111 | if (this.selected > -1) { 1112 | return this.notify(Projector.EVENT_CLUSTER_SELECTED, { 1113 | id: index 1114 | }); 1115 | } else { 1116 | return this.notify(Projector.EVENT_CLUSTER_SELECTED, { 1117 | id: -1 1118 | }); 1119 | } 1120 | }; 1121 | 1122 | Projector.prototype.updatePoints = function(index, color) { 1123 | var all, cloud, i, _i; 1124 | cloud = this.points[index]; 1125 | all = cloud.vertices.length; 1126 | for (i = _i = 0; 0 <= all ? _i < all : _i > all; i = 0 <= all ? ++_i : --_i) { 1127 | cloud.colors[i].setHex(color); 1128 | } 1129 | return this.points[index].colorsNeedUpdate = true; 1130 | }; 1131 | 1132 | Projector.prototype.startTimer = function(index) { 1133 | this.toggleClusterVisibility(index); 1134 | return window.setTimeout(this.onTimer, 2 * Utility.SECOND, index); 1135 | }; 1136 | 1137 | Projector.prototype.clustersVisible = function() { 1138 | var cloud, result, _i, _len, _ref; 1139 | result = 0; 1140 | _ref = this.particles; 1141 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1142 | cloud = _ref[_i]; 1143 | if (cloud.visible) { 1144 | result++; 1145 | } 1146 | } 1147 | return result; 1148 | }; 1149 | 1150 | return Projector; 1151 | 1152 | })(Subject); 1153 | 1154 | module.exports = Projector; 1155 | 1156 | 1157 | },{"./Palette.coffee":5,"./Selector.coffee":8,"./Subject.coffee":10,"./Utility.coffee":12}],8:[function(require,module,exports){ 1158 | var Palette, Selector, Utility, 1159 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 1160 | 1161 | Utility = require('./Utility.coffee'); 1162 | 1163 | Palette = require('./Palette.coffee'); 1164 | 1165 | Selector = (function() { 1166 | Selector.prototype.active = false; 1167 | 1168 | Selector.prototype.direction = Utility.DIRECTION.TOP; 1169 | 1170 | Selector.prototype.selectorTop = null; 1171 | 1172 | Selector.prototype.selectorFront = null; 1173 | 1174 | Selector.prototype.selectorSide = null; 1175 | 1176 | Selector.prototype.mouseStart = null; 1177 | 1178 | Selector.prototype.mouse = null; 1179 | 1180 | Selector.prototype.mouseEnd = null; 1181 | 1182 | Selector.prototype.min = null; 1183 | 1184 | Selector.prototype.max = null; 1185 | 1186 | function Selector(parent) { 1187 | this.toggle = __bind(this.toggle, this); 1188 | this.isActive = __bind(this.isActive, this); 1189 | this.mouseStart = new THREE.Vector3(); 1190 | this.mouse = new THREE.Vector3(); 1191 | this.mouseEnd = new THREE.Vector3(); 1192 | this.min = new THREE.Vector3(); 1193 | this.max = new THREE.Vector3(); 1194 | this.selectorTop = this.createSelector(Utility.DIRECTION.TOP); 1195 | parent.add(this.selectorTop); 1196 | this.selectorFront = this.createSelector(Utility.DIRECTION.FRONT); 1197 | parent.add(this.selectorFront); 1198 | this.selectorSide = this.createSelector(Utility.DIRECTION.SIDE); 1199 | parent.add(this.selectorSide); 1200 | this.setActive(false); 1201 | } 1202 | 1203 | Selector.prototype.setActive = function(active) { 1204 | this.active = active; 1205 | this.selectorTop.visible = this.active; 1206 | this.selectorFront.visible = this.active; 1207 | this.selectorSide.visible = this.active; 1208 | return this.active; 1209 | }; 1210 | 1211 | Selector.prototype.setDirection = function(direction) { 1212 | this.direction = direction; 1213 | return console.log("Selector.setDirection " + this.direction); 1214 | }; 1215 | 1216 | Selector.prototype.isActive = function() { 1217 | return this.active; 1218 | }; 1219 | 1220 | Selector.prototype.toggle = function() { 1221 | return this.setActive(!this.active); 1222 | }; 1223 | 1224 | Selector.prototype.start = function(mouse) { 1225 | this.mouse = mouse; 1226 | this.setActive(true); 1227 | if (!this.contains(mouse, this.direction)) { 1228 | return this.mouseStart = mouse; 1229 | } else { 1230 | switch (this.direction) { 1231 | case Utility.DIRECTION.TOP: 1232 | return this.mouseStart = this.getStart(mouse, this.selectorTop); 1233 | case Utility.DIRECTION.FRONT: 1234 | return this.mouseStart = this.getStart(mouse, this.selectorFront); 1235 | case Utility.DIRECTION.SIDE: 1236 | return this.mouseStart = this.getStart(mouse, this.selectorSide); 1237 | } 1238 | } 1239 | }; 1240 | 1241 | Selector.prototype.getStart = function(mouse, selector) { 1242 | var distanceTo0, distanceTo1, distanceTo2, distanceTo3, shortest, start; 1243 | distanceTo0 = mouse.distanceTo(selector.geometry.vertices[0]); 1244 | distanceTo1 = mouse.distanceTo(selector.geometry.vertices[1]); 1245 | distanceTo2 = mouse.distanceTo(selector.geometry.vertices[2]); 1246 | distanceTo3 = mouse.distanceTo(selector.geometry.vertices[3]); 1247 | shortest = Math.min(distanceTo0, distanceTo1, distanceTo2, distanceTo3); 1248 | if (shortest === distanceTo0) { 1249 | start = selector.geometry.vertices[2].clone(); 1250 | } 1251 | if (shortest === distanceTo1) { 1252 | start = selector.geometry.vertices[3].clone(); 1253 | } 1254 | if (shortest === distanceTo2) { 1255 | start = selector.geometry.vertices[0].clone(); 1256 | } 1257 | if (shortest === distanceTo3) { 1258 | start = selector.geometry.vertices[1].clone(); 1259 | } 1260 | return start; 1261 | }; 1262 | 1263 | Selector.prototype.update = function(mouse) { 1264 | this.mouse = mouse; 1265 | switch (this.direction) { 1266 | case Utility.DIRECTION.TOP: 1267 | this.selectorTop.geometry.vertices[0].x = this.mouseStart.x; 1268 | this.selectorTop.geometry.vertices[0].y = 100; 1269 | this.selectorTop.geometry.vertices[0].z = this.mouseStart.z; 1270 | this.selectorTop.geometry.vertices[1].x = this.mouse.x; 1271 | this.selectorTop.geometry.vertices[1].y = 100; 1272 | this.selectorTop.geometry.vertices[1].z = this.mouseStart.z; 1273 | this.selectorTop.geometry.vertices[2].x = this.mouse.x; 1274 | this.selectorTop.geometry.vertices[2].y = 100; 1275 | this.selectorTop.geometry.vertices[2].z = this.mouse.z; 1276 | this.selectorTop.geometry.vertices[3].x = this.mouseStart.x; 1277 | this.selectorTop.geometry.vertices[3].y = 100; 1278 | this.selectorTop.geometry.vertices[3].z = this.mouse.z; 1279 | this.selectorTop.geometry.vertices[4].x = this.mouseStart.x; 1280 | this.selectorTop.geometry.vertices[4].y = 100; 1281 | this.selectorTop.geometry.vertices[4].z = this.mouseStart.z; 1282 | this.selectorFront.geometry.vertices[0].x = this.mouseStart.x; 1283 | this.selectorFront.geometry.vertices[0].z = 100; 1284 | this.selectorFront.geometry.vertices[1].x = this.mouse.x; 1285 | this.selectorFront.geometry.vertices[1].z = 100; 1286 | this.selectorFront.geometry.vertices[2].x = this.mouse.x; 1287 | this.selectorFront.geometry.vertices[2].z = 100; 1288 | this.selectorFront.geometry.vertices[3].x = this.mouseStart.x; 1289 | this.selectorFront.geometry.vertices[3].z = 100; 1290 | this.selectorFront.geometry.vertices[4].x = this.mouseStart.x; 1291 | this.selectorFront.geometry.vertices[4].z = 100; 1292 | this.selectorSide.geometry.vertices[0].x = 100; 1293 | this.selectorSide.geometry.vertices[0].z = this.mouseStart.z; 1294 | this.selectorSide.geometry.vertices[1].x = 100; 1295 | this.selectorSide.geometry.vertices[1].z = this.mouseStart.z; 1296 | this.selectorSide.geometry.vertices[2].x = 100; 1297 | this.selectorSide.geometry.vertices[2].z = this.mouse.z; 1298 | this.selectorSide.geometry.vertices[3].x = 100; 1299 | this.selectorSide.geometry.vertices[3].z = this.mouse.z; 1300 | this.selectorSide.geometry.vertices[4].x = 100; 1301 | this.selectorSide.geometry.vertices[4].z = this.mouseStart.z; 1302 | break; 1303 | case Utility.DIRECTION.FRONT: 1304 | this.selectorFront.geometry.vertices[0].x = this.mouseStart.x; 1305 | this.selectorFront.geometry.vertices[0].y = this.mouseStart.y; 1306 | this.selectorFront.geometry.vertices[0].z = 100; 1307 | this.selectorFront.geometry.vertices[1].x = this.mouse.x; 1308 | this.selectorFront.geometry.vertices[1].y = this.mouseStart.y; 1309 | this.selectorFront.geometry.vertices[1].z = 100; 1310 | this.selectorFront.geometry.vertices[2].x = this.mouse.x; 1311 | this.selectorFront.geometry.vertices[2].y = this.mouse.y; 1312 | this.selectorFront.geometry.vertices[2].z = 100; 1313 | this.selectorFront.geometry.vertices[3].x = this.mouseStart.x; 1314 | this.selectorFront.geometry.vertices[3].y = this.mouse.y; 1315 | this.selectorFront.geometry.vertices[3].z = 100; 1316 | this.selectorFront.geometry.vertices[4].x = this.mouseStart.x; 1317 | this.selectorFront.geometry.vertices[4].y = this.mouseStart.y; 1318 | this.selectorFront.geometry.vertices[4].z = 100; 1319 | this.selectorTop.geometry.vertices[0].x = this.mouseStart.x; 1320 | this.selectorTop.geometry.vertices[0].y = 100; 1321 | this.selectorTop.geometry.vertices[1].x = this.mouse.x; 1322 | this.selectorTop.geometry.vertices[1].y = 100; 1323 | this.selectorTop.geometry.vertices[2].x = this.mouse.x; 1324 | this.selectorTop.geometry.vertices[2].y = 100; 1325 | this.selectorTop.geometry.vertices[3].x = this.mouseStart.x; 1326 | this.selectorTop.geometry.vertices[3].y = 100; 1327 | this.selectorTop.geometry.vertices[4].x = this.mouseStart.x; 1328 | this.selectorTop.geometry.vertices[4].y = 100; 1329 | this.selectorSide.geometry.vertices[0].x = 100; 1330 | this.selectorSide.geometry.vertices[0].y = this.mouseStart.y; 1331 | this.selectorSide.geometry.vertices[1].x = 100; 1332 | this.selectorSide.geometry.vertices[1].y = this.mouse.y; 1333 | this.selectorSide.geometry.vertices[2].x = 100; 1334 | this.selectorSide.geometry.vertices[2].y = this.mouse.y; 1335 | this.selectorSide.geometry.vertices[3].x = 100; 1336 | this.selectorSide.geometry.vertices[3].y = this.mouseStart.y; 1337 | this.selectorSide.geometry.vertices[4].x = 100; 1338 | this.selectorSide.geometry.vertices[4].y = this.mouseStart.y; 1339 | break; 1340 | case Utility.DIRECTION.SIDE: 1341 | this.selectorSide.geometry.vertices[0].x = 100; 1342 | this.selectorSide.geometry.vertices[0].y = this.mouseStart.y; 1343 | this.selectorSide.geometry.vertices[0].z = this.mouseStart.z; 1344 | this.selectorSide.geometry.vertices[1].x = 100; 1345 | this.selectorSide.geometry.vertices[1].y = this.mouse.y; 1346 | this.selectorSide.geometry.vertices[1].z = this.mouseStart.z; 1347 | this.selectorSide.geometry.vertices[2].x = 100; 1348 | this.selectorSide.geometry.vertices[2].y = this.mouse.y; 1349 | this.selectorSide.geometry.vertices[2].z = this.mouse.z; 1350 | this.selectorSide.geometry.vertices[3].x = 100; 1351 | this.selectorSide.geometry.vertices[3].y = this.mouseStart.y; 1352 | this.selectorSide.geometry.vertices[3].z = this.mouse.z; 1353 | this.selectorSide.geometry.vertices[4].x = 100; 1354 | this.selectorSide.geometry.vertices[4].y = this.mouseStart.y; 1355 | this.selectorSide.geometry.vertices[4].z = this.mouseStart.z; 1356 | this.selectorTop.geometry.vertices[0].y = 100; 1357 | this.selectorTop.geometry.vertices[0].z = this.mouseStart.z; 1358 | this.selectorTop.geometry.vertices[1].y = 100; 1359 | this.selectorTop.geometry.vertices[1].z = this.mouseStart.z; 1360 | this.selectorTop.geometry.vertices[2].y = 100; 1361 | this.selectorTop.geometry.vertices[2].z = this.mouse.z; 1362 | this.selectorTop.geometry.vertices[3].y = 100; 1363 | this.selectorTop.geometry.vertices[3].z = this.mouse.z; 1364 | this.selectorTop.geometry.vertices[4].y = 100; 1365 | this.selectorTop.geometry.vertices[4].z = this.mouseStart.z; 1366 | this.selectorFront.geometry.vertices[0].y = this.mouseStart.y; 1367 | this.selectorFront.geometry.vertices[0].z = 100; 1368 | this.selectorFront.geometry.vertices[1].y = this.mouseStart.y; 1369 | this.selectorFront.geometry.vertices[1].z = 100; 1370 | this.selectorFront.geometry.vertices[2].y = this.mouse.y; 1371 | this.selectorFront.geometry.vertices[2].z = 100; 1372 | this.selectorFront.geometry.vertices[3].y = this.mouse.y; 1373 | this.selectorFront.geometry.vertices[3].z = 100; 1374 | this.selectorFront.geometry.vertices[4].y = this.mouseStart.y; 1375 | this.selectorFront.geometry.vertices[4].z = 100; 1376 | } 1377 | this.selectorTop.geometry.verticesNeedUpdate = true; 1378 | this.selectorFront.geometry.verticesNeedUpdate = true; 1379 | return this.selectorSide.geometry.verticesNeedUpdate = true; 1380 | }; 1381 | 1382 | Selector.prototype.end = function(mouseEnd) { 1383 | this.mouseEnd = mouseEnd; 1384 | return this.updateBounds(); 1385 | }; 1386 | 1387 | Selector.prototype.updateBounds = function() { 1388 | this.min.x = Math.min(this.getMinX(this.selectorTop), this.getMinX(this.selectorFront)); 1389 | this.max.x = Math.max(this.getMaxX(this.selectorTop), this.getMaxX(this.selectorFront)); 1390 | this.min.y = Math.min(this.getMinY(this.selectorFront), this.getMinY(this.selectorSide)); 1391 | this.max.y = Math.max(this.getMaxY(this.selectorFront), this.getMaxY(this.selectorSide)); 1392 | this.min.z = Math.min(this.getMinZ(this.selectorTop), this.getMinZ(this.selectorSide)); 1393 | return this.max.z = Math.max(this.getMaxZ(this.selectorTop), this.getMaxZ(this.selectorSide)); 1394 | }; 1395 | 1396 | Selector.prototype.contains = function(point, direction) { 1397 | var inside; 1398 | inside = true; 1399 | switch (direction) { 1400 | case Utility.DIRECTION.ALL: 1401 | if (point.x < this.min.x || point.x > this.max.x) { 1402 | inside = false; 1403 | } 1404 | if (point.y < this.min.y || point.y > this.max.y) { 1405 | inside = false; 1406 | } 1407 | if (point.z < this.min.z || point.z > this.max.z) { 1408 | inside = false; 1409 | } 1410 | break; 1411 | case Utility.DIRECTION.TOP: 1412 | if (point.x < this.min.x || point.x > this.max.x) { 1413 | inside = false; 1414 | } 1415 | if (point.z < this.min.z || point.z > this.max.z) { 1416 | inside = false; 1417 | } 1418 | break; 1419 | case Utility.DIRECTION.FRONT: 1420 | if (point.x < this.min.x || point.x > this.max.x) { 1421 | inside = false; 1422 | } 1423 | if (point.y < this.min.y || point.y > this.max.y) { 1424 | inside = false; 1425 | } 1426 | break; 1427 | case Utility.DIRECTION.SIDE: 1428 | if (point.z < this.min.z || point.z > this.max.z) { 1429 | inside = false; 1430 | } 1431 | if (point.y < this.min.y || point.y > this.max.y) { 1432 | inside = false; 1433 | } 1434 | } 1435 | return inside; 1436 | }; 1437 | 1438 | Selector.prototype.getMinX = function(selector) { 1439 | var i, minX, vertices, _i; 1440 | vertices = selector.geometry.vertices; 1441 | minX = vertices[0].x; 1442 | for (i = _i = 1; _i <= 4; i = ++_i) { 1443 | if (vertices[i].x < minX) { 1444 | minX = vertices[i].x; 1445 | } 1446 | } 1447 | return minX; 1448 | }; 1449 | 1450 | Selector.prototype.getMaxX = function(selector) { 1451 | var i, maxX, vertices, _i; 1452 | vertices = selector.geometry.vertices; 1453 | maxX = vertices[0].x; 1454 | for (i = _i = 1; _i <= 4; i = ++_i) { 1455 | if (vertices[i].x > maxX) { 1456 | maxX = vertices[i].x; 1457 | } 1458 | } 1459 | return maxX; 1460 | }; 1461 | 1462 | Selector.prototype.getMinY = function(selector) { 1463 | var i, minY, vertices, _i; 1464 | vertices = selector.geometry.vertices; 1465 | minY = vertices[0].y; 1466 | for (i = _i = 1; _i <= 4; i = ++_i) { 1467 | if (vertices[i].y < minY) { 1468 | minY = vertices[i].y; 1469 | } 1470 | } 1471 | return minY; 1472 | }; 1473 | 1474 | Selector.prototype.getMaxY = function(selector) { 1475 | var i, maxY, vertices, _i; 1476 | vertices = selector.geometry.vertices; 1477 | maxY = vertices[0].y; 1478 | for (i = _i = 1; _i <= 4; i = ++_i) { 1479 | if (vertices[i].y > maxY) { 1480 | maxY = vertices[i].y; 1481 | } 1482 | } 1483 | return maxY; 1484 | }; 1485 | 1486 | Selector.prototype.getMinZ = function(selector) { 1487 | var i, minZ, vertices, _i; 1488 | vertices = selector.geometry.vertices; 1489 | minZ = vertices[0].z; 1490 | for (i = _i = 1; _i <= 4; i = ++_i) { 1491 | if (vertices[i].z < minZ) { 1492 | minZ = vertices[i].z; 1493 | } 1494 | } 1495 | return minZ; 1496 | }; 1497 | 1498 | Selector.prototype.getMaxZ = function(selector) { 1499 | var i, maxZ, vertices, _i; 1500 | vertices = selector.geometry.vertices; 1501 | maxZ = vertices[0].z; 1502 | for (i = _i = 1; _i <= 4; i = ++_i) { 1503 | if (vertices[i].z > maxZ) { 1504 | maxZ = vertices[i].z; 1505 | } 1506 | } 1507 | return maxZ; 1508 | }; 1509 | 1510 | Selector.prototype.createSelector = function(direction) { 1511 | var SIZE, geometry, selector; 1512 | SIZE = 100; 1513 | geometry = new THREE.Geometry(); 1514 | switch (direction) { 1515 | case Utility.DIRECTION.TOP: 1516 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, +SIZE)); 1517 | geometry.vertices.push(new THREE.Vector3(-SIZE, +SIZE, +SIZE)); 1518 | geometry.vertices.push(new THREE.Vector3(-SIZE, +SIZE, -SIZE)); 1519 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, -SIZE)); 1520 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, +SIZE)); 1521 | break; 1522 | case Utility.DIRECTION.FRONT: 1523 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, +SIZE)); 1524 | geometry.vertices.push(new THREE.Vector3(-SIZE, +SIZE, +SIZE)); 1525 | geometry.vertices.push(new THREE.Vector3(-SIZE, -SIZE, +SIZE)); 1526 | geometry.vertices.push(new THREE.Vector3(+SIZE, -SIZE, +SIZE)); 1527 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, +SIZE)); 1528 | break; 1529 | case Utility.DIRECTION.SIDE: 1530 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, +SIZE)); 1531 | geometry.vertices.push(new THREE.Vector3(+SIZE, -SIZE, +SIZE)); 1532 | geometry.vertices.push(new THREE.Vector3(+SIZE, -SIZE, -SIZE)); 1533 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, -SIZE)); 1534 | geometry.vertices.push(new THREE.Vector3(+SIZE, +SIZE, +SIZE)); 1535 | } 1536 | return selector = new THREE.Line(geometry, new THREE.LineBasicMaterial({ 1537 | color: Palette.SELECTOR.getHex() 1538 | }), THREE.LineStrip); 1539 | }; 1540 | 1541 | return Selector; 1542 | 1543 | })(); 1544 | 1545 | module.exports = Selector; 1546 | 1547 | 1548 | },{"./Palette.coffee":5,"./Utility.coffee":12}],9:[function(require,module,exports){ 1549 | var Storage, Subject, 1550 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 1551 | __hasProp = {}.hasOwnProperty, 1552 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 1553 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 1554 | 1555 | Subject = require('./Subject.coffee'); 1556 | 1557 | Storage = (function(_super) { 1558 | __extends(Storage, _super); 1559 | 1560 | Storage.EVENT_DATAFILE_READY = "EVENT_DATAFILE_READY"; 1561 | 1562 | Storage.EVENT_JSON_READY = "EVENT_JSON_READY"; 1563 | 1564 | Storage.EVENT_DATA_READY = "EVENT_DATA_READY"; 1565 | 1566 | Storage.EVENT_SCREENSHOT_OK = "EVENT_SCREENSHOT_OK"; 1567 | 1568 | Storage.prototype.datafile = null; 1569 | 1570 | Storage.prototype.data = null; 1571 | 1572 | Storage.prototype.points = 0; 1573 | 1574 | Storage.prototype.clusterIDs = null; 1575 | 1576 | Storage.prototype.clusters = 0; 1577 | 1578 | Storage.prototype.saved = 0; 1579 | 1580 | function Storage() { 1581 | this.processPoint = __bind(this.processPoint, this); 1582 | this.onSaveResponse = __bind(this.onSaveResponse, this); 1583 | this.onJSON = __bind(this.onJSON, this); 1584 | this.onDatafile = __bind(this.onDatafile, this); 1585 | Storage.__super__.constructor.call(this); 1586 | this.clusterIDs = new Array(); 1587 | } 1588 | 1589 | Storage.prototype.onDatafile = function(datafile) { 1590 | this.datafile = datafile; 1591 | this.notify(Storage.EVENT_DATAFILE_READY); 1592 | return this.requestJSON(this.datafile); 1593 | }; 1594 | 1595 | Storage.prototype.onJSON = function(data) { 1596 | this.data = data; 1597 | this.notify(Storage.EVENT_JSON_READY); 1598 | $.each(this.data.points, this.processPoint); 1599 | return this.notify(Storage.EVENT_DATA_READY); 1600 | }; 1601 | 1602 | Storage.prototype.onSaveResponse = function(message) { 1603 | console.log("DataProjector.onSaveResponse " + message); 1604 | return this.notify(Storage.EVENT_SCREENSHOT_OK); 1605 | }; 1606 | 1607 | Storage.prototype.requestData = function() { 1608 | return this.requestDatafile(); 1609 | }; 1610 | 1611 | Storage.prototype.requestDatafile = function() { 1612 | return this.onDatafile("data.json"); 1613 | }; 1614 | 1615 | Storage.prototype.requestJSON = function(datafile) { 1616 | var file; 1617 | this.datafile = datafile; 1618 | file = this.datafile + "?" + String(Math.round(Math.random() * 99999)); 1619 | return $.getJSON(file, this.onJSON); 1620 | }; 1621 | 1622 | Storage.prototype.saveImage = function(base64Image) { 1623 | return $.post('/upload', { 1624 | id: ++this.saved, 1625 | image: base64Image 1626 | }, this.onSaveResponse); 1627 | }; 1628 | 1629 | Storage.prototype.processPoint = function(nodeName, nodeData) { 1630 | var _ref; 1631 | if (_ref = nodeData.cid, __indexOf.call(this.clusterIDs, _ref) < 0) { 1632 | this.clusterIDs.push(nodeData.cid); 1633 | this.clusters = this.clusterIDs.length; 1634 | } 1635 | return this.points++; 1636 | }; 1637 | 1638 | Storage.prototype.getDatafile = function() { 1639 | return this.datafile; 1640 | }; 1641 | 1642 | Storage.prototype.getData = function() { 1643 | return this.data; 1644 | }; 1645 | 1646 | Storage.prototype.getClusters = function() { 1647 | return this.clusters; 1648 | }; 1649 | 1650 | Storage.prototype.getPoints = function() { 1651 | return this.points; 1652 | }; 1653 | 1654 | Storage.prototype.getSaved = function() { 1655 | return this.saved; 1656 | }; 1657 | 1658 | return Storage; 1659 | 1660 | })(Subject); 1661 | 1662 | module.exports = Storage; 1663 | 1664 | 1665 | },{"./Subject.coffee":10}],10:[function(require,module,exports){ 1666 | var Subject; 1667 | 1668 | Subject = (function() { 1669 | Subject.prototype.observers = null; 1670 | 1671 | function Subject() { 1672 | this.observers = new Array(); 1673 | } 1674 | 1675 | Subject.prototype.attach = function(o) { 1676 | return this.observers.push(o); 1677 | }; 1678 | 1679 | Subject.prototype.detach = function(o) { 1680 | var index; 1681 | index = this.observers.indexOf(o); 1682 | if (index >= 0) { 1683 | return this.observers.splice(index, 1); 1684 | } 1685 | }; 1686 | 1687 | Subject.prototype.notify = function(type, data) { 1688 | var o, _i, _len, _ref, _results; 1689 | if (data == null) { 1690 | data = null; 1691 | } 1692 | _ref = this.observers; 1693 | _results = []; 1694 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1695 | o = _ref[_i]; 1696 | _results.push(o.update(this, type, data)); 1697 | } 1698 | return _results; 1699 | }; 1700 | 1701 | return Subject; 1702 | 1703 | })(); 1704 | 1705 | module.exports = Subject; 1706 | 1707 | 1708 | },{}],11:[function(require,module,exports){ 1709 | var Palette, Panel, Toolbar, Utility, 1710 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 1711 | __hasProp = {}.hasOwnProperty, 1712 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 1713 | 1714 | Utility = require('./Utility.coffee'); 1715 | 1716 | Panel = require('./Panel.coffee'); 1717 | 1718 | Palette = require('./Palette.coffee'); 1719 | 1720 | Toolbar = (function(_super) { 1721 | __extends(Toolbar, _super); 1722 | 1723 | Toolbar.EVENT_MENU = "EVENT_MENU"; 1724 | 1725 | Toolbar.EVENT_INFO = "EVENT_INFO"; 1726 | 1727 | Toolbar.EVENT_PERSPECTIVE = "EVENT_PERSPECTIVE"; 1728 | 1729 | Toolbar.EVENT_ORTHOGRAPHIC = "EVENT_ORTHOGRAPHIC"; 1730 | 1731 | Toolbar.EVENT_DUAL = "EVENT_DUAL"; 1732 | 1733 | Toolbar.EVENT_RESET = "EVENT_RESET"; 1734 | 1735 | Toolbar.EVENT_CLEAR = "EVENT_CLEAR"; 1736 | 1737 | Toolbar.EVENT_BOX = "EVENT_BOX"; 1738 | 1739 | Toolbar.EVENT_VIEWPORT = "EVENT_VIEWPORT"; 1740 | 1741 | Toolbar.EVENT_SELECT = "EVENT_SELECT"; 1742 | 1743 | Toolbar.EVENT_VIEW_TOP = "EVENT_VIEW_TOP"; 1744 | 1745 | Toolbar.EVENT_VIEW_FRONT = "EVENT_VIEW_FRONT"; 1746 | 1747 | Toolbar.EVENT_VIEW_SIDE = "EVENT_VIEW_SIDE"; 1748 | 1749 | Toolbar.EVENT_SPIN_LEFT = "EVENT_SPIN_LEFT"; 1750 | 1751 | Toolbar.EVENT_SPIN_STOP = "EVENT_SPIN_STOP"; 1752 | 1753 | Toolbar.EVENT_SPIN_RIGHT = "EVENT_SPIN_RIGHT"; 1754 | 1755 | Toolbar.EVENT_ANIMATE = "EVENT_ANIMATE"; 1756 | 1757 | Toolbar.prototype.dispatcher = null; 1758 | 1759 | function Toolbar(id) { 1760 | this.setAnimateButtonSelected = __bind(this.setAnimateButtonSelected, this); 1761 | this.setSpinButtonSelected = __bind(this.setSpinButtonSelected, this); 1762 | this.setViewButtonSelected = __bind(this.setViewButtonSelected, this); 1763 | this.setSelectButtonSelected = __bind(this.setSelectButtonSelected, this); 1764 | this.setViewportButtonSelected = __bind(this.setViewportButtonSelected, this); 1765 | this.setBoxButtonSelected = __bind(this.setBoxButtonSelected, this); 1766 | this.blinkClearButton = __bind(this.blinkClearButton, this); 1767 | this.blinkResetButton = __bind(this.blinkResetButton, this); 1768 | this.setCameraButtonSelected = __bind(this.setCameraButtonSelected, this); 1769 | this.setInfoButtonSelected = __bind(this.setInfoButtonSelected, this); 1770 | this.setMenuButtonSelected = __bind(this.setMenuButtonSelected, this); 1771 | this.unblinkButton = __bind(this.unblinkButton, this); 1772 | this.blinkButton = __bind(this.blinkButton, this); 1773 | this.setButtonSelected = __bind(this.setButtonSelected, this); 1774 | this.initialize = __bind(this.initialize, this); 1775 | this.createDispatcher = __bind(this.createDispatcher, this); 1776 | this.onClick = __bind(this.onClick, this); 1777 | this.onKeyDown = __bind(this.onKeyDown, this); 1778 | var item, _i, _len, _ref; 1779 | Toolbar.__super__.constructor.call(this, id); 1780 | this.createDispatcher(); 1781 | _ref = this.dispatcher; 1782 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1783 | item = _ref[_i]; 1784 | $(item.id).click({ 1785 | type: item.type 1786 | }, this.onClick); 1787 | } 1788 | document.addEventListener('keydown', this.onKeyDown, false); 1789 | this.initialize(); 1790 | } 1791 | 1792 | Toolbar.prototype.onKeyDown = function(event) { 1793 | var item, modifier, _i, _len, _ref, _results; 1794 | modifier = Utility.NO_KEY; 1795 | if (event.shiftKey) { 1796 | modifier = Utility.SHIFT_KEY; 1797 | } 1798 | _ref = this.dispatcher; 1799 | _results = []; 1800 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 1801 | item = _ref[_i]; 1802 | if ((item.key === event.keyCode) && (item.modifier === modifier)) { 1803 | _results.push(this.notify(item.type)); 1804 | } else { 1805 | _results.push(void 0); 1806 | } 1807 | } 1808 | return _results; 1809 | }; 1810 | 1811 | Toolbar.prototype.onClick = function(event) { 1812 | return this.notify(event.data.type); 1813 | }; 1814 | 1815 | Toolbar.prototype.createDispatcher = function() { 1816 | return this.dispatcher = [ 1817 | { 1818 | id: "#menuButton", 1819 | key: 77, 1820 | modifier: Utility.NO_KEY, 1821 | type: Toolbar.EVENT_MENU 1822 | }, { 1823 | id: "#infoButton", 1824 | key: 73, 1825 | modifier: Utility.NO_KEY, 1826 | type: Toolbar.EVENT_INFO 1827 | }, { 1828 | id: "#perspectiveButton", 1829 | key: 80, 1830 | modifier: Utility.NO_KEY, 1831 | type: Toolbar.EVENT_PERSPECTIVE 1832 | }, { 1833 | id: "#orthographicButton", 1834 | key: 79, 1835 | modifier: Utility.NO_KEY, 1836 | type: Toolbar.EVENT_ORTHOGRAPHIC 1837 | }, { 1838 | id: "#dualButton", 1839 | key: 68, 1840 | modifier: Utility.NO_KEY, 1841 | type: Toolbar.EVENT_DUAL 1842 | }, { 1843 | id: "#resetButton", 1844 | key: 82, 1845 | modifier: Utility.NO_KEY, 1846 | type: Toolbar.EVENT_RESET 1847 | }, { 1848 | id: "#clearButton", 1849 | key: 67, 1850 | modifier: Utility.NO_KEY, 1851 | type: Toolbar.EVENT_CLEAR 1852 | }, { 1853 | id: "#boxButton", 1854 | key: 66, 1855 | modifier: Utility.NO_KEY, 1856 | type: Toolbar.EVENT_BOX 1857 | }, { 1858 | id: "#viewportButton", 1859 | key: 86, 1860 | modifier: Utility.NO_KEY, 1861 | type: Toolbar.EVENT_VIEWPORT 1862 | }, { 1863 | id: "#selectButton", 1864 | key: 83, 1865 | modifier: Utility.NO_KEY, 1866 | type: Toolbar.EVENT_SELECT 1867 | }, { 1868 | id: "#viewTopButton", 1869 | key: 49, 1870 | modifier: Utility.NO_KEY, 1871 | type: Toolbar.EVENT_VIEW_TOP 1872 | }, { 1873 | id: "#viewFrontButton", 1874 | key: 50, 1875 | modifier: Utility.NO_KEY, 1876 | type: Toolbar.EVENT_VIEW_FRONT 1877 | }, { 1878 | id: "#viewSideButton", 1879 | key: 51, 1880 | modifier: Utility.NO_KEY, 1881 | type: Toolbar.EVENT_VIEW_SIDE 1882 | }, { 1883 | id: "#spinLeftButton", 1884 | key: 37, 1885 | modifier: Utility.NO_KEY, 1886 | type: Toolbar.EVENT_SPIN_LEFT 1887 | }, { 1888 | id: "#spinStopButton", 1889 | key: 32, 1890 | modifier: Utility.NO_KEY, 1891 | type: Toolbar.EVENT_SPIN_STOP 1892 | }, { 1893 | id: "#spinRightButton", 1894 | key: 39, 1895 | modifier: Utility.NO_KEY, 1896 | type: Toolbar.EVENT_SPIN_RIGHT 1897 | }, { 1898 | id: "#animateButton", 1899 | key: 65, 1900 | modifier: Utility.NO_KEY, 1901 | type: Toolbar.EVENT_ANIMATE 1902 | } 1903 | ]; 1904 | }; 1905 | 1906 | Toolbar.prototype.initialize = function() { 1907 | this.setButtonSelected("#menuButton", true); 1908 | this.setButtonSelected("#infoButton", true); 1909 | this.setButtonSelected("#perspectiveButton", false); 1910 | this.setButtonSelected("#orthographicButton", false); 1911 | this.setButtonSelected("#dualButton", true); 1912 | this.setButtonSelected("#boxButton", true); 1913 | this.setButtonSelected("#viewportButton", true); 1914 | this.setButtonSelected("#selectButton", false); 1915 | this.setButtonSelected("#viewTopButton", true); 1916 | this.setButtonSelected("#viewFrontButton", false); 1917 | this.setButtonSelected("#viewSideButton", false); 1918 | this.setButtonSelected("#spinLeftButton", false); 1919 | this.setButtonSelected("#spinStopButton", true); 1920 | this.setButtonSelected("#spinRightButton", false); 1921 | return this.setButtonSelected("#animateButton", false); 1922 | }; 1923 | 1924 | Toolbar.prototype.setButtonSelected = function(id, selected) { 1925 | var color; 1926 | color = Palette.BUTTON.getStyle(); 1927 | if (selected) { 1928 | color = Palette.BUTTON_SELECTED.getStyle(); 1929 | } 1930 | return $(id).css('color', color); 1931 | }; 1932 | 1933 | Toolbar.prototype.blinkButton = function(id) { 1934 | this.setButtonSelected(id, true); 1935 | return window.setTimeout(this.unblinkButton, 200, id); 1936 | }; 1937 | 1938 | Toolbar.prototype.unblinkButton = function(id) { 1939 | console.log("Toolbar.unblinkButton " + id); 1940 | return this.setButtonSelected(id, false); 1941 | }; 1942 | 1943 | Toolbar.prototype.setMenuButtonSelected = function(selected) { 1944 | return this.setButtonSelected("#menuButton", selected); 1945 | }; 1946 | 1947 | Toolbar.prototype.setInfoButtonSelected = function(selected) { 1948 | return this.setButtonSelected("#infoButton", selected); 1949 | }; 1950 | 1951 | Toolbar.prototype.setCameraButtonSelected = function(selected1, selected2, selected3) { 1952 | this.setButtonSelected("#perspectiveButton", selected1); 1953 | this.setButtonSelected("#orthographicButton", selected2); 1954 | return this.setButtonSelected("#dualButton", selected3); 1955 | }; 1956 | 1957 | Toolbar.prototype.blinkResetButton = function() { 1958 | return this.blinkButton("#resetButton"); 1959 | }; 1960 | 1961 | Toolbar.prototype.blinkClearButton = function() { 1962 | return this.blinkButton("#clearButton"); 1963 | }; 1964 | 1965 | Toolbar.prototype.setBoxButtonSelected = function(selected) { 1966 | return this.setButtonSelected("#boxButton", selected); 1967 | }; 1968 | 1969 | Toolbar.prototype.setViewportButtonSelected = function(selected) { 1970 | return this.setButtonSelected("#viewportButton", selected); 1971 | }; 1972 | 1973 | Toolbar.prototype.setSelectButtonSelected = function(selected) { 1974 | return this.setButtonSelected("#selectButton", selected); 1975 | }; 1976 | 1977 | Toolbar.prototype.setViewButtonSelected = function(selected1, selected2, selected3) { 1978 | this.setButtonSelected("#viewTopButton", selected1); 1979 | this.setButtonSelected("#viewFrontButton", selected2); 1980 | return this.setButtonSelected("#viewSideButton", selected3); 1981 | }; 1982 | 1983 | Toolbar.prototype.setSpinButtonSelected = function(selected1, selected2, selected3) { 1984 | this.setButtonSelected("#spinLeftButton", selected1); 1985 | this.setButtonSelected("#spinStopButton", selected2); 1986 | return this.setButtonSelected("#spinRightButton", selected3); 1987 | }; 1988 | 1989 | Toolbar.prototype.setAnimateButtonSelected = function(selected) { 1990 | return this.setButtonSelected("#animateButton", selected); 1991 | }; 1992 | 1993 | return Toolbar; 1994 | 1995 | })(Panel); 1996 | 1997 | module.exports = Toolbar; 1998 | 1999 | 2000 | },{"./Palette.coffee":5,"./Panel.coffee":6,"./Utility.coffee":12}],12:[function(require,module,exports){ 2001 | var Utility; 2002 | 2003 | Utility = (function() { 2004 | function Utility() {} 2005 | 2006 | Utility.DIRECTION = { 2007 | ALL: 0, 2008 | TOP: 1, 2009 | FRONT: 2, 2010 | SIDE: 3 2011 | }; 2012 | 2013 | Utility.DEGREE = Math.PI / 180; 2014 | 2015 | Utility.SECOND = 1000; 2016 | 2017 | Utility.NO_KEY = "NO_KEY"; 2018 | 2019 | Utility.SHIFT_KEY = "SHIFT_KEY"; 2020 | 2021 | Utility.CTRL_KEY = "CTRL_KEY"; 2022 | 2023 | Utility.ALT_KEY = "ALT_KEY"; 2024 | 2025 | Utility.printVector3 = function(vector) { 2026 | return console.log(vector.x.toFixed(1) + " : " + vector.y.toFixed(1) + " : " + vector.z.toFixed(1)); 2027 | }; 2028 | 2029 | return Utility; 2030 | 2031 | })(); 2032 | 2033 | module.exports = Utility; 2034 | 2035 | 2036 | },{}]},{},[1]) 2037 | ; --------------------------------------------------------------------------------