├── test └── index.test.js ├── README.md ├── package.json ├── .gitignore └── src └── index.js /test/index.test.js: -------------------------------------------------------------------------------- 1 | // TODO: tests! 2 | 3 | test('should ', () => {}) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spark AR Util 2 | A collection of utility functions for Spark AR projects. 3 | 4 | This is still a work in progress and might change quite a lot. 5 | 6 | If you would like to contribute just open a PR or issue :) 7 | 8 | ## TODO 9 | - [x] Get initial functions committed 10 | - [ ] Add tests 11 | - [ ] Split functions into modules 12 | - [ ] Add basic shader methods 13 | - [ ] Publish on NPM 14 | - [ ] Document functions 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spark-ar-util", 3 | "version": "0.1.0", 4 | "description": "Helpers for Spark AR development", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ashleymarkfletcher/spark-ar-util.git" 12 | }, 13 | "author": "Ashley Fletcher (https://ashleymarkfletcher.com)", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/ashleymarkfletcher/spark-ar-util/issues" 17 | }, 18 | "homepage": "https://github.com/ashleymarkfletcher/spark-ar-util#readme", 19 | "devDependencies": { 20 | "jest": "^24.7.1" 21 | }, 22 | "dependencies": {} 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const Scene = require('Scene') 2 | const Diagnostics = require('Diagnostics') 3 | const TouchGestures = require('TouchGestures') 4 | const Time = require('Time') 5 | const FaceTracking = require('FaceTracking') 6 | const Animation = require('Animation') 7 | const Reactive = require('Reactive') 8 | const Textures = require('Textures') 9 | const Materials = require('Materials') 10 | const CameraInfo = require('CameraInfo') 11 | 12 | export const randomNumber = (lower, upper) => Math.floor(Math.random() * upper) + lower 13 | export const randomElement = elements => elements[randomNumber(0, elements.length)] 14 | export const randomFloat = (lower, upper) => Math.random() * upper + lower 15 | export const randomNegativePositive = num => { 16 | num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1 17 | return num 18 | } 19 | 20 | export const log = str => Diagnostics.log(str) 21 | export const watch = (str, signal) => Diagnostics.watch(str, signal) 22 | 23 | export const toggle = (element, hide) => (element.hidden = hide) 24 | export const hide = element => toggle(element, true) 25 | export const show = element => toggle(element, false) 26 | 27 | export const showMultiple = elements => elements.forEach(show) 28 | export const hideMultiple = elements => elements.forEach(hide) 29 | 30 | export const updateText = (element, text) => { 31 | element.text = text 32 | } 33 | 34 | export const tapRegistrar = (element, fn) => TouchGestures.onTap(element).subscribe(fn) 35 | 36 | // 37 | // Get a set of duplicated elements as an array. 38 | // parent should be the parent scene item containing the children 39 | // childPrefix is the prefix for the element set: 40 | // e.g. for "plane0" the childPrefix is "plane". 41 | // 42 | export const getChildren = (parent, childPrefix) => { 43 | let children = [] 44 | let i = 0 45 | let hasMatch = true 46 | 47 | do { 48 | try { 49 | children.push(parent.child(childPrefix + i)) 50 | i++ 51 | } catch (err) { 52 | hasMatch = false 53 | } 54 | } while (hasMatch) 55 | 56 | return children 57 | } 58 | 59 | // basic 2d distance collision 60 | export const collision = function(x1, y1, x2, y2, distance) { 61 | return Math.hypot(x2 - x1, y2 - y1) <= distance 62 | } 63 | 64 | export const checkCollisions = (objects, colliderX, colliderY, onCollision) => { 65 | objects.forEach(o => { 66 | const oX = o.element.transform.x.pinLastValue() 67 | const oZ = o.element.transform.z.pinLastValue() 68 | 69 | if (collision(colliderX, colliderY, oX, oZ, maxXdistance, maxZdistance)) { 70 | onCollision(o) 71 | } 72 | }) 73 | } 74 | 75 | // countdown from a number and trigger a function 76 | export const countdown = (from, to, time, everyTime, onComplete, triggerOnStart) => { 77 | let count = from 78 | 79 | if (triggerOnStart) { 80 | everyTime(count) 81 | count-- 82 | } 83 | 84 | const timer = () => 85 | Time.setTimeout(() => { 86 | everyTime(count) 87 | 88 | if (count !== to) timer() 89 | else onComplete(count) 90 | count-- 91 | }, time) 92 | 93 | return timer() 94 | } 95 | 96 | // use strings 97 | export const swapMaterialTextureByName = (matName, textureName) => { 98 | var mat = Materials.get(matName) 99 | mat.diffuse = Textures.get(textureName) 100 | } 101 | 102 | // use mat/tex objects 103 | export const swapMaterialTexture = (material, texture) => { 104 | material.diffuse = texture 105 | } 106 | 107 | export const swapSceneObjectTexture = (sceneObject, texture) => { 108 | const material = sceneObject.material 109 | material.diffuse = texture 110 | } 111 | 112 | // same as above but with an array of texures to choose from 113 | export const randomTexture = (textures, material) => swapMaterialTexture(material, randomElement(textures)) 114 | 115 | export const randomizeTextures = (objArray, textures) => 116 | objArray.forEach(obj => randomTexture(textures, obj.element.material)) 117 | 118 | export const tween = (sampler, from, to, params, onComplete) => { 119 | const driverParamsDefault = { 120 | durationMilliseconds: 1000, 121 | loopCount: 1, // can be Infinity 122 | mirror: false 123 | } 124 | 125 | const driverParams = { ...driverParamsDefault, ...params } 126 | 127 | const driver = Animation.timeDriver(driverParams) 128 | const animSampler = Animation.samplers[sampler || 'linear'](from, to) 129 | const signal = Animation.animate(driver, animSampler) 130 | driver.start() 131 | 132 | if (onComplete) driver.onCompleted.subscribe(onComplete) 133 | 134 | return { signal: signal, driver: driver } 135 | } 136 | 137 | export const opacityTween = (material, from, to, params, onComplete) => { 138 | const anim = tween('linear', from, to, params, onComplete) 139 | material.opacity = anim.signal 140 | return anim 141 | } 142 | 143 | export const uvScroll = (material, offsetU, offsetV, driverParameters, onComplete) => { 144 | const driverParams = { 145 | ...{ durationMilliseconds: 2000, loopCount: Infinity, mirror: false }, 146 | ...driverParameters 147 | } 148 | 149 | const anim = tween('linear', 0, 1, driverParams, onComplete) 150 | 151 | if (offsetU) material.diffuseTextureTransform.offsetU 152 | if (offsetV) material.diffuseTextureTransform.offsetV 153 | 154 | return anim 155 | } 156 | 157 | // scale all the axis by the animation signal 158 | export const scaleTween = (element, driverParams, sampler, from, to, axisArray, onComplete) => { 159 | const anim = tween(sampler, from, to, driverParams, onComplete) 160 | axisArray.forEach(axis => (element.transform['scale' + axis.toUpperCase()] = anim.signal)) 161 | } 162 | 163 | export const translateTween = (element, driverParams, from, to, sampler, axisArray) => { 164 | const anim = tween(driverParams, sampler, from, to) 165 | axisArray.forEach(axis => (element.transform[axis] = anim.signal)) 166 | } 167 | 168 | export const axisRotation = (axis_x, axis_y, axis_z, angle_degrees) => { 169 | var norm = Math.sqrt(axis_x * axis_x + axis_y * axis_y + axis_z * axis_z) 170 | axis_x /= norm 171 | axis_y /= norm 172 | axis_z /= norm 173 | var angle_radians = (angle_degrees * Math.PI) / 180.0 174 | var cos = Math.cos(angle_radians / 2) 175 | var sin = Math.sin(angle_radians / 2) 176 | return Reactive.rotation(cos, axis_x * sin, axis_y * sin, axis_z * sin) 177 | } 178 | 179 | export const rotateTween = (element, driverParams, sampler, from, to, axisArray, onComplete) => { 180 | const anim = tween(sampler, from, to, driverParams, onComplete) 181 | 182 | axisArray.forEach(axis => (element.transform['rotation' + axis.toUpperCase()] = anim.signal)) 183 | } 184 | 185 | export const bounce = (element, duration, from, to) => { 186 | const driverParameters = { 187 | durationMilliseconds: duration / 2, 188 | loopCount: 2, 189 | mirror: true 190 | } 191 | 192 | return scaleTween(element, driverParameters, 'easeInOutQuad', from, to, ['x', 'y']) 193 | } 194 | 195 | // eg 000123 196 | export const padWithZeros = (num, length) => { 197 | const numString = num.toString() 198 | const strLength = numString.length 199 | let paddedStr = num.toString() 200 | 201 | for (let i = 0; i <= length; i++) { 202 | if (strLength > i) continue 203 | paddedStr = '0' + paddedStr 204 | } 205 | 206 | return paddedStr 207 | } 208 | 209 | // 210 | // Creates a throttled function that only invokes the provided 211 | // function at most once per every wait milliseconds 212 | // Inspired by: https://www.30secondsofcode.org/js/s/throttle 213 | // 214 | export const throttle = (fn, wait) => { 215 | let inThrottle, lastFn, lastTime 216 | return function() { 217 | const context = this, 218 | args = arguments 219 | if (!inThrottle) { 220 | fn.apply(context, args) 221 | lastTime = Date.now() 222 | inThrottle = true 223 | } else { 224 | lastFn && Time.clearTimeout(lastFn) 225 | lastFn = Time.setTimeout(function() { 226 | if (Date.now() - lastTime >= wait) { 227 | fn.apply(context, args) 228 | lastTime = Date.now() 229 | } 230 | }, Math.max(wait - (Date.now() - lastTime), 0)) 231 | } 232 | } 233 | } 234 | --------------------------------------------------------------------------------