UNPKG

phaser-arcade-slopes

Version:

A Phaser CE plugin that brings sloped tile collision handling to Phaser's Arcade Physics engine

801 lines (669 loc) 26.2 kB
/** * @author Chris Andrew <chris@hexus.io> * @copyright 2016-2021 Chris Andrew * @license MIT */ /** * Solves tile collisions using the Separating Axis Theorem. * * @class Phaser.Plugin.ArcadeSlopes.SatSolver * @constructor * @param {object} options - Options for the SAT solver. */ Phaser.Plugin.ArcadeSlopes.SatSolver = function (options) { /** * Options for the SAT solver. * * @property {object} options */ this.options = Phaser.Utils.mixin(options || {}, { // Whether to store debug data with all encountered physics bodies debug: false, // Whether to prefer the minimum Y offset over the smallest separation preferY: false }); /** * A pool of arrays to use for calculations. * * @property {Array[]} arrayPool */ this.arrayPool = []; for (var i = 0; i < 10; i++) { this.arrayPool.push([]); } /** * A pool of vectors to use for calculations. * * @property {SAT.Vector[]} vectorPool */ this.vectorPool = []; for (i = 0; i < 20; i++) { this.vectorPool.push(new SAT.Vector()); } /** * A pool of responses to use for collision tests. * * @property {SAT.Response[]} responsePool */ this.responsePool = []; for (i = 0; i < 20; i++) { this.responsePool.push(new SAT.Response()); } }; /** * Prepare the given SAT response by inverting the overlap vectors. * * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#prepareResponse * @param {SAT.Response} response * @return {SAT.Response} */ Phaser.Plugin.ArcadeSlopes.SatSolver.prepareResponse = function (response) { // Invert our overlap vectors so that we have them facing outwards response.overlapV.scale(-1); response.overlapN.scale(-1); return response; }; /** * Reset the given SAT response's properties to their default values. * * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#resetResponse * @param {SAT.Response} response * @return {SAT.Response} */ Phaser.Plugin.ArcadeSlopes.SatSolver.resetResponse = function (response) { response.overlapN.x = 0; response.overlapN.y = 0; response.overlapV.x = 0; response.overlapV.y = 0; response.clear(); return response; }; /** * Copy the values of one SAT response to another. * * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#copyResponse * @param {SAT.Response} a - The source response. * @param {SAT.Response} b - The target response. * @return {SAT.Response} */ Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse = function (a, b) { b.a = a.a; b.b = a.b; b.aInB = a.aInB; b.bInA = a.bInA; b.overlap = a.overlap; b.overlapN.copy(a.overlapN); b.overlapV.copy(a.overlapV); return b; }; /** * Calculate the minimum X offset given an overlap vector. * * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#minimumOffsetX * @param {SAT.Vector} vector - The overlap vector. * @return {integer} */ Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetX = function (vector) { return ((vector.y * vector.y) / vector.x) + vector.x; }; /** * Calculate the minimum Y offset given an overlap vector. * * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#minimumOffsetY * @param {SAT.Vector} vector - The overlap vector. * @return {integer} */ Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetY = function (vector) { return ((vector.x * vector.x) / vector.y) + vector.y; }; /** * Determine whether the given body is moving against the overlap vector of the * given response on the Y axis. * * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#movingAgainstY * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {SAT.Response} response - The SAT response. * @return {boolean} - Whether the body is moving against the overlap vector. */ Phaser.Plugin.ArcadeSlopes.SatSolver.movingAgainstY = function (body, response) { return (response.overlapV.y < 0 && body.velocity.y > 0) || (response.overlapV.y > 0 && body.velocity.y < 0); }; // TODO: shouldPreferX() /** * Determine whether a body should be separated on the Y axis only, given an SAT * response. * * Returns true if options.preferY is true, the overlap vector is non-zero * for each axis and the body is moving against the overlap vector. * * TODO: Adapt for circle bodies, somehow. Disable for now? * TODO: Would be amazing to check to ensure that there are no other surrounding collisions. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldPreferY * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {SAT.Response} response - The SAT response. * @return {boolean} - Whether to separate on the Y axis only. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldPreferY = function (body, response) { return (this.options.preferY || body.slopes.preferY) && // Enabled globally or on the body response.overlapV.y !== 0 && response.overlapV.x !== 0 && // There's an overlap on both axes Phaser.Plugin.ArcadeSlopes.SatSolver.movingAgainstY(body, response); // And we're moving into the shape }; /** * Separate a body from a tile using the given SAT response. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#separate * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {Phaser.Tile} tile - The tile. * @param {SAT.Response} response - The SAT response. * @param {boolean} force - Whether to force separation. * @return {boolean} - Whether the body was separated. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.separate = function (body, tile, response, force) { // Test whether we need to separate from the tile by checking its edge // properties and any separation constraints if (!force && !this.shouldSeparate(tile.index, body, tile, response)) { return false; } // Run any custom tile callbacks, with local callbacks taking priority over // layer level callbacks if (tile.collisionCallback && !tile.collisionCallback.call(tile.collisionCallbackContext, body.sprite, tile)) { return false; } else if (tile.layer.callbacks[tile.index] && !tile.layer.callbacks[tile.index].callback.call(tile.layer.callbacks[tile.index].callbackContext, body.sprite, tile)) { return false; } // Separate the body from the tile, using the minimum Y offset if preferred if (this.shouldPreferY(body, response)) { body.position.y += Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetY(response.overlapV); } else { body.position.x += response.overlapV.x; body.position.y += response.overlapV.y; } return true; }; /** * Apply velocity changes (friction and bounce) to a body given a tile and * SAT collision response. * * TODO: Optimize by pooling bounce and friction vectors. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#applyVelocity * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {Phaser.Tile} tile - The tile. * @param {SAT.Response} response - The SAT response. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.applyVelocity = function (body, tile, response) { // Project our velocity onto the overlap normal for the bounce vector (Vn) var bounce = this.vectorPool.pop().copy(body.slopes.velocity).projectN(response.overlapN); // Then work out the surface vector (Vt) var friction = this.vectorPool.pop().copy(body.slopes.velocity).sub(bounce); // Apply bounce coefficients bounce.x = bounce.x * (-body.bounce.x); bounce.y = bounce.y * (-body.bounce.y); // Apply friction coefficients friction.x = friction.x * (1 - body.slopes.friction.x - tile.slope.friction.x); friction.y = friction.y * (1 - body.slopes.friction.y - tile.slope.friction.y); // Now we can get our new velocity by adding the bounce and friction vectors body.velocity.x = bounce.x + friction.x; body.velocity.y = bounce.y + friction.y; // Process collision pulling this.pull(body, response); // Recycle the vectors we used for bounce and friction this.vectorPool.push(bounce, friction); }; /** * Update the position and velocity values of the slopes body. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#updateValues * @param {Phaser.Physics.Arcade.Body} body - The physics body. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.updateValues = function (body) { // Update the body polygon position body.polygon.pos.x = body.x; body.polygon.pos.y = body.y; // Update the body's velocity vector body.slopes.velocity.x = body.velocity.x; body.slopes.velocity.y = body.velocity.y; }; /** * Update the flags of a physics body using a given SAT response. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#updateFlags * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {SAT.Response} response - The SAT response. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.updateFlags = function (body, response) { // Set the touching values body.touching.up = body.touching.up || response.overlapV.y > 0; body.touching.down = body.touching.down || response.overlapV.y < 0; body.touching.left = body.touching.left || response.overlapV.x > 0; body.touching.right = body.touching.right || response.overlapV.x < 0; body.touching.none = !body.touching.up && !body.touching.down && !body.touching.left && !body.touching.right; // Set the blocked values body.blocked.up = body.blocked.up || response.overlapV.x === 0 && response.overlapV.y > 0; body.blocked.down = body.blocked.down || response.overlapV.x === 0 && response.overlapV.y < 0; body.blocked.left = body.blocked.left || response.overlapV.y === 0 && response.overlapV.x > 0; body.blocked.right = body.blocked.right || response.overlapV.y === 0 && response.overlapV.x < 0; }; /** * Pull the body into a collision response based on its slopes options. * * TODO: Don't return after any condition is met, accumulate values into a * single SAT.Vector and apply at the end. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#pull * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {SAT.Response} response - The SAT response. * @return {boolean} - Whether the body was pulled. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.pull = function (body, response) { if (!body.slopes.pullUp && !body.slopes.pullDown && !body.slopes.pullLeft && !body.slopes.pullRight && !body.slopes.pullTopLeft && !body.slopes.pullTopRight && !body.slopes.pullBottomLeft && !body.slopes.pullBottomRight) { return false; } // Clone and flip the overlap normal so that it faces into the collision var overlapN = response.overlapN.clone().scale(-1); if (body.slopes.pullUp && overlapN.y < 0) { // Scale it by the configured amount pullUp = overlapN.clone().scale(body.slopes.pullUp); // Apply it to the body velocity body.velocity.x += pullUp.x; body.velocity.y += pullUp.y; return true; } if (body.slopes.pullDown && overlapN.y > 0) { pullDown = overlapN.clone().scale(body.slopes.pullDown); body.velocity.x += pullDown.x; body.velocity.y += pullDown.y; return true; } if (body.slopes.pullLeft && overlapN.x < 0) { pullLeft = overlapN.clone().scale(body.slopes.pullLeft); body.velocity.x += pullLeft.x; body.velocity.y += pullLeft.y; return true; } if (body.slopes.pullRight && overlapN.x > 0) { pullRight = overlapN.clone().scale(body.slopes.pullRight); body.velocity.x += pullRight.x; body.velocity.y += pullRight.y; return true; } if (body.slopes.pullTopLeft && overlapN.x < 0 && overlapN.y < 0) { pullUp = overlapN.clone().scale(body.slopes.pullTopLeft); body.velocity.x += pullUp.x; body.velocity.y += pullUp.y; return true; } if (body.slopes.pullTopRight && overlapN.x > 0 && overlapN.y < 0) { pullDown = overlapN.clone().scale(body.slopes.pullTopRight); body.velocity.x += pullDown.x; body.velocity.y += pullDown.y; return true; } if (body.slopes.pullBottomLeft && overlapN.x < 0 && overlapN.y > 0) { pullLeft = overlapN.clone().scale(body.slopes.pullBottomLeft); body.velocity.x += pullLeft.x; body.velocity.y += pullLeft.y; return true; } if (body.slopes.pullBottomRight && overlapN.x > 0 && overlapN.y > 0) { pullRight = overlapN.clone().scale(body.slopes.pullBottomRight); body.velocity.x += pullRight.x; body.velocity.y += pullRight.y; return true; } return false; }; /** * Determine whether everything required to process a collision is available. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldCollide * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {Phaser.Tile} tile - The tile. * @return {boolean} */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldCollide = function (body, tile) { return body.enable && body.polygon && body.slopes && tile.collides && tile.slope && tile.slope.polygon; }; /** * Flattens the specified array of points onto a unit vector axis, * resulting in a one dimensional range of the minimum and * maximum value on that axis. * * Copied verbatim from SAT.flattenPointsOn. * * @see SAT.flattenPointsOn * @static * @method Phaser.Plugin.ArcadeSlopes.SatSolver#flattenPointsOn * @param {SAT.Vector[]} points - The points to flatten. * @param {SAT.Vector} normal - The unit vector axis to flatten on. * @param {number[]} result - An array. After calling this, * result[0] will be the minimum value, * result[1] will be the maximum value. */ Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn = function (points, normal, result) { var min = Number.MAX_VALUE; var max = -Number.MAX_VALUE; var len = points.length; for (var i = 0; i < len; i++ ) { // The magnitude of the projection of the point onto the normal var dot = points[i].dot(normal); if (dot < min) { min = dot; } if (dot > max) { max = dot; } } result[0] = min; result[1] = max; }; /** * Determine whether two polygons are separated by a given axis. * * Tailored to only push out in the direction of the given axis. * * Adapted from SAT.isSeparatingAxis. * * @see {SAT.isSeparatingAxis} * @method Phaser.Plugin.ArcadeSlopes.SatSolver#isSeparatingAxis * @param {SAT.Polygon} a - The first polygon. * @param {SAT.Polygon} b - The second polygon. * @param {SAT.Vector} axis - The axis (unit sized) to test against. * The points of both polygons are projected * onto this axis. * @param {SAT.Response} response - The response to populate if the polygons are * not separated by the given axis. * @return {boolean} true if it is a separating axis, false otherwise. If false, * and a response is passed in, information about how much overlap and * the direction of the overlap will be populated. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.isSeparatingAxis = function (a, b, axis, response) { var aPos = a.pos; var bPos = b.pos; var aPoints = a.calcPoints; var bPoints = b.calcPoints; var rangeA = this.arrayPool.pop(); var rangeB = this.arrayPool.pop(); // The magnitude of the offset between the two polygons var offsetV = this.vectorPool.pop().copy(bPos).sub(aPos); var projectedOffset = offsetV.dot(axis); // Project the polygons onto the axis. Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn(aPoints, axis, rangeA); Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn(bPoints, axis, rangeB); // Move B's range to its position relative to A. rangeB[0] += projectedOffset; rangeB[1] += projectedOffset; // Check if there is a gap. If there is, this is a separating axis and we can stop if (rangeA[0] >= rangeB[1] || rangeB[0] >= rangeA[1]) { this.vectorPool.push(offsetV); this.arrayPool.push(rangeA); this.arrayPool.push(rangeB); return true; } var option1, option2; // This is not a separating axis. If we're calculating a response, calculate // the overlap var overlap = 0; if (rangeA[0] < rangeB[0]) { // A starts further left than B response.aInB = false; if (rangeA[1] < rangeB[1]) { // A ends before B does. We have to pull A out of B //overlap = rangeA[1] - rangeB[0]; response.bInA = false; }// else { // B is fully inside A. Pick the shortest way out. //option1 = rangeA[1] - rangeB[0]; //option2 = rangeB[1] - rangeA[0]; //overlap = option1 < option2 ? option1 : -option2; //} } else { // B starts further left than A response.bInA = false; if (rangeA[1] > rangeB[1]) { // B ends before A ends. We have to push A out of B overlap = rangeA[0] - rangeB[1]; response.aInB = false; } else { // A is fully inside B. Pick the shortest way out. option1 = rangeA[1] - rangeB[0]; option2 = rangeB[1] - rangeA[0]; //overlap = option1 < option2 ? option1 : -option2; if (option1 >= option2) { overlap = -option2; } } } // If this is the smallest amount of overlap we've seen so far, set it // as the minimum overlap. var absOverlap = Math.abs(overlap); if (absOverlap < response.overlap) { response.overlap = absOverlap; response.overlapN.copy(axis); if (overlap < 0) { response.overlapN.reverse(); } } this.vectorPool.push(offsetV); this.arrayPool.push(rangeA); this.arrayPool.push(rangeB); return false; }; /** * Test whether two polygons overlap. * * Takes a response object that will be populated with the shortest * viable separation vector. Ignores collision responses that don't oppose * velocity enough. * * Returns true if there is a collision and false otherwise. * * Tailored to work with an AABB as the first polygon. * * Adapted from SAT.testPolygonPolygon. * * @see {SAT.testPolygonPolygon} * @method Phaser.Plugin.ArcadeSlopes.SatSolver#testPolygonPolygon * @param {SAT.Polygon} a - The first polygon. * @param {SAT.Polygon} b - The second polygon. * @param {SAT.Response} response - The response object to populate with overlap information. * @param {SAT.Vector} velocity - The velocity vector to ignore. * @param {SAT.Vector[]} ignore - The axes to ignore. * @return {boolean} - Whether the the two polygons overlap. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.testPolygonPolygon = function (a, b, response, velocity, ignore) { var aPoints = a.calcPoints; var aLen = aPoints.length; var bPoints = b.calcPoints; var bLen = bPoints.length; var i, j, k; var responses = this.arrayPool.pop(); var axes = this.arrayPool.pop(); responses.length = 0; axes.length = 0; // If any of the edge normals of A is a separating axis, no intersection for (i = 0; i < aLen; i++) { responses[i] = this.responsePool.pop(); responses[i].clear(); axes[i] = a.normals[i]; if (this.isSeparatingAxis(a, b, a.normals[i], responses[i])) { for (k = 0; k < responses.length; k++) { this.responsePool.push(responses[k]); } this.arrayPool.push(responses, axes); return false; } } // If any of the edge normals of B is a separating axis, no intersection for (i = 0, j = aLen; i < bLen; i++, j++) { responses[j] = this.responsePool.pop(); responses[j].clear(); axes[j] = b.normals[i]; if (this.isSeparatingAxis(a, b, b.normals[i], responses[j])) { for (k = 0; k < responses.length; k++) { this.responsePool.push(responses[k]); } this.arrayPool.push(responses, axes); return false; } } // Since none of the edge normals of A or B are a separating axis, there is // an intersection var viable = false; var ignored = false; var velocityTestVector = this.vectorPool.pop(); // Determine the shortest desirable and viable separation from the responses for (i = 0; i < responses.length; i++) { // Is the overlap in the range we want? // TODO: Less than the max of tile width/height? if (!(responses[i].overlap > 0 && responses[i].overlap < Number.MAX_VALUE)) { continue; } // Is the overlap direction too close to that of the velocity direction? if (velocity && velocityTestVector.copy(responses[i].overlapN).scale(-1).dot(velocity) > 0) { continue; } ignored = false; // Is the axis of the overlap in the extra ignore list? for (j = 0; j < ignore.length; j++) { if (axes[i].x === ignore[j].x && axes[i].y === ignore[j].y) { ignored = true; break; } } // Skip this response if its normal is ignored if (ignored) { continue; } // Is this response's overlap shorter than that of the current? if (responses[i].overlap < response.overlap) { viable = true; response.aInB = responses[i].aInB; response.bInA = responses[i].bInA; response.overlap = responses[i].overlap; response.overlapN = responses[i].overlapN; } } // Set the polygons on the response and calculate the overlap vector if (viable) { response.a = a; response.b = b; response.overlapV.copy(response.overlapN).scale(response.overlap); } // Recycle the temporary responses, arrays and vectors used for calculations for (k = 0; k < responses.length; k++) { this.responsePool.push(responses[k]); } this.arrayPool.push(responses, axes); this.vectorPool.push(velocityTestVector); return viable; }; /** * Separate the given body and tile from each other and apply any relevant * changes to the body's velocity. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#collide * @param {integer} i - The tile index. * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {Phaser.Tile} tile - The tile. * @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer. * @param {boolean} overlapOnly - Whether to only check for an overlap. * @return {boolean} - Whether the body was separated. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.collide = function (i, body, tile, tilemapLayer, overlapOnly) { // Update the body's polygon position and velocity vector this.updateValues(body); // Bail out if we don't have everything we need if (!this.shouldCollide(body, tile)) { return false; } // Cater for SAT.js requiring center-origin circles if (body.isCircle) { body.polygon.pos.x += body.halfWidth; body.polygon.pos.y += body.halfHeight; } // Update the tile polygon position tile.slope.polygon.pos.x = tile.worldX + tilemapLayer.getCollisionOffsetX(); tile.slope.polygon.pos.y = tile.worldY + tilemapLayer.getCollisionOffsetY(); // Create the body's response if it doesn't have one body.slopes.sat.response = body.slopes.sat.response || new SAT.Response(); // Acquire a temporary response from the pool var response = this.responsePool.pop(); Phaser.Plugin.ArcadeSlopes.SatSolver.resetResponse(response); // Test for an overlap var circleOverlap = body.isCircle && SAT.testCirclePolygon(body.polygon, tile.slope.polygon, response); var polygonOverlap = !body.isCircle && this.testPolygonPolygon(body.polygon, tile.slope.polygon, response, body.slopes.velocity, tile.slope.ignormals); // Bail if there isn't one, leaving the body's response as is if (!circleOverlap && !polygonOverlap) { this.responsePool.push(response); return false; } // Invert our overlap vectors so that we have them facing outwards Phaser.Plugin.ArcadeSlopes.SatSolver.prepareResponse(response); // If we're only testing for the overlap, we can bail here if (overlapOnly) { Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse(response, body.slopes.sat.response); this.responsePool.push(response); return true; } // Bail out if no separation occurred if (!this.separate(body, tile, response)) { this.responsePool.push(response); return false; } // Copy the temporary response into the body's response, then recycle it Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse(response, body.slopes.sat.response); this.responsePool.push(response); response = body.slopes.sat.response; // Update the overlap properties of the body body.overlapX = response.overlapV.x; body.overlapY = response.overlapV.y; // Set the tile that the body separated from body.slopes.tile = tile; // Apply any velocity changes as a result of the collision this.applyVelocity(body, tile, response); // Update the touching and blocked flags of the physics body this.updateFlags(body, response); return true; }; /** * Determine whether to separate a body from a tile, given an SAT response. * * Checks against the tile's collision flags and slope edge flags. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldSeparate * @param {integer} i - The tile index. * @param {Phaser.Physics.Arcade.Body} body - The physics body. * @param {Phaser.Tile} tile - The tile. * @param {SAT.Response} response - The initial collision response. * @return {boolean} - Whether to pursue the narrow phase. */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldSeparate = function (i, body, tile, response) { // Bail if the body is disabled or there is no overlap if (!(body.enable && response.overlap)) { return false; } // Only separate if the body is moving into the collision // if (response.overlapV.clone().scale(-1).dot(body.slopes.velocity) < 0) { // return false; // } // Otherwise we should separate normally return true; }; /** * Render the given SAT response as a set of lines from the given position. * * TODO: Actually maybe just collect the lines here for drawing later? * Or, make this static and just something you can call in the * context of a game, or game state. * * @method Phaser.Plugin.ArcadeSlopes.SatSolver#debug * @param {Phaser.Point} position * @param {SAT.Response} response */ Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.debug = function (position, response) { // TODO: Implement. };