├── .gitignore ├── LICENSE ├── README.md ├── defmath └── defmath.lua ├── example ├── example.script ├── main.collection └── main.input_binding ├── game.project └── scrap.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .externalToolBuilders 2 | .DS_Store 3 | .lock-wscript 4 | build 5 | *.pyc 6 | .project 7 | .cproject 8 | builtins 9 | .internal 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DefMath 2 | A module with a set of math functions for Defold 3 | 4 | ## Installation 5 | You can use DefMath in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add: 6 | 7 | https://github.com/subsoap/defmath/archive/master.zip 8 | 9 | Once added, you must require the main Lua module via 10 | 11 | ``` 12 | local defmath = require("defmath.defmath") 13 | ``` 14 | Then you can use the DefMath functions for example via 15 | 16 | ``` 17 | print(defmath.round_decimal(0.12345678,3)) 18 | ``` 19 | -------------------------------------------------------------------------------- /defmath/defmath.lua: -------------------------------------------------------------------------------- 1 | -- This module has many useful math functions and shortcuts 2 | 3 | local M = {} 4 | 5 | -- Angle-Diff (gets the smallest angle between two angles, using radians) 6 | function M.anglediff_rad(rad1, rad2) 7 | local a = rad1 - rad2 8 | a = (a + math.pi) % (math.pi * 2) - math.pi 9 | return a 10 | end 11 | 12 | -- Angle-Diff (gets the smallest angle between two angles, using degrees) 13 | function M.anglediff_deg(deg1, deg2) 14 | local a = deg1 - deg2 15 | a = (a + 180) % (180 * 2) - 180 16 | return a 17 | end 18 | 19 | -- Round 20 | function M.round(x) 21 | local a = x % 1 22 | x = x - a 23 | if a < 0.5 then a = 0 24 | else a = 1 end 25 | return x + a 26 | end 27 | 28 | -- Round to decimal point 29 | function M.round_decimal(number, decimal) 30 | decimal = 10 ^ (decimal or 0) 31 | return math.floor(number*decimal+0.5)/decimal 32 | end 33 | 34 | -- Clamp 35 | function M.clamp(x, min, max) 36 | if x > max then x = max 37 | elseif x < min then x = min 38 | end 39 | return x 40 | end 41 | 42 | -- Sign 43 | function M.sign(x) 44 | if x >= 0 then return 1 45 | else return -1 46 | end 47 | end 48 | 49 | -- Checks if two numbers have the same sign 50 | function M.same_sign(a,b) 51 | return M.sign(a) == M.sign(b) 52 | end 53 | 54 | -- Vect to Quat 55 | function M.vect_to_quat(vect) 56 | return vmath.quat_rotation_z(math.atan2(vect.y, vect.x)) 57 | end 58 | 59 | -- Vect to Quat + 90 degrees (perpendicular) 60 | function M.vect_to_quat90(vect) 61 | return vmath.quat_rotation_z(math.atan2(vect.y, vect.x) + math.pi/2) 62 | end 63 | 64 | -- Random float from -1 to 1 65 | function M.rand11() 66 | return((math.random() - 0.5) * 2) 67 | end 68 | 69 | -- Random float in range 70 | function M.rand_range(min, max) 71 | return math.random() * (max - min) + min 72 | end 73 | 74 | -- Linear interpolation 75 | function M.lerp(start, stop, amount) 76 | amount = M.clamp(amount, 0, 1) 77 | return((1-amount) * start + amount * stop) 78 | end 79 | 80 | -- Distance between two 2d points 81 | function M.dist2d(x1, y1, x2, y2) 82 | return ((x2-x1)^2+(y2-y1)^2)^0.5 83 | end 84 | 85 | -- Distance betwen two 3d points 86 | function M.dist3d(x1, y1, z1, x2, y2, z2) 87 | return ((x2-x1)^2+(y2-y1)^2+(z2-z1)^2)^0.5 88 | end 89 | 90 | -- Angle in radians of vector between two points 91 | function M.angle_of_vector_between_two_points(x1,y1, x2,y2) 92 | return math.atan2(y2-y1, x2-x1) 93 | end 94 | 95 | -- Clears bad RNG and sets seed to be based on OS time 96 | function M.setup_rng() 97 | math.randomseed(os.time() + math.random()) 98 | math.random(); math.random(); math.random() -- clear bad rng 99 | end 100 | 101 | -- Check if number is within range 102 | function M.is_within_range(number, min, max) 103 | return number >= min and number <= max 104 | end 105 | 106 | -- Convert radians to degrees 107 | function M.radians_to_degrees(radian) 108 | return 180 / math.pi * radian 109 | end 110 | 111 | -- Convert degrees to radians 112 | function M.degrees_to_radians(degrees) 113 | return math.pi / 180 * degrees 114 | end 115 | 116 | -- Wrap a number to betwen 0 and a range bound 117 | function M.wrap(number, bound) 118 | if (number < 0) then 119 | return bound - (math.fmod(-number, bound)) 120 | else 121 | return math.fmod(number, bound) 122 | end 123 | end 124 | 125 | -- Bound a number so that it is no larger than max and no smaller than min 126 | function M.bound(number, min, max) 127 | if number < min then 128 | number = min 129 | elseif number > max then 130 | number = max 131 | end 132 | return number 133 | end 134 | 135 | -- Adds an amount to a value within the value going over max or below min 136 | function M.bound_add(number, amount, min, max) 137 | number = number + amount 138 | return M.bound(number, min, max) 139 | end 140 | 141 | -- Sums a set of values 142 | function M.sum(...) 143 | local sum = 0 144 | for a = 1, select("#", ...) do 145 | local v = select(a, ...) 146 | sum = sum + v 147 | end 148 | return sum 149 | end 150 | 151 | -- Average of set of values 152 | function M.average(...) 153 | local arg ={...} 154 | return (M.sum(...) / #arg) 155 | end 156 | 157 | -- Get difference between two values 158 | function M.difference(a,b) 159 | return math.abs(a-b) 160 | end 161 | 162 | -- if a number is even 163 | function M.is_even(number) 164 | return math.fmod(number, 2) == 0 165 | end 166 | 167 | -- if a number is odd 168 | function M.is_odd(number) 169 | return M.is_even(number) ~= true 170 | end 171 | 172 | -- Wrap an angle between -180 and +180 173 | function M.wrap_angle(angle) 174 | if angle > 180 then 175 | angle = M.wrap_angle(angle - 360) 176 | elseif angle < -180 then 177 | angle = M.wrap_angle(angle + 360) 178 | end 179 | return angle 180 | end 181 | 182 | -- Angle between vmath vectors in radians 183 | function M.angle_between_vectors(vector1, vector2) 184 | return math.atan2(vector2.y-vector1.y, vector2.x-vector1.x) 185 | end 186 | 187 | -- Average midpoint of a set of vmath vectors 188 | function M.average_midpoint_of_vectors(...) 189 | local vectors = {...} 190 | local x,y,c = 0,0,#vectors 191 | for i=1, c do 192 | x = x + vectors[i].x 193 | y = y + vectors[i].y 194 | end 195 | return x/c, y/c 196 | 197 | end 198 | 199 | -- Fraction of 200 | function M.fraction_of(a,b) 201 | return a/b 202 | end 203 | 204 | -- Percent of 205 | function M.percent_of(a,b) 206 | return M.fraction_of(a,b) * 100 207 | end 208 | 209 | -- Used for MinMax Range 210 | function M.min_max(min, max, value) 211 | return math.min(math.max(value, min), max) 212 | end 213 | 214 | function M.min_value(smallest, value) 215 | return math.max(value, smallest) 216 | end 217 | 218 | function M.max_value(largest, value) 219 | return math.min(value, largest) 220 | end 221 | 222 | -- Used to find a point in a tringle 223 | -- x0, y0 are the points that are being checked against the rest to see if it's inside 224 | function M.in_triangle(x0, y0, x1, y1, x2, y2, x3, y3) 225 | local b0 = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1) 226 | local b1 = ( (x2 - x0) * (y3 - y0) - (x3 - x0) * (y2 - y0)) / b0 227 | if b1 <= 0 then return false end 228 | 229 | local b2 = ( (x3 - x0) * (y1 - y0) - (x1 - x0) * (y3 - y0)) / b0 230 | if b2 <= 0 then return false end 231 | 232 | local b3 = ( (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)) / b0 233 | if b3 <= 0 then return false end 234 | 235 | return true 236 | end 237 | 238 | -- arr must be 6 in legth: top x/y, bottom right x/y, bottom left x/y 239 | function M.in_triangle(x0, y0, arr) 240 | 241 | local b0 = (arr[3] - arr[1]) * (arr[6] - arr[2]) - (arr[5] - arr[1]) * (arr[4] - arr[2]) 242 | local b1 = ( (arr[3] - x0) * (arr[6] - y0) - (arr[5] - x0) * (arr[4] - y0)) / b0 243 | if b1 <= 0 then return false end 244 | 245 | local b2 = ( (arr[5] - x0) * (arr[2] - y0) - (arr[1] - x0) * (arr[6] - y0)) / b0 246 | if b2 <= 0 then return false end 247 | 248 | local b3 = ( (arr[1] - x0) * (arr[4] - y0) - (arr[3] - x0) * (arr[2] - y0)) / b0 249 | if b3 <= 0 then return false end 250 | 251 | return true 252 | end 253 | -- box intersection of 2 square 254 | function M.box_intersect(ax, ay, awidth, aheight, bx, by, bwidth, bheight) 255 | return (math.abs(ax - bx) * 2 < (awidth + bwidth)) and (math.abs(ay - by) * 2 < (aheight + bheight)) 256 | end 257 | --see if a single point is within a rotated rectangle 258 | function M.check_point_in_rect(rx, ry, rw, rh, rot, px, py) 259 | local dx = px - rx 260 | local dy = py - ry 261 | 262 | local h1 = math.sqrt(dx * dx + dy * dy) 263 | local currA = math.atan2(dy, dx) 264 | 265 | local newA = currA - rot 266 | 267 | local x2 = math.cos(newA) * h1 268 | local y2 = math.sin(newA) * h1 269 | 270 | if (x2 > - 0.5 * rw and x2 < 0.5 * rw and y2 > - 0.5 * rh and y2 < 0.5 * rh) then 271 | return true 272 | end 273 | 274 | return false 275 | end 276 | 277 | return M 278 | -------------------------------------------------------------------------------- /example/example.script: -------------------------------------------------------------------------------- 1 | local defmath = require("defmath.defmath") 2 | 3 | function init(self) 4 | self.wrap_counter = 0 5 | self.wrap_angle_counter = 0 6 | 7 | print(defmath.dist2d(0,0,100,100)) -- 141.42 8 | print(defmath.dist3d(0,0,0,100,100,100)) -- 173.205 9 | print(defmath.angle_of_vector_between_two_points(0,0,0,1)) -- radians, 1.5707963267949, 90deg 10 | print(defmath.round_decimal(0.12345678,3)) 11 | defmath.setup_rng() 12 | print(math.random(1,100)) 13 | print(defmath.radians_to_degrees(defmath.degrees_to_radians(90))) 14 | print(defmath.sum(1,1,1,1,1)) 15 | print(defmath.average(1,2,3,4,5,6,7,8,9)) 16 | print(defmath.difference(-10,10)) 17 | print(defmath.is_even(10)) 18 | print(defmath.is_odd(10)) 19 | print(defmath.wrap_angle(360)) 20 | 21 | local vector1 = vmath.vector3(1,0,0) 22 | local vector2 = vmath.vector3(0,1,0) 23 | 24 | print(defmath.radians_to_degrees(defmath.angle_between_vectors(vector1,vector2))) 25 | 26 | local vector3 = vmath.vector3(-1,0,0) 27 | local vector4 = vmath.vector3(0,-1,0) 28 | 29 | print(defmath.average_midpoint_of_vectors(vector1, vector2, vector3, vector4)) 30 | 31 | print(defmath.fraction_of(5,15)) 32 | print(defmath.percent_of(5,15)) 33 | end 34 | 35 | function final(self) 36 | -- Add finalization code here 37 | -- Remove this function if not needed 38 | end 39 | 40 | function update(self, dt) 41 | -- remove and false to test these 42 | while self.wrap_counter < 30 and false do 43 | self.wrap_counter = self.wrap_counter + 1 44 | print(defmath.wrap(self.wrap_counter,10)) 45 | end 46 | while self.wrap_angle_counter < 1000 and false do 47 | self.wrap_angle_counter = self.wrap_angle_counter + math.random(25) 48 | print(defmath.wrap_angle(self.wrap_angle_counter)) 49 | end 50 | end 51 | 52 | function on_message(self, message_id, message, sender) 53 | -- Add message-handling code here 54 | -- Remove this function if not needed 55 | end 56 | 57 | function on_input(self, action_id, action) 58 | -- Add input-handling code here 59 | -- Remove this function if not needed 60 | end 61 | 62 | function on_reload(self) 63 | -- Add reload-handling code here 64 | -- Remove this function if not needed 65 | end 66 | -------------------------------------------------------------------------------- /example/main.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"example\"\n" 7 | " component: \"/example/example.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/main.input_binding: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subsoap/defmath/c67c227322334056cea7a631f3ddcdf2bcfd480c/example/main.input_binding -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = DefMath 3 | 4 | [library] 5 | include_dirs = defmath 6 | 7 | [input] 8 | game_binding = /example/main.input_bindingc 9 | 10 | [bootstrap] 11 | main_collection = /example/main.collectionc 12 | 13 | -------------------------------------------------------------------------------- /scrap.txt: -------------------------------------------------------------------------------- 1 | Explore the vmath module of Defold. Many of the functions below have built in versions. The information below is provided as a reference in case you have need. 2 | 3 | You can use ZeroBrane Studio to quickly test code samples. 4 | 5 | Angles, Radians, Degrees 6 | 7 | With Defold (and in math), 0 degrees is equal to east, 90 to north, 180 to west, 270 to south, and 360 to east again. 8 | 9 | Radians are often used in game development as an alternative to degrees for representing angles. Many programming languages have built in functions for converting between the two. Radians are far more useful, are cleaner, and easier to use once you understand them. If you do not have a strong understanding of them you should dig into any resources you can find which explain them until they click for you. 10 | 11 | Radians are based on the radius of a circle (the distance between the center of a circle and any point along its length) - 1 radius measure is equal to 1 radian along the circle's length. 1 radian is about 57 degrees. 12 | 13 | There are 2pi radians in a circle. In most math, and in Defold, we begin to count from 0 facing right and then begin to count counter clockwise upward for pi/2 (90 degrees), then continue to count until we are facing left at pi (180 degrees), then continue to count until we are facing downward at 3pi/2 (270 degrees), and then continuing to count until we are again facing right at 2pi (360 degrees) to complete a full circle. 14 | 15 | There are about 6.283... radians for the length of a circle. That is equal to 2 * pi. To visualize this better imagine you have a picture of a circle and a dot at its center. You take some string and cut it to the length from center of the circle to any point on the circle's circumference. You place the cut string along the circumference, and repeat measuring, cutting string, and placing. You will find that your seventh piece of string doesn't fit - only about 28% of its length fits. So you have 6.28... pieces of string. 16 | 17 | math.pi = 3.14... 18 | math.deg() converts radians into degrees 19 | math.rad() converts degrees into radians 20 | 21 | To convert from degrees to radians divide the degrees by (180/pi). 22 | To convert from radians to degrees multiply the radians by (180/pi). 23 | 24 | print(math.deg(2 * math.pi)) -- 360 25 | print((2 * math.pi) * (180/math.pi)) -- 360 26 | 27 | print(math.rad(360)) -- 2pi ... 6.2831853071796 28 | print(360 / (180/math.pi)) -- 2pi ... 6.2831853071796 29 | print(2*math.pi) -- 2pi ... 6.2831853071796 30 | 31 | 2pi = one full circle 32 | pi = one half circle 33 | 2pi/3 = one third circle 34 | pi/2 = one fourth circle 35 | 2pi/5 = one fifth circle 36 | pi/3 = one sixth circle 37 | 2pi/7 = one seventh circle 38 | pi/4 = one eighth circle 39 | 2pi/9 = one ninth circle 40 | pi/5 = one tenth circle 41 | 2pi/11 = one eleventh circle 42 | pi/6 = one twelfth circle 43 | 44 | 30 degrees = pi/6 radians 45 | 45 degrees = pi/4 radians 46 | 60 degrees = pi/3 radians 47 | 90 degrees = pi/2 radians 48 | 180 degrees = pi radians 49 | 270 degrees = 3pi/2 radians 50 | 360 degrees = 2pi radians 51 | 52 | Dot Product (inner product) - Calculating Angle Differences 53 | 54 | The dot product of two vectors tells you how far apart they are in a single number - the angle amount between them. It produces a scalar value - how much of one vector is pointing in the direction of another. 55 | 56 | vector1.x * vector2.x + vector1.y * vector2.y 57 | 58 | This is equal to 59 | 60 | vector1.magnitude * vector2.magnitude * math.cos(angle between vector 1 and vector 2) 61 | 62 | If vector1 and vector2 are unit vectors (magnitude of 1) then their dot product will be equal to just math.cos of the angle between the two vectors. Defold does have built in functionality for creating and using vectors and normalization of vectors which you should use when working with the built in vectors. 63 | 64 | If you are using unit vectors then the dot product of two vectors can give you useful information. This can be useful when you want to avoid other CPU expensive functionality. 65 | 66 | If > 0 : angle between vectors is less than 90 degrees - it is acute 67 | If < 0 : angle between vectors is more than 90 degrees - it is obtuse 68 | If == 0 : angle between vectors is 90 degrees - the vectors are orthogonal 69 | If == 1 : angle between the two vectors is 0 degrees - vectors are parallel and point in same direction 70 | If == -1 : angle between the two vectors is 180 degrees - vectors are parallel, point in opposite directions 71 | 72 | This information can be used to find relative orientation of other objects in your game. Such as if certain objects are facing others. If you rotate one of the vectors by 90 degrees first you can find if objects are to the left or the right of each other. 73 | 74 | Rounding errors may give you values very close to these options in situations where they should be exact (such as being exactly 0). You may want to add extra code to see if values are close enough for you to accept the various conditions. 75 | 76 | Cross Product (vector product) tells you the perpendicular direction of any two vectors, how much a vector is perpendicular to another. It can give the axis to rotate around to face something. The 2d form returns a scalar. Its 3d form returns a vector. 77 | 78 | cross_product_vectors = function (x1, y1, x2, y2) 79 | return x1*y2 - y1*x2 -- 0 == parallel 80 | end 81 | 82 | cross_product_vectors_3d = function (x1, y1, z1, x2, y2, z2) 83 | resX = y1 * z2 - z1 * y2 84 | resY = z1 * x2 - x1 * z2 85 | resZ = x1 * y2 - y1 * x2 86 | return resX, resY, resZ 87 | end 88 | 89 | If the z of both vectors in the 3d version is zero then the 3d form will return the "same end result" as the 2d form. 90 | 91 | print(cross_product_vectors(1,2,3,4)) -- -2 92 | print(cross_product_vectors_3d(1,2,0,3,4,0)) -- 0 0 -2 93 | 94 | 95 | 96 | Angle Between Two 2D Vectors - difference in orientation 97 | 98 | angle_between_vectors = function (x1, y1, x2, y2) 99 | local arc = math.sqrt((x1*x1 + y1*y1) * (x2 * x2 + y2 * y2)) 100 | if arc > 0 then 101 | arc = math.acos((x1*x2 + y1*y2) / arc) 102 | if x1*y2 - y1*x2 < 0 then 103 | arc = -arc 104 | end 105 | end 106 | return arc 107 | end 108 | 109 | print(angle_between_vectors(1,1,-1,-1)) -- 3.14... 180 degrees 110 | 111 | Angles in Defold 112 | 113 | When changing the direction of a 2d object in Defold, by using the Euler (pronounced oiler) property, the angle starts facing right and 0 degrees, goes up to 90 degrees and so on. 114 | 115 | go.animate("." "euler.z", go.PLAYBACK_LOOP_PINGPONG, 360, go.EASING_LINEAR, 10) 116 | 117 | Animates the current Game Object so that its angle direction on the z plane is animated back and forth between its current (default 0) and + 360 amount of difference. If you were to first set the euler.z to another value first you would see the animation would go back and forth between a 360 degree arc from that value and +360 degrees of that value. 118 | 119 | go.set(".", "euler.z", 180) 120 | go.animate("." "euler.z", go.PLAYBACK_LOOP_PINGPONG, 360, go.EASING_LINEAR, 10) 121 | 122 | Points and Vectors 123 | 124 | A point is a coordinate - it can be x,y for a coordinate on a 2D space, or x,y,z for a coordinate on a 3D space. 125 | 126 | A vector is a direction toward a point (relative to an origin) along with a magnitude (length). 127 | 128 | Convert Radian Angle to a 2D Vector 129 | 130 | angle_to_vector = function (angle, magnitude) 131 | magnitude = magnitude or 1 -- if no magnitude supplied make it a unit vector 132 | local x = math.cos ( angle ) * magnitude 133 | local y = math.sin ( angle ) * magnitude 134 | return x, y 135 | end 136 | 137 | Converting Vectors to Radian Angle 138 | vector_to_angle = function (x,y) 139 | return math.atan2 (y, x) 140 | end 141 | 142 | temp_angle = math.pi 143 | temp_vector_x, temp_vector_y = angle_to_vector(temp_angle) 144 | print(temp_vector_x, temp_vector_y) -- -1 1.2246063538224e-016 145 | new_angle = vector_to_angle(temp_vector_x, temp_vector_y) 146 | print(new_angle) -- 3.1415926535898 147 | 148 | x = math.cos(math.pi) 149 | y = math.sin(math.pi) 150 | radian = math.atan2(y,x) 151 | print(radian) 152 | print(math.pi) 153 | 154 | Get magnitude (length) of 2D Vector 155 | 156 | get_vector_magnitude = function (x,y) 157 | return math.sqrt( x * x + y * y) 158 | end 159 | 160 | print(get_vector_magnitude(2,9)) -- 9.21... 161 | 162 | Get magnitude (length) of 3D Vector 163 | 164 | get_vector_magnitude_3d = function (x,y,z) 165 | return math.sqrt( x * x + y * y + z * z) 166 | end 167 | 168 | Rotating 2D Vectors 169 | 170 | Vectors can be rotated without computation by 90 or 180 degrees simply by swapping / negating their x,y values. 171 | 172 | x,y = -y,x to rotate 90 degrees counterclockwise 173 | x,y = y,-x to rotate 90 degrees clockwise 174 | x,y = -x, -y to rotate 180 degrees 175 | 176 | Rotating a vector based on a radian angle 177 | 178 | rotate_vector = function(x, y, angle) 179 | local c = math.cos(angle) 180 | local s = math.sin(angle) 181 | return c*x - s*y, s*x + c*y 182 | end 183 | 184 | print(rotate_vector(1,1, math.pi)) -- rotates by 180 degrees to -1, -1 185 | 186 | Scaling 2D Vectors 187 | 188 | To scale a vector means to increase its magnitude (length) by another magnitude - here called a scalar. A scaled vector has the same orientation but a new magnitude. 189 | 190 | scale_vector = function (x,y,scalar) 191 | return x * scalar, y * scalar 192 | end 193 | 194 | If you scale a vector by its inverse length (1 / magnitude of vector) the vector will get a length of 1 - it will become a unit vector - it will become normalized. 195 | 196 | Normalizing 2D Vectors 197 | 198 | Normalization of vectors maintains their orientation, but reduces their magnitude (length) to 1. This new vector is called a unit vector, and is very useful for certain calculations. 199 | 200 | normalize_vector = function (x,y) 201 | if x == 0 and y == 0 then return 0, 0 end 202 | local magnitude = math.sqrt(x * x + y * y) 203 | return x / magnitude, y / magnitude 204 | end 205 | 206 | print(normalize_vector(100,0)) --> 1,0 207 | 208 | 209 | Checking Circular Collisions - Check for collisions between circle shapes 210 | 211 | circular_collision_check = function (ax, ay, bx, by, ar, br) 212 | -- x and y positions both both objects 213 | -- along with their radius 214 | local combined_radius = ar + br 215 | local dx = bx - ax 216 | local dy = by - ay 217 | local dist = math.sqrt(dx * dx + dy * dy) -- check distance between objects 218 | return dist < combined_radius -- check if the distance is less than the combined radius (overlap) 219 | end 220 | 221 | print(circular_collision_check(1,1,30,25,5,10)) -- false 222 | print(circular_collision_check(1,2,36,25,50,100)) -- true 223 | 224 | Distance Between 2D Points - Find distance between objects on a 2d space 225 | 226 | distance_between_points = function (x1,y1,x2,y2) 227 | local dX = x2 - x1 228 | local dY = y2 - y1 229 | return math.sqrt((dX^2) + (dY^2)) 230 | end 231 | 232 | print(distance_between_points(0,0,1,1)) -- 1.4142... 233 | print(distance_between_points(0,0,0,8)) -- 8 234 | print(distance_between_points(12,4,45,64)) -- 8 235 | 236 | Distance Between 3D Points - Find distance between objects in a 3d space 237 | 238 | distance_between_points_3d = function (x1,y1,z1,x2,y2,z2) 239 | local dX = x2 - x1 240 | local dY = y2 - y1 241 | local dZ = z2 - z1 242 | return math.sqrt((dX^2) + (dY^2) + (dZ^2)) 243 | end 244 | print(distance_between_points_3d(0,0,0,1,1,0)) -- 1.4142... 245 | 246 | Get the distance between two x positions 247 | 248 | get_distance_x = function (x1, x2) 249 | return math.sqrt( (x2 - x1) ^ 2) 250 | end 251 | 252 | print(get_distance_x(-5,10)) -- 15 253 | 254 | Get the distance between two y positions 255 | 256 | get_distance_y = function (y1, y2) 257 | return math.sqrt( (y2 - y1) ^ 2) 258 | end 259 | 260 | print(get_distance_y(-5,10)) -- 15 261 | 262 | Average of radian angles 263 | 264 | average_angles = function (...) 265 | local x,y = 0,0 266 | for i=1, select("#", ...) do 267 | local a = select(i, ...) 268 | x, y = x + math.cos(a), y + math.sin(a) 269 | end 270 | return math.atan2(y, x) 271 | end 272 | 273 | print(average_angles(1.4, 1.2, 1.6)) 274 | 275 | Find the angle in degrees between 0,0 and a point 276 | 277 | angle_of_point = function (x, y) 278 | local radian = math.atan2(y,x) 279 | local angle = radian*180/math.pi 280 | if angle < 0 then angle = 360 + angle end 281 | return angle 282 | end 283 | 284 | print(angle_of_point(0,-4)) 285 | 286 | Find the degrees between two points 287 | 288 | angle_between_points = function (x1, y1, x2, y2) 289 | local dX, dY = x2 - x1, y2 - y1 290 | return angle_of_point(dX, dY) 291 | end 292 | 293 | print(angle_between_points(0,0,1,1)) -- 45 294 | 295 | Find smallest angle between two angles - for dealing with "angles" below 0 or greater than 360 and finding the shortest turn to reach the second 296 | 297 | smallest_angle_difference = function ( a1, a2 ) 298 | local a = a1 - a2 299 | 300 | if ( a > 180 ) then 301 | a = a - 360 302 | elseif (a < -180) then 303 | a = a + 360 304 | end 305 | 306 | return a 307 | end 308 | 309 | print(smallest_angle_difference(720,270)) -- 90 310 | 311 | Round up to the nearest multiple of a number - say you have a number 450 and you want to round it to 500, or round to 1000 if the number was 624 - this sort of thing is useful for dynamic layouts 312 | 313 | round_up_nearest_multiple = function (input, target) 314 | return math.ceil( input / target) * target 315 | end 316 | 317 | print(round_up_nearest_multiple(24, 100)) -- 100 318 | print(round_up_nearest_multiple(124, 100)) -- 200 319 | 320 | Rounds up or down to the nearest container size multiple 321 | 322 | round_nearest_multiple = function (input, target) 323 | local result = math.floor( (input / target) + 0.5) * target 324 | if result == 0 then result = target end -- ensure non-zero container size 325 | return result 326 | end 327 | 328 | print(round_nearest_multiple(24, 100)) -- 100 329 | print(round_nearest_multiple(124, 100)) -- 100 330 | print(round_nearest_multiple(155, 100)) -- 200 331 | 332 | Clamps a number into a range 333 | 334 | clamp_number = function (number, low, high) 335 | return math.min(math.max(number, low), high) 336 | end 337 | 338 | print(clamp_number(45, 0, 100)) -- 45 339 | print(clamp_number(45, 50, 100)) -- 50 340 | print(clamp_number(101, 0, 100)) -- 100 341 | 342 | Lerp - linear interpolation - useful to use with delta time (dt) to get smooth movements 343 | 344 | lerp_number = function (start, stop, amount) 345 | amount = clamp_number(amount, 0, 1) -- amount is also called the "factor" 346 | return((1-amount) * start + amount * stop) 347 | end 348 | 349 | print(lerp_number(0,1,0.4)) -- 0.4 350 | print(lerp_number(20,100,0.1)) -- 28 351 | 352 | 353 | Normalize (percentage) two numbers so that they sum to unity (1) 354 | Note that there are multiple meanings for "normalization" 355 | This kind of normalization is useful for example for turning event counts into probabilities 356 | Or conditional probabilities, such as rolling a dice 357 | Also useful in graphics stats 358 | When normalizing vectors it means change the magnitude to 1 so that it's more useful for orientation 359 | 360 | normalize_numbers = function (a,b) 361 | n = a + b 362 | if n == 0 then return 0,0,0 363 | else 364 | return a/n,b/n,n 365 | end 366 | end 367 | 368 | print(normalize_numbers(5,10)) -- 0.333... 0.666... 3 369 | print(normalize_numbers(1,99)) -- 0.01 0.99 100 370 | 371 | Get sign of number : 1 if positive, -1 if negative, 0 if 0 372 | 373 | get_sign = function (number) 374 | return number > 0 and 1 or number < 0 and -1 or 0 375 | end 376 | 377 | Round a number 378 | 379 | round_number = function(number) 380 | return math.floor(number + 0.5) 381 | end 382 | 383 | -- normalize all values of an array to be within the range of 0 to 1 based on their "weight" 384 | -- divide each number in your sample by the sum of all the numbers in your sample 385 | 386 | Matrices 387 | 388 | A matrix is an array of related values. Vectors can be represented as matrices. In most game dev situations, you will be using matrices which have the same number of rows and columns. 389 | 390 | A B 391 | C D 392 | 393 | The above is a matrix with a 2x2 dimension. A is element 1,1 and D is element 2,2. 394 | 395 | A B C D 396 | E F G H 397 | 398 | The above is a matrix with a 2x4 dimension. A is element 1,1 and H is element 4,2. 399 | 400 | Vectors can be represented as matrices either as a long row or a tall column. These representations allow you to modify matrices with vectors. 401 | 402 | x y z 403 | 404 | or 405 | 406 | x 407 | y 408 | z 409 | 410 | Adding, Subtracting of Matrices 411 | 412 | To add two matrices together you simply add the elements together. Same with subtraction. 413 | 414 | A B 415 | C D 416 | + 417 | W X 418 | Y Z 419 | = 420 | A+W B+X 421 | C+Y Z+D 422 | 423 | Multiplying Matrices 424 | 425 | You can multiply a matrix by a single number, a scalar, by multiplying that number by each element individually. 426 | 427 | 2 428 | x 429 | 1 0 430 | 2 -4 431 | = 432 | 2 0 433 | 4 -8 434 | 435 | To multiply two matrices together it requires to do the dot product of the rows and columns - multiply matching members and then add them up together. 436 | 437 | Consider two matrices multiplied together 438 | 439 | 1 2 3 440 | 4 5 6 441 | x 442 | 7 8 443 | 9 10 444 | 11 12 445 | = 446 | (1,2,3)*(7,9,11) (1,2,3)*(8,10,12) 447 | (4,5,6)*(7,9,11) (4,5,6)*(8,10,12) 448 | = 449 | 1*7+2*9+3*11 1*8+2*10+3*12 450 | 4*7+5*9+6*11 4*8+5*10+6*12 451 | = 452 | 58 64 453 | 139 154 454 | 455 | 456 | To be able to multiply two matrices, the number of columns in the first matrix must equal the number of rows in the second matrix. The resulting matrix will have the same number of rows as the second matrix, and the same number of columns as the first matrix. A 1x3 matrix multiplied by a 3x4 matrix produces a 1x4 matrix. 457 | 458 | A B 459 | C D 460 | * 461 | W X 462 | Y Z 463 | = 464 | A*W+B*Y A*X+B*Z 465 | C*W+D*Y C*X+D*Z 466 | 467 | Matrix multiplication is not commutative, order matters. If you multiply two matrices together in a different order you will get different results. 468 | 469 | 1 2 470 | 3 4 471 | x 472 | 5 6 473 | 7 8 474 | = 475 | (1,2)*(5,7) (1,2)*(6,8) 476 | (3,4)*(5,7) (3,4)*(6,8) 477 | = 478 | 1*5+2*7 1*6+2*8 479 | 3*5+4*7 3*6+4*8 480 | = 481 | 19 22 482 | 43 50 483 | 484 | vs 485 | 486 | 5 6 487 | 7 8 488 | x 489 | 1 2 490 | 3 4 491 | = 492 | (5,6)*(1,3) (5,6)*(2,4) 493 | (7,8)*(1,3) (7,8)*(2,4) 494 | = 495 | 5*1+6*3 5*2+6*4 496 | 7*1+8*3 7*2+8*4 497 | = 498 | 23 34 499 | 31 46 500 | 501 | You can write your own functions for deal with matrices, but Defold has most of what you need built into its own API. 502 | 503 | Matrices are mostly used with graphics programming such as transformation matrices in rendering. 504 | 505 | Quaternions 506 | 507 | Unit quaternions (magnitude of 1) are useful for representing orientation. Imagine a sphere with a dot at its center. Imagine an airplane's tail end attached to the dot. The airplane can be rotated around the sphere to face any direction, and it can be rotated so that its upward direction is changed. If you can imagine this then you can imagine how a unit quaternion looks like and how it can be useful. While Euler angles are convenient for 2D z rotation, for anything 3D you will want to use quaternions for representing orientation change. 508 | 509 | 510 | 511 | local function find_center_of_points_2d(points) 512 | local total_x = total_x or 0 513 | local total_y = total_y or 0 514 | local count = #points 515 | 516 | for id, point in pairs(points) do 517 | total_x = total_x + point.x 518 | total_y = total_y + point.y 519 | end 520 | 521 | if not (total_x == 0) then total_x = total_x / count end 522 | if not (total_y == 0) then total_y = total_y / count end 523 | 524 | return total_x, total_y 525 | end 526 | 527 | points_2d = {{x=0,y=0}, {x=10,y=10}, {x=100,y=100}, {x=-10,y=-100}, {x=54,y=-247} } 528 | 529 | midpoint_x, midpoint_y = find_center_of_points_2d(points_2d) 530 | print(midpoint_x, midpoint_y) 531 | 532 | local function find_center_of_points_3d(points) 533 | local total_x = total_x or 0 534 | local total_y = total_y or 0 535 | local total_z = total_z or 0 536 | local count = #points 537 | 538 | for id, point in pairs(points) do 539 | total_x = total_x + point.x 540 | total_y = total_y + point.y 541 | total_z = total_z + point.z 542 | end 543 | 544 | if not (total_x == 0) then total_x = total_x / count end 545 | if not (total_y == 0) then total_y = total_y / count end 546 | if not (total_z == 0) then total_z = total_z / count end 547 | 548 | return total_x, total_y, total_z 549 | end 550 | 551 | points_3d = {{x=0,y=0, z=22}, {x=10,y=10, z=15}, {x=100,y=100, z=0}, {x=-10,y=-100, z=0}, {x=54,y=-247, z=0} } 552 | 553 | midpoint3d_x, midpoint3d_y, midpoint3d_z = find_center_of_points_3d(points_3d) 554 | print(midpoint3d_x, midpoint3d_y, midpoint3d_z) --------------------------------------------------------------------------------