├── makefile_exports.txt ├── styles └── main.css ├── shaders.h ├── makefile ├── README.md ├── index.html ├── shaders.cpp ├── input.ts ├── triangle.ts ├── main.cpp ├── triangle.js └── jquery.mousewheel.js /makefile_exports.txt: -------------------------------------------------------------------------------- 1 | ["_initGL", "_drawTriangle"] -------------------------------------------------------------------------------- /styles/main.css: -------------------------------------------------------------------------------- 1 | html {min-height:100%;position:relative} 2 | body{height:100%} 3 | 4 | .topleft { 5 | position:absolute; 6 | top:0; 7 | left:0; 8 | } -------------------------------------------------------------------------------- /shaders.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADERS_H 2 | #define SHADERS_H 3 | 4 | #include 5 | 6 | GLuint loadShader(GLenum type, const char *source); 7 | GLuint buildProgram(GLuint vertexShader, GLuint fragmentShader, const char * vertexPositionName); 8 | 9 | #endif -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CC=emcc 2 | SOURCES:=$(wildcard *.cpp) 3 | EXPORTS_FILE=makefile_exports.txt 4 | LDFLAGS=-O2 --llvm-opts 2 5 | OUTPUT=glcore.js 6 | 7 | all: $(SOURCES) $(OUTPUT) 8 | 9 | $(OUTPUT): $(SOURCES) 10 | $(CC) $(SOURCES) --bind -s FULL_ES2=1 -s EXPORTED_FUNCTIONS=@$(EXPORTS_FILE) -std=c++11 $(LDFLAGS) -o $(OUTPUT) 11 | 12 | clean: 13 | rm $(OUTPUT) 14 | rm $(OUTPUT).map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Native code + Emscripten + WebGL. Simmer gently. 2 | =============================== 3 | 4 | These are project files for a tutorial on integrating Emscripten-generated code into JS environment based on our experiments at [Scott Logic](http://www.scottlogic.com/). 5 | 6 | Build the project using 7 | 8 | make 9 | 10 | and open `index.html` 11 | 12 | For more information [read the tutorial](http://www.scottlogic.com/blog/2014/03/12/native-code-emscripten-webgl-simmer-gently.html) or simply [see live demo](http://ilyalopatkin.github.io/emscripten_webgl_simmer_gently/) 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGLTriangle 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /shaders.cpp: -------------------------------------------------------------------------------- 1 | #include "shaders.h" 2 | #include 3 | 4 | using namespace std; 5 | 6 | GLuint loadShader(GLenum type, const char *source) 7 | { 8 | //create a shader 9 | GLuint shader = glCreateShader(type); 10 | if (shader == 0) 11 | { 12 | cerr << "Error creating shader" << endl; 13 | return 0; 14 | } 15 | 16 | //load the shader source to the shader object and compile it 17 | glShaderSource(shader, 1, &source, NULL); 18 | glCompileShader(shader); 19 | 20 | //check if the shader compiled successfully 21 | GLint compiled; 22 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 23 | if (!compiled) 24 | { 25 | cerr << "Shader compilation error" << endl; 26 | glDeleteShader(shader); 27 | return 0; 28 | } 29 | 30 | return shader; 31 | } 32 | 33 | GLuint buildProgram(GLuint vertexShader, GLuint fragmentShader, const char * vertexPositionName) 34 | { 35 | //create a GL program and link it 36 | GLuint programObject = glCreateProgram(); 37 | glAttachShader(programObject, vertexShader); 38 | glAttachShader(programObject, fragmentShader); 39 | glBindAttribLocation(programObject, 0, vertexPositionName); 40 | glLinkProgram(programObject); 41 | 42 | //check if the program linked successfully 43 | GLint linked; 44 | glGetProgramiv(programObject, GL_LINK_STATUS, &linked); 45 | if (!linked) 46 | { 47 | cerr << "Program link error" << endl; 48 | glDeleteProgram(programObject); 49 | return 0; 50 | } 51 | return programObject; 52 | } -------------------------------------------------------------------------------- /input.ts: -------------------------------------------------------------------------------- 1 | module TriangleExample { 2 | declare var $; 3 | 4 | export interface Point { 5 | x: number; 6 | y: number; 7 | } 8 | 9 | export interface IPanZoomable { 10 | pan(offset: Point); 11 | zoom(ratio: number, origin: Point); 12 | } 13 | 14 | export class MouseController { 15 | isMouseDown = false; 16 | lastMouseCoord: Point = null; 17 | zoomSensitivity: number = 2.0; 18 | 19 | constructor(private canvas: HTMLCanvasElement, private area: IPanZoomable) { 20 | $(canvas).mousewheel(this.onMouseWheel.bind(this)); 21 | canvas.onmousedown = this.onMouseDown.bind(this); 22 | canvas.onmouseup = this.onMouseUp.bind(this); 23 | canvas.onmousemove = this.onMouseMove.bind(this); 24 | canvas.onmouseleave = this.onMouseLeave.bind(this); 25 | } 26 | 27 | private getLocalCoord(ev: MouseEvent): Point { 28 | return { x: ev.clientX - this.canvas.offsetLeft, y: ev.clientY - this.canvas.offsetTop }; 29 | } 30 | 31 | onMouseDown(ev: MouseEvent) { 32 | var e = this.getLocalCoord(ev); 33 | this.isMouseDown = true; 34 | this.lastMouseCoord = e; 35 | } 36 | 37 | onMouseUp(ev: MouseEvent) { 38 | var e = this.getLocalCoord(ev); 39 | this.isMouseDown = false; 40 | this.lastMouseCoord = null; 41 | } 42 | 43 | onMouseMove(ev: MouseEvent) { 44 | var e = this.getLocalCoord(ev); 45 | if (this.isMouseDown) { 46 | var offset = { x: e.x - this.lastMouseCoord.x, y: e.y - this.lastMouseCoord.y }; 47 | this.lastMouseCoord = e; 48 | this.area.pan(offset); 49 | } 50 | } 51 | 52 | onMouseLeave(ev: MouseEvent) { 53 | this.isMouseDown = false; 54 | } 55 | 56 | onMouseWheel(ev: any) { 57 | ev.wheelDelta = ev.deltaY * 120.0; 58 | var e = this.getLocalCoord(ev); 59 | this.area.zoom(1.0 + ev.wheelDelta / 1200.0 * this.zoomSensitivity, e); 60 | return false; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /triangle.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module TriangleExample { 4 | //type declarations for emscripten module and wrappers 5 | declare var Module: { 6 | cwrap: (name: string, returnType: string, params: string[]) => any; 7 | setValue: (ptr: number, value: number, type: string) => void; 8 | } 9 | declare var _malloc: (number) => number; 10 | declare var _free: (number) => void; 11 | 12 | //bindings to C++ functions 13 | class Bindings { 14 | public static initGL: (width: number, height: number) => number 15 | = Module.cwrap('initGL', 'number', ['number', 'number']); 16 | public static drawTriangle: (translationPtr: number) => void 17 | = Module.cwrap('drawTriangle', '', ['number']); 18 | } 19 | 20 | //a helper for some JS-to-Emscripten conversions 21 | class HeapUtils { 22 | public static floatArrayToHeap(arr: number[]): number { 23 | var arrayPointer = _malloc(arr.length * 4); 24 | for (var i = 0; i < arr.length; i++) 25 | Module.setValue(arrayPointer + i * 4, arr[i], 'float'); 26 | return arrayPointer; 27 | } 28 | } 29 | 30 | //our program that draws a triangle 31 | export class Program implements IPanZoomable { 32 | //current translation of the triangle 33 | private translation = { originX: 0, originY: 0, zoom: 1.0 }; 34 | //mouse event handler 35 | private mouseController; 36 | 37 | constructor(private canvas: HTMLCanvasElement) { 38 | //initialise the GL context, call the compiled native function 39 | var initialised = Bindings.initGL(canvas.width, canvas.height); 40 | if (!initialised) { 41 | console.log("Could not initialise GL"); 42 | return; 43 | } 44 | //get the mouse listen to canvas mouse events 45 | this.mouseController = new MouseController(canvas, this); 46 | //request redraw 47 | this.invalidate(); 48 | } 49 | 50 | //translate the whole GL scene by offset 51 | pan(offset: Point) { 52 | var glOffset = { 53 | x: offset.x / this.canvas.width * 2.0 / this.translation.zoom, 54 | y: offset.y / this.canvas.height * 2.0 / this.translation.zoom 55 | }; 56 | this.translation.originX += glOffset.x; 57 | this.translation.originY -= glOffset.y; 58 | this.invalidate(); 59 | } 60 | 61 | //zoom by the given ratio 62 | zoom(ratio: number, origin: Point) { 63 | this.translation.zoom *= ratio; 64 | this.invalidate(); 65 | } 66 | 67 | //render the scene 68 | private render() { 69 | //convert the JS translation object to an emscripten array of floats 70 | var translationPtr = HeapUtils.floatArrayToHeap( 71 | [this.translation.originX, this.translation.originY, this.translation.zoom] 72 | ); 73 | //call the native draw function 74 | Bindings.drawTriangle(translationPtr); 75 | //free the array memory 76 | _free(translationPtr); 77 | } 78 | 79 | public invalidate() { 80 | window.requestAnimationFrame(this.render.bind(this)); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #ifdef EMSCRIPTEN 7 | #include 8 | #endif 9 | 10 | #include "shaders.h" 11 | 12 | using namespace std; 13 | 14 | GLuint programObject; 15 | SDL_Surface* screen; 16 | 17 | GLfloat vVertices[] = { 18 | 0.0f, 0.5f, 0.0f, 19 | -0.5f, -0.5f, 0.0f, 20 | 0.5f, -0.5f, 0.0f 21 | }; 22 | GLint uniformOriginX, uniformOriginY, uniformZoom; 23 | 24 | 25 | extern "C" int initGL(int width, int height) 26 | { 27 | //initialise SDL 28 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) == 0) 29 | { 30 | screen = SDL_SetVideoMode(width, height, 0, SDL_OPENGL); 31 | if (screen == NULL) 32 | { 33 | cerr << "Could not set video mode: " << SDL_GetError() << endl; 34 | return 0; 35 | } 36 | } 37 | else 38 | { 39 | cerr << "Could not initialize SDL: " << SDL_GetError() << endl; 40 | return 0; 41 | } 42 | 43 | //SDL initialised successfully, now load shaders and geometry 44 | 45 | const char vertexShaderSource[] = 46 | "attribute vec4 vPosition; \n" 47 | "uniform float originX, originY; \n" 48 | "uniform float zoom; \n" 49 | "varying vec3 color; \n" 50 | "void main() \n" 51 | "{ \n" 52 | " gl_Position = vPosition; \n" 53 | " gl_Position.x = (originX + gl_Position.x) * zoom;\n" 54 | " gl_Position.y = (originY + gl_Position.y) * zoom;\n" 55 | " color = gl_Position.xyz + vec3(0.5); \n" 56 | "} \n"; 57 | 58 | const char fragmentShaderSource[] = 59 | "precision mediump float; \n" 60 | "varying vec3 color; \n" 61 | "void main() \n" 62 | "{ \n" 63 | " gl_FragColor = vec4 ( color, 1.0 ); \n" 64 | "} \n"; 65 | 66 | //load vertex and fragment shaders 67 | GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexShaderSource); 68 | GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentShaderSource); 69 | programObject = buildProgram(vertexShader, fragmentShader, "vPosition"); 70 | 71 | //save location of uniform variables 72 | uniformOriginX = glGetUniformLocation(programObject, "originX"); 73 | uniformOriginY = glGetUniformLocation(programObject, "originY"); 74 | uniformZoom = glGetUniformLocation(programObject, "zoom"); 75 | 76 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 77 | //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); 78 | glViewport(0, 0, width, height); 79 | return 1; 80 | } 81 | 82 | extern "C" void drawTriangle(float *translation) 83 | { 84 | //fill the screen with the clear color 85 | glClear(GL_COLOR_BUFFER_BIT); 86 | 87 | //enable our shader program 88 | glUseProgram(programObject); 89 | //set up the translation 90 | glUniform1f(uniformOriginX, translation[0]); 91 | glUniform1f(uniformOriginY, translation[1]); 92 | glUniform1f(uniformZoom, translation[2]); 93 | //set up the vertices array 94 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices); 95 | glEnableVertexAttribArray(0); 96 | //draw the triangle 97 | glDrawArrays(GL_TRIANGLES, 0, 3); 98 | 99 | //swap buffer to make whatever we've drawn to backbuffer appear on the screen 100 | SDL_GL_SwapBuffers(); 101 | } -------------------------------------------------------------------------------- /triangle.js: -------------------------------------------------------------------------------- 1 | var TriangleExample; 2 | (function (TriangleExample) { 3 | var MouseController = (function () { 4 | function MouseController(canvas, area) { 5 | this.canvas = canvas; 6 | this.area = area; 7 | this.isMouseDown = false; 8 | this.lastMouseCoord = null; 9 | this.zoomSensitivity = 2.0; 10 | $(canvas).mousewheel(this.onMouseWheel.bind(this)); 11 | canvas.onmousedown = this.onMouseDown.bind(this); 12 | canvas.onmouseup = this.onMouseUp.bind(this); 13 | canvas.onmousemove = this.onMouseMove.bind(this); 14 | canvas.onmouseleave = this.onMouseLeave.bind(this); 15 | } 16 | MouseController.prototype.getLocalCoord = function (ev) { 17 | return { x: ev.clientX - this.canvas.offsetLeft, y: ev.clientY - this.canvas.offsetTop }; 18 | }; 19 | 20 | MouseController.prototype.onMouseDown = function (ev) { 21 | var e = this.getLocalCoord(ev); 22 | this.isMouseDown = true; 23 | this.lastMouseCoord = e; 24 | }; 25 | 26 | MouseController.prototype.onMouseUp = function (ev) { 27 | var e = this.getLocalCoord(ev); 28 | this.isMouseDown = false; 29 | this.lastMouseCoord = null; 30 | }; 31 | 32 | MouseController.prototype.onMouseMove = function (ev) { 33 | var e = this.getLocalCoord(ev); 34 | if (this.isMouseDown) { 35 | var offset = { x: e.x - this.lastMouseCoord.x, y: e.y - this.lastMouseCoord.y }; 36 | this.lastMouseCoord = e; 37 | this.area.pan(offset); 38 | } 39 | }; 40 | 41 | MouseController.prototype.onMouseLeave = function (ev) { 42 | this.isMouseDown = false; 43 | }; 44 | 45 | MouseController.prototype.onMouseWheel = function (ev) { 46 | ev.wheelDelta = ev.deltaY * 120.0; 47 | var e = this.getLocalCoord(ev); 48 | this.area.zoom(1.0 + ev.wheelDelta / 1200.0 * this.zoomSensitivity, e); 49 | return false; 50 | }; 51 | return MouseController; 52 | })(); 53 | TriangleExample.MouseController = MouseController; 54 | })(TriangleExample || (TriangleExample = {})); 55 | /// 56 | var TriangleExample; 57 | (function (TriangleExample) { 58 | 59 | 60 | //bindings to C++ functions 61 | var Bindings = (function () { 62 | function Bindings() { 63 | } 64 | Bindings.initGL = Module.cwrap('initGL', 'number', ['number', 'number']); 65 | Bindings.drawTriangle = Module.cwrap('drawTriangle', '', ['number']); 66 | return Bindings; 67 | })(); 68 | 69 | //a helper for some JS-to-Emscripten conversions 70 | var HeapUtils = (function () { 71 | function HeapUtils() { 72 | } 73 | HeapUtils.floatArrayToHeap = function (arr) { 74 | var arrayPointer = _malloc(arr.length * 4); 75 | for (var i = 0; i < arr.length; i++) 76 | Module.setValue(arrayPointer + i * 4, arr[i], 'float'); 77 | return arrayPointer; 78 | }; 79 | return HeapUtils; 80 | })(); 81 | 82 | //our program that draws a triangle 83 | var Program = (function () { 84 | function Program(canvas) { 85 | this.canvas = canvas; 86 | //current translation of the triangle 87 | this.translation = { originX: 0, originY: 0, zoom: 1.0 }; 88 | //initialise the GL context, call the compiled native function 89 | var initialised = Bindings.initGL(canvas.width, canvas.height); 90 | if (!initialised) { 91 | console.log("Could not initialise GL"); 92 | return; 93 | } 94 | 95 | //get the mouse listen to canvas mouse events 96 | this.mouseController = new TriangleExample.MouseController(canvas, this); 97 | 98 | //request redraw 99 | this.invalidate(); 100 | } 101 | //translate the whole GL scene by offset 102 | Program.prototype.pan = function (offset) { 103 | var glOffset = { 104 | x: offset.x / this.canvas.width * 2.0 / this.translation.zoom, 105 | y: offset.y / this.canvas.height * 2.0 / this.translation.zoom 106 | }; 107 | this.translation.originX += glOffset.x; 108 | this.translation.originY -= glOffset.y; 109 | this.invalidate(); 110 | }; 111 | 112 | //zoom by the given ratio 113 | Program.prototype.zoom = function (ratio, origin) { 114 | this.translation.zoom *= ratio; 115 | this.invalidate(); 116 | }; 117 | 118 | //render the scene 119 | Program.prototype.render = function () { 120 | //convert the JS translation object to an emscripten array of floats 121 | var translationPtr = HeapUtils.floatArrayToHeap([this.translation.originX, this.translation.originY, this.translation.zoom]); 122 | 123 | //call the native draw function 124 | Bindings.drawTriangle(translationPtr); 125 | 126 | //free the array memory 127 | _free(translationPtr); 128 | }; 129 | 130 | Program.prototype.invalidate = function () { 131 | window.requestAnimationFrame(this.render.bind(this)); 132 | }; 133 | return Program; 134 | })(); 135 | TriangleExample.Program = Program; 136 | })(TriangleExample || (TriangleExample = {})); 137 | -------------------------------------------------------------------------------- /jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Version: 3.1.9 5 | * 6 | * Requires: jQuery 1.2.2+ 7 | */ 8 | 9 | (function (factory) { 10 | if ( typeof define === 'function' && define.amd ) { 11 | // AMD. Register as an anonymous module. 12 | define(['jquery'], factory); 13 | } else if (typeof exports === 'object') { 14 | // Node/CommonJS style for Browserify 15 | module.exports = factory; 16 | } else { 17 | // Browser globals 18 | factory(jQuery); 19 | } 20 | }(function ($) { 21 | 22 | var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'], 23 | toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? 24 | ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'], 25 | slice = Array.prototype.slice, 26 | nullLowestDeltaTimeout, lowestDelta; 27 | 28 | if ( $.event.fixHooks ) { 29 | for ( var i = toFix.length; i; ) { 30 | $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks; 31 | } 32 | } 33 | 34 | var special = $.event.special.mousewheel = { 35 | version: '3.1.9', 36 | 37 | setup: function() { 38 | if ( this.addEventListener ) { 39 | for ( var i = toBind.length; i; ) { 40 | this.addEventListener( toBind[--i], handler, false ); 41 | } 42 | } else { 43 | this.onmousewheel = handler; 44 | } 45 | // Store the line height and page height for this particular element 46 | $.data(this, 'mousewheel-line-height', special.getLineHeight(this)); 47 | $.data(this, 'mousewheel-page-height', special.getPageHeight(this)); 48 | }, 49 | 50 | teardown: function() { 51 | if ( this.removeEventListener ) { 52 | for ( var i = toBind.length; i; ) { 53 | this.removeEventListener( toBind[--i], handler, false ); 54 | } 55 | } else { 56 | this.onmousewheel = null; 57 | } 58 | }, 59 | 60 | getLineHeight: function(elem) { 61 | return parseInt($(elem)['offsetParent' in $.fn ? 'offsetParent' : 'parent']().css('fontSize'), 10); 62 | }, 63 | 64 | getPageHeight: function(elem) { 65 | return $(elem).height(); 66 | }, 67 | 68 | settings: { 69 | adjustOldDeltas: true 70 | } 71 | }; 72 | 73 | $.fn.extend({ 74 | mousewheel: function(fn) { 75 | return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel'); 76 | }, 77 | 78 | unmousewheel: function(fn) { 79 | return this.unbind('mousewheel', fn); 80 | } 81 | }); 82 | 83 | 84 | function handler(event) { 85 | var orgEvent = event || window.event, 86 | args = slice.call(arguments, 1), 87 | delta = 0, 88 | deltaX = 0, 89 | deltaY = 0, 90 | absDelta = 0; 91 | event = $.event.fix(orgEvent); 92 | event.type = 'mousewheel'; 93 | 94 | // Old school scrollwheel delta 95 | if ( 'detail' in orgEvent ) { deltaY = orgEvent.detail * -1; } 96 | if ( 'wheelDelta' in orgEvent ) { deltaY = orgEvent.wheelDelta; } 97 | if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; } 98 | if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; } 99 | 100 | // Firefox < 17 horizontal scrolling related to DOMMouseScroll event 101 | if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { 102 | deltaX = deltaY * -1; 103 | deltaY = 0; 104 | } 105 | 106 | // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy 107 | delta = deltaY === 0 ? deltaX : deltaY; 108 | 109 | // New school wheel delta (wheel event) 110 | if ( 'deltaY' in orgEvent ) { 111 | deltaY = orgEvent.deltaY * -1; 112 | delta = deltaY; 113 | } 114 | if ( 'deltaX' in orgEvent ) { 115 | deltaX = orgEvent.deltaX; 116 | if ( deltaY === 0 ) { delta = deltaX * -1; } 117 | } 118 | 119 | // No change actually happened, no reason to go any further 120 | if ( deltaY === 0 && deltaX === 0 ) { return; } 121 | 122 | // Need to convert lines and pages to pixels if we aren't already in pixels 123 | // There are three delta modes: 124 | // * deltaMode 0 is by pixels, nothing to do 125 | // * deltaMode 1 is by lines 126 | // * deltaMode 2 is by pages 127 | if ( orgEvent.deltaMode === 1 ) { 128 | var lineHeight = $.data(this, 'mousewheel-line-height'); 129 | delta *= lineHeight; 130 | deltaY *= lineHeight; 131 | deltaX *= lineHeight; 132 | } else if ( orgEvent.deltaMode === 2 ) { 133 | var pageHeight = $.data(this, 'mousewheel-page-height'); 134 | delta *= pageHeight; 135 | deltaY *= pageHeight; 136 | deltaX *= pageHeight; 137 | } 138 | 139 | // Store lowest absolute delta to normalize the delta values 140 | absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) ); 141 | 142 | if ( !lowestDelta || absDelta < lowestDelta ) { 143 | lowestDelta = absDelta; 144 | 145 | // Adjust older deltas if necessary 146 | if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { 147 | lowestDelta /= 40; 148 | } 149 | } 150 | 151 | // Adjust older deltas if necessary 152 | if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { 153 | // Divide all the things by 40! 154 | delta /= 40; 155 | deltaX /= 40; 156 | deltaY /= 40; 157 | } 158 | 159 | // Get a whole, normalized value for the deltas 160 | delta = Math[ delta >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta); 161 | deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta); 162 | deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta); 163 | 164 | // Add information to the event object 165 | event.deltaX = deltaX; 166 | event.deltaY = deltaY; 167 | event.deltaFactor = lowestDelta; 168 | // Go ahead and set deltaMode to 0 since we converted to pixels 169 | // Although this is a little odd since we overwrite the deltaX/Y 170 | // properties with normalized deltas. 171 | event.deltaMode = 0; 172 | 173 | // Add event and delta to the front of the arguments 174 | args.unshift(event, delta, deltaX, deltaY); 175 | 176 | // Clearout lowestDelta after sometime to better 177 | // handle multiple device types that give different 178 | // a different lowestDelta 179 | // Ex: trackpad = 3 and mouse wheel = 120 180 | if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); } 181 | nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200); 182 | 183 | return ($.event.dispatch || $.event.handle).apply(this, args); 184 | } 185 | 186 | function nullLowestDelta() { 187 | lowestDelta = null; 188 | } 189 | 190 | function shouldAdjustOldDeltas(orgEvent, absDelta) { 191 | // If this is an older event and the delta is divisable by 120, 192 | // then we are assuming that the browser is treating this as an 193 | // older mouse wheel event and that we should divide the deltas 194 | // by 40 to try and get a more usable deltaFactor. 195 | // Side note, this actually impacts the reported scroll distance 196 | // in older browsers and can cause scrolling to be slower than native. 197 | // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false. 198 | return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0; 199 | } 200 | 201 | })); 202 | --------------------------------------------------------------------------------