├── .gitignore ├── README.md ├── gamelib.nimble ├── LICENSE ├── gamelib ├── tileset.nim ├── files.nim ├── ticker.nim ├── ninepatch.nim ├── text.nim ├── textureregion.nim ├── tilemap.nim ├── scenegraph.nim ├── animation.nim ├── textureatlas.nim ├── tween.nim └── collisions.nim └── alite.html /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SDLGamelib 2 | A library of functions to make creating games using Nim and SDL2 easier. This does not intend to be a full blown engine and tries to keep all the components loosely coupled so that individual parts can be used separately. 3 | -------------------------------------------------------------------------------- /gamelib.nimble: -------------------------------------------------------------------------------- 1 | [Package] 2 | name = "gamelib" 3 | version = "0.0.2" 4 | author = "Peter Munch-Ellingsen" 5 | description = """A library of functions to make creating games using Nim and SDL2 easier. 6 | This does not intend to be a full blown engine and tries to keep all the components loosly coupled so that individual parts can be used separately.""" 7 | license = "MIT" 8 | 9 | SkipFiles = """ 10 | alite.html 11 | LICENSE 12 | .gitignore 13 | README.md 14 | """ 15 | 16 | [Deps] 17 | Requires: """ 18 | nim >= 0.14.0 19 | sdl2 >= 1.1 20 | """ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 PMunch 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. 22 | -------------------------------------------------------------------------------- /gamelib/tileset.nim: -------------------------------------------------------------------------------- 1 | import json 2 | import textureregion 3 | import sdl2 4 | import sdl2/image 5 | 6 | type 7 | TileSet* = ref object 8 | tiles*: seq[TextureRegion] 9 | 10 | proc initTileset*(): TileSet = 11 | new result 12 | result.tiles = @[] 13 | 14 | proc loadIntoTileset*(renderer: RendererPtr, tileset: TileSet, data: JsonNode, offset = 0) = 15 | assert(data["type"].str == "tileset") 16 | let texture = renderer.loadTexture(data["image"].str) 17 | if tileset.tiles.len < offset + data["tilecount"].num: 18 | tileset.tiles.setLen(offset + data["tilecount"].num) 19 | let 20 | margin = data["margin"].num.cint 21 | spacing = data["spacing"].num.cint 22 | columns = data["columns"].num.cint 23 | tilewidth = data["tilewidth"].num.cint 24 | tileheight = data["tileheight"].num.cint 25 | for tile in 0.. newZ: 105 | renderNode.z = newZ 106 | sceneGraph.delete(renderNode) 107 | var nextNode = renderNode.next 108 | renderNode.next = nil 109 | renderNode.last = nil 110 | sceneGraph.insert(nextNode, renderNode, true) 111 | if renderNode.z < newZ: 112 | renderNode.z = newZ 113 | sceneGraph.delete(renderNode) 114 | var lastNode = renderNode.last 115 | if lastNode == nil: 116 | lastNode = sceneGraph.renderables 117 | renderNode.next = nil 118 | renderNode.last = nil 119 | sceneGraph.insert(lastNode, renderNode, false) 120 | 121 | proc render*(renderer: RendererPtr, sceneGraph: SceneGraph, x,y: cint) = 122 | ## Renders the SceneGraph by calling all the RenderProcs in the order 123 | ## dictated by their Z values 124 | var node = sceneGraph.renderables 125 | while node != nil: 126 | node.render(renderer,x,y) 127 | node = node.next 128 | 129 | proc newSceneGraph*(): SceneGraph = 130 | ## Creates a new, empty, scene graph 131 | new result 132 | -------------------------------------------------------------------------------- /gamelib/animation.nim: -------------------------------------------------------------------------------- 1 | ## An animation is a general purpose container that has a collection of elements 2 | ## which is cycled as time passes. To get the current frame call the frame() 3 | ## procedure. 4 | ## 5 | ## A special class, AnimationTR, is created as an alias for an animation of 6 | ## TextureRegions. As AnimationTR object can either be created from a single 7 | ## texture, texture region or a sequence of texture regions. In the two first 8 | ## cases the texture or texture region will be split into a sequence of texture 9 | ## regions based on it's frames count for internal use (note that the texture is 10 | ## not modified). 11 | ## 12 | ## Currently two AnimationTypes are supported: cycle and pingpong. Cycle wraps 13 | ## around when it comes to the end while pingpong reverses the animation and 14 | ## runs it back backwards. 15 | ## 16 | ## The tick function takes the number of seconds since it was called to ensure 17 | ## that animations are always running at the same speed. 18 | ## 19 | ## Rendering is supported for the AnimationTR type and uses the same options as 20 | ## a texture region. 21 | 22 | 23 | import sdl2 24 | import textureregion 25 | 26 | type 27 | AnimationType* {.pure.} = ## Enum type to select what the animation should do 28 | ## when it comes to it's end. Pingpong means that it reverts direction and 29 | ## cycle means that it starts over. 30 | enum pingpong, cycle 31 | 32 | Animation*[T] = ref object 33 | ## Animation type to pass into these procedures 34 | frames*: seq[T] ## The sequence of frames 35 | frameIndex*: int ## The current frame, change to jump to a specific frame 36 | timeSinceLastFrame: float 37 | longestFrameTime:float 38 | animationType: AnimationType 39 | speed: int 40 | 41 | AnimationTR* = ## Convenience type for an animation of TextureRegions 42 | Animation[TextureRegion] 43 | 44 | proc setFps*(animation: Animation, fps:float) = 45 | ## Set the FPS of the given animation. Can be negative to run the animation in 46 | ## reverse, or zero to stop the animation. 47 | if fps>0: 48 | animation.longestFrameTime = 1/fps 49 | animation.speed = 1 50 | elif fps<0: 51 | animation.longestFrameTime = -1/fps 52 | animation.speed = -1 53 | else: 54 | animation.speed = 0 55 | 56 | proc setAnimationType*(animation: Animation, animationType: AnimationType) = 57 | ## Set the animation type. 58 | animation.animationType = animationType 59 | 60 | proc tick*(animation: Animation, time: float) = 61 | ## Move the animation forwards given the time since the last tick. 62 | if animation.speed != 0: 63 | animation.timeSinceLastFrame += time 64 | while animation.timeSinceLastFrame > animation.longestFrameTime: 65 | animation.frameIndex += animation.speed 66 | if animation.frameIndex > animation.frames.high or animation.frameIndex <= 0: 67 | case animation.animationType: 68 | of AnimationType.cycle: 69 | animation.frameIndex = 0 70 | of AnimationType.pingpong: 71 | animation.speed = if animation.speed == 1: -1 else: 1 72 | animation.frameIndex = animation.frameIndex.clamp(0,animation.frames.high) 73 | animation.timeSinceLastFrame -= animation.longestFrameTime 74 | 75 | proc newAnimation*[T](frames: seq[T], fps: float = 12, animationType: AnimationType = AnimationType.cycle): Animation[T] = 76 | ## Create a new animation from a set of frames and optionally an FPS and 77 | ## AnimationType. 78 | new result 79 | result.frames = frames 80 | result.animationType = animationType 81 | result.speed = 1 82 | result.setFps(fps) 83 | 84 | proc frame*[T](animation: Animation[T]): T= 85 | ## Gets the current frame for an animation 86 | animation.frames[animation.frameIndex] 87 | 88 | 89 | # SDL specific stuff: 90 | 91 | proc render*(renderer: RendererPtr, animation: AnimationTR, x,y: cint, rotation: float = 0, scaleX, scaleY: float = 1, alpha:uint8 = 255) = 92 | ## Convenience procedure for rendering the current frame of an animation of 93 | ## TextureRegions. 94 | renderer.render(animation.frame,x,y,rotation,scaleX,scaleY,alpha) 95 | 96 | template render*(renderer: RendererPtr, animation: AnimationTR, pos: Point, rotation:float = 0, scaleX, scaleY: float = 1, alpha:uint8 = 255) = 97 | ## Convenience procedure for rendering the current frame of an animation of 98 | ## TextureRegions. 99 | render(renderer,animation,pos.x,pos.y,rotation,scaleX,scaleY,alpha) 100 | 101 | proc newAnimation*(textureRegion: TextureRegion, frames: int, fps: float = 12, animationType: AnimationType = AnimationType.cycle): AnimationTR = 102 | ## Convenience procedure to create an Animation from a texture region and the 103 | ## number of frames in the region (horizontal split only for now). 104 | new result 105 | result.animationType = animationType 106 | result.speed = 1 107 | result.frames = @[] 108 | result.setFps(fps) 109 | 110 | var size = textureRegion.size 111 | if not textureRegion.rotated: 112 | size.x = (size.x / frames).cint 113 | else: 114 | size.y = (size.y / frames).cint 115 | 116 | for i in 0... 14 | 15 | # The code for the Bezier curves were copied from: 16 | # https://github.com/gre/bezier-easing and rewritten to Nim. This work is 17 | # licensed under the MIT License and it's original copyright comment is 18 | # included below. 19 | 20 | #[/** 21 | * https://github.com/gre/bezier-easing 22 | * BeazierEasing - use bezier curve for transition easing function 23 | * by Gaëtan Renaudeau 2014 - 2015 – MIT License 24 | */]# 25 | 26 | import tables 27 | import math 28 | 29 | type 30 | EaseFunction* = ## Type for an EaseFunction that can be used in a Tween. 31 | ## Takes the time from 0 to the set duration and returns the current state. 32 | proc(t: float): float 33 | 34 | Tween* = ref object of RootObj 35 | ## Regular tween object that goes from 0 to 1 36 | EasingFunction: proc(x: float): float 37 | cached: bool 38 | cache: float 39 | t*: float 40 | duration*: float 41 | 42 | TweenValue* = ref object of Tween 43 | ## Tween object that goes from the gives start and stop value 44 | fromValue: float 45 | ratio: float 46 | 47 | TweenSeq* = ref object of Tween 48 | ## Tween object that returns a sequence of float between the two sequences 49 | fromValues: seq[float] 50 | ratios: seq[float] 51 | 52 | Ease* {.pure.} = enum 53 | ## Enum type for the built in easing functions 54 | Linear, InSine, OutSine, InOutSine, 55 | InQuad, OutQuad, InOutQuad, 56 | InCubic, OutCubic, InOutCubic, 57 | InQuart, OutQuart, InOutQuart, 58 | InQuint, OutQuint, InOutQuint, 59 | InExpo, OutExpo, InOutExpo, 60 | InCirc, OutCirc, InOutCirc, 61 | InBack, OutBack, InOutBack, 62 | OutBounce, InBounce, InOutBounce 63 | InElastic, OutElastic, InOutElastic 64 | 65 | 66 | #// These values are established by empiricism with tests (tradeoff: performance VS precision) 67 | const 68 | newtonIterations = 4 69 | newtonMinSlope = 0.001 70 | subdivisionPrecision = 0.0000001 71 | subdivisionMaxIterations = 10 72 | 73 | kSplineTableSize = 11 74 | kSampleStepSize = 1.0 / (kSplineTableSize - 1.0) 75 | 76 | proc A (aA1, aA2: float): float = 1.0 - 3.0 * aA2 + 3.0 * aA1 77 | proc B (aA1, aA2: float): float = 3.0 * aA2 - 6.0 * aA1 78 | proc C (aA1: float):float = 3.0 * aA1 79 | 80 | #// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. 81 | proc calcBezier (aT, aA1, aA2: float): float = ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT 82 | 83 | #// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. 84 | proc getSlope (aT, aA1, aA2: float): float = 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1) 85 | 86 | template doWhile(a: typed, b: untyped): untyped = 87 | b 88 | while a: 89 | b 90 | 91 | proc binarySubdivide (aX, aA, aB, mX1, mX2: float): float = 92 | var 93 | currentX, currentT, i = 0.0 94 | aA = aA 95 | aB = aB 96 | doWhile((abs(currentX) > subdivisionPrecision) and (i < subdivisionMaxIterations)): 97 | currentT = aA + (aB - aA) / 2.0 98 | currentX = calcBezier(currentT, mX1, mX2) - aX 99 | if currentX > 0.0: 100 | aB = currentT 101 | else: 102 | aA = currentT 103 | i += 1 104 | return currentT 105 | 106 | proc newtonRaphsonIterate (aX, aGuessT, mX1, mX2: float): float = 107 | var aGuessT = aGuessT 108 | for i in 0..= newtonMinSlope: 145 | return newtonRaphsonIterate(aX, guessForT, mX1, mX2) 146 | elif (initialSlope == 0.0): 147 | return guessForT 148 | else: 149 | return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2) 150 | 151 | return proc (t:float): float = 152 | if mX1 == mY1 and mX2 == mY2: 153 | return t #// linear 154 | 155 | #// Because JavaScript number are imprecise, we should guarantee the extremes are right. 156 | if t == 0: 157 | return 0 158 | if t == 1: 159 | return 1 160 | 161 | return calcBezier(getTForX(t), mY1, mY2) 162 | 163 | var easeFunctions = { 164 | Ease.Linear: EaseFunction(proc(t:float):float = t), 165 | Ease.InSine: EaseFunction(proc(t:float):float = 1-sin(PI/2+t*PI/2)), 166 | Ease.OutSine: EaseFunction(proc(t:float):float = sin(t*PI/2)), 167 | Ease.InOutSine: EaseFunction(proc(t:float):float = (1-sin(PI/2+t*PI))/2), 168 | Ease.InQuad: EaseFunction(proc(t:float):float = pow(t,2)), 169 | Ease.OutQuad: EaseFunction(proc(t:float):float = t*(2-t)), 170 | Ease.InOutQuad: EaseFunction(proc(t:float):float =(if t<0.5: 2*t*t else: -1+(4-2*t)*t)), 171 | Ease.InCubic: EaseFunction(proc(t:float):float = pow(t,3)), 172 | Ease.OutCubic: EaseFunction(proc(t:float):float = pow(t-1,3)+1), 173 | Ease.InOutCubic: EaseFunction(proc(t:float):float =(if t<0.5: 4*pow(t,3) else: (t-1)*pow(2*t-2,2)+1)), 174 | Ease.InQuart: EaseFunction(proc(t:float):float = pow(t,4)), 175 | Ease.OutQuart: EaseFunction(proc(t:float):float = 1-pow(t-1,4)), 176 | Ease.InOutQuart: EaseFunction(proc(t:float):float = (if t<0.5: 8*pow(t,4) else: 1-8*pow(t-1,4))), 177 | Ease.InQuint: EaseFunction(proc(t:float):float = pow(t,5)), 178 | Ease.OutQuint: EaseFunction(proc(t:float):float = 1+pow(t-1,5)), 179 | Ease.InOutQuint: EaseFunction(proc(t:float):float = (if t<0.5: 16*pow(t,5) else: 1+16*pow(t-1,5))), 180 | Ease.InExpo: EaseFunction(proc(t:float):float = pow(2,10*(t/1-1))), 181 | Ease.OutExpo: EaseFunction(proc(t:float):float = 1*(-pow(2,-10*t/1)+1)), 182 | Ease.InOutExpo: EaseFunction(proc(t:float):float = (if t<0.5: 0.5*pow(2,(10 * (t*2 - 1))) else: 0.5*(-pow(2, -10*(t*2-1))+2))), 183 | Ease.InCirc: EaseFunction(proc(t:float):float = -1*(sqrt(1 - t * t) - 1)), 184 | Ease.OutCirc: EaseFunction(proc(t:float):float = 1*sqrt(1 - (t-1).pow(2))), 185 | Ease.InOutCirc: EaseFunction(proc(t:float):float = (if t < 0.5: -0.5*(sqrt(1 - pow(t*2,2)) - 1) else: 0.5*(sqrt(1 - pow(t*2-2,2)) + 1))), 186 | Ease.InBack: EaseFunction(proc(t:float):float = t * t * (2.70158 * t - 1.70158)), 187 | Ease.OutBack: EaseFunction(proc(t:float):float = 1 - (1-t) * (1-t) * (2.70158 * (1-t) - 1.70158)), 188 | Ease.InOutBack: EaseFunction(proc(t:float):float = (if t<0.5: 0.5*(4*t*t*(3.5949095 * t*2 - 2.5949095)) else: 0.5 * ((t*2 - 2).pow(2) * ((4.9572369875) * (t*2-2) + 3.9572369875) + 2))), 189 | Ease.OutBounce: EaseFunction( 190 | proc(t:float):float = 191 | if t<1/2.75: 192 | 7.5625*t.pow(2) 193 | elif t<2/2.75: 194 | 7.5625*(t - (1.5 / 2.75)).pow(2) + 0.75 195 | elif t<2.5/2.75: 196 | 7.5625*(t - (2.25 / 2.75)).pow(2) + 0.9375 197 | else: 198 | 7.5625*(t - (2.625 / 2.75)).pow(2) + 0.984375 199 | ) 200 | }.toTable 201 | easeFunctions[Ease.InBounce] = 202 | EaseFunction( 203 | proc(t:float):float = 204 | 1-easeFunctions[Ease.OutBounce](1 - t) 205 | ) 206 | easeFunctions[Ease.InOutBounce] = 207 | EaseFunction( 208 | proc(t:float):float = 209 | if t<0.5: 210 | easeFunctions[Ease.InBounce](t*2)*0.5 211 | else: 212 | easeFunctions[Ease.OutBounce](t*2-1)*0.5+1*0.5 213 | ) 214 | easeFunctions[Ease.InElastic] = 215 | EaseFunction( 216 | proc(t:float):float = 217 | -(pow(2, 10 * (t - 1))*sin(((t-1) * 1 - 0.075038041) * (2 * PI) / 0.3)) 218 | ) 219 | easeFunctions[Ease.OutElastic] = 220 | EaseFunction( 221 | proc(t:float):float = 222 | pow(2, -10 * t) * sin((t * 1 - 0.075038041) * (2 * PI) / 0.3) + 1 223 | ) 224 | easeFunctions[Ease.InOutElastic] = 225 | EaseFunction( 226 | proc(t:float):float = 227 | if t<0.5: 228 | -0.5*(pow(2, 10 * (t*2 - 1)) * sin(((t*2-1) * 1 - 0.075038041) * (2 * PI) / 0.3)) 229 | else: 230 | pow(2, -10 * (t*2 - 1)) * sin(((t*2-1) * 1 - 0.075038041) * (2 * PI) / 0.3) * 0.5 + 1 231 | ) 232 | proc initTween*(duration:float, easingFunc: EaseFunction): Tween = 233 | ## Initialize a new tween with a duration and a custom easing function. 234 | new result 235 | result.duration = duration 236 | result.EasingFunction = easingFunc 237 | 238 | template initTween*(duration:float, ease: Ease): Tween = 239 | ## Initialize a new tween with a duration and a built in ease function 240 | initTween(duration, easeFunctions[ease]) 241 | 242 | template initTween*(duration:float, mX1, mY1, mX2, mY2: float): Tween = 243 | ## Initialize a new tween with a duration and a custom Bezier curve 244 | initTween(duration, getEasingFunction(mX1, mY1, mX2, mY2)) 245 | 246 | proc tick*(tween: Tween or TweenValue or TweenSeq, tickLength: float) = 247 | ## Ticks the tween forward 248 | if tween.t <= tween.duration: 249 | tween.cached = false 250 | tween.t += tickLength 251 | 252 | proc value*(tween: Tween): float = 253 | ## Returns the value of a Tween at the current time. NOTE: This caches the 254 | ## value so calling this multiple times is safe. 255 | if tween.cached: 256 | return tween.cache 257 | if tween.t <= tween.duration: 258 | tween.cache = tween.EasingFunction(tween.t/tween.duration) 259 | elif tween.t > tween.duration: 260 | tween.cache = tween.EasingFunction(1) 261 | tween.cached = true 262 | return tween.cache 263 | 264 | template initTweenValue*(duration:float, fromValue, toValue:float , ease: Ease): TweenValue = 265 | ## Initialize a new tween with a duration, start value, stop value, and a 266 | ## built-in easing function. 267 | initTweenValue(duration, fromValue, toValue, easeFunctions[ease]) 268 | 269 | proc initTweenValue*(duration: float, fromValue, toValue: float, easingFunc: EaseFunction): TweenValue = 270 | ## Initialize a new tween with a duration, start value, stop value, and a 271 | ## custom easing function. 272 | new result 273 | result.duration = duration 274 | result.fromValue = fromValue 275 | result.ratio = toValue-fromValue 276 | result.EasingFunction = easingFunc 277 | 278 | proc initTweenValue*(duration:float, fromValue, toValue, mX1, mY1, mX2, mY2: float): TweenValue = 279 | ## Initialize a new tween with a duration, start value, stop value, and a 280 | ## custom bezier curve. 281 | initTweenValue(duration, fromValue, toValue, getEasingFunction(mX1, mY1, mX2, mY2)) 282 | 283 | proc value*(tween: TweenValue): float = 284 | ## Returns the value of a TweenValue at the current time. NOTE: This caches 285 | ## the value so calling this multiple times is safe. 286 | var tweenValue = tween.Tween.value() 287 | return tween.fromValue + tweenValue*tween.ratio 288 | 289 | template initTweenValues*(fromValues: seq[float], toValues: seq[float], ease: Ease): TweenSeq = 290 | ## Initialize a new tween with a duration, start values, stop values, and a 291 | ## built-in easing function. 292 | initTweenValues(fromValues, toValues, easeFunctions[ease]) 293 | 294 | proc initTweenValues*(fromValues: seq[float], toValues: seq[float], easingFunc: EaseFunction): TweenSeq = 295 | ## Initialize a new tween with a duration, start values, stop values, and a 296 | ## custom easing function. 297 | if fromValues.len != toValues.len: 298 | raise newException(ValueError, "fromValues and toValues much be of the same length") 299 | new result 300 | result.fromValues = fromValues 301 | result.ratios = @[] 302 | for i in 0..fromValues.high: 303 | result.ratios.add toValues[i]-fromValues[i] 304 | result.EasingFunction = easingFunc 305 | 306 | proc initTweenValues*(fromValues: seq[float], toValues: seq[float], mX1, mY1, mX2, mY2: float): TweenSeq = 307 | ## Initialize a new tween with a duration, start values, stop values, and a 308 | ## custom bezier curve. 309 | initTweenValues(fromValues, toValues, getEasingFunction(mX1, mY1, mX2, mY2)) 310 | 311 | proc values*(tween: TweenSeq,i:int): seq[float] = 312 | ## Returns the sequence of values of a TweenSeq. NOTE: This caches 313 | ## the value so calling this multiple times is safe. 314 | result = @[] 315 | let ratio = tween.Tween.value() 316 | for i in 0..(tween.fromValues).high: 317 | result.add tween.fromValues[i] + ratio*tween.ratios[i] 318 | -------------------------------------------------------------------------------- /gamelib/collisions.nim: -------------------------------------------------------------------------------- 1 | ## Collisions for various geometric shapes. Supports rectangles, triangles, 2 | ## circles and polygons. Also includes procedures for checking if a point is 3 | ## within any of the supported shapes. 4 | ## 5 | ## There are three kinds of procedures for each shape; within, collides, and 6 | ## collision. Within takes a point and a shape to check against. Collides takes 7 | ## two shapes and returns a boolean, this is typically faster than the collision 8 | ## procedure as it can terminate early. Collision also takes two shapes but 9 | ## returns the smallest rectangle which contains the entire overlap. 10 | ## 11 | ## The helper function bound is also available to return the smallest rectangle 12 | ## encompassing all the points given to it. There is also a direction function 13 | ## that takes two rectangles and returns a Direction enum value. 14 | 15 | 16 | # Collides implemented for: 17 | # Triangle -> Triangle 18 | # Triangle -> Circle 19 | # Rectangle -> Triangle 20 | # Rectangle -> Circle 21 | # Rectangle -> Rectangle 22 | # Infinite line -> Circle 23 | # Circle -> Circle 24 | # Rectangle -> Polygon 25 | # Triangle -> Polygon 26 | # Circle -> Polygon 27 | # Polygon -> Polygon 28 | # 29 | # Collision implemented for: 30 | # Line -> Line (intersect) 31 | # Line -> Circle (intersect) 32 | # Rectangle -> Rectangle 33 | # Rectangle -> Circle 34 | # Rectangle -> Triangle 35 | # Triangle -> Triangle 36 | # Triangle -> Circle 37 | # Circle -> Circle 38 | # Rectangle -> Polygon 39 | # Triangle -> Polygon 40 | # Circle -> Polygon 41 | # Polygon -> Polygon 42 | 43 | import sdl2 44 | import math 45 | 46 | type 47 | Direction* {.pure.} = ## Compass directions that explains how two rectangles 48 | ## overlap. North means that either the two top corners are covered, or 49 | ## that no corners are covered and the only intersection is within the top 50 | ## line of the rectangle. North-east means that only the top-left corner is 51 | ## covered. The other directions work the same way. 52 | enum north, northeast, east, southeast, south, southwest, west, northwest, none 53 | 54 | proc direction*(rect1, rect2: Rect): Direction = 55 | ## Takes two rectangles and returns a Direction enum type. 56 | result = 57 | if rect2.x == rect1.x: 58 | if rect2.y == rect1.y: 59 | if rect2.w != rect1.w: 60 | if rect2.h != rect1.h: 61 | Direction.northwest 62 | else: 63 | Direction.west 64 | else: 65 | if rect2.h != rect1.h: 66 | Direction.north 67 | else: 68 | Direction.none 69 | else: 70 | if rect2.w != rect1.w: 71 | if rect2.y+rect2.h != rect1.y+rect1.h: 72 | Direction.west 73 | else: 74 | Direction.southwest 75 | else: 76 | Direction.south 77 | else: 78 | if rect2.h == rect1.h: 79 | Direction.east 80 | else: 81 | if rect2.y > rect1.y: 82 | if rect2.y+rect2.h != rect1.y+rect1.h: 83 | if rect2.x+rect2.w == rect1.x+rect1.w: 84 | Direction.east 85 | else: 86 | Direction.none 87 | else: 88 | if rect2.x+rect2.w != rect1.x+rect1.w: 89 | Direction.south 90 | else: 91 | Direction.southeast 92 | else: 93 | if rect2.x+rect2.w != rect1.x+rect1.w: 94 | Direction.north 95 | else: 96 | Direction.northeast 97 | 98 | proc bound*(points: varargs[Point]): ref Rect = 99 | ## Takes a variable amount of points and returns the smallest Rect that 100 | ## contains all points. 101 | new result 102 | result.x = cint.high 103 | result.y = cint.high 104 | for point in points: 105 | result.x = min(result.x, point.x) 106 | result.y = min(result.y, point.y) 107 | for point in points: 108 | result.w = max(result.w, point.x-result.x) 109 | result.h = max(result.h, point.y-result.y) 110 | 111 | proc bound*(points: seq[Point]): ref Rect = 112 | ## Takes a sequence of points and returns the smallest Rect that contains all 113 | ## points. 114 | new result 115 | result.x = cint.high 116 | result.y = cint.high 117 | for point in points: 118 | result.x = min(result.x, point.x) 119 | result.y = min(result.y, point.y) 120 | for point in points: 121 | result.w = max(result.w, point.x-result.x) 122 | result.h = max(result.h, point.y-result.y) 123 | #return rect(x,y,w,h) 124 | 125 | proc within*(point: Point, centre: Point, radius: int): bool = 126 | ## Checks if the point is within the circle. 127 | if sqrt((centre.x-point.x).float.pow(2)+(centre.y-point.y).float.pow(2)).cint < radius: 128 | return true 129 | return false 130 | 131 | proc within*(point, tri1v1, tri1v2, tri1v3: Point): bool = 132 | ## Checks if the point is within the trangle formed by the three last points. 133 | let alpha = ((tri1v2.y - tri1v3.y)*(point.x - tri1v3.x) + (tri1v3.x - tri1v2.x)*(point.y - tri1v3.y)) / ((tri1v2.y - tri1v3.y)*(tri1v1.x - tri1v3.x) + (tri1v3.x - tri1v2.x)*(tri1v1.y - tri1v3.y)) 134 | if 0 < alpha: 135 | let beta = ((tri1v3.y - tri1v1.y)*(point.x - tri1v3.x) + (tri1v1.x - tri1v3.x)*(point.y - tri1v3.y)) / ((tri1v2.y - tri1v3.y)*(tri1v1.x - tri1v3.x) + (tri1v3.x - tri1v2.x)*(tri1v1.y - tri1v3.y)) 136 | if 0 < beta: 137 | if 0 < 1 - alpha - beta: 138 | return true 139 | return false 140 | 141 | proc within*(point: Point, rect: Rect): bool= 142 | ## Checks if the point is within the given rectangle 143 | if 144 | (point.x>rect.x and point.xrect.y and point.y0 for P2 left of the line through P0 and P1 154 | # =0 for P2 on the line 155 | # <0 for P2 right of the line 156 | proc isLeft( P0, P1, P2: Point ): int {.inline.} = 157 | return ( (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y) ) 158 | 159 | # wn_PnPoly(): winding number test for a point in a polygon 160 | # Input: P = a point, 161 | # V[] = vertex points of a polygon V[n+1] with V[n]=V[0] 162 | # Return: wn = the winding number (=0 only when P is outside) 163 | proc wn_PnPoly( P: Point, V:seq[Point] ): int = 164 | var wn:int = 0 # the winding number counter 165 | 166 | # loop through all edges of the polygon 167 | for i in 0.. P.y): # an upward crossing 170 | if (isLeft( V[i], V[i+1], P) > 0): # P left of edge 171 | wn+=1 # have a valid up intersect 172 | else: # start y > P.y (no test needed) 173 | if (V[i+1].y <= P.y): # a downward crossing 174 | if (isLeft( V[i], V[i+1], P) < 0): # P right of edge 175 | wn-=1 # have a valid down intersect 176 | return wn 177 | 178 | proc within*(point: Point, polygon: seq[Point]): bool = 179 | ## Checks if the point is within the polygon created by a sequence of points. 180 | ## The sequence should NOT have polygon[0]==polygon[polygon.high] 181 | var V = polygon 182 | V.add V[0] 183 | return wn_PnPoly(point, V) != 0 184 | 185 | proc intersection*(line1Start, line1End, line2Start, line2End: Point): ref Point = 186 | ## Returns the point in which the two line segments intersect or nil if there 187 | ## is no intersection. 188 | let 189 | A1 = line1End.y-line1Start.y 190 | B1 = line1Start.x-line1End.x 191 | C1 = A1*line1Start.x+B1*line1Start.y 192 | A2 = line2End.y-line2Start.y 193 | B2 = line2Start.x-line2End.x 194 | C2 = A2*line2Start.x+B2*line2Start.y 195 | det = A1*B2 - A2*B1 196 | if det != 0: 197 | let 198 | x = ((B2*C1 - B1*C2)/det).cint 199 | y = ((A1*C2 - A2*C1)/det).cint 200 | if min(line1Start.x,line1End.x) <= x and 201 | x <= max(line1Start.x,line1End.x) and 202 | min(line2Start.x,line2End.x) <= x and 203 | x <= max(line2Start.x,line2End.x) and 204 | min(line1Start.y,line1End.y) <= y and 205 | y <= max(line1Start.y,line1End.y) and 206 | min(line2Start.y,line2End.y) <= y and 207 | y <= max(line2Start.y,line2End.y) : 208 | new result 209 | result.x = x 210 | result.y = y 211 | return result 212 | return nil 213 | 214 | proc intersection*(lineStart, lineEnd: Point, centre: Point, radius: int): seq[Point] = 215 | ## Returns a sequence of zero, one, or two points of intersection between a 216 | ## line segment and a circle. 217 | let 218 | startCentered = point(lineStart.x-centre.x,lineStart.y-centre.y) 219 | endCentered = point(lineEnd.x-centre.x,lineEnd.y-centre.y) 220 | proc sign(x: int): int = 221 | (x > 0).int - (x < 0).int 222 | let 223 | dx = endCentered.x-startCentered.x 224 | dy = endCentered.y-startCentered.y 225 | dr = sqrt(dx.float.pow(2)+dy.float.pow(2)) 226 | D = startCentered.x*endCentered.y-endCentered.x*startCentered.y 227 | disc = radius.float.pow(2)*dr.float.pow(2)-D.float.pow(2) 228 | if disc >= 0: 229 | let 230 | ix = sign(dy).float*dx.float*sqrt(disc) 231 | iy = abs(dy).float*sqrt(disc) 232 | if disc == 0: 233 | let 234 | x = ((D.float*dy.float+ix)/dr.pow(2)).cint 235 | y = ((-D.float*dx.float+iy)/dr.pow(2)).cint 236 | if min(startCentered.x,endCentered.x) <= x and 237 | x <= max(startCentered.x,endCentered.x) and 238 | min(startCentered.y,endCentered.y) <= y and 239 | y <= max(startCentered.y,endCentered.y): 240 | return @[point(x+centre.x,y+centre.y)] 241 | else: 242 | let 243 | x1 = ((D.float*dy.float+ix)/dr.pow(2)).cint 244 | x2 = ((D.float*dy.float-ix)/dr.pow(2)).cint 245 | y1 = ((-D.float*dx.float+iy)/dr.pow(2)).cint 246 | y2 = ((-D.float*dx.float-iy)/dr.pow(2)).cint 247 | result = @[] 248 | if min(startCentered.x,endCentered.x) <= x1 and 249 | x1 <= max(startCentered.x,endCentered.x) and 250 | min(startCentered.y,endCentered.y) <= y1 and 251 | y1 <= max(startCentered.y,endCentered.y): 252 | result.add point(x1+centre.x,y1+centre.y) 253 | if min(startCentered.x,endCentered.x) <= x2 and 254 | x2 <= max(startCentered.x,endCentered.x) and 255 | min(startCentered.y,endCentered.y) <= y2 and 256 | y2 <= max(startCentered.y,endCentered.y): 257 | result.add point(x2+centre.x,y2+centre.y) 258 | 259 | proc collides*(tri1v1, tri1v2, tri1v3, tri2v1, tri2v2, tri2v3: Point): bool = 260 | ## Checks if the two triangles created by the six points intersects with each 261 | ## other. 262 | for l1 in [(tri1v1,tri1v2),(tri1v2,tri1v3),(tri1v3,tri1v1)]: 263 | for l2 in [(tri2v1,tri2v2),(tri2v2,tri2v3),(tri2v3,tri2v1)]: 264 | if intersection(l1[0],l1[1],l2[0],l2[1]) != nil: 265 | return true 266 | if tri2v1.within(tri1v1, tri1v2, tri1v3): 267 | return true 268 | if tri1v1.within(tri2v1, tri2v2, tri2v3): 269 | return true 270 | return false 271 | 272 | proc collides*(rect: Rect, tri1v1, tri1v2, tri1v3: Point): bool = 273 | ## Checks if the given rectangle and the triangle created by the three points 274 | ## intersects with each other. 275 | for l1 in [(point(rect.x,rect.y),point(rect.x+rect.w,rect.y)),(point(rect.x+rect.w,rect.y),point(rect.x+rect.w,rect.y+rect.h)),(point(rect.x+rect.w,rect.y+rect.h),point(rect.x,rect.y+rect.h)),(point(rect.x,rect.y+rect.h),point(rect.x,rect.y))]: 276 | for l2 in [(tri1v1,tri1v2),(tri1v2,tri1v3),(tri1v3,tri1v1)]: 277 | if intersection(l1[0],l1[1],l2[0],l2[1]) != nil: 278 | return true 279 | if tri1v1.within rect: 280 | return true 281 | if point(rect.x, rect.y).within(tri1v1, tri1v2, tri1v3): 282 | return true 283 | return false 284 | 285 | 286 | proc collides*(rect: Rect, centre: Point, radius: cint): bool = 287 | ## Checks if the given rectangle intersects with the circle given by the 288 | ## centre and a radius. 289 | let 290 | x1 = sqrt(abs(radius.float.pow(2)-((rect.y+rect.h)-centre.y).float.pow(2))).cint 291 | x2 = sqrt(abs(radius.float.pow(2)-(rect.y-centre.y).float.pow(2))).cint 292 | y1 = sqrt(abs(radius.float.pow(2)-((rect.x+rect.w)-centre.x).float.pow(2))).cint 293 | y2 = sqrt(abs(radius.float.pow(2)-(rect.x-centre.x).float.pow(2))).cint 294 | 295 | let h = 296 | if not (centre.x>rect.x and centre.xrect.x: 298 | y1 299 | else: 300 | y2 301 | else: 302 | radius 303 | 304 | let w = 305 | if not (centre.y>rect.y and centre.yrect.y: 307 | x1 308 | else: 309 | x2 310 | else: 311 | radius 312 | let 313 | xpw = if centre.x+w > rect.x+rect.w: rect.x+rect.w else: centre.x+w 314 | xmw = if centre.x-w < rect.x: rect.x else: centre.x-w 315 | yph = if centre.y+h > rect.y+rect.h: rect.y+rect.h else: centre.y+h 316 | ymh = if centre.y-h < rect.y: rect.y else: centre.y-h 317 | if xmw < rect.x+rect.w and xpw > rect.x and ymh < rect.y+rect.h and yph > rect.y: 318 | return true 319 | return false 320 | 321 | proc collides*(rect1, rect2: Rect): bool = 322 | ## Checks if the two given rectangles intersects with each other. 323 | let 324 | x1inx2 = (rect1.x>rect2.x and rect1.xrect1.x and rect2.xrect2.y and rect1.yrect1.y and rect2.y 0: 384 | return true 385 | if centre.within(polygon): 386 | return true 387 | for p in polygon: 388 | if p.within(centre, radius): 389 | return true 390 | 391 | proc collides*(triv1,triv2,triv3: Point, centre: Point, radius: int): bool = 392 | ## Checks if the triangle given by the three points intersects with the circle 393 | ## given by the centre point and the radius. 394 | for line in [(triv1,triv2),(triv2,triv3),(triv3,triv1)]: 395 | if intersection(line[0],line[1],centre,radius).len > 0: 396 | return true 397 | if triv1.within(centre,radius): 398 | return true 399 | if centre.within(triv1,triv2,triv3): 400 | return true 401 | 402 | proc collides*(centre1: Point, radius1: int, centre2: Point, radius2: int): bool = 403 | ## Checks if the two circles given by the two centres and the two radii 404 | ## intersects. 405 | let d = sqrt((centre1.x-centre2.x).float.pow(2)+(centre1.y-centre2.y).float.pow(2)).cint 406 | if d < radius1+radius2: 407 | return true 408 | 409 | proc collision*(tri1v1, tri1v2, tri1v3, tri2v1, tri2v2, tri2v3: Point): ref Rect = 410 | ## Checks if the two triangles created by the six points intersects with each 411 | ## other and returns the smallest rectangle that contains the collision. 412 | var intersections: seq[Point] = @[] 413 | for l1 in [(tri1v1,tri1v2),(tri1v2,tri1v3),(tri1v3,tri1v1)]: 414 | for l2 in [(tri2v1,tri2v2),(tri2v2,tri2v3),(tri2v3,tri2v1)]: 415 | let intersection = intersection(l1[0],l1[1],l2[0],l2[1]) 416 | if intersection != nil: 417 | intersections.add intersection[] 418 | for p in [tri2v1, tri2v2, tri2v3]: 419 | if p.within(tri1v1, tri1v2, tri1v3): 420 | intersections.add p 421 | for p in [tri1v1, tri1v2, tri1v3]: 422 | if p.within(tri2v1, tri2v2, tri2v3): 423 | intersections.add p 424 | if intersections.len>0: 425 | return intersections.bound 426 | return nil 427 | 428 | proc collision*(rect: Rect, tri1v1, tri1v2, tri1v3: Point): ref Rect = 429 | ## Checks if the given rectangle and the triangle created by the three points 430 | ## intersects with each other and returns the smallest rectangle that 431 | ## contains the collision. 432 | var intersections: seq[Point] = @[] 433 | for l1 in [(point(rect.x,rect.y),point(rect.x+rect.w,rect.y)),(point(rect.x+rect.w,rect.y),point(rect.x+rect.w,rect.y+rect.h)),(point(rect.x+rect.w,rect.y+rect.h),point(rect.x,rect.y+rect.h)),(point(rect.x,rect.y+rect.h),point(rect.x,rect.y))]: 434 | for l2 in [(tri1v1,tri1v2),(tri1v2,tri1v3),(tri1v3,tri1v1)]: 435 | let intersection = intersection(l1[0],l1[1],l2[0],l2[1]) 436 | if intersection != nil: 437 | intersections.add intersection[] 438 | for p in [tri1v1, tri1v2, tri1v3]: 439 | if p.within rect: 440 | intersections.add p 441 | for p in [point(rect.x,rect.y),point(rect.x+rect.w,rect.y),point(rect.x+rect.w,rect.y+rect.h),point(rect.x,rect.y+rect.h)]: 442 | if p.within(tri1v1, tri1v2, tri1v3): 443 | intersections.add p 444 | if intersections.len>0: 445 | return intersections.bound 446 | return nil 447 | 448 | proc collision*(rect: Rect, centre: Point, radius: cint): ref Rect = 449 | ## Checks if the given rectangle intersects with the circle given by the 450 | ## centre and a radius and returns the smallest rectangle that contains the 451 | ## collision. 452 | let 453 | x1 = sqrt(abs(radius.float.pow(2)-((rect.y+rect.h)-centre.y).float.pow(2))).cint 454 | x2 = sqrt(abs(radius.float.pow(2)-(rect.y-centre.y).float.pow(2))).cint 455 | y1 = sqrt(abs(radius.float.pow(2)-((rect.x+rect.w)-centre.x).float.pow(2))).cint 456 | y2 = sqrt(abs(radius.float.pow(2)-(rect.x-centre.x).float.pow(2))).cint 457 | 458 | let h = 459 | if not (centre.x>rect.x and centre.xrect.x: 461 | y1 462 | else: 463 | y2 464 | else: 465 | radius 466 | 467 | let w = 468 | if not (centre.y>rect.y and centre.yrect.y: 470 | x1 471 | else: 472 | x2 473 | else: 474 | radius 475 | let 476 | xpw = if centre.x+w > rect.x+rect.w: rect.x+rect.w else: centre.x+w 477 | xmw = if centre.x-w < rect.x: rect.x else: centre.x-w 478 | yph = if centre.y+h > rect.y+rect.h: rect.y+rect.h else: centre.y+h 479 | ymh = if centre.y-h < rect.y: rect.y else: centre.y-h 480 | if xmw < rect.x+rect.w and xpw > rect.x and ymh < rect.y+rect.h and yph > rect.y: 481 | new result 482 | result.x = xmw 483 | result.y = ymh 484 | result.w = xpw - xmw 485 | result.h = yph - ymh 486 | else: 487 | return nil 488 | 489 | proc collision*(rect1, rect2: Rect): ref Rect = 490 | ## Checks if the two given rectangles intersects with each other and returns 491 | ## the smallest rectangle that contains the collision. 492 | new result 493 | let 494 | x1inx2 = (rect1.x>rect2.x and rect1.xrect1.x and rect2.xrect2.y and rect1.yrect1.y and rect2.y rect2.w: 505 | result.w = rect2.w 506 | else: 507 | result.x = rect1.x 508 | result.w = rect2.x+rect2.w-rect1.x 509 | if result.w > rect1.w: 510 | result.w = rect1.w 511 | if y2iny1: 512 | result.y = rect2.y 513 | result.h = rect1.y+rect1.h-rect2.y 514 | if result.h > rect2.h: 515 | result.h = rect2.h 516 | else: 517 | result.y = rect1.y 518 | result.h = rect2.y+rect2.h-rect1.y 519 | if result.h > rect1.h: 520 | result.h = rect1.h 521 | return result 522 | return nil 523 | 524 | proc collision*(rect: Rect, polygon: seq[Point]): ref Rect = 525 | ## Checks if the rectangle and the polygon described by the sequence of points 526 | ## intersects with each other and returns the smallest rectangle that 527 | ## contains the collision. The sequence should NOT have 528 | ## polygon[0]==polygon[polygon.high]. 529 | var intersections: seq[Point] = @[] 530 | for i in 0..polygon.high: 531 | for line in [(point(rect.x,rect.y),point(rect.x+rect.w,rect.y)),(point(rect.x+rect.w,rect.y),point(rect.x+rect.w,rect.y+rect.h)),(point(rect.x+rect.w,rect.y+rect.h),point(rect.x,rect.y+rect.h)),(point(rect.x,rect.y+rect.h),point(rect.x,rect.y))]: 532 | let intersection = intersection(polygon[i],polygon[(i+1) mod polygon.high],line[0],line[1]) 533 | if intersection != nil: 534 | intersections.add intersection[] 535 | for p in polygon: 536 | if p.within(rect): 537 | intersections.add p 538 | for p in [point(rect.x,rect.y),point(rect.x+rect.w,rect.y),point(rect.x+rect.w,rect.y+rect.h),point(rect.x,rect.y+rect.h)]: 539 | if p.within(polygon): 540 | intersections.add p 541 | if intersections.len>0: 542 | return intersections.bound 543 | return nil 544 | 545 | proc collision*(tri1v1, tri1v2, tri1v3: Point, polygon: seq[Point]): ref Rect = 546 | ## Checks if the triangle given by the three first points intersects with the 547 | ## polygon described by the sequence of points and returns the smallest 548 | ## rectangle that contains the collision. The sequence should NOT have 549 | ## polygon[0]==polygon[polygon.high] 550 | var intersections: seq[Point] = @[] 551 | for i in 0..polygon.high: 552 | for line in [(tri1v1,tri1v2),(tri1v2,tri1v3),(tri1v3,tri1v1)]: 553 | let intersection = intersection(polygon[i],polygon[(i+1) mod polygon.high],line[0],line[1]) 554 | if intersection != nil: 555 | intersections.add intersection[] 556 | for p in polygon: 557 | if p.within(tri1v1,tri1v2,tri1v3): 558 | intersections.add p 559 | for p in [tri1v1,tri1v2,tri1v3]: 560 | if p.within(polygon): 561 | intersections.add p 562 | if intersections.len>0: 563 | return intersections.bound 564 | return nil 565 | 566 | proc collision*(polygon1: seq[Point], polygon2: seq[Point]): ref Rect = 567 | ## Checks if the two polygons described by the two sequences of points 568 | ## intersects and returns the smallest rectangle that contains the collision. 569 | ## The sequences should NOT have polygon[0]==polygon[polygon.high] 570 | var intersections: seq[Point] = @[] 571 | for i in 0..polygon1.high: 572 | for j in 0..polygon2.high: 573 | let intersection = intersection(polygon1[i],polygon1[(i+1) mod polygon1.high],polygon2[j],polygon2[(j+1) mod polygon2.high]) 574 | if intersection != nil: 575 | intersections.add intersection[] 576 | for p in polygon1: 577 | if p.within(polygon2): 578 | intersections.add p 579 | for p in polygon2: 580 | if p.within(polygon1): 581 | intersections.add p 582 | if intersections.len>0: 583 | return intersections.bound 584 | return nil 585 | 586 | proc collision*(centre: Point, radius: int, polygon: seq[Point]): ref Rect = 587 | ## Checks if the circle described by the centre point and the radius and the 588 | ## polygon described by the sequence of points intersects and returns the 589 | ## smallest rectangle that contains the collision. The sequence should NOT 590 | ## have polygon[0]==polygon[polygon.high] 591 | var intersections: seq[Point] = @[] 592 | for i in 0..polygon.high: 593 | let intersects = intersection(polygon[i],polygon[(i+1) mod polygon.high],centre, radius) 594 | for intersection in intersects: 595 | intersections.add intersection 596 | if centre.within(polygon): 597 | intersections.add centre 598 | for p in polygon: 599 | if p.within(centre, radius): 600 | intersections.add p 601 | if intersections.len>0: 602 | return intersections.bound 603 | return nil 604 | 605 | proc collision*(triv1,triv2,triv3: Point, centre: Point, radius: int): ref Rect = 606 | ## Checks if the triangle given by the three points intersects with the circle 607 | ## given by the centre point and the radius and returns the smallest 608 | ## rectangle that contains the collision. 609 | var intersections: seq[Point] = @[] 610 | for line in [(triv1,triv2),(triv2,triv3),(triv3,triv1)]: 611 | for intersection in intersection(line[0],line[1],centre,radius): 612 | intersections.add intersection 613 | for p in [triv1,triv2,triv3]: 614 | if p.within(centre,radius): 615 | intersections.add p 616 | for p in [point(centre.x+radius,centre.y),point(centre.x-radius,centre.y),point(centre.x,centre.y+radius.cint),point(centre.x,centre.y-radius.cint)]: 617 | if p.within(triv1,triv2,triv3): 618 | intersections.add p 619 | if intersections.len>0: 620 | return intersections.bound 621 | 622 | proc collision*(centre1: Point, radius1: int, centre2: Point, radius2: int): ref Rect = 623 | ## Checks if the two circles given by the two centres and the two radii 624 | ## intersects and returns the smallest rectangle that contains the collision. 625 | let d = sqrt((centre1.x-centre2.x).float.pow(2)+(centre1.y-centre2.y).float.pow(2)).cint 626 | if d > radius1+radius2: 627 | return nil 628 | if d < abs(radius1-radius2): 629 | if radius10: 658 | return intersections.bound 659 | -------------------------------------------------------------------------------- /alite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ALite - Agile, light task tracker 5 | 7 | 8 |
9 | 10 |

Future features

11 |

This category contains features that should or will be added to the library.

12 |
StatusTitleAssignee
NinePatch generation
NinePatch rotation
NinePatch modes
Create examples
More collision types
Add tweening
Add scene graph
Add ticker
Create DocGen docs

Create examples

Write some simple examples to show how the library is intended to be used.

CategoryFuture features
StatusNot done
Assignee
Points
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------