├── Async_Await_boomboxExample.js ├── BetterGameLoop.js ├── BooleanOR2Pulse.ts ├── ColorSpaceConvert.js ├── Composition_and_FunctionV88.js ├── DaysCountDownToEvent ├── script.js └── util.js ├── DelayPatchSignalsWithPromises.js ├── EmitterParticleFade.js ├── GalleryTextureDimensions.js ├── HairColorAverage.js ├── NumberToText.js ├── README.md ├── ReactiveNumberToText.js ├── Swizzle ├── README.md ├── Swizzle.js └── SwizzleDemo.arprojpkg ├── dynamicInstanceOnTap.js ├── dynamic_block_instancing.ts ├── get3DPosition.js ├── getChildObjects.js ├── getTextureBasedOnUserLanguage.js ├── gradientStep.js ├── lightEstimation.js ├── persistenceModule.js ├── retouchAllMaterials.ts ├── safeUtilDistanceFunctionsForLowPrecision.sca └── tapToChangeMat.js /Async_Await_boomboxExample.js: -------------------------------------------------------------------------------- 1 | // How to load in modules 2 | const Animation = require('Animation'); 3 | const Scene = require('Scene'); 4 | const TouchGestures = require('TouchGestures'); 5 | 6 | // Use export keyword to make a symbol available in scripting debug console 7 | export const Diagnostics = require('Diagnostics'); 8 | export const sceneRoot = Scene.root; 9 | 10 | // Enables async/await in JS [part 1] 11 | (async function() { 12 | 13 | 14 | // To access scene objects 15 | const [base_jnt,speaker_left_jnt,speaker_right_jnt,planeTracker0,placer] = await Promise.all([ 16 | sceneRoot.findFirst('base_jnt'), 17 | sceneRoot.findFirst('speaker_left_jnt'), 18 | sceneRoot.findFirst('speaker_right_jnt'), 19 | sceneRoot.findFirst('planeTracker0'), 20 | sceneRoot.findFirst('placer') 21 | ]); 22 | 23 | const baseDriverParameters = { 24 | durationMilliseconds: 400, 25 | loopCount: Infinity, 26 | mirror: true 27 | }; 28 | 29 | const baseDriver = Animation.timeDriver(baseDriverParameters); 30 | baseDriver.start(); 31 | 32 | const baseSampler = Animation.samplers.easeInQuint(0.9, 1); 33 | 34 | const baseAnimation = await Animation.animate(baseDriver,baseSampler); 35 | 36 | const baseTransform = await base_jnt.transform; 37 | 38 | baseTransform.scaleX = await baseAnimation; 39 | baseTransform.scaleY = await baseAnimation; 40 | baseTransform.scaleZ = await baseAnimation; 41 | 42 | const speakerDriverParameters = { 43 | durationMilliseconds: 200, 44 | loopCount: Infinity, 45 | mirror: true 46 | }; 47 | 48 | const speakerDriver = Animation.timeDriver(speakerDriverParameters); 49 | speakerDriver.start(); 50 | 51 | const speakerSampler = Animation.samplers.easeOutElastic(0.7, 0.85); 52 | 53 | const speakerAnimation = await Animation.animate(speakerDriver,speakerSampler); 54 | 55 | const speakerLeftTransform = await speaker_left_jnt.transform; 56 | 57 | speakerLeftTransform.scaleX = await speakerAnimation; 58 | speakerLeftTransform.scaleY = await speakerAnimation; 59 | speakerLeftTransform.scaleZ = await speakerAnimation; 60 | 61 | const speakerRightTransform = await speaker_right_jnt.transform; 62 | 63 | speakerRightTransform.scaleX = await speakerAnimation; 64 | speakerRightTransform.scaleY = await speakerAnimation; 65 | speakerRightTransform.scaleZ = await speakerAnimation; 66 | 67 | TouchGestures.onPan().subscribe(function(gesture) { 68 | planeTracker0.trackPoint(gesture.location, gesture.state); 69 | }); 70 | 71 | const placerTransform = await placer.transform; 72 | 73 | TouchGestures.onPinch().subscribeWithSnapshot( { 74 | 'lastScaleX' : placerTransform.scaleX, 75 | 'lastScaleY' : placerTransform.scaleY, 76 | 'lastScaleZ' : placerTransform.scaleZ 77 | }, function (gesture, snapshot) { 78 | placerTransform.scaleX = gesture.scale.mul(snapshot.lastScaleX); 79 | placerTransform.scaleY = gesture.scale.mul(snapshot.lastScaleY); 80 | placerTransform.scaleZ = gesture.scale.mul(snapshot.lastScaleZ); 81 | }); 82 | 83 | TouchGestures.onRotate().subscribeWithSnapshot( { 84 | 'lastRotationY' : placerTransform.rotationY, 85 | }, function (gesture, snapshot) { 86 | const correctRotation = gesture.rotation.mul(-1); 87 | placerTransform.rotationY = correctRotation.add(snapshot.lastRotationY); 88 | }); 89 | 90 | // To log messages to the console 91 | Diagnostics.log('success.'); 92 | 93 | // Enables async/await in JS [part 2] 94 | })(); 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /BetterGameLoop.js: -------------------------------------------------------------------------------- 1 | // 2 | // In this script we show a better way to 3 | // do callbacks on frame since intervals can be unsteady. 4 | // This can be used in game loops and for running aniations 5 | // without the animation module. It is very similar to the 6 | // requestAnimationFrame approuch in web. 7 | // 8 | // Original post was created by Lasse Mejlvang Tvedt 9 | // URL: https://www.facebook.com/groups/SparkARcommunity/permalink/807800406298670/ 10 | // 11 | 12 | // 13 | // Spark AR Modules 14 | // 15 | const Time = require('Time'); 16 | 17 | // Store current frame. 18 | let frame = 0; 19 | 20 | // Subnscribe to runtime since this is updated 21 | // on every frame at current frame rate, and takes 22 | // framedrops into the calculations 23 | Time.ms.monitor().subscribe(evt => { 24 | // Count frames 25 | frame++; 26 | // Store time since loop startet 27 | const now = evt.newValue; 28 | // Calculate time between current and previous frame 29 | // Delta is the number of miliseconds passed since last frame. 30 | // This can be used for moving objects in a consistent speed even 31 | // with frame drops. 32 | const delta = now - evt.oldValue; 33 | 34 | // 35 | // Do your magic 36 | // 37 | }); 38 | -------------------------------------------------------------------------------- /BooleanOR2Pulse.ts: -------------------------------------------------------------------------------- 1 | // Made by Tomás Pietravallo for Diego Aguirre & The Spark AR Community 2 | 3 | // IMPORTANT 👇 4 | // This file is TYPESCRIPT, *NOT JAVASCRIPT*. 5 | // You need to create/use Typescript (.ts) files in Spark AR Studio for it to work 6 | 7 | // Import modules 8 | import Patches from 'Patches'; 9 | // Import Typescript types 10 | import 'Reactive.BoolSignal'; 11 | 12 | (async function () { 13 | // `await` is used to resolve the getBoolean() promise 14 | // `: BoolSignal` is used to denote the type of the variable, this will provide you with intellisense in VSCode 15 | const input1: BoolSignal = await Patches.outputs.getBoolean('InputBool1'); 16 | const input2: BoolSignal = await Patches.outputs.getBoolean('InputBool2'); 17 | 18 | // `or()` is the Reactive funtion for the boolean operator OR (commonly denoted with || ) 19 | // `onOn()` creates an event every time the OR operation switches from false -> true. 20 | // `Patches.inputs.setPulse()` returns a pulse to the patch editor 21 | Patches.inputs.setPulse('OutputPulse1', input1.or(input2).onOn()); 22 | 23 | /** 24 | * Note that both the From Script & To Script patches need to be placed in the editor. 25 | * Drag & drop the script to the editor to place the From Script variables (only needs to be done once). 26 | * Click on the yellow arrow for every 'To Script' variable. 27 | * 28 | * Make sure the types of the variables you create have the same type you provide as the input/output of the code. 29 | * Variable names are case sensitive. 30 | * 31 | * To create Patch Bridge variables, select any Javascript or Typescript files and head to the inspector panel. 32 | * Learn more about Patch Bridge variables here: https://sparkar.facebook.com/ar-studio/learn/patch-editor/bridging 33 | */ 34 | })(); -------------------------------------------------------------------------------- /ColorSpaceConvert.js: -------------------------------------------------------------------------------- 1 | const Time = require('Time'); 2 | const Material = require('Materials'); 3 | const Reactive = require('Reactive'); 4 | const Shaders = require('Shaders'); 5 | 6 | //This define the input colorspace and output colorspace you wish to covert 7 | const colorspaceconfig = { 8 | inColorSpace: 'HSV', 9 | outColorSpace: 'RGB' 10 | }; 11 | 12 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 13 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 14 | (async function(){ 15 | const material = await Material.findFirst('material0'); 16 | 17 | let hue = 0; 18 | let color; 19 | 20 | // Create an interval that will run the code inside every 500ms 21 | Time.setInterval(()=>{ 22 | // Set a random hue value from 0-1 for every 500ms 23 | // Get a random number from the Math object/module 24 | hue = Math.random() 25 | // Convert into a Vector3 and then from HSV to RGB 26 | // Use the color configuration we created earlier when defining "colorspaceconfig" 27 | color = Shaders.colorSpaceConvert(Reactive.pack3(hue, 1, 1), colorspaceconfig); 28 | // Pack to vec4 with alpha 1 29 | color = Reactive.pack4(color.x, color.y, color.z, 1) 30 | 31 | // Set textureslot and assign to material 32 | const textureslot = Shaders.DefaultMaterialTextures.DIFFUSE; 33 | material.setTextureSlot(textureslot, color); 34 | 35 | // Set the interval duration to 500ms 👇 36 | }, 500); 37 | })(); 38 | -------------------------------------------------------------------------------- /Composition_and_FunctionV88.js: -------------------------------------------------------------------------------- 1 | // by Sohail Mehra and Noland Chaliha 2 | const D = require('Diagnostics'); 3 | const S = require('Shaders'); 4 | const Tex = require('Textures'); 5 | const R = require('Reactive'); 6 | const M = require('Materials'); 7 | 8 | Promise.all([ 9 | Tex.findFirst('cameraTexture0'), 10 | M.findFirst('user_mat'), 11 | ]).then(onReady); 12 | 13 | function onReady(assets) { 14 | const diffuse = assets[0]; 15 | const defaultMaterial = assets[1]; 16 | const diffuseTexture = diffuse.signal; 17 | 18 | const vtx_uv = S.vertexAttribute({ variableName: S.VertexAttribute.TEX_COORDS }); 19 | const vtx_plus_one = R.add(vtx_uv, R.pack2(1,1)); // runs in vtx shader 20 | const frag_uv = S.fragmentStage(vtx_uv); 21 | const frag_plus_one = R.add(frag_uv, R.pack2(1,1)); // runs in fragment shader 22 | const diffuseColor = S.textureSampler(diffuseTexture, vtx_uv); 23 | 24 | // sm - composition 25 | // const func_uv = S.functionVec2(); 26 | // const func_plus_one = R.add(func_uv, R.pack2(1,1)); //runs in fragment shader 27 | // const diffuseColor = S.composition(diffuseTexture, func_uv); //order of samples determined by chain usage 28 | 29 | // sm - material 30 | const diffuseTexSlot = S.DefaultMaterialTextures.DIFFUSE; 31 | defaultMaterial.setTextureSlot(diffuseTexSlot, diffuseColor); 32 | } 33 | 34 | // by Sohail Mehra and Noland Chaliha -------------------------------------------------------------------------------- /DaysCountDownToEvent/script.js: -------------------------------------------------------------------------------- 1 | // 2 | // In this snippet we are showing how to make a countdown 3 | // to an event. The countdown will end at the same time 4 | // for all users in dsifferent timezones worldwide. 5 | // 6 | // Note: If the user have manually set the time on their 7 | // device to the future or past this script will behave 8 | // differently on their device. 9 | // 10 | // 11 | // Original script was created by Lasse Mejlvang Tvedt 12 | // 13 | 14 | // Project requirements: 15 | // a fromScript boolean value called pastDate 16 | // a fromScript scalar value called daysUntilEvent 17 | 18 | // 19 | // Spark AR Modules 20 | // 21 | const Patches = require('Patches'); 22 | const Time = require('Time'); 23 | 24 | // 25 | // Local Dependencies 26 | // 27 | import { createGlobalDate, convertMStoDays } from './util'; 28 | 29 | // Use createGlobalDate helper functiomn to create 30 | // a UTC date in local timezone 31 | // Aruguments: 32 | // year (required) 33 | // month (required) 34 | // day (optional) 35 | // hours (optional) 36 | // minutes = (optional) 37 | // seconds = (optional) 38 | // ms = (optional) 39 | // The time should be in UTC. UTC is almost the same as GMT. 40 | // UTC was a new universal standard created to adapt to earths 41 | // varying rotation (Leap seconds). The difference between UTC 42 | // and GMT is minimal. So Paris is GMT+1 then subtract 1 hour to 43 | // the time when setting the time 44 | const theGrandFinale = createGlobalDate( 45 | 2020, // year 46 | 2, // month 47 | 27, // day 48 | 17 // hour NOTE: if the eveny is at 18:00 in Paris (GMT+1), then it's 18 - 1; 49 | ); 50 | 51 | // Get current time from then the filter experience was started. 52 | const timeNow = new Date(); 53 | 54 | // Use elapsed time since filter started to detect if 55 | // we are closer to the final event. 56 | const subscriptionTime = Time.ms.monitor({fireOnInitialValue: false}).subscribe(evt => { 57 | // Calculate miliseconds left until event. 58 | // event time in ms - (time when filter experience was started + elpsed time since filter experience started) 59 | // = Miliseconds until event is supposed to happen. 60 | const timeLeftUntilEvent = theGrandFinale.getTime() - (timeNow.getTime() + evt.newValue); 61 | 62 | // If is or less than zero, then we're past the deadline 63 | if (timeLeftUntilEvent <= 0) { 64 | // Tell that we are past event date. 65 | Patches.inputs.setBoolean('pastDate', true); 66 | // Unsubscribe the time listener since we don't 67 | // need to check for this anymore 68 | subscriptionTime.unsubscribe(); 69 | } else { 70 | // There are still some time left until the event 71 | // Convert time left into whole days, and update value in patches 72 | Patches.inputs.setScalar('daysUntilEvent', Math.round(convertMStoDays(timeLeftUntilEvent))); 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /DaysCountDownToEvent/util.js: -------------------------------------------------------------------------------- 1 | // 2 | // Utility functions 3 | // 4 | 5 | // 6 | // Function for creating a date object with 7 | // UTC input date in the users local timezone. 8 | // 9 | export const createGlobalDate = ( 10 | year, 11 | month, 12 | day = 0, 13 | hours = 0, 14 | minutes = 0, 15 | seconds = 0, 16 | ms = 0 17 | ) => { 18 | // Note: JavaScript counts months from 0 to 11. 19 | // January is 0. December is 11. 20 | month = month - 1; 21 | return new Date(Date.UTC(year, month, day, hours, minutes, seconds, ms)); 22 | } 23 | 24 | // 25 | // Function for converting MS to full days 26 | // 27 | export const convertMStoDays = ms => { 28 | return ms / 1000 / 60 / 60 / 24; 29 | } 30 | -------------------------------------------------------------------------------- /DelayPatchSignalsWithPromises.js: -------------------------------------------------------------------------------- 1 | // 2 | // In this snippet we are playing with promises 3 | // to delay pulses sent to patches, and sequencing pulses etc. 4 | // 5 | // Original script was created by Lasse Mejlvang Tvedt 6 | // 7 | 8 | // Project requirements: 9 | // a fromScript pulse value called trigger 10 | 11 | // 12 | // Spark AR Modules 13 | // 14 | const R = require('Reactive'); 15 | const D = require('Diagnostics'); 16 | const Patches = require('Patches'); 17 | const Time = require('Time'); 18 | 19 | // 20 | // Async delay function 21 | // This will return a promise that is 22 | // resolved after x miliseconds 23 | // 24 | const delay = (ms) => new Promise(resolve => Time.setTimeout(() => resolve(), ms)); 25 | 26 | // 27 | // Function for sending pulse 28 | // This is just a wrapper for sending a pulse 29 | // through the trigger input. 30 | // it returns as a promise so it can be chanied 31 | // and trigger something after the pulse was 32 | // properly sent. 33 | // 34 | const sendPulse = () => { 35 | D.log('Pulse'); 36 | return Patches.inputs.setPulse('trigger', R.once()).catch(() => { 37 | // If the function fails to send the patch variable, explain what happened and how to fix the issue 38 | throw new Error(`You need to add a Patch bridge variable of type "Pulse" called "trigger" to the Patch editor, learn more about those here: https://sparkar.facebook.com/ar-studio/learn/patch-editor/bridging/#sending-variables-from-the-patch-editor-to-scripts .\n\nNote: even after creating the variable, it is important you drag the script file to the patch editor for it to be accessible and the code to work`) 39 | }); 40 | } 41 | 42 | // 43 | // Another example on how you can chain 44 | // promises to send pulses on intervals. 45 | // 46 | function sendThreePulsesWithInterval(interval) { 47 | // Start of by triggering one pulse 48 | // We are returiing it as a promise so we can 49 | // detect when all pulses have been sent. 50 | return sendPulse() 51 | // Wait X MS (interval value) 52 | .then(() => delay(interval)) 53 | // Send second pulse 54 | .then(sendPulse) 55 | // Wait again 56 | .then(() => delay(interval)) 57 | // Send last pulse 58 | .then(sendPulse); 59 | } 60 | 61 | 62 | // Wait 5000 miliseconds (5 Seconds) and 63 | // then send a pulse, and then log 64 | // that a pulse was sent. 65 | D.log('Wait 5 seconds before pulse'); 66 | delay(5000).then(sendPulse).then(() => { 67 | D.log('A pulse was sent after 5 seconds.'); 68 | 69 | // Trigger the sendThreePulsesWithInterval function 70 | // with 2000 MS between each pulse 71 | D.log('Send 3 pulses'); 72 | sendThreePulsesWithInterval(2000).then(() => { 73 | // Log when all 3 pulses have been sent. 74 | D.log('3 pulses was sent'); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /EmitterParticleFade.js: -------------------------------------------------------------------------------- 1 | // 2 | // In this script we show how to fade out particles from 3 | // a emitter. 4 | // 5 | // Original post was created by Josh Beckwith 6 | // URL: https://www.facebook.com/groups/SparkARcommunity/permalink/683989935346385/ 7 | // 8 | 9 | // 10 | // Include modules 11 | // 12 | const Scene = require('Scene'); 13 | const Animation = require('Animation'); 14 | 15 | // Load 'emitter0' object. 16 | Scene.root.findFirst('emitter0').then(emitter => { 17 | // emitter0 loaded and ready to be used. 18 | 19 | // Animate per channel in a HSVA color model. 20 | // We specify a easing functions for the 21 | // rate of change of a channel over time. 22 | // More info per under channel. 23 | // 24 | // Read more about HSV color model here: 25 | // https://en.wikipedia.org/wiki/HSL_and_HSV 26 | // 27 | // Read more about easing functions here: 28 | // https://easings.net/en 29 | emitter.hsvaColorModulationModifier = Animation.samplers.HSVA([ 30 | // H for hue. 31 | // Here we tell the Hue channel should have a constant 32 | // value of 1 during the lifespan of a particle. 33 | // So the Hue vil always stay 1 from start to finish. 34 | Animation.samplers.constant(1), 35 | // S for saturation. 36 | // Here we use contsant again. 37 | Animation.samplers.constant(1), 38 | // V for value. 39 | // And the same here. 40 | Animation.samplers.constant(1), 41 | // A for alpha. 42 | // Here we use a easeInQuad easing function to gradually 43 | // change the Alpha channel value for the particle over time. 44 | // The value will transition from 1 (100% visible) to 0 45 | // (0% visible) with a non-linear speed. 46 | // 47 | // Read more about easeInQuad here: 48 | // https://easings.net/en#easeInQuad 49 | Animation.samplers.easeInQuad(1, 0) 50 | ]); 51 | 52 | // The same rules can be applied to other particle properties 53 | // such as size. Here we are trying out the easeInCirc easing 54 | // function. All available easing functions in Spark AR 55 | // can be found here: 56 | // https://sparkar.facebook.com/ar-studio/learn/documentation/reference/classes/animationmodule.samplerfactory/ 57 | emitter.sizeModifier = Animation.samplers.easeInCirc(0, 0.01); 58 | 59 | // Other particle properties that can be modified include 60 | // positionModifier and velocityModifier. 61 | // More information is specified here: 62 | // https://sparkar.facebook.com/ar-studio/learn/documentation/reference/classes/scenemodule.particlesystem 63 | }); 64 | -------------------------------------------------------------------------------- /GalleryTextureDimensions.js: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // The following example demonstrates how to get the width and height of the 3 | // Gallery Texture and pass them to Patch Editor 4 | // 5 | // Project setup: 6 | // - Add a gallery texture asset (Assets > Add asset > Gallery Texture) and 7 | // keep its default name. You can change it later. 8 | // - Inspect the script asset and add two From Script variables: 9 | // GalleryTextureWidth and GalleryTextureHeight 10 | // - Click the 'Create Producer Patch' button which is below the variables 11 | //============================================================================== 12 | 13 | const Patches = require('Patches'); 14 | const Textures = require('Textures'); 15 | 16 | (async function() { 17 | const galleryTexture = await Textures.findFirst('galleryTexture0'); 18 | await Patches.inputs.setScalar('GalleryTextureWidth', galleryTexture.width); 19 | await Patches.inputs.setScalar('GalleryTextureHeight', galleryTexture.height); 20 | })(); 21 | -------------------------------------------------------------------------------- /HairColorAverage.js: -------------------------------------------------------------------------------- 1 | // Load in the required modules 2 | const Materials = require('Materials'); 3 | const Segmentation = require('Segmentation'); 4 | const Reactive = require('Reactive'); 5 | const Shaders = require('Shaders'); 6 | 7 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 8 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 9 | (async function () { 10 | // Find a Material called "defaultMaterial0" or throw an error explaining what happened if it cannot be found 11 | const [mat] = await Promise.all([ 12 | Materials.findFirst('defaultMaterial0') 13 | ]).catch(() => { 14 | throw new Error(`There is no material called defaultMaterial0 on your project, please create one, or modify the script to match an existing material`) 15 | }); 16 | 17 | // Getting averge color of the hair 18 | const color = Reactive.pack4( 19 | Segmentation.hair.averageColor.red, 20 | Segmentation.hair.averageColor.green, 21 | Segmentation.hair.averageColor.blue, 22 | Segmentation.hair.averageColor.alpha 23 | ); 24 | 25 | // Getting the texture Slot 26 | const textureslot = Shaders.DefaultMaterialTextures.DIFFUSE; 27 | 28 | // Setting the color in the Texture slot 29 | mat.setTextureSlot(textureslot, color); 30 | })(); 31 | -------------------------------------------------------------------------------- /NumberToText.js: -------------------------------------------------------------------------------- 1 | // SparkAr module 2 | const Scene = require("Scene"); 3 | const Patches = require("Patches"); 4 | 5 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 6 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 7 | (async function () { 8 | // importing the text elements, and explain what happened if the code were to fail 9 | let textObject = await Scene.root.findFirst("2dText0").catch(() => { throw new Error(`no object called 2dText0 found on your Scene, please create one or modify the script for it to work`) }); 10 | 11 | // getting the score toScript value text 12 | Patches.outputs.getScalar("score").then((patchScore) => { 13 | // Monitor the value of the scalar signal 14 | patchScore.monitor({fireOnInitialValue: true}).subscribe((evt) => { 15 | // set the text property of our scene object to the new value (converted into a string) 16 | textObject.text = evt.newValue.toString(); 17 | }); 18 | }).catch(() => { 19 | // throw an error explaining what happened if the code were to fail to load the Patch bridge variable 20 | throw new Error(`A Patch bridge variable of Type Scalar and name "score" was not found.\nTo create Patch Bridge variables, select any Javascript or Typescript files and head to the inspector panel.\nLearn more about Patch Bridge variables here: https://sparkar.facebook.com/ar-studio/learn/patch-editor/bridging`) 21 | }) ; 22 | })(); 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SparkAR-Snippets 2 | A collection of code snippets gathered from the Spark AR Community group. 3 | 4 | ## Content Overview 5 | Here is a basic rundown of what some of these scripts do: 6 | 7 | Learn basics ❤️: 8 | - [get3DPosition.js](./get3DPosition.js) see how to access the position of an object (and send it back to the patch editor) 9 | - [tapToChangeMat.js](./tapToChangeMat.js) change an object's material on change 10 | 11 | Slightly more advanced 💪: 12 | - [ColorSpaceConvert.js](./ColorSpaceConvert.js) showcases how to create a material with a color that changes randomly 13 | - [dynamicInstanceOnTap.js](dynamicInstanceOnTap.js) learn how to instance objects dynamically 14 | 15 | Really useful 👀: 16 | - [EmitterParticleFade.js](./EmitterParticleFade.js) fade particles over time 17 | - [getTextureBasedOnUserLanguage.js](getTextureBasedOnUserLanguage.js) change which assets are used based on language! 18 | 19 | There are many more scripts and projects not included in this brief overview, feel free to explore or contribute your own 20 | -------------------------------------------------------------------------------- /ReactiveNumberToText.js: -------------------------------------------------------------------------------- 1 | //import statements 2 | const Patches = require('Patches') 3 | const Reactive = require('Reactive'); 4 | 5 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 6 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 7 | (async function() { 8 | //Get Number Value 9 | var score = Patches.outputs.getScalar('NumValue') 10 | var str = '' 11 | 12 | //Format String such as if less then 10, number will be in '01','02' etc format 13 | str = score.ge(10).ifThenElse(score.toString(),Reactive.concat('0',score.toString())) 14 | 15 | //Set variable output string value 16 | Patches.inputs.setString('TextValue',str) 17 | })(); 18 | -------------------------------------------------------------------------------- /Swizzle/README.md: -------------------------------------------------------------------------------- 1 | # Swizzle 2 | 3 | **Swizzle** is an equivalent of [Swizzle Patch](https://sparkar.facebook.com/ar-studio/learn/patch-editor/utility-patches/) in script. 4 | 5 | 6 | 7 | ## Install 8 | 9 | 0. [Download Swizzle](https://raw.githubusercontent.com/Spark-AR-Community/SparkAR-Snippets/master/Swizzle/Swizzle.js) (Right click and Save as). 10 | 11 | 1. Drag/Drop or import `Swizzle.js` to Spark AR. 12 | 13 | 2. Import `siwzzle` function. 14 | 15 | ```javascript 16 | import { swizzle } from './Swizzle'; 17 | ``` 18 | 19 | 3. You can also [Click Here to Download Sample Project (v98)](https://raw.githubusercontent.com/Spark-AR-Community/SparkAR-Snippets/master/Swizzle/SwizzleDemo.arprojpkg). 20 | 21 | 22 | 23 | ## Usage 24 | 25 | ```javascript 26 | import { swizzle } from './Swizzle'; 27 | 28 | const Reactive = require('Reactive'); 29 | 30 | swizzle(Reactive.val(.1), 'xxx1') 31 | // Reactive.pack4(.1, .1, .1, 1) 32 | 33 | swizzle(Reactive.pack2(.1, .3), 'yx') 34 | // Reactive.pack2(.3, .1) 35 | 36 | swizzle(Reactive.pack3(.1, .3, .5), 'xzz0') 37 | // Reactive.pack4(.1 , .5, .5, 0) 38 | 39 | swizzle(Reactive.pack4(.1, .3, .5, .7), '0101') 40 | // Reactive.pack4(0, 1, 0, 1) 41 | 42 | swizzle(Reactive.vector(.1, .3, .5), 'z') 43 | // Reactive.val(.5) 44 | 45 | swizzle(Reactive.RGBA(.3, .8, .6, 1).toVec4(), 'bgr1') 46 | // Reactive.pack4(.6, .8, .3, 1) 47 | 48 | swizzle(Reactive.HSVA(.2, .8, .6, 1).toVec4(), 'rrxz') 49 | // Reactive.pack4(.2, .2, .2, .6) 50 | ``` 51 | 52 | 53 | 54 | ### swizzle(value, specifier) 55 | 56 | Take input numbers and output them in a different order. 57 | 58 | - `value`: A number or vector that you want to reorder. 59 | - `specifier`: The order to output the values. Use (xyzw) and (01). 60 | 61 | ### vec4_toRGBA(point4D) 62 | 63 | Convert Point4DSignal to RGBASignal. 64 | 65 | ### vec4_toHSVA(point4D) 66 | 67 | Convert Point4DSignal to HSVASignal. -------------------------------------------------------------------------------- /Swizzle/Swizzle.js: -------------------------------------------------------------------------------- 1 | const Reactive = require('Reactive'); 2 | 3 | /** 4 | * Take input numbers and output them in a different order. 5 | * Input values correspond to the swizzle value (xyzw) in the order theyre inputted. For example, an input of (1,2,3) and a swizzle value of (yxz) would output (2,1,3). You can also use 0 and 1. For example, a swizzle value of (x01) would output (1,0,1). 6 | * @param {*} value A number or vector that you want to reorder. 7 | * @param {string} specifier The order to output the values. Use (xyzw) and (01). 8 | * @returns {*} The values in your chosen order. 9 | */ 10 | export function swizzle(value, specifier) { 11 | const signal = element => { 12 | const swizzleSignal = property => { 13 | if (typeof (value) == 'number') { 14 | if (property == 'x' || property == 'r') { 15 | return value; 16 | } else { 17 | throw `Specifier '${property}' in '${specifier}' can't be used with this signal.`; 18 | } 19 | } else if (value['pinLastValue'] != undefined) { 20 | if (property == 'x' || property == 'r') { 21 | return value; 22 | } else { 23 | throw `Specifier '${property}' in '${specifier}' can't be used with this signal.`; 24 | } 25 | } else { 26 | if (value[property] == undefined) { 27 | throw `Specifier '${property}' in '${specifier}' can't be used with this signal.`; 28 | } else { 29 | return value[property]; 30 | } 31 | } 32 | } 33 | 34 | switch (element) { 35 | case '0': return 0; 36 | case '1': return 1; 37 | case 'x': return swizzleSignal('x'); 38 | case 'y': return swizzleSignal('y'); 39 | case 'z': return swizzleSignal('z'); 40 | case 'w': return swizzleSignal('w'); 41 | case 'r': return swizzleSignal('x'); 42 | case 'g': return swizzleSignal('y'); 43 | case 'b': return swizzleSignal('z'); 44 | case 'a': return swizzleSignal('w'); 45 | default: throw `Invalid swizzle element specifier: '${element}' in '${specifier}'`; 46 | } 47 | } 48 | 49 | switch (specifier.length) { 50 | case 1: return signal(specifier[0]); 51 | case 2: return Reactive.pack2(signal(specifier[0]), signal(specifier[1])); 52 | case 3: return Reactive.pack3(signal(specifier[0]), signal(specifier[1]), signal(specifier[2])); 53 | case 4: return Reactive.pack4(signal(specifier[0]), signal(specifier[1]), signal(specifier[2]), signal(specifier[3])); 54 | default: throw `Invalid swizzle specifier: '${specifier}'`; 55 | } 56 | } 57 | 58 | /** 59 | * Convert Point4DSignal to RGBASignal. 60 | * @param {Point4DSignal} point4D 61 | */ 62 | export function vec4_toRGBA(point4D) { 63 | return Reactive.RGBA(point4D.x, point4D.y, point4D.z, point4D.w); 64 | } 65 | 66 | /** 67 | * Convert Point4DSignal to HSVASignal. 68 | * @param {Point4DSignal} point4D 69 | */ 70 | export function vec4_toHSVA(point4D) { 71 | return Reactive.HSVA(point4D.x, point4D.y, point4D.z, point4D.w); 72 | } -------------------------------------------------------------------------------- /Swizzle/SwizzleDemo.arprojpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spark-AR-Community/SparkAR-Snippets/57bc2151223e9cecc3a8a4bd037f69098022f701/Swizzle/SwizzleDemo.arprojpkg -------------------------------------------------------------------------------- /dynamicInstanceOnTap.js: -------------------------------------------------------------------------------- 1 | /* 2 | Prerequisites 3 | * Make sure you added Script Dynamic Instancing on your capability 4 | * Make sure you seleced Tap under TouchGestures on your capability 5 | * 6 | * If you fail to add these to your project's Capabilities you will get error messages 7 | */ 8 | 9 | // Create plane Dynamically in code On Tap 10 | 11 | // load in modules 12 | const Scene = require('Scene'); 13 | const TouchGestures = require("TouchGestures"); 14 | const Reactive = require("Reactive"); 15 | 16 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 17 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 18 | (async function () { 19 | 20 | // Accessing the Focal Distance scene object 21 | const [fd] = await Promise.all([ 22 | 23 | Scene.root.findFirst("Focal Distance"), 24 | 25 | ]); 26 | 27 | // Array to store the created elements 28 | var totalPlane = []; 29 | var x = 0; 30 | 31 | // Tap function 32 | TouchGestures.onTap().subscribe(async () => { 33 | 34 | // Promise that creates plane on tap 35 | let [newPlane] = await Promise.all([ 36 | 37 | Scene.create("Plane", { 38 | "name": "Plane", 39 | "width": 0.1, 40 | "height": 0.1, 41 | "transform": Reactive.transform( 42 | // The transform's position, scale, and rotation values: 43 | Reactive.point(x,0,0), 44 | Reactive.scale(1,1,1), 45 | Reactive.quaternionFromEuler(0,0,0) 46 | ), 47 | "hidden": false, 48 | }), 49 | ]); 50 | //Storing the created plane 51 | totalPlane.push(newPlane); 52 | //Adding the created plane to the parent and the scene 53 | fd.addChild(totalPlane[totalPlane.length - 1]); 54 | x += .1; 55 | }); 56 | 57 | // Note: Because on each tap we increment x by 0.1, and use that x variable to create our dynamic plane 58 | // If you tap multiple times then the planes will get instanced next to each other, instead of the same spot 59 | 60 | })(); 61 | -------------------------------------------------------------------------------- /dynamic_block_instancing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * In order to use this snippet in your project you will need to add the following capability to your project: 3 | * 4 | * + Scripting Dynamic Instantiation 5 | * 6 | * You do this by going to: 7 | * Menu bar > Project > Edit properties... > Capabilities > (+) Scripting Dynamic Instantiation 8 | * 9 | * If you do no add this capability manually before trying to use or follow this snippet you will get an error/warning 10 | * 11 | * This is a Typescript file -- a file ending with .ts 12 | * This means it's just Javascript with extra fancy stuff (that is optional) 13 | * 14 | * You can find a project package with the capabilities set up and the code up and running here: https://drive.google.com/file/d/18qA1dSaLObAjHwplEKawvPEKVQ76AH_b/view?usp=sharing 15 | * 16 | * --- 17 | * 18 | * This example was inspired by Ommy (https://www.instagram.com/autonommy/) go follow her 19 | * Written by Tomas Pietravallo 20 | */ 21 | 22 | 23 | // Import modules for the Spark Studio APIs 24 | import Scene from 'Scene'; 25 | import Blocks from 'Blocks'; 26 | import Time from 'Time'; 27 | import Diagnostics from 'Diagnostics'; 28 | import Reactive from 'Reactive'; 29 | 30 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 31 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 32 | async function createAndDestroyBlock() { 33 | // Make sure you set "block0" to the name of a block within your project's assets 34 | const myBlock = await Blocks.instantiate("block0", {}); 35 | 36 | // Add the block to the Scene root as a child (the Block's parent will be the Scene's focal distance) 37 | // If you simply instance the block but don't parent it, it will not show on the Scene 38 | await Scene.root.addChild(myBlock); 39 | 40 | // Create a timer that destroys the block, 500 milliseconds after it was created 41 | Time.setTimeout(() => { 42 | // Destroy the block instance 43 | Scene.destroy(myBlock); 44 | 45 | // Note: you can unparent first by using Scene.root.removeChild, but Scene.destroy unparents automatically 46 | }, 500); 47 | }; 48 | 49 | // Create a timer that will execute the code inside every second (1000 milliseconds) 50 | // We'll pass the createAndDestroyBlock as the code to be executed 51 | // If we never called the createAndDestroyBlock the code inside would never run (it'd do nothing) 52 | Time.setInterval(createAndDestroyBlock, 1000 ); -------------------------------------------------------------------------------- /get3DPosition.js: -------------------------------------------------------------------------------- 1 | // code by RBKavin 2 | 3 | // this code allow us to get the 3d position of any object and send it to patch editor 4 | 5 | // Importing modules 6 | const Scene = require("Scene"); 7 | const Patches = require("Patches"); 8 | const Reactive = require("Reactive"); 9 | 10 | Promise.all([ 11 | // it gets the first element thats named object 12 | Scene.root.findFirst("object") 13 | ]).then(result => { 14 | // result[0] contain the object 15 | const obj = result[0]; 16 | 17 | // need to create a from script vec 3 called objPos 18 | // objects position will be sent to the objPos 19 | Patches.inputs.setPoint("objPos", obj.transform.position); 20 | 21 | //objects World Position will be sent to the vex 3 fromScript called objWorldPos 22 | Patches.inputs.setPoint("objWorldPos", obj.worldTransform.position); 23 | }); 24 | -------------------------------------------------------------------------------- /getChildObjects.js: -------------------------------------------------------------------------------- 1 | // code by kavin kumar 2 | 3 | //this is a example of accessing child object using the parent 4 | //this will help you when u have children having same name with multiple parents 5 | 6 | // load in modules 7 | const Scene = require('Scene'); 8 | const D = require('Diagnostics'); 9 | 10 | //promise start 11 | 12 | Promise.all([ 13 | // it goes to the object1 first and then it looks for object1 child named object2 14 | Scene.root.findByPath("**/object1/object2") 15 | ]).then((result) => { 16 | D.log(result[0][0].name) 17 | }); 18 | -------------------------------------------------------------------------------- /getTextureBasedOnUserLanguage.js: -------------------------------------------------------------------------------- 1 | // 2 | // In this example we are trying to get a texture based on language 3 | // set on the users device. It will look for textures named in the 4 | // same naming convention as Spark AR's Locale.locale: "Provides the 5 | // ISO 639-1 language + ISO 3166-1 region compliant locale identifier, 6 | // e.g. en_US or zh_HK." 7 | // Source: https://sparkar.facebook.com/ar-studio/learn/documentation/reference/classes/localemodule 8 | // 9 | // It will try getting the texture named ISO 639-1 language + ISO 3166-1 (eg. 'en_US') first, and then 10 | // the texture named in similar to ISO 639-1 (eg. 'en') language, and at last a fallback 11 | // name that the user provide ('en' by default) if no relevant to the locale is found. 12 | // 13 | // Script was created by Lasse Mejlvang Tvedt while helping a friend. 14 | // 15 | 16 | // 17 | // Spark AR Modules 18 | // 19 | const D = require('Diagnostics'); 20 | const Locale = require('Locale'); 21 | const Tex = require('Textures'); 22 | 23 | // 24 | // Function for loading a texture based on language 25 | // on the users device, 26 | // 27 | function getTextureBasedOnLanguage(fallback = 'en') { 28 | // Use a promise since we need to wait to check if anything is loaded 29 | return new Promise((resolve, reject) => { 30 | // Locale.locale is a StringSignal so we will have to wait until 31 | // the singal is set. 32 | const subscriberLocale = Locale.locale.monitor({ fireOnInitialValue: true}).subscribe(evt => { 33 | // Only trigger this event once. 34 | subscriberLocale.unsubscribe(); 35 | 36 | // Cache locale and fallback to "foo_bar" in case 37 | // locale is set. Tex.findFirst probably needs 38 | // a value and not a empty string. 39 | // Also, Spark AR Studio will always return 'foo_bar' 40 | const isoLocale = evt.newValue || 'foo_bar'; 41 | 42 | // Try loading all the textures 43 | Promise.all([ 44 | // Try loading the texture for the full language 45 | // & region code (ISO 639-1 + ISO 3166-1). 46 | Tex.findFirst(isoLocale), 47 | // Also try load the texture for language (ISO 639-1) 48 | // without region. 49 | Tex.findFirst(isoLocale.split('_')[0]), 50 | // Try loading fallback texture as well. 51 | Tex.findFirst(fallback), 52 | ]).then(textures => { 53 | // Get the first texture that is successfully loaded. 54 | // If didnt find any this will return null. 55 | const texture = textures.find(texture => texture); 56 | 57 | // If any texture was succesfully loaded 58 | if (texture) { 59 | // Return that texture 60 | resolve(texture) 61 | return; 62 | } 63 | 64 | // Tell that no textures was found if didnt 65 | // find any relevant textures. 66 | reject('No texture found.'); 67 | }); 68 | }); 69 | }); 70 | } 71 | 72 | // Get texture based on user locale, with texture named 73 | // 'fallbackName' as fallback. 74 | getTextureBasedOnLanguage('fallbackName').then(texture => { 75 | // If a texture was found and loaded 76 | D.log(texture); 77 | }).catch(err => { 78 | // If no txture was found. 79 | D.log(err); 80 | }); 81 | -------------------------------------------------------------------------------- /gradientStep.js: -------------------------------------------------------------------------------- 1 | // This is the script equivalent of chaining multiple Step Gradient patches together. 2 | // If you are looking a easy way to do this in patches, you can use Mate Steinforth's 3 | // Easy Gradient Patch: https://gumroad.com/l/yYREv 4 | 5 | // Import dependencies 6 | const R = require('Reactive'); 7 | const S = require('Shaders'); 8 | 9 | // Helper function for creating gradients. 10 | export function gradientStep(gradient, steps = []) { 11 | if (steps.length < 2) { 12 | throw 'You need at least 2 colors to make an gradient.'; 13 | } 14 | 15 | let previousStep = steps[0][0]; 16 | for(let i = 1; i < steps.length; i++) { 17 | previousStep = R.mix(previousStep, steps[i][0], R.smoothStep(gradient, steps[i-1][1], steps[i][1])); 18 | } 19 | 20 | return previousStep; 21 | } 22 | 23 | // 24 | // Usage 25 | // 26 | // Create gradient type you want. 27 | // HORIZONTAL: The gradient will be in horizontal direction. 28 | // CIRCULAR: The gradient will radiate outward in a circular direction. 29 | // VERTICAL: The gradient will be in vertical direction. 30 | // https://sparkar.facebook.com/ar-studio/learn/documentation/reference/enums/shadersmodule.gradienttype 31 | const gradientType = S.gradient({type: 'HORIZONTAL'}); 32 | 33 | const gradient = gradientStep(gradientType, [ 34 | // Array, first value is the color, second is the location of the color 35 | // on the gradient (The same way as photoshop). 36 | [R.pack4(1, 0, 0, 1), 0], 37 | [R.pack4(0, 1, 0, 1), .5], 38 | [R.pack4(0, 0, 1, 1), 1], 39 | // Here you can add as many colors as you want 40 | // Just remember to have the locations in right order. 41 | ]); 42 | -------------------------------------------------------------------------------- /lightEstimation.js: -------------------------------------------------------------------------------- 1 | const LightingEstimation = require('LightingEstimation'); 2 | const Reactive = require('Reactive'); 3 | const Scene = require('Scene'); 4 | 5 | // 6 | // Load assets 7 | // 8 | Promise.all([ 9 | Scene.root.findFirst('ambientLight0'), 10 | ]).then(onReady); 11 | 12 | function onReady(assets) { 13 | 14 | const ambientLight0 = assets[0]; 15 | 16 | // Calculate light intensity by subtracting the frame brightness from 1, the 17 | // darker the frame brightness, the greater the light intensity 18 | const lightIntensity = Reactive.sub(1,LightingEstimation.frameBrightness); 19 | 20 | // Bind the light intensity to the intensity of the ambient light 21 | ambientLight0.intensity = lightIntensity; 22 | } -------------------------------------------------------------------------------- /persistenceModule.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is an example of how to use the persistence module. 3 | For this example we'll store the value of a slider (a real use case) but you can store pretty much anything. 4 | 5 | Project setup: 6 | - Persistence: 7 | Whitelist your key (in this example 'savedSliderValue') 8 | Go to Project > Edit Properties... > Capabilities > + Persistence > Write you key in the text box. 9 | The key used in this example project is "savedSliderValue" 10 | 11 | - NativeUI: 12 | Enable the Slider 13 | */ 14 | 15 | // Load the required modules 16 | const NativeUI = require('NativeUI'); 17 | const Persistence = require('Persistence'); 18 | const Patches = require('Patches'); 19 | const Diagnostics = require('Diagnostics'); 20 | 21 | // Shortcut for 'NativeUI.slider' 22 | const slider = NativeUI.slider; 23 | let sliderValue = 0.5; // Slider values range from 0 to 1. This value will be the default if no value was stored previously 24 | 25 | Persistence.local.get('savedSliderValue').then((savedSliderValue) => { 26 | /* 27 | This conditional won't execute if the savedSliderValue is undefined/null. 28 | Thus if no object was stored the code inside won't get executed and sliderValue will preserve it's current value 29 | If you are confused as to how if(savedSliderValue){} works see https://developer.mozilla.org/en-US/docs/Glossary/Truthy 30 | */ 31 | if (savedSliderValue) { 32 | // The line below gets the 'value' attribute of the object, see lines 46 & 47 33 | sliderValue = savedSliderValue.value; 34 | } 35 | }) 36 | .then(() => { 37 | // Configure the slider, the slider must be configured after getting the value 38 | slider.value = sliderValue; 39 | slider.visible = true; 40 | 41 | slider.value 42 | .monitor({ fireOnInitialValue: true }) // monitor changes in the value 43 | .select('newValue') // you can either get the 'newValue' or 'oldValue' 44 | .subscribe((newSliderValue) => { // The function in .subscribe() gets executed everytime the value changes 45 | // You have to store things as objects. So we create an object with a property called 'value' 46 | Persistence.local.set('savedSliderValue', { value: newSliderValue }); 47 | sliderValue = newSliderValue; 48 | 49 | /* 50 | Optional: send the value to the patch editor. 51 | Select a script and create a From Script variable called 'slider' 52 | The 'Patches' module is already imported on line 17 53 | */ 54 | // Patches.inputs.setScalar('slider', sliderValue); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /retouchAllMaterials.ts: -------------------------------------------------------------------------------- 1 | // Made by Tomás Pietravallo for the Spark AR Community 2 | 3 | // IMPORTANT 👇 4 | // This file is TYPESCRIPT, *NOT JAVASCRIPT*. 5 | // You need to create/use Typescript (.ts) files in Spark AR Studio for it to work 6 | 7 | 8 | // Import the modules needed 9 | import Materials from 'Materials'; 10 | import NativeUI from 'NativeUI'; 11 | 12 | // Check whether the Slider capability is turned on 13 | // This needs to be done because Spark doesn't turn it on automatically 14 | if(NativeUI.slider == null){ 15 | throw `The Slider capability isn't turned on. 16 | Please go to Project > Capablities > NativeUI > ✅ Slider. 17 | You are doing great, keep going ✨` 18 | }; 19 | 20 | // Using async allows us to use await, for more info on async/await you can visit: https://github.com/tomaspietravallo/sparkar-fdccc2020#asyncawait 21 | (async function () { 22 | 23 | // Use Typescript types to get Intelisense in VSCode 24 | // Materials.getAll returns an array (inside a Promise) with all the materials in the project 25 | // `await` is used to wait for the getAll() promise 26 | const materials: MaterialBase[] = await Materials.getAll(); 27 | 28 | // Make the slider visible 29 | NativeUI.slider.visible = true; 30 | 31 | // Make the default slider value 1.0 32 | // This is the max value, and will be the default in case the user doesn't change it 33 | NativeUI.slider.value = 1.0; 34 | 35 | // Loop over the Materials.getAll array 36 | // This means the code will apply for *all* materials in your project 37 | for (let index = 0; index < materials.length; index++) { 38 | // Use Array[index] to get a particular material from the array 39 | // `m` is the material we check on the particular loop iteration 40 | const m = materials[index]; 41 | 42 | // If this particular material isn't 'Retouch' (doesn't have the skinSmoothingFactor property), skip it 43 | if (m.skinSmoothingFactor == null) return; 44 | 45 | // Set the smoothing factor to the slider value 46 | // skinSmoothingFactor values range from 0-1, just like the slider values 47 | m.skinSmoothingFactor = NativeUI.slider.value; 48 | }; 49 | 50 | })(); 51 | 52 | 53 | /** 54 | * Note: 55 | * The (function(){ ... })(); syntax is called an 'IIFE' (Immediately Invoked Function Expression) 56 | * It is one of many ways to use functions, you can learn more about it here: 57 | * https://developer.mozilla.org/en-US/docs/Glossary/IIFE 58 | */ -------------------------------------------------------------------------------- /safeUtilDistanceFunctionsForLowPrecision.sca: -------------------------------------------------------------------------------- 1 | using namespace std; 2 | #import < utils > 3 | //following Ognjen Dragoljević's post about Low Precision shader practices 4 | //https://www.facebook.com/groups/SparkARcommunity/permalink/1131916640553710/ 5 | //ref https://gist.github.com/BarakChamo/bbaa5080acad2e3f8080e3bdd42325a7 6 | //the built-ins for SparkSL are 7 | //these 8 | 9 | // vec2 safeNormalize(vec2 v); 10 | // vec3 safeNormalize(vec3 v); 11 | // vec4 safeNormalize(vec4 v); 12 | // float safeLength(vec2 v); 13 | // float safeLength(vec3 v); 14 | // float safeLength(vec4 v); 15 | // float safeDistance(vec2 p0, vec2 p1); 16 | // float safeDistance(vec3 p0, vec3 p1); 17 | // float safeDistance(vec4 p0, vec4 p1); 18 | // float safeAtan(float y, float x);] 19 | 20 | //but below are some more interesting ones 21 | float euclideanDistance(float p1, float p2) { 22 | float d1 = (p1 - p2); 23 | return sqrt(pow(d1, 2.0)); 24 | } 25 | 26 | float euclideanDistance(vec2 p1, vec2 p2) { 27 | float d1 = (p1.x - p2.x); 28 | float d2 = (p1.y - p2.y); 29 | return sqrt(pow(d1, 2.0) + pow(d2, 2.0)); 30 | } 31 | 32 | float euclideanDistance(vec3 p1, vec3 p2) { 33 | float d1 = (p1.x - p2.x); 34 | float d2 = (p1.y - p2.y); 35 | float d3 = (p1.z - p2.z); 36 | return sqrt(pow(d1, 2.0) + pow(d2, 2.0) + pow(d3, 2.0)); 37 | } 38 | 39 | /* Manhattan distance */ 40 | /* https://en.wikipedia.org/wiki/Taxicab_geometry */ 41 | float manhattanDistance(float p1, float p2) { 42 | float d1 = abs(p1 - p2); 43 | return d1; 44 | } 45 | 46 | float manhattanDistance(vec2 p1, vec2 p2) { 47 | float d1 = abs(p1.x - p2.x); 48 | float d2 = abs(p1.y - p2.y); 49 | return d1 + d2; 50 | } 51 | 52 | float manhattanDistance(vec3 p1, vec3 p2) { 53 | float d1 = abs(p1.x - p2.x); 54 | float d2 = abs(p1.y - p2.y); 55 | float d3 = abs(p1.z - p2.z); 56 | return d1 + d2 + d3; 57 | } 58 | 59 | /* Minkowski distance */ 60 | /* https://en.wikipedia.org/wiki/Minkowski_distance */ 61 | float minkowskiDistance(float p1, float p2, float power) { 62 | float d1 = pow(abs(p1 - p2), power); 63 | return pow(d1, 1.0 / power); 64 | } 65 | 66 | float minkowskiDistance(vec2 p1, vec2 p2, float power) { 67 | float d1 = pow(abs(p1.x - p2.x), power); 68 | float d2 = pow(abs(p1.y - p2.y), power); 69 | return pow(d1 + d2, 1.0 / power); 70 | } 71 | 72 | float minkowskiDistance(vec3 p1, vec3 p2, float power) { 73 | float d1 = pow(abs(p1.x - p2.x), power); 74 | float d2 = pow(abs(p1.y - p2.y), power); 75 | float d3 = pow(abs(p1.z - p2.z), power); 76 | return pow(d1 + d2 + d3, 1.0 / power); 77 | } 78 | 79 | /* Chebyshev distance */ 80 | /* https://en.wikipedia.org/wiki/Chebyshev_distance */ 81 | float chebyshevDistance(float p1, float p2) { 82 | float d1 = abs(p1 - p2); 83 | return d1; 84 | } 85 | 86 | float chebyshevDistance(vec2 p1, vec2 p2) { 87 | float d1 = abs(p1.x - p2.x); 88 | float d2 = abs(p1.y - p2.y); 89 | return max(d1, d2); 90 | } 91 | 92 | float chebyshevDistance(vec3 p1, vec3 p2) { 93 | float d1 = abs(p1.x - p2.x); 94 | float d2 = abs(p1.y - p2.y); 95 | float d3 = abs(p1.z - p2.z); 96 | return max(d1, max(d2, d3)); 97 | } 98 | 99 | 100 | 101 | 102 | 103 | //#define constant Time = getTime(); 104 | 105 | vec2 hash( vec2 p ) { 106 | p = vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))); 107 | return fract(sin(p)*18.5453); 108 | } 109 | 110 | float random (vec2 st) { 111 | return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); 112 | } 113 | 114 | /* distance tesselation */ 115 | vec2 tessellate( in vec2 x, in float wf ) { 116 | vec2 n = floor( x ); 117 | vec2 f = fract( x ); 118 | 119 | // weight factor 120 | float w = random(n) * wf; 121 | 122 | // MODIFY THIS: 123 | // Maximum distance for distance function 124 | // 2.0 and up should cover the whole distance 125 | // Less will form distance bubbles 126 | vec2 m; 127 | m = vec2( 2.0 ); 128 | // m = vec2( 0.5 ); 129 | 130 | // Cover a unit range around the point 131 | for( int j=-1; j<=1; j++ ) 132 | for( int i=-1; i<=1; i++ ) 133 | { 134 | // distance point (edges of the unit square around the point) 135 | vec2 g = vec2( float(i), float(j) ); 136 | 137 | // random point inside the unit square 138 | // Basically, random point inside the tile 139 | vec2 o = hash( n + g ); 140 | float Time = getTime(); 141 | 142 | // move the point around the tile based on sin(time); 143 | vec2 r = g - f + (0.5+0.5*sin(Time+6.2831*o)); 144 | float w = random(o) * wf; 145 | 146 | // Get the vector's distance from origin 147 | // This is similar to the self dot product operation dot(r,r); 148 | float d; 149 | 150 | // MODIFY THIS: try using different functions 151 | d = euclideanDistance(vec2(0), r); 152 | // d = manhattanDistance(vec2(0), r); 153 | d = minkowskiDistance(vec2(0), r, 2.0 + sin(Time * 2.0)); 154 | // d = chebyshevDistance(vec2(0), r); 155 | 156 | // Additively weight the distance 157 | d += w; 158 | 159 | if( d Tap under your project's capabilities 4 | // Then load in this script and change the names in this script to match your objects and materials 5 | 6 | // Rewritten for Spark AR v86+ by Balraj Bains 2021, updated by Tomas Pietravallo 2023 7 | // Original author for pre v86 Bridget Walsh Clair :) 8 | 9 | // Load modules 10 | const Scene = require('Scene'); 11 | const Materials = require('Materials'); 12 | const TouchGestures = require('TouchGestures'); 13 | 14 | // Create an Async function to be able to use Await with promises (which is "easier to use/read") 15 | // Learn more about Async on this blog post by Meta Spark: https://sparkar.facebook.com/ar-studio/learn/tutorials/first-lines-of-code/ 16 | (async function() { 17 | 18 | // Access materials -- or throw an error explaining what happened if they cannot be found 19 | const mats = await Promise.all([ 20 | Materials.findFirst('green'), 21 | Materials.findFirst('red'), 22 | Materials.findFirst('blue') 23 | ]).catch(() => { throw new Error(`Materials named red, green, and blue were not found on your project, please create them or update the script to match existing materials`) }); 24 | 25 | // Access scene objects 26 | const objectToChange = await Scene.root.findFirst('faceMesh0'); 27 | 28 | // Check if the object could be found 29 | if (objectToChange == undefined) { 30 | throw new Error(`An object called faceMesh0 could not be found on your project, please create it or update the script to match existing objects`) 31 | } 32 | 33 | // Assign the value of the current index in the mats array to the specified scene object 34 | let matIndex = 0; 35 | objectToChange.material = mats[matIndex]; 36 | TouchGestures.onTap().subscribe(toggleMat); 37 | 38 | function toggleMat() { 39 | matIndex++; 40 | // loop back to zero if the index is larger than the amount of materials 41 | if (matIndex >= mats.length) { 42 | matIndex = 0; 43 | } 44 | objectToChange.material = mats[matIndex]; 45 | }; 46 | 47 | })(); 48 | --------------------------------------------------------------------------------