├── .eslintignore ├── .eslintrc ├── .gitignore ├── .hintrc ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── azure-pipelines-cd.yml ├── azure-pipelines-ci.yml ├── babel.config.js ├── jest.config.ts ├── license.md ├── package-lock.json ├── package.json ├── packages ├── blocks │ ├── build │ │ └── preparePublish.js │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── blocks │ │ │ ├── babylon │ │ │ │ └── demo │ │ │ │ │ ├── effects │ │ │ │ │ ├── blackAndWhiteBlock.block.glsl │ │ │ │ │ ├── blurBlock.deserializer.ts │ │ │ │ │ ├── blurBlock.serializer.ts │ │ │ │ │ ├── blurBlock.ts │ │ │ │ │ ├── compositionBlock.deserializer.ts │ │ │ │ │ ├── compositionBlock.fragment.glsl │ │ │ │ │ ├── compositionBlock.serializer.ts │ │ │ │ │ ├── compositionBlock.ts │ │ │ │ │ ├── contrastBlock.block.glsl │ │ │ │ │ ├── desaturateBlock.block.glsl │ │ │ │ │ ├── directionalBlurBlock.deserializer.ts │ │ │ │ │ ├── directionalBlurBlock.serializer.ts │ │ │ │ │ ├── directionalBlurBlock.ts │ │ │ │ │ ├── exposureBlock.block.glsl │ │ │ │ │ ├── greenScreenBlock.block.glsl │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── kaleidoscopeBlock.ts │ │ │ │ │ ├── maskBlock.block.glsl │ │ │ │ │ ├── pixelateBlock.block.glsl │ │ │ │ │ ├── posterizeBlock.block.glsl │ │ │ │ │ ├── spritesheetBlock.fragment.glsl │ │ │ │ │ ├── spritesheetBlock.ts │ │ │ │ │ └── tintBlock.ts │ │ │ │ │ ├── transitions │ │ │ │ │ ├── index.ts │ │ │ │ │ └── wipeBlock.block.glsl │ │ │ │ │ └── utilities │ │ │ │ │ ├── index.ts │ │ │ │ │ └── premultiplyAlphaBlock.block.glsl │ │ │ ├── blockNamespaces.ts │ │ │ ├── blockTypes.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── registration │ │ │ ├── IBlockRegistration.ts │ │ │ ├── blockSerializers.ts │ │ │ ├── builtInBlockRegistrations.ts │ │ │ └── index.ts │ ├── tsconfig.build.json │ └── tsconfig.json ├── browserExtension │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── assets │ │ │ ├── icons │ │ │ │ ├── smartFilter_128.png │ │ │ │ ├── smartFilter_16.png │ │ │ │ ├── smartFilter_24.png │ │ │ │ └── smartFilter_32.png │ │ │ └── manifest.json │ │ ├── background.ts │ │ └── editorLauncher.ts │ ├── tsconfig.json │ └── webpack.config.js ├── core │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── IDisposable.ts │ │ ├── blockFoundation │ │ │ ├── aggregateBlock.ts │ │ │ ├── baseBlock.ts │ │ │ ├── customAggregateBlock.ts │ │ │ ├── customShaderBlock.ts │ │ │ ├── disableableShaderBlock.ts │ │ │ ├── index.ts │ │ │ ├── inputBlock.deserializer.ts │ │ │ ├── inputBlock.serialization.types.ts │ │ │ ├── inputBlock.serializer.ts │ │ │ ├── inputBlock.ts │ │ │ ├── outputBlock.ts │ │ │ ├── shaderBlock.ts │ │ │ └── textureOptions.ts │ │ ├── command │ │ │ ├── command.ts │ │ │ ├── commandBuffer.ts │ │ │ ├── commandBufferDebugger.ts │ │ │ └── index.ts │ │ ├── connection │ │ │ ├── connectionPoint.ts │ │ │ ├── connectionPointCompatibilityState.ts │ │ │ ├── connectionPointDirection.ts │ │ │ ├── connectionPointType.ts │ │ │ ├── connectionPointWithDefault.ts │ │ │ └── index.ts │ │ ├── editorUtils │ │ │ ├── editableInPropertyPage.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── optimization │ │ │ ├── dependencyGraph.ts │ │ │ ├── index.ts │ │ │ ├── optimizedShaderBlock.ts │ │ │ └── smartFilterOptimizer.ts │ │ ├── runtime │ │ │ ├── index.ts │ │ │ ├── renderTargetGenerator.ts │ │ │ ├── shaderRuntime.ts │ │ │ ├── smartFilterRuntime.ts │ │ │ └── strongRef.ts │ │ ├── serialization │ │ │ ├── importCustomBlockDefinition.ts │ │ │ ├── index.ts │ │ │ ├── serializedBlockDefinition.ts │ │ │ ├── serializedShaderBlockDefinition.ts │ │ │ ├── serializedSmartFilter.ts │ │ │ ├── smartFilterDeserializer.ts │ │ │ ├── smartFilterSerializer.ts │ │ │ └── v1 │ │ │ │ ├── defaultBlockSerializer.ts │ │ │ │ ├── index.ts │ │ │ │ ├── shaderBlockSerialization.types.ts │ │ │ │ └── smartFilterSerialization.types.ts │ │ ├── smartFilter.ts │ │ ├── utils │ │ │ ├── buildTools │ │ │ │ ├── buildShaders.ts │ │ │ │ ├── convertGlslIntoBlock.ts │ │ │ │ ├── convertGlslIntoShaderProgram.ts │ │ │ │ ├── convertShaders.ts │ │ │ │ ├── determineVersion.ts │ │ │ │ ├── shaderCode.types.ts │ │ │ │ ├── shaderConverter.ts │ │ │ │ ├── versionUp.ts │ │ │ │ └── watchShaders.ts │ │ │ ├── index.ts │ │ │ ├── renderTargetUtils.ts │ │ │ ├── shaderCodeUtils.ts │ │ │ ├── textureLoaders.ts │ │ │ ├── textureUtils.ts │ │ │ └── uniqueIdGenerator.ts │ │ └── version.ts │ ├── test │ │ └── unit │ │ │ ├── determineVersion.test.js │ │ │ ├── importCustomBlockDefinition.test.js │ │ │ ├── shaderConverter.test.ts │ │ │ └── smartFilterOptimizer.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.buildTools.build.json │ └── tsconfig.json ├── demo │ ├── index.html │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── app.ts │ │ ├── backgroundOption.ts │ │ ├── configuration │ │ │ ├── blockFactory.ts │ │ │ ├── smartFilters.ts │ │ │ ├── smartFilters │ │ │ │ ├── hardCoded │ │ │ │ │ ├── hardCodedSmartFilterNames.ts │ │ │ │ │ ├── simpleLogo.ts │ │ │ │ │ ├── simplePhotoEdit.ts │ │ │ │ │ └── simpleWebcam.ts │ │ │ │ └── serialized │ │ │ │ │ └── serializedSimpleLogo.json │ │ │ └── texturePresets.ts │ │ ├── helpers │ │ │ ├── createThinEngine.ts │ │ │ └── launchEditor.ts │ │ ├── smartFilterLoader.ts │ │ ├── smartFilterRenderer.ts │ │ └── textureRenderHelper.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── www │ │ └── assets │ │ ├── favicon.ico │ │ ├── frame.png │ │ ├── kittens.jpg │ │ ├── logo.png │ │ ├── logo.svg │ │ └── title.png ├── editor │ ├── build │ │ ├── copyAssets.js │ │ └── preparePublish.js │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── assets │ │ │ ├── imgs │ │ │ │ ├── add.svg │ │ │ │ ├── copy.svg │ │ │ │ ├── delete.svg │ │ │ │ ├── downArrow.svg │ │ │ │ ├── logo.png │ │ │ │ ├── popOut.svg │ │ │ │ ├── square.svg │ │ │ │ └── triangle.svg │ │ │ └── styles │ │ │ │ ├── components │ │ │ │ ├── log.scss │ │ │ │ ├── nodeList.scss │ │ │ │ └── propertyTab.scss │ │ │ │ ├── graphSystem │ │ │ │ ├── blockNodeData.module.scss │ │ │ │ └── display │ │ │ │ │ ├── common.module.scss │ │ │ │ │ ├── inputDisplayManager.module.scss │ │ │ │ │ └── outputDisplayManager.module.scss │ │ │ │ └── main.scss │ │ ├── blockTools.ts │ │ ├── components │ │ │ ├── log │ │ │ │ └── logComponent.tsx │ │ │ ├── nodeList │ │ │ │ └── nodeListComponent.tsx │ │ │ ├── preview │ │ │ │ ├── previewAreaComponent.tsx │ │ │ │ └── previewAreaControlComponent.tsx │ │ │ └── propertyTab │ │ │ │ ├── inputsPropertyTabComponent.tsx │ │ │ │ ├── properties │ │ │ │ ├── color3PropertyTabComponent.tsx │ │ │ │ ├── color4PropertyTabComponent.tsx │ │ │ │ ├── floatPropertyTabComponent.tsx │ │ │ │ ├── imageSourcePropertyTabComponent.tsx │ │ │ │ └── vector2PropertyTabComponent.tsx │ │ │ │ └── propertyTabComponent.tsx │ │ ├── configuration │ │ │ ├── blockEditorRegistration.ts │ │ │ ├── constants.ts │ │ │ ├── customInputDisplayManager.ts │ │ │ ├── editorBlocks │ │ │ │ ├── blockNames.ts │ │ │ │ ├── editorBlockRegistrations.ts │ │ │ │ ├── inputBlockDeserializer.ts │ │ │ │ └── webCamInputBlock │ │ │ │ │ ├── webCamInputBlock.ts │ │ │ │ │ ├── webCamRuntime.ts │ │ │ │ │ └── webCamSession.ts │ │ │ └── getBlockEditorRegistration.ts │ │ ├── constants.ts │ │ ├── custom.d.ts │ │ ├── globalState.ts │ │ ├── graphEditor.tsx │ │ ├── graphSystem │ │ │ ├── blockNodeData.ts │ │ │ ├── connectionPointPortData.ts │ │ │ ├── display │ │ │ │ ├── inputDisplayManager.ts │ │ │ │ └── outputDisplayManager.ts │ │ │ ├── getEditorData.ts │ │ │ ├── properties │ │ │ │ ├── genericNodePropertyComponent.tsx │ │ │ │ └── inputNodePropertyComponent.tsx │ │ │ ├── registerDefaultInput.ts │ │ │ ├── registerElbowSupport.ts │ │ │ ├── registerNodePortDesign.ts │ │ │ ├── registerToDisplayLedger.ts │ │ │ ├── registerToPropertyLedger.ts │ │ │ └── registerToTypeLedger.ts │ │ ├── helpers │ │ │ ├── blockKeyConverters.ts │ │ │ ├── debounce.ts │ │ │ ├── editorTextureLoaders.ts │ │ │ ├── observableProperty.ts │ │ │ ├── registerAnimations.ts │ │ │ ├── serializationTools.ts │ │ │ └── textureAssetCache.ts │ │ ├── index.ts │ │ ├── initializePreview.ts │ │ ├── portal.tsx │ │ ├── sharedComponents │ │ │ ├── checkBoxLineComponent.tsx │ │ │ ├── draggableBlockLineComponent.tsx │ │ │ ├── dynamicOptionsLineComponent.tsx │ │ │ ├── fileButtonLineComponent.tsx │ │ │ ├── floatSliderComponent.tsx │ │ │ ├── lazyTextInputLineComponent.tsx │ │ │ ├── lineContainerComponent.tsx │ │ │ └── lineWithFileButtonComponent.tsx │ │ └── smartFilterEditorControl.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── sfe │ ├── index.html │ ├── license.md │ ├── package.json │ ├── readme.md │ ├── src │ ├── app.ts │ ├── blockRegistration │ │ ├── addCustomBlockToBlockEditorRegistration.ts │ │ ├── blockFactory.ts │ │ ├── generateCustomBlockEditorRegistrations.ts │ │ └── removeCustomBlockFromBlockEditorRegistration.ts │ ├── customBlockManager.ts │ ├── defaultSmartFilter.ts │ ├── smartFilterLoadSave │ │ ├── constants.ts │ │ ├── copySmartFilter.ts │ │ ├── downloadSmartFilter.ts │ │ ├── hashFunctions.ts │ │ ├── loadSmartFilterFromFile.ts │ │ ├── loadSmartFilterFromSnippetServer.ts │ │ ├── loadStartingSmartFilter.ts │ │ ├── pasteSmartFilter.ts │ │ ├── saveToSnipperServer.ts │ │ └── serializeSmartFilter.ts │ ├── smartFilterLoader.ts │ ├── smartFilterRenderer.ts │ └── texturePresets.ts │ ├── tsconfig.json │ ├── webpack.config.js │ └── www │ └── assets │ ├── favicon.ico │ ├── kittens.jpg │ └── logo.png ├── readme.md ├── tsconfig.common.build.json ├── tsconfig.common.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/**/*.* 2 | **/*.config.* 3 | build/**/*.* 4 | packages/demo/src/configuration/smartFilters/serialized/*.json 5 | packages/browserExtension/src/assets/manifest.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | # And OS or Editor folders 3 | [Bb]in/ 4 | *.tmp 5 | *.log 6 | *.DS_Store 7 | ._* 8 | Thumbs.db 9 | .cache 10 | .tmproj 11 | nbproject 12 | *.sublime-project 13 | *.sublime-workspace 14 | .idea 15 | .directory 16 | .history 17 | .tempChromeProfileForDebug 18 | junit.xml 19 | 20 | # Node Modules 21 | **/node_modules/* 22 | 23 | # Build tools 24 | /packages/core/buildTools/ 25 | 26 | # Local dist 27 | **/www/scripts/** 28 | **/www/index.html 29 | dist 30 | .temp 31 | *.tsbuildinfo 32 | 33 | # unignore 34 | !/src/smartFiltersEditor/components/log 35 | 36 | # generated shader TS files 37 | **/*.shader.ts 38 | **/*.fragment.ts 39 | **/*.block.ts 40 | 41 | # browser extension output 42 | packages/browserExtension/unpackedExtension -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["development"], 3 | "hints": { 4 | "axe/language": [ 5 | "default", 6 | { 7 | "html-has-lang": "off" 8 | } 9 | ], 10 | "meta-viewport": "off", 11 | "no-inline-styles": "off", 12 | "axe/forms": [ 13 | "default", 14 | { 15 | "label": "off" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/dist 2 | **/*.config.* 3 | **/build 4 | **/.git 5 | **/.svn 6 | **/.hg -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "printWidth": 120, 5 | "singleQuote": false, 6 | "semi": true, 7 | "endOfLine": "auto", 8 | "overrides": [ 9 | { 10 | "files": [ 11 | ".eslintrc", 12 | ".hintrc" 13 | ], 14 | "options": { 15 | "trailingComma": "none" 16 | } 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "msedge", 9 | "request": "launch", 10 | "name": "Launch Demo (Edge)", 11 | "url": "http://localhost:8080", 12 | "outFiles": ["!**/node_modules/**"], 13 | "webRoot": "${workspaceFolder}", 14 | "preLaunchTask": "Serve and watch demo (Dev)" 15 | }, 16 | { 17 | "type": "chrome", 18 | "request": "launch", 19 | "name": "Launch Demo (Chrome)", 20 | "url": "http://localhost:8080", 21 | "webRoot": "${workspaceFolder}", 22 | "preLaunchTask": "Serve and watch demo (Dev)" 23 | }, 24 | { 25 | "type": "msedge", 26 | "request": "launch", 27 | "name": "Launch SFE (Edge)", 28 | "url": "http://localhost:8081", 29 | "webRoot": "${workspaceFolder}", 30 | "outFiles": ["!**/node_modules/**"], 31 | "preLaunchTask": "Serve and watch SFE (Dev)" 32 | }, 33 | { 34 | "type": "chrome", 35 | "request": "launch", 36 | "name": "Launch SFE (Chrome)", 37 | "url": "http://localhost:8081", 38 | "webRoot": "${workspaceFolder}", 39 | "outFiles": ["!**/node_modules/**"], 40 | "preLaunchTask": "Serve and watch SFE (Dev)" 41 | }, 42 | { 43 | "type": "node", 44 | "request": "launch", 45 | "name": "Run and Debug unit tests", 46 | "runtimeArgs": [ 47 | "--inspect-brk", 48 | "${workspaceRoot}/node_modules/jest/bin/jest.js", 49 | "--runInBand", 50 | "--selectProjects", 51 | "unit" 52 | ], 53 | "console": "integratedTerminal", 54 | "internalConsoleOptions": "neverOpen" 55 | }, 56 | { 57 | "type": "node", 58 | "request": "launch", 59 | "name": "Run and Debug current unit test file", 60 | "runtimeArgs": [ 61 | "--inspect-brk", 62 | "${workspaceRoot}/node_modules/jest/bin/jest.js", 63 | "--runInBand", 64 | "--testPathPattern", 65 | "${fileBasename}" // Run only the currently open file 66 | ], 67 | "console": "integratedTerminal", 68 | "internalConsoleOptions": "neverOpen" 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "acumin", 4 | "ADDRESSMODE", 5 | "babylonjs", 6 | "Consts", 7 | "desaturated", 8 | "deserializer", 9 | "deserializers", 10 | "dmax", 11 | "dmin", 12 | "fontawesome", 13 | "fortawesome", 14 | "fract", 15 | "glsl", 16 | "hintrc", 17 | "htpasswd", 18 | "imgs", 19 | "minifiers", 20 | "multilines", 21 | "NPOT", 22 | "Premultiplies", 23 | "premultiply", 24 | "PULLREQUESTID", 25 | "resizer", 26 | "runtimes", 27 | "SAMPLINGMODE", 28 | "Sebastien", 29 | "smartfilter", 30 | "smax", 31 | "smin", 32 | "Spector", 33 | "spritesheet", 34 | "texel", 35 | "tsbuildinfo", 36 | "tsdoc", 37 | "Undecorates", 38 | "unmerge", 39 | "VANDENBERGHE", 40 | "webgl", 41 | "webgpu" 42 | ], 43 | "files.exclude": { 44 | "**/node_modules": true, 45 | "**/dist": false, 46 | "**/*.shader.ts": true, 47 | "**/*.fragment.ts": true, 48 | "**/*.block.ts": true 49 | }, 50 | "[typescript]": { 51 | "editor.defaultFormatter": "esbenp.prettier-vscode" 52 | }, 53 | "search.exclude": { 54 | "**/.git": true, 55 | "**/node_modules": true, 56 | "**/tmp": true, 57 | "**/dist": true, 58 | "**/.temp": true, 59 | "**/*.tsbuildinfo": true, 60 | "**/stats.json": true, 61 | "**/www/scripts": true, 62 | "junit.xml": true 63 | }, 64 | "cmake.configureOnOpen": false 65 | } 66 | -------------------------------------------------------------------------------- /azure-pipelines-cd.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - deploy 3 | 4 | # Do not run on PR builds 5 | pr: none 6 | 7 | jobs: 8 | - job: Deploy 9 | displayName: "Build and Publish" 10 | pool: 11 | vmImage: "Ubuntu-latest" 12 | demands: npm 13 | steps: 14 | - script: "npm install" 15 | displayName: "Npm install" 16 | - script: "npm run build" 17 | displayName: "Npm run build" 18 | - script: "npm run test" 19 | displayName: "Jest Unit Tests" 20 | - script: "npm run preparePublish" 21 | displayName: "Npm preparePublish (version setting, cross-package dependency configuration)" 22 | - script: "npm run build" 23 | displayName: "Build again after preparePublish to pick up version number changes" 24 | - task: Npm@1 25 | displayName: "Npm publish" 26 | inputs: 27 | command: "custom" 28 | customCommand: "publish --access public -w @babylonjs/smart-filters && publish --access public -w @babylonjs/smart-filters-blocks" 29 | customEndpoint: "NPMWithAccessToken" 30 | - script: | 31 | # SFE 32 | cd packages/sfe/www 33 | zip -r ../../../dist.zip * 34 | cd ../../.. 35 | curl $(DEPLOYMENT_SERVER)/upload -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: $(BASIC_AUTH)" -F "path=smartFiltersEditor" -F "storageAccount=babylontools" -F "zip=@dist.zip" 36 | curl "$(DEPLOYMENT_SERVER)/purgeTest?endpoint=smart-filters-editor&profileName=babylonjstools&wait=true" -i -H "Authorization: $(BASIC_AUTH)" 37 | displayName: "SFE deployment" 38 | -------------------------------------------------------------------------------- /azure-pipelines-ci.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - main 8 | 9 | pool: 10 | vmImage: ubuntu-latest 11 | 12 | pr: 13 | autoCancel: true 14 | branches: 15 | include: 16 | - main 17 | 18 | jobs: 19 | - job: TscBuild 20 | displayName: "1. Tsc Build" 21 | pool: 22 | vmImage: "Ubuntu-latest" 23 | demands: npm 24 | steps: 25 | - script: "npm install" 26 | displayName: "Npm install" 27 | - script: "npm run build" 28 | displayName: "Npm run build" 29 | 30 | - job: ESLint 31 | displayName: "2. ESLint" 32 | pool: 33 | vmImage: "Ubuntu-latest" 34 | demands: npm 35 | steps: 36 | - script: "npm install" 37 | displayName: "Npm install" 38 | - script: "npm run build" 39 | displayName: "Npm run build" 40 | - script: "npm run lint:check" 41 | displayName: "Run ESLint Check" 42 | 43 | - job: UnitTests 44 | displayName: "3. Unit Tests" 45 | pool: 46 | vmImage: "Ubuntu-latest" 47 | demands: npm 48 | steps: 49 | - script: "npm install" 50 | displayName: "Npm install" 51 | - script: "npm run build" 52 | displayName: "Npm run build" 53 | - script: "npm run test" 54 | displayName: "Jest Unit Tests" 55 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | '@babel/preset-react', 5 | ], 6 | env: { 7 | test: { 8 | presets: [ 9 | '@babel/preset-env', 10 | '@babel/preset-react', 11 | ], 12 | plugins: [ 13 | '@babel/plugin-transform-modules-commonjs', 14 | ], 15 | }, 16 | }, 17 | }; -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/blocks/build/preparePublish.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as fs from "fs"; 3 | 4 | const corePackageText = fs.readFileSync("../core/package.json"); 5 | const corePackageJSON = JSON.parse(corePackageText.toString()); 6 | 7 | const blocksPackageText = fs.readFileSync("package.json"); 8 | const blocksPackageJSON = JSON.parse(blocksPackageText.toString()); 9 | 10 | console.log("Setting blocks package version to match core package version:", corePackageJSON.version); 11 | blocksPackageJSON.version = corePackageJSON.version; 12 | 13 | console.log("Adding dependency on core package to blocks package"); 14 | if (!blocksPackageJSON.dependencies) { 15 | blocksPackageJSON.dependencies = {}; 16 | } 17 | blocksPackageJSON.dependencies["@babylonjs/smart-filters"] = corePackageJSON.version; 18 | 19 | console.log("Saving changes to blocks package.json"); 20 | fs.writeFileSync("package.json", JSON.stringify(blocksPackageJSON, null, 4)); 21 | -------------------------------------------------------------------------------- /packages/blocks/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/blocks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/smart-filters-blocks", 3 | "version": "0.0.0", 4 | "description": "Babylon.js Smart Filter Block Library", 5 | "keywords": [ 6 | "video", 7 | "composition", 8 | "3D", 9 | "2D", 10 | "javascript", 11 | "html5", 12 | "webgl", 13 | "webgl2", 14 | "webgpu", 15 | "babylon" 16 | ], 17 | "license": "MIT", 18 | "readme": "README.md", 19 | "main": "dist/index", 20 | "module": "dist/index", 21 | "esnext": "dist/index", 22 | "types": "dist/index", 23 | "type": "module", 24 | "sideEffects": false, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/BabylonJS/SmartFilters.git" 28 | }, 29 | "files": [ 30 | "dist", 31 | "src", 32 | "license.md", 33 | "readme.md" 34 | ], 35 | "scripts": { 36 | "clean": "rimraf dist && rimraf tsconfig.build.tsbuildinfo && rimraf --glob ./src/blocks/**/*.block.ts && rimraf --glob ./src/blocks/**/*.fragment.ts", 37 | "preparePublish": "node build/preparePublish.js", 38 | "build": "npm run build:runTools && npm run build:blocks", 39 | "build:blocks": "tsc -p ./tsconfig.build.json", 40 | "build:runTools": "node ../core/dist/utils/buildTools/buildShaders.js ./src/blocks @babylonjs/smart-filters", 41 | "watch": "concurrently \"npm run watch:blocks\" \"npm run watch:shaders\"", 42 | "watch:blocks": "tsc -p ./tsconfig.build.json --watch", 43 | "watch:shaders": "node ../core/dist/utils/buildTools/watchShaders.js ./src/blocks @babylonjs/smart-filters", 44 | "test": "echo \"Error: run test from the root of the monorepo\" && exit 1" 45 | }, 46 | "peerDependencies": { 47 | "@babylonjs/core": "^7.47.3 || ^8.0.1" 48 | } 49 | } -------------------------------------------------------------------------------- /packages/blocks/readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | ## Blocks 4 | 5 | This package contains a library of blocks for use with Babylon Smart Filters (see @babylonjs/smart-filters). 6 | 7 | See the full documentation at [doc.babylonjs.com](https://doc.babylonjs.com/features/featuresDeepDive/smartFilters/) 8 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/blackAndWhiteBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "BlackAndWhiteBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | 11 | vec4 blackAndWhite(vec2 vUV) { // main 12 | vec4 color = texture2D(input, vUV); 13 | 14 | float luminance = dot(color.rgb, vec3(0.3, 0.59, 0.11)); 15 | vec3 bg = vec3(luminance, luminance, luminance); 16 | 17 | return vec4(bg, color.a); 18 | } 19 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/blurBlock.deserializer.ts: -------------------------------------------------------------------------------- 1 | import { type SmartFilter, type ISerializedBlockV1 } from "@babylonjs/smart-filters"; 2 | import { BlurBlock } from "./blurBlock.js"; 3 | 4 | /** 5 | * The definition of the extra data serialized for blur blocks. 6 | */ 7 | export interface ISerializedBlurBlockV1 extends ISerializedBlockV1 { 8 | /** 9 | * The extra data of the block. 10 | */ 11 | data: { 12 | /** 13 | * The blur texture ratio per pass. 14 | */ 15 | blurTextureRatioPerPass: number; 16 | 17 | /** 18 | * The size of the blur. 19 | */ 20 | blurSize: number; 21 | }; 22 | } 23 | 24 | /** 25 | * V1 Blur Deserializer. 26 | * @param smartFilter - The SmartFilter to deserialize the block into 27 | * @param serializedBlock - The serialized block data 28 | * @returns A deserialized BlurBlock 29 | */ 30 | export function blurBlockDeserializer(smartFilter: SmartFilter, serializedBlock: ISerializedBlurBlockV1) { 31 | const block = new BlurBlock(smartFilter, serializedBlock.name); 32 | 33 | // If data is missing, use the default value set by the block 34 | block.blurTextureRatioPerPass = serializedBlock.data.blurTextureRatioPerPass ?? block.blurSize; 35 | block.blurSize = serializedBlock.data.blurSize ?? block.blurSize; 36 | return block; 37 | } 38 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/blurBlock.serializer.ts: -------------------------------------------------------------------------------- 1 | import type { BaseBlock, IBlockSerializerV1 } from "@babylonjs/smart-filters"; 2 | import type { BlurBlock } from "./blurBlock"; 3 | import { blurBlockType } from "../../../blockTypes.js"; 4 | import { babylonDemoEffectsNamespace } from "../../../blockNamespaces.js"; 5 | 6 | /** 7 | * The V1 serializer for a Blur Block. 8 | * Though it is an aggregate block, Blur creates and manages its own blocks 9 | * internally, so there's no need to worry about serializing them. 10 | */ 11 | export const blurBlockSerializer: IBlockSerializerV1 = { 12 | blockType: blurBlockType, 13 | serialize: (block: BaseBlock) => { 14 | if (block.getClassName() !== blurBlockType) { 15 | throw new Error("Was asked to serialize an unrecognized block type"); 16 | } 17 | 18 | const blurBlock = block as unknown as BlurBlock; 19 | return { 20 | name: block.name, 21 | uniqueId: block.uniqueId, 22 | blockType: blurBlockType, 23 | namespace: babylonDemoEffectsNamespace, 24 | comments: block.comments, 25 | data: { 26 | blurTextureRatioPerPass: blurBlock.blurTextureRatioPerPass, 27 | blurSize: blurBlock.blurSize, 28 | }, 29 | }; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/compositionBlock.deserializer.ts: -------------------------------------------------------------------------------- 1 | import type { ISerializedBlockV1, SmartFilter } from "@babylonjs/smart-filters"; 2 | import { CompositionBlock } from "./compositionBlock.js"; 3 | 4 | /** 5 | * The definition of the extra data serialized for composition blocks. 6 | */ 7 | export interface ISerializedCompositionBlockV1 extends ISerializedBlockV1 { 8 | /** 9 | * The extra data of the block. 10 | */ 11 | data: { 12 | /** 13 | * The alpha mode of the composition. 14 | */ 15 | alphaMode: number; 16 | }; 17 | } 18 | 19 | /** 20 | * V1 Composition Deserializer. 21 | * @param smartFilter - The SmartFilter to deserialize the block into 22 | * @param serializedBlock - The serialized block data 23 | * @returns A deserialized CompositionBlock 24 | */ 25 | export function compositionDeserializer(smartFilter: SmartFilter, serializedBlock: ISerializedCompositionBlockV1) { 26 | const block = new CompositionBlock(smartFilter, serializedBlock.name); 27 | 28 | // If data is missing, use the default value set by the block 29 | block.alphaMode = serializedBlock.data.alphaMode ?? block.alphaMode; 30 | return block; 31 | } 32 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/compositionBlock.fragment.glsl: -------------------------------------------------------------------------------- 1 | uniform sampler2D background; // main 2 | uniform sampler2D foreground; 3 | 4 | uniform vec2 scaleUV; 5 | uniform vec2 translateUV; 6 | uniform float alphaMode; 7 | uniform float foregroundAlphaScale; 8 | 9 | vec4 composition(vec2 vUV) { // main 10 | vec4 background = texture2D(background, vUV); 11 | 12 | vec2 transformedUV = vUV * (1.0 / scaleUV) + translateUV; 13 | if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || transformedUV.y < 0.0 || transformedUV.y > 1.0) { 14 | return background; 15 | } 16 | 17 | vec4 foreground = texture2D(foreground, transformedUV); 18 | foreground.a *= foregroundAlphaScale; 19 | 20 | // SRC is foreground, DEST is background 21 | if (alphaMode == 0.) { 22 | return foreground; 23 | } 24 | else if (alphaMode == 1.) { 25 | return foreground.a * foreground + background; 26 | } 27 | else if (alphaMode == 2.) { 28 | return mix(background, foreground, foreground.a); 29 | } 30 | else if (alphaMode == 3.) { 31 | return background - foreground * background; 32 | } 33 | else if (alphaMode == 4.) { 34 | return foreground * background; 35 | } 36 | 37 | return background; 38 | } -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/compositionBlock.serializer.ts: -------------------------------------------------------------------------------- 1 | import type { CompositionBlock } from "./compositionBlock"; 2 | import { compositionBlockType } from "../../../blockTypes.js"; 3 | import { babylonDemoEffectsNamespace } from "../../../blockNamespaces.js"; 4 | import type { IBlockSerializerV1, BaseBlock } from "@babylonjs/smart-filters"; 5 | 6 | /** 7 | * The V1 serializer for a Composition Block 8 | */ 9 | export const compositionBlockSerializer: IBlockSerializerV1 = { 10 | blockType: compositionBlockType, 11 | serialize: (block: BaseBlock) => { 12 | if (block.getClassName() !== compositionBlockType) { 13 | throw new Error("Was asked to serialize an unrecognized block type"); 14 | } 15 | 16 | const compositionBlock = block as unknown as CompositionBlock; 17 | return { 18 | name: block.name, 19 | uniqueId: block.uniqueId, 20 | blockType: compositionBlockType, 21 | namespace: babylonDemoEffectsNamespace, 22 | comments: block.comments, 23 | data: { 24 | alphaMode: compositionBlock.alphaMode, 25 | }, 26 | }; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/contrastBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "ContrastBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | // { "default": 0.5 } 11 | uniform float intensity; 12 | 13 | vec4 contrast(vec2 vUV) { // main 14 | vec4 color = texture2D(input, vUV); 15 | 16 | float contrastLMin = mix(-2., 0., intensity * 2.0); 17 | float contrastLMax = mix(3., 1., intensity * 2.0); 18 | 19 | vec3 contrastMin = remap(color.rgb, contrastLMin, contrastLMax, 0., 1.); 20 | 21 | float intensityMapped = remap(intensity, 0.5, 1., 0., 1.0); 22 | float contrastHMin = mix(0., 0.45, intensityMapped); 23 | float contrastHMax = mix(1., 0.5, intensityMapped); 24 | 25 | vec3 contrastMax = remap(color.rgb, contrastHMin, contrastHMax, 0., 1.); 26 | 27 | return vec4(mix(contrastMin, contrastMax, step(intensity, 0.5)), color.a); 28 | } 29 | 30 | float remap(float i, float smin, float smax, float dmin, float dmax) { 31 | return dmin + (i - smin) * (dmax - dmin) / (smax - smin); 32 | } 33 | 34 | vec3 remap(vec3 i, float smin, float smax, float dmin, float dmax) { 35 | return dmin + (i - smin) * (dmax - dmin) / (smax - smin); 36 | } 37 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/desaturateBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "DesaturateBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | // { "default": 0.3 } 11 | uniform float intensity; 12 | 13 | vec4 desaturate(vec2 vUV) { // main 14 | float saturationStrength = 1. - intensity; 15 | 16 | vec4 color = texture2D(input, vUV); 17 | 18 | float tempMin = min(min(color.x, color.y), color.z); 19 | float tempMax = max(max(color.x, color.y), color.z); 20 | float tempMerge = 0.5 * (tempMin + tempMax); 21 | 22 | return vec4(mix(color.rgb, vec3(tempMerge, tempMerge, tempMerge), saturationStrength), color.a); 23 | } 24 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/directionalBlurBlock.deserializer.ts: -------------------------------------------------------------------------------- 1 | import type { ISerializedBlockV1, SmartFilter } from "@babylonjs/smart-filters"; 2 | import { DirectionalBlurBlock } from "./directionalBlurBlock.js"; 3 | 4 | /** 5 | * The definition of the extra data serialized for directional blur blocks. 6 | */ 7 | export interface ISerializedDirectionalBlurBlockV1 extends ISerializedBlockV1 { 8 | /** 9 | * The extra data of the block. 10 | */ 11 | data: { 12 | /** 13 | * The horizontal width of the blur. 14 | */ 15 | blurHorizontalWidth: number; 16 | 17 | /** 18 | * The vertical width of the blur. 19 | */ 20 | blurVerticalWidth: number; 21 | 22 | /** 23 | * The blur texture ratio. 24 | */ 25 | blurTextureRatio: number; 26 | }; 27 | } 28 | 29 | /** 30 | * V1 Directional Blur Deserializer. 31 | * @param smartFilter - The SmartFilter to deserialize the block into 32 | * @param serializedBlock - The serialized block data 33 | * @returns A deserialized DirectionalBlurBlock 34 | */ 35 | export function directionalBlurDeserializer( 36 | smartFilter: SmartFilter, 37 | serializedBlock: ISerializedDirectionalBlurBlockV1 38 | ) { 39 | const block = new DirectionalBlurBlock(smartFilter, serializedBlock.name); 40 | 41 | // If data is missing, use the default value set by the block 42 | block.blurHorizontalWidth = serializedBlock.data.blurHorizontalWidth ?? block.blurHorizontalWidth; 43 | block.blurVerticalWidth = serializedBlock.data.blurVerticalWidth ?? block.blurVerticalWidth; 44 | block.blurTextureRatio = serializedBlock.data.blurTextureRatio ?? block.blurTextureRatio; 45 | return block; 46 | } 47 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/directionalBlurBlock.serializer.ts: -------------------------------------------------------------------------------- 1 | import type { IBlockSerializerV1, BaseBlock } from "@babylonjs/smart-filters"; 2 | import type { DirectionalBlurBlock } from "./directionalBlurBlock"; 3 | import { directionalBlurBlockType } from "../../../blockTypes.js"; 4 | import { babylonDemoEffectsNamespace } from "../../../blockNamespaces.js"; 5 | 6 | /** 7 | * The V1 serializer for a Directional Blur Block 8 | */ 9 | export const directionalBlurBlockSerializer: IBlockSerializerV1 = { 10 | blockType: directionalBlurBlockType, 11 | serialize: (block: BaseBlock) => { 12 | if (block.getClassName() !== directionalBlurBlockType) { 13 | throw new Error("Was asked to serialize an unrecognized block type"); 14 | } 15 | 16 | const directionalBlurBlock = block as DirectionalBlurBlock; 17 | return { 18 | name: block.name, 19 | uniqueId: block.uniqueId, 20 | blockType: directionalBlurBlockType, 21 | namespace: babylonDemoEffectsNamespace, 22 | comments: block.comments, 23 | data: { 24 | blurTextureRatio: directionalBlurBlock.blurTextureRatio, 25 | blurHorizontalWidth: directionalBlurBlock.blurHorizontalWidth, 26 | blurVerticalWidth: directionalBlurBlock.blurVerticalWidth, 27 | }, 28 | }; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/exposureBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "ExposureBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | uniform float amount; 11 | 12 | vec4 exposure(vec2 vUV) { // main 13 | vec4 color = texture2D(input, vUV); 14 | return vec4(color.rgb * amount, color.a); 15 | } 16 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/greenScreenBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "GreenScreenBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | uniform sampler2D background; 11 | uniform vec3 reference; 12 | uniform float distance; 13 | 14 | vec4 greenScreen(vec2 vUV) { // main 15 | vec4 color = texture2D(input, vUV); 16 | vec4 background = texture2D(background, vUV); 17 | 18 | if (length(color.rgb - reference) < distance) { 19 | return background; 20 | } 21 | 22 | return color; 23 | } 24 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./blackAndWhiteBlock.block.js"; 2 | export * from "./blurBlock.js"; 3 | export * from "./compositionBlock.js"; 4 | export * from "./contrastBlock.block.js"; 5 | export * from "./desaturateBlock.block.js"; 6 | export * from "./directionalBlurBlock.js"; 7 | export * from "./exposureBlock.block.js"; 8 | export * from "./greenScreenBlock.block.js"; 9 | export * from "./kaleidoscopeBlock.js"; 10 | export * from "./maskBlock.block.js"; 11 | export * from "./pixelateBlock.block.js"; 12 | export * from "./posterizeBlock.block.js"; 13 | export * from "./spritesheetBlock.js"; 14 | export * from "./tintBlock.js"; 15 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/maskBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "MaskBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | uniform sampler2D mask; 11 | 12 | vec4 maskBlock(vec2 vUV) { // main 13 | vec4 color = texture2D(input, vUV); 14 | vec3 maskColor = texture2D(mask, vUV).rgb; 15 | float luminance = dot(maskColor, vec3(0.3, 0.59, 0.11)); 16 | 17 | return vec4(color.rgb * luminance, luminance * color.a); 18 | } 19 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/pixelateBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "PixelateBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "Manual" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | // { "default": "0.3" } 11 | uniform float intensity; 12 | // { "default": false } 13 | uniform bool disabled; 14 | // { "autoBind": "outputAspectRatio" } 15 | uniform vec2 aspect; 16 | 17 | const float videoPixelatePower = 6.0; 18 | const float videoPixelateMin = 10.0; 19 | const float videoPixelateMax = 1920.0; 20 | 21 | vec4 pixelate(vec2 vUV) { // main 22 | if (!disabled) { 23 | float pixelateStrength = mix(videoPixelateMin, videoPixelateMax, pow(1. - intensity, videoPixelatePower)); 24 | vec2 pixelate = vec2(pixelateStrength * aspect.x, pixelateStrength); 25 | vec2 pixelSize = vec2(1. / pixelate); 26 | vUV = pixelSize * (floor(pixelate * vUV) + 0.5); 27 | } 28 | return texture2D(input, vUV); 29 | } -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/posterizeBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "PosterizeBlock", 4 | "namespace": "Babylon.Demo.Effects", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | // { "default": 0.3 } 11 | uniform float intensity; 12 | 13 | const float posterizePower = 6.0; 14 | const float minLevel = 2.0; 15 | const float maxLevel = 256.0; 16 | 17 | vec4 posterize(vec2 vUV) { // main 18 | vec4 color = texture2D(input, vUV); 19 | 20 | float posterizeStrength = mix(minLevel, maxLevel, pow(1. - intensity, posterizePower)); 21 | vec3 posterize = vec3(posterizeStrength); 22 | color.rgb = floor(color.rgb / (1.0 / posterize)) * (1.0 / posterize); 23 | 24 | return color; 25 | } 26 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/spritesheetBlock.fragment.glsl: -------------------------------------------------------------------------------- 1 | uniform sampler2D input; // main 2 | uniform float time; 3 | uniform float rows; 4 | uniform float cols; 5 | uniform float frames; 6 | uniform bool disabled; 7 | 8 | vec4 mainImage(vec2 vUV) { // main 9 | if (!disabled) { 10 | float invRows = 1.0 / rows; 11 | float invCols = 1.0 / cols; 12 | 13 | // Get offset of frame 14 | float frame = mod(floor(time), frames); 15 | float row = (rows - 1.0) - floor(frame * invCols); // Reverse row direction b/c UVs start from bottom 16 | float col = mod(frame, cols); 17 | 18 | // Add offset, then scale UV down to frame size 19 | vUV = vec2( 20 | (vUV.x + col) * invCols, 21 | (vUV.y + row) * invRows 22 | ); 23 | } 24 | 25 | return texture2D(input, vUV); 26 | } -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/effects/tintBlock.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionPointType, type SerializedShaderBlockDefinition } from "@babylonjs/smart-filters"; 2 | 3 | /** 4 | * This is included to show how a serialized block definition can be loaded and used. 5 | * This object could have been deserialized from a JSON file, for example. 6 | */ 7 | export const deserializedTintBlockDefinition: SerializedShaderBlockDefinition = { 8 | format: "shaderBlockDefinition", 9 | formatVersion: 1, 10 | blockType: "TintBlock", 11 | namespace: "Babylon.Demo.Effects", 12 | shaderProgram: { 13 | fragment: { 14 | uniform: ` 15 | uniform sampler2D _input_; // main 16 | uniform vec3 _tint_; 17 | uniform float _amount_; 18 | `, 19 | mainInputTexture: "_input_", 20 | mainFunctionName: "_mainImage_", 21 | functions: [ 22 | { 23 | name: "_mainImage_", 24 | code: ` 25 | vec4 _mainImage_(vec2 vUV) { 26 | vec4 color = texture2D(_input_, vUV); 27 | vec3 tinted = mix(color.rgb, _tint_, _amount_); 28 | return vec4(tinted, color.a); 29 | }`, 30 | }, 31 | ], 32 | }, 33 | }, 34 | inputConnectionPoints: [ 35 | { 36 | name: "input", 37 | type: ConnectionPointType.Texture, 38 | }, 39 | { 40 | name: "tint", 41 | type: ConnectionPointType.Color3, 42 | defaultValue: { r: 1, g: 0, b: 0 }, 43 | }, 44 | { 45 | name: "amount", 46 | type: ConnectionPointType.Float, 47 | defaultValue: 0.25, 48 | }, 49 | ], 50 | disableOptimization: false, 51 | }; 52 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/transitions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./wipeBlock.block.js"; 2 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/transitions/wipeBlock.block.glsl: -------------------------------------------------------------------------------- 1 | // { "smartFilterBlockType": "WipeBlock", "namespace": "Babylon.Demo.Transitions" } 2 | 3 | uniform sampler2D textureA; 4 | uniform sampler2D textureB; 5 | uniform float progress; 6 | 7 | vec4 wipe(vec2 vUV) { // main 8 | vec4 colorA = texture2D(textureA, vUV); 9 | vec4 colorB = texture2D(textureB, vUV); 10 | return mix(colorB, colorA, step(progress, vUV.y)); 11 | } -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./premultiplyAlphaBlock.block.js"; 2 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/babylon/demo/utilities/premultiplyAlphaBlock.block.glsl: -------------------------------------------------------------------------------- 1 | /* 2 | { 3 | "smartFilterBlockType": "PremultiplyAlphaBlock", 4 | "namespace": "Babylon.Demo.Utilities", 5 | "blockDisableStrategy": "AutoSample" 6 | } 7 | */ 8 | 9 | uniform sampler2D input; // main 10 | 11 | vec4 premultiply(vec2 vUV) { // main 12 | vec4 color = texture2D(input, vUV); 13 | return vec4(color.rgb * color.a, color.a); 14 | } -------------------------------------------------------------------------------- /packages/blocks/src/blocks/blockNamespaces.ts: -------------------------------------------------------------------------------- 1 | export const babylonDemoEffectsNamespace = "Babylon.Demo.Effects"; 2 | export const babylonDemoUtilitiesNamespace = "Babylon.Demo.Utilities"; 3 | export const babylonDemoTransitionsNamespace = "Babylon.Demo.Transitions"; 4 | export const inputsNamespace = "Inputs"; 5 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/blockTypes.ts: -------------------------------------------------------------------------------- 1 | export const pixelateBlockType = "PixelateBlock"; 2 | export const blackAndWhiteBlockType = "BlackAndWhiteBlock"; 3 | export const exposureBlockType = "ExposureBlock"; 4 | export const contrastBlockType = "ContrastBlock"; 5 | export const desaturateBlockType = "DesaturateBlock"; 6 | export const posterizeBlockType = "PosterizeBlock"; 7 | export const kaleidoscopeBlockType = "KaleidoscopeBlock"; 8 | export const greenScreenBlockType = "GreenScreenBlock"; 9 | export const glassBlockType = "GlassBlock"; 10 | export const frameBlockType = "FrameBlock"; 11 | export const blurBlockType = "BlurBlock"; 12 | export const directionalBlurBlockType = "DirectionalBlurBlock"; 13 | export const compositionBlockType = "CompositionBlock"; 14 | export const glitchBlockType = "GlitchBlock"; 15 | export const tileBlockType = "TileBlock"; 16 | export const wipeBlockType = "WipeBlock"; 17 | export const maskBlockType = "MaskBlock"; 18 | export const particleBlockType = "ParticleBlock"; 19 | export const spritesheetBlockType = "SpritesheetBlock"; 20 | export const tintBlockType = "TintBlock"; 21 | export const premultiplyAlphaBlockType = "PremultiplyAlphaBlock"; 22 | -------------------------------------------------------------------------------- /packages/blocks/src/blocks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./babylon/demo/effects/index.js"; 2 | export * from "./babylon/demo/transitions/index.js"; 3 | export * from "./babylon/demo/utilities/index.js"; 4 | export * from "./blockTypes.js"; 5 | export * from "./blockNamespaces.js"; 6 | -------------------------------------------------------------------------------- /packages/blocks/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./registration/index.js"; 2 | export * from "./blocks/index.js"; 3 | -------------------------------------------------------------------------------- /packages/blocks/src/registration/IBlockRegistration.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import type { SmartFilter, SmartFilterDeserializer, ISerializedBlockV1, BaseBlock } from "@babylonjs/smart-filters"; 3 | 4 | /** 5 | * An object which describes a block definition, as well as a factory for creating a new instance of the block or 6 | * deserializing it 7 | */ 8 | export interface IBlockRegistration { 9 | /** 10 | * The block type of the block 11 | */ 12 | blockType: string; 13 | 14 | /** 15 | * Creates an instance of the block, either fresh or deserialized from a serialized block 16 | * @param smartFilter - The smart filter to create the block for 17 | * @param engine - The engine to use for creating blocks 18 | * @param smartFilterDeserializer - The deserializer to use for deserializing blocks 19 | * @param serializedBlock - The serialized block to deserialize, if any 20 | * @returns - A promise for a new instance of the block 21 | */ 22 | factory?: ( 23 | smartFilter: SmartFilter, 24 | engine: ThinEngine, 25 | smartFilterDeserializer: SmartFilterDeserializer, 26 | serializedBlock?: ISerializedBlockV1 27 | ) => Promise; 28 | 29 | /** 30 | * The namespace of the block 31 | */ 32 | namespace: string; 33 | 34 | /** 35 | * A tooltip for the block if displayed in an editor, for instance 36 | */ 37 | tooltip: string; 38 | 39 | /** 40 | * True if this is an input block 41 | */ 42 | isInput?: boolean; 43 | 44 | /** 45 | * If true, this represents a custom block (not one that was programmatically included) 46 | */ 47 | isCustom?: boolean; 48 | } 49 | -------------------------------------------------------------------------------- /packages/blocks/src/registration/blockSerializers.ts: -------------------------------------------------------------------------------- 1 | import type { IBlockSerializerV1 } from "@babylonjs/smart-filters"; 2 | import { blurBlockSerializer } from "../blocks/babylon/demo/effects/blurBlock.serializer.js"; 3 | import { directionalBlurBlockSerializer } from "../blocks/babylon/demo/effects/directionalBlurBlock.serializer.js"; 4 | import { compositionBlockSerializer } from "../blocks/babylon/demo/effects/compositionBlock.serializer.js"; 5 | import { 6 | blackAndWhiteBlockType, 7 | pixelateBlockType, 8 | exposureBlockType, 9 | contrastBlockType, 10 | desaturateBlockType, 11 | posterizeBlockType, 12 | kaleidoscopeBlockType, 13 | greenScreenBlockType, 14 | maskBlockType, 15 | particleBlockType, 16 | spritesheetBlockType, 17 | tintBlockType, 18 | premultiplyAlphaBlockType, 19 | wipeBlockType, 20 | } from "../blocks/blockTypes.js"; 21 | 22 | /** 23 | * Any blocks that do not need to make use of ISerializedBlockV1.data can use the default serialization and 24 | * should go in this list. If the serializer needs to store additional info in ISerializedBlockV1.data (e.g. 25 | * webcam source name), then it should be registered in additionalBlockSerializers below. 26 | */ 27 | export const blocksUsingDefaultSerialization: string[] = [ 28 | blackAndWhiteBlockType, 29 | pixelateBlockType, 30 | exposureBlockType, 31 | contrastBlockType, 32 | desaturateBlockType, 33 | posterizeBlockType, 34 | kaleidoscopeBlockType, 35 | greenScreenBlockType, 36 | maskBlockType, 37 | particleBlockType, 38 | spritesheetBlockType, 39 | tintBlockType, 40 | premultiplyAlphaBlockType, 41 | wipeBlockType, 42 | ]; 43 | 44 | /** 45 | * Any blocks which require serializing more information than just the connections should be registered here. 46 | * They should make use of the ISerializedBlockV1.data field to store this information. 47 | */ 48 | export const additionalBlockSerializers: IBlockSerializerV1[] = [ 49 | blurBlockSerializer, 50 | directionalBlurBlockSerializer, 51 | compositionBlockSerializer, 52 | ]; 53 | -------------------------------------------------------------------------------- /packages/blocks/src/registration/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./builtInBlockRegistrations.js"; 2 | export * from "./IBlockRegistration.js"; 3 | -------------------------------------------------------------------------------- /packages/blocks/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.build.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "./src", 6 | "outDir": "./dist", 7 | "composite": true 8 | }, 9 | 10 | "include": ["./src/**/*.ts"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/blocks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "../", 6 | "outDir": "./dist", 7 | "composite": true 8 | }, 9 | 10 | "include": ["../**/*.ts"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/browserExtension/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/browserExtension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/smart-filters-browser-extension", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "A browser extension to allow for debugging Babylon.js Smart Filters in websites", 6 | "keywords": [ 7 | "video", 8 | "composition", 9 | "3D", 10 | "2D", 11 | "javascript", 12 | "html5", 13 | "webgl", 14 | "webgl2", 15 | "webgpu", 16 | "babylon" 17 | ], 18 | "license": "MIT", 19 | "readme": "README.md", 20 | "sideEffects": false, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/BabylonJS/SmartFilters.git" 24 | }, 25 | "files": [ 26 | "dist", 27 | "src", 28 | "license.md", 29 | "readme.md" 30 | ], 31 | "scripts": { 32 | "clean": "rimraf dist && rimraf unpackedExtension && rimraf tsconfig.build.tsbuildinfo", 33 | "versionUp": "node tools/versionUp.js", 34 | "build": "webpack --env=prod", 35 | "watch": "webpack --env=dev --watch", 36 | "test": "echo \"Error: run test from the root of the monorepo\" && exit 1" 37 | }, 38 | "devDependencies": { 39 | "@fortawesome/fontawesome-svg-core": "^6.1.0", 40 | "@fortawesome/free-solid-svg-icons": "^6.1.0", 41 | "@fortawesome/react-fontawesome": "^0.1.18", 42 | "@types/chrome": "^0.0.268", 43 | "@types/react": "^17.0.30", 44 | "@types/react-dom": "^17.0.10", 45 | "copy-webpack-plugin": "^13.0.0", 46 | "css-loader": "^7.1.0", 47 | "file-loader": "^6.2.0", 48 | "react": "^17.0.2", 49 | "react-dom": "^17.0.2", 50 | "sass": "^1.85.1", 51 | "sass-loader": "^16.0.5", 52 | "source-map-loader": "^3.0.0", 53 | "style-loader": "^3.3.0", 54 | "ts-loader": "^9.4.1", 55 | "url-loader": "^4.1.1", 56 | "webpack": "^5.94.0", 57 | "webpack-bundle-analyzer": "^4.7.0", 58 | "webpack-cli": "^4.10.0", 59 | "webpack-dev-server": "^5.2.1" 60 | } 61 | } -------------------------------------------------------------------------------- /packages/browserExtension/readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | # PREVIEW WARNING 4 | 5 | This package is currently in preview form. There are known issues which will be addressed before leaving preview. 6 | 7 | ## Known issues 8 | 9 | 1. Dragging from a connection point creates a new connection, but it should be in read only mode. 10 | 11 | ## Browser Extension 12 | 13 | This browser extension is intended to aid in the development of web applications that make use of Smart Filters. 14 | 15 | ### Usage 16 | 17 | 1. Build: `npm install` then `npm run build` 18 | 1. Install in Chrome or Edge 19 | - Go to Manage Extensions 20 | - Turn on Developer Mode 21 | - Click "Load Unpacked" 22 | - Browse to packages/browserExtension/unpackedExtension 23 | 1. Update the web application you are debugging to set these globals: 24 | - window.currentSmartFilter to the current SmartFilter instance 25 | - window.thinEngine to the ThinEngine instance used when creating your SmartFilter 26 | 1. Browse to the web application you want to debug, and click the Smart Filter Debugger button in the Extensions menu (consider pinning it to make it easier to get to) 27 | 1. A popup will appear with the Editor in it 28 | 1. You can modify the values of input blocks and see the results in real time in your web application 29 | 30 | #### Unsupported Cases 31 | 32 | 1. Changing input textures is not currently supported 33 | 1. Changes to the structure of the Smart Filter (e.g. changing connections, deleting blocks, adding blocks) is not currently supported 34 | 35 | ### Dev Inner Loop 36 | 37 | To work on the browser extension: 38 | 39 | 1. Run `npm run watch:browserExtension` in the root of the repo 40 | 1. If you haven't done the installation steps above, do them now (only needs to be done once) 41 | 1. To make changes 42 | 1. Make your change in the code, and confirm your incremental build is green in the terminal 43 | 1. Reload your web application, click the Smart Filter Debugger extension button again, and you should see your changes 44 | 1. To debug, open the browser Dev Tools in the web application and you will be able to find the browser extension code in the Sources tab and debug it 45 | -------------------------------------------------------------------------------- /packages/browserExtension/src/assets/icons/smartFilter_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/browserExtension/src/assets/icons/smartFilter_128.png -------------------------------------------------------------------------------- /packages/browserExtension/src/assets/icons/smartFilter_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/browserExtension/src/assets/icons/smartFilter_16.png -------------------------------------------------------------------------------- /packages/browserExtension/src/assets/icons/smartFilter_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/browserExtension/src/assets/icons/smartFilter_24.png -------------------------------------------------------------------------------- /packages/browserExtension/src/assets/icons/smartFilter_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/browserExtension/src/assets/icons/smartFilter_32.png -------------------------------------------------------------------------------- /packages/browserExtension/src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Smart Filter Debugger", 4 | "version": "0.1.0", 5 | "description": "Debugger for Babylon.js Smart Filters", 6 | "permissions": [ 7 | "scripting", 8 | "activeTab" 9 | ], 10 | "background": { 11 | "service_worker": "scripts/background.js" 12 | }, 13 | "action": { 14 | "default_icon": { 15 | "16": "icons/smartFilter_16.png", 16 | "24": "icons/smartFilter_24.png", 17 | "32": "icons/smartFilter_32.png" 18 | } 19 | }, 20 | "icons": { 21 | "16": "icons/smartFilter_16.png", 22 | "24": "icons/smartFilter_24.png", 23 | "128": "icons/smartFilter_128.png" 24 | } 25 | } -------------------------------------------------------------------------------- /packages/browserExtension/src/background.ts: -------------------------------------------------------------------------------- 1 | chrome.action.onClicked.addListener(async (tab: chrome.tabs.Tab) => { 2 | // Execute script in the current tab and all iframes within the tab 3 | await chrome.scripting.executeScript({ 4 | target: { tabId: tab!.id!, allFrames: true }, 5 | files: ["./scripts/editorLauncher.js"], 6 | world: "MAIN", 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/browserExtension/src/editorLauncher.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { SmartFilterEditorControl } from "@babylonjs/smart-filters-editor-control"; 3 | 4 | const filter = (window as any).currentSmartFilter; 5 | const engine = (window as any).thinEngine; 6 | 7 | if (filter) { 8 | console.log("A SmartFilter was found in the page, launching the editor"); 9 | // Display the editor 10 | SmartFilterEditorControl.Show({ 11 | engine, 12 | filter, 13 | }); 14 | } else { 15 | console.log("No SmartFilter was found in the page"); 16 | } 17 | -------------------------------------------------------------------------------- /packages/browserExtension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "../", 6 | "outDir": "./dist", 7 | "composite": true, 8 | "types": [ 9 | // (various types, e.g. jquery, core-js), 10 | "chrome" 11 | ], 12 | "resolveJsonModule": true 13 | }, 14 | "include": [ 15 | "./src/**/*.ts", 16 | "./src/**/*.tsx", 17 | "../blocks/**/*.ts", 18 | "../core/src/**/*.ts", 19 | "../core/src/**/*.tsx", 20 | "../editor/src/**/*.ts", 21 | "../editor/src/**/*.tsx" 22 | ], 23 | "exclude": ["**/node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/smart-filters", 3 | "version": "1.0.0", 4 | "description": "Babylon.js Smart Filter core", 5 | "keywords": [ 6 | "video", 7 | "composition", 8 | "3D", 9 | "2D", 10 | "javascript", 11 | "html5", 12 | "webgl", 13 | "webgl2", 14 | "webgpu", 15 | "babylon" 16 | ], 17 | "license": "MIT", 18 | "readme": "README.md", 19 | "main": "dist/index", 20 | "module": "dist/index", 21 | "esnext": "dist/index", 22 | "types": "dist/index", 23 | "type": "module", 24 | "sideEffects": [ 25 | "./dist/utils/buildTools/**" 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/BabylonJS/SmartFilters.git" 30 | }, 31 | "files": [ 32 | "dist", 33 | "src", 34 | "license.md", 35 | "readme.md" 36 | ], 37 | "scripts": { 38 | "clean": "rimraf dist && rimraf tsconfig.build.tsbuildinfo", 39 | "preparePublish": "node dist/utils/buildTools/versionUp.js", 40 | "build": "tsc -p ./tsconfig.build.json", 41 | "watch": "tsc -p ./tsconfig.build.json --watch", 42 | "test": "echo \"Error: run test from the root of the monorepo\" && exit 1" 43 | }, 44 | "peerDependencies": { 45 | "@babylonjs/core": "^7.47.3 || ^8.0.1" 46 | } 47 | } -------------------------------------------------------------------------------- /packages/core/readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | ## Core 4 | 5 | A Smart Filter is a node based description of effects to apply on various inputs in order to create a visual output on a canvas element or specified render target. 6 | 7 | The main usage is for video processing and composition but it could be easily reused for picture edition or procedural creation. 8 | 9 | See the full documentation at [doc.babylonjs.com](https://doc.babylonjs.com/features/featuresDeepDive/smartFilters/) 10 | -------------------------------------------------------------------------------- /packages/core/src/IDisposable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Define an interface for all classes that will hold resources 3 | */ 4 | export interface IDisposable { 5 | /** 6 | * Releases all held resources 7 | */ 8 | dispose(): void; 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/blockFoundation/index.ts: -------------------------------------------------------------------------------- 1 | export { BaseBlock } from "./baseBlock.js"; 2 | export { InputBlock, type InputBlockEditorData } from "./inputBlock.js"; 3 | export { type AnyInputBlock } from "./inputBlock.js"; 4 | export { ShaderBlock } from "./shaderBlock.js"; 5 | export { CustomShaderBlock } from "./customShaderBlock.js"; 6 | export { CustomAggregateBlock } from "./customAggregateBlock.js"; 7 | export { DisableableShaderBlock, BlockDisableStrategy } from "./disableableShaderBlock.js"; 8 | export { AggregateBlock } from "./aggregateBlock.js"; 9 | export { type IDisableableBlock } from "./disableableShaderBlock.js"; 10 | -------------------------------------------------------------------------------- /packages/core/src/blockFoundation/textureOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The format of a texture - corresponds to the Babylon.js TextureFormat constants 3 | */ 4 | export enum TextureFormat { 5 | /** Babylon Constants.TEXTUREFORMAT_RGBA */ 6 | RGBA = 5, 7 | /** Babylon Constants.TEXTUREFORMAT_R */ 8 | R = 6, 9 | /** Babylon Constants.TEXTUREFORMAT_RG */ 10 | RG = 7, 11 | } 12 | 13 | /** 14 | * The type of a texture - corresponds to the Babylon.js TextureType constants 15 | */ 16 | export enum TextureType { 17 | /** Babylon Constants.TEXTURETYPE_UNSIGNED_BYTE */ 18 | UNSIGNED_BYTE = 0, 19 | /** Babylon Constants.TEXTURETYPE_FLOAT */ 20 | FLOAT = 1, 21 | /** Babylon Constants.TEXTURETYPE_HALF_FLOAT */ 22 | HALF_FLOAT = 2, 23 | } 24 | 25 | // IMPORTANT: Update textureOptionsMatch() if you add more fields to OutputTextureOptions 26 | /** 27 | * Describes the requirements for the output texture of a shader block. 28 | */ 29 | export type OutputTextureOptions = { 30 | /** 31 | * The texture size ratio (output size of this block / size of the Smart Filter output) 32 | */ 33 | ratio: number; 34 | 35 | /** 36 | * The format of the texture 37 | */ 38 | format: TextureFormat; 39 | 40 | /** 41 | * The type of the texture 42 | */ 43 | type: TextureType; 44 | }; 45 | 46 | /** 47 | * Compares two OutputTextureOptions to see if they match. 48 | * @param a - The first OutputTextureOptions 49 | * @param b - The second OutputTextureOptions 50 | * @returns True if the two options match, false otherwise 51 | */ 52 | export function textureOptionsMatch(a: OutputTextureOptions | undefined, b: OutputTextureOptions | undefined): boolean { 53 | if (a === undefined || b === undefined) { 54 | return false; 55 | } 56 | return a.ratio === b.ratio && a.format === b.format && a.type === b.type; 57 | } 58 | -------------------------------------------------------------------------------- /packages/core/src/command/command.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the owner of a command. 3 | */ 4 | export interface ICommandOwner { 5 | /** 6 | * The friendly name of the owner. 7 | */ 8 | readonly name: string; 9 | 10 | /** 11 | * The blockType of the owner; 12 | */ 13 | readonly blockType: string; 14 | } 15 | 16 | /** 17 | * Represents a the action of a @see Command. This is what will be executed during a command buffer execution. 18 | */ 19 | export type CommandAction = () => void; 20 | 21 | /** 22 | * Represents a command to execute. 23 | * 24 | * A command contains a function that will be executed at runtime by the smart filter. 25 | * 26 | * It also contains the owner of the command for debugging purposes. 27 | */ 28 | export type Command = { 29 | /** 30 | * The friendly name of the command. 31 | */ 32 | readonly name: string; 33 | 34 | /** 35 | * The owner of the command. 36 | * In practice, it will mostly be a block, the smart filter or a tool injecting commands. 37 | */ 38 | readonly owner: ICommandOwner; 39 | 40 | /** 41 | * Defines the action to execute. 42 | */ 43 | readonly action: CommandAction; 44 | }; 45 | 46 | /** 47 | * Creates a new command. 48 | * @param name - The friendly name of the command 49 | * @param owner - The owner of the command 50 | * @param action - The action to execute when the command is executed 51 | * @returns The new command 52 | */ 53 | export function createCommand(name: string, owner: ICommandOwner, action: CommandAction): Command { 54 | return { 55 | name, 56 | owner, 57 | action, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /packages/core/src/command/commandBuffer.ts: -------------------------------------------------------------------------------- 1 | import type { Command } from "./command"; 2 | 3 | /** 4 | * Represents the action to run when calling `visitCommands` on a @see CommandBuffer. 5 | */ 6 | export type CommandsVisitor = (command: Command) => void; 7 | 8 | /** 9 | * @see CommandsVisitor used to execute the commands of a @see CommandBuffer. 10 | * @param command - The command to execute. 11 | */ 12 | function executeCommandsVisitor(command: Command) { 13 | command.action(); 14 | } 15 | 16 | /** 17 | * A command buffer is a list of commands to execute. 18 | * This is used to store the list of tasks the current smart filter needs to execute. 19 | */ 20 | export class CommandBuffer { 21 | private readonly _commands: Command[] = []; 22 | 23 | /** 24 | * Creates a new command buffer. 25 | * @param args - the list of commands to add to the command buffer 26 | */ 27 | constructor(...args: Command[]) { 28 | for (const command of args) { 29 | this.push(command); 30 | } 31 | } 32 | 33 | /** 34 | * Adds a command to the command buffer. 35 | * @param command - the command to add 36 | */ 37 | public push(command: Command) { 38 | this._commands.push(command); 39 | } 40 | 41 | /** 42 | * Clears the command buffer and empty the list of commands. 43 | */ 44 | public clear() { 45 | this._commands.length = 0; 46 | } 47 | 48 | /** 49 | * Visits all the commands in the command buffer. 50 | * @param commandVisitor - The action to execute on each command 51 | */ 52 | public visitCommands(commandVisitor: CommandsVisitor): void { 53 | for (const command of this._commands) { 54 | commandVisitor(command); 55 | } 56 | } 57 | 58 | /** 59 | * Execute all the commands in the command buffer. 60 | */ 61 | public execute() { 62 | this.visitCommands(executeCommandsVisitor); 63 | } 64 | 65 | /** 66 | * Dispose the resources associated to the command buffer. 67 | */ 68 | public dispose() { 69 | this.clear(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/core/src/command/commandBufferDebugger.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@babylonjs/core/Misc/logger.js"; 2 | import type { CommandBuffer } from "./commandBuffer"; 3 | 4 | /** 5 | * Logs all the commands associated to a command buffer. 6 | * @param commandBuffer - The command buffer to log 7 | */ 8 | export function logCommands(commandBuffer: Readonly) { 9 | Logger.Log("----- Command buffer commands -----"); 10 | commandBuffer.visitCommands((command) => { 11 | Logger.Log(` Owner: ${command.owner.blockType} (${command.owner.name}) - Command: ${command.name}`); 12 | }); 13 | Logger.Log("-----------------------------------"); 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/command/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./command.js"; 2 | export * from "./commandBuffer.js"; 3 | export { logCommands } from "./commandBufferDebugger.js"; 4 | -------------------------------------------------------------------------------- /packages/core/src/connection/connectionPointCompatibilityState.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines how compatible connection points are. 3 | */ 4 | export enum ConnectionPointCompatibilityState { 5 | /** Points are compatibles */ 6 | Compatible, 7 | /** Points are incompatible because of their types */ 8 | TypeIncompatible, 9 | /** Points are incompatible because of their directions */ 10 | DirectionIncompatible, 11 | /** Points are incompatible because they are in the same hierarchy **/ 12 | HierarchyIssue, 13 | } 14 | 15 | /** 16 | * Gets a user friendly message for the given compatibility state. 17 | * @param state - Defines the compatibility state 18 | * @returns the message associated with a compatibility state. 19 | */ 20 | export function getCompatibilityIssueMessage(state: ConnectionPointCompatibilityState): string { 21 | switch (state) { 22 | case ConnectionPointCompatibilityState.TypeIncompatible: 23 | return "Cannot connect two different connection types"; 24 | case ConnectionPointCompatibilityState.DirectionIncompatible: 25 | return "Cannot connect with the same direction"; 26 | case ConnectionPointCompatibilityState.HierarchyIssue: 27 | return "Source block cannot be connected with one of its ancestors"; 28 | default: 29 | return ""; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/connection/connectionPointDirection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the direction of a connection point. 3 | */ 4 | export enum ConnectionPointDirection { 5 | /** Input */ 6 | Input = 0, 7 | /** Output */ 8 | Output = 1, 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/connection/connectionPointType.ts: -------------------------------------------------------------------------------- 1 | import type { ThinTexture } from "@babylonjs/core/Materials/Textures/thinTexture"; 2 | import type { IColor3Like, IColor4Like, IVector2Like } from "@babylonjs/core/Maths/math.like"; 3 | import type { Nullable } from "@babylonjs/core/types"; 4 | 5 | /** 6 | * Defines the type of a connection point. 7 | */ 8 | export enum ConnectionPointType { 9 | /** Float */ 10 | Float = 1, 11 | /** Texture */ 12 | Texture = 2, 13 | /** Color3 */ 14 | Color3 = 3, 15 | /** Color4 */ 16 | Color4 = 4, 17 | /** Boolean */ 18 | Boolean = 5, 19 | /** Vector2 */ 20 | Vector2 = 6, 21 | } 22 | 23 | /** 24 | * A union of all supported connection point types 25 | */ 26 | export type AllConnectionPointTypes = 27 | | ConnectionPointType.Float 28 | | ConnectionPointType.Texture 29 | | ConnectionPointType.Color3 30 | | ConnectionPointType.Color4 31 | | ConnectionPointType.Boolean 32 | | ConnectionPointType.Vector2; 33 | 34 | /** 35 | * Retrieves the type of the value from the Connection point type. 36 | */ 37 | // prettier-ignore 38 | export type ConnectionPointValue = 39 | T extends ConnectionPointType.Float ? number : 40 | T extends ConnectionPointType.Texture ? Nullable : 41 | T extends ConnectionPointType.Color3 ? IColor3Like : 42 | T extends ConnectionPointType.Color4 ? IColor4Like : 43 | T extends ConnectionPointType.Boolean ? boolean : 44 | T extends ConnectionPointType.Vector2 ? IVector2Like : 45 | never; 46 | -------------------------------------------------------------------------------- /packages/core/src/connection/connectionPointWithDefault.ts: -------------------------------------------------------------------------------- 1 | import type { BaseBlock } from "../blockFoundation/baseBlock"; 2 | import { ConnectionPoint, type RuntimeData } from "./connectionPoint.js"; 3 | import type { ConnectionPointDirection } from "./connectionPointDirection"; 4 | import type { ConnectionPointType } from "./connectionPointType"; 5 | 6 | /** 7 | * A ConnectionPoint whose runtimeData is never null - if not hooked up to a connection, it will use a default value. 8 | */ 9 | export class ConnectionPointWithDefault< 10 | U extends ConnectionPointType = ConnectionPointType, 11 | > extends ConnectionPoint { 12 | /** 13 | * The runtime data for this ConnectionPoint - it will never be null - if not hooked up to a connection, it will use the default value. 14 | */ 15 | public override runtimeData: RuntimeData; 16 | 17 | /** 18 | * Create a new ConnectionPointWithDefault 19 | * @param name - The name the connection point has in the block 20 | * @param ownerBlock - The block the connection point belongs to 21 | * @param type - The type of the connection point 22 | * @param direction - The direction of the connection point 23 | * @param runtimeData - The runtimeData to use for this connection point if no connection is made 24 | */ 25 | constructor( 26 | name: string, 27 | ownerBlock: BaseBlock, 28 | type: U, 29 | direction: ConnectionPointDirection, 30 | runtimeData: RuntimeData 31 | ) { 32 | super(name, ownerBlock, type, direction, runtimeData); 33 | this.runtimeData = runtimeData; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/connection/index.ts: -------------------------------------------------------------------------------- 1 | export { ConnectionPointDirection } from "./connectionPointDirection.js"; 2 | export { ConnectionPointType } from "./connectionPointType.js"; 3 | export { type ConnectionPointValue } from "./connectionPointType.js"; 4 | export { 5 | ConnectionPointCompatibilityState, 6 | getCompatibilityIssueMessage, 7 | } from "./connectionPointCompatibilityState.js"; 8 | export { ConnectionPoint } from "./connectionPoint.js"; 9 | export { type RuntimeData } from "./connectionPoint.js"; 10 | -------------------------------------------------------------------------------- /packages/core/src/editorUtils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./editableInPropertyPage.js"; 2 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./command/index.js"; 2 | export * from "./connection/index.js"; 3 | export * from "./blockFoundation/index.js"; 4 | export * from "./editorUtils/index.js"; 5 | export * from "./optimization/index.js"; 6 | export * from "./runtime/index.js"; 7 | export * from "./serialization/index.js"; 8 | export * from "./utils/index.js"; 9 | 10 | export { type IDisposable } from "./IDisposable.js"; 11 | export { SmartFilter, type InitializationData } from "./smartFilter.js"; 12 | export * from "./version.js"; 13 | 14 | // So that users of the Smart Filters core can easily modify the logger settings (e.g. to change the logging level) 15 | export { Logger } from "@babylonjs/core/Misc/logger.js"; 16 | -------------------------------------------------------------------------------- /packages/core/src/optimization/index.ts: -------------------------------------------------------------------------------- 1 | export { SmartFilterOptimizer } from "./smartFilterOptimizer.js"; 2 | -------------------------------------------------------------------------------- /packages/core/src/runtime/index.ts: -------------------------------------------------------------------------------- 1 | export { type StrongRef } from "./strongRef.js"; 2 | export { createStrongRef } from "./strongRef.js"; 3 | export { DisableableShaderBinding, ShaderBinding, ShaderRuntime } from "./shaderRuntime.js"; 4 | export { type SmartFilterRuntime } from "./smartFilterRuntime.js"; 5 | export { InternalSmartFilterRuntime } from "./smartFilterRuntime.js"; 6 | export { RenderTargetGenerator } from "./renderTargetGenerator.js"; 7 | -------------------------------------------------------------------------------- /packages/core/src/runtime/strongRef.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This describes a strong reference to any data type. 3 | */ 4 | export type StrongRef = { 5 | /** 6 | * The value of the strong reference. 7 | */ 8 | value: T; 9 | }; 10 | 11 | /** 12 | * Create a strong reference to the given value. 13 | * @param value - the value to wrap in a strong reference 14 | * @returns the strong reference containing the value 15 | */ 16 | export function createStrongRef(value: T): StrongRef { 17 | return { value }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/serialization/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./v1/index.js"; 2 | export * from "./serializedSmartFilter.js"; 3 | export * from "./smartFilterDeserializer.js"; 4 | export * from "./smartFilterSerializer.js"; 5 | export * from "./serializedShaderBlockDefinition.js"; 6 | export * from "./serializedBlockDefinition.js"; 7 | export * from "./importCustomBlockDefinition.js"; 8 | -------------------------------------------------------------------------------- /packages/core/src/serialization/serializedBlockDefinition.ts: -------------------------------------------------------------------------------- 1 | import type { SerializedShaderBlockDefinition } from "./serializedShaderBlockDefinition"; 2 | import type { SerializedSmartFilter } from "./serializedSmartFilter"; 3 | 4 | /** 5 | * Type that represents any type of serialized block definition - shader or aggregate. 6 | */ 7 | export type SerializedBlockDefinition = (SerializedShaderBlockDefinition | SerializedSmartFilter) & { 8 | /** 9 | * The type of block this is. 10 | */ 11 | blockType: string; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/core/src/serialization/serializedShaderBlockDefinition.ts: -------------------------------------------------------------------------------- 1 | import type { SerializedShaderBlockDefinitionV1 } from "./v1/shaderBlockSerialization.types"; 2 | 3 | /** 4 | * Type union of all versions of serialized SmartFilter block definitions 5 | * A block definition is an object which is used to create a CustomShaderBlock instance. 6 | */ 7 | export type SerializedShaderBlockDefinition = SerializedShaderBlockDefinitionV1; 8 | -------------------------------------------------------------------------------- /packages/core/src/serialization/serializedSmartFilter.ts: -------------------------------------------------------------------------------- 1 | import type { SerializedSmartFilterV1 } from "./v1/smartFilterSerialization.types"; 2 | 3 | /** 4 | * Type union of all versions of serialized SmartFilters 5 | */ 6 | export type SerializedSmartFilter = SerializedSmartFilterV1; 7 | -------------------------------------------------------------------------------- /packages/core/src/serialization/v1/defaultBlockSerializer.ts: -------------------------------------------------------------------------------- 1 | import { ShaderBlock } from "../../blockFoundation/shaderBlock.js"; 2 | import type { BaseBlock } from "../../blockFoundation/baseBlock"; 3 | import type { ISerializedBlockV1, SerializeBlockV1 } from "./smartFilterSerialization.types"; 4 | 5 | /** 6 | * The default V1 block serializer which can be used for any block that relies only on ConnectionPoints 7 | * and does not have any constructor parameters or class properties that need to be serialized. 8 | * @param block - The block to serialize 9 | * @returns The serialized block 10 | */ 11 | export const defaultBlockSerializer: SerializeBlockV1 = (block: BaseBlock): ISerializedBlockV1 => { 12 | return { 13 | name: block.name, 14 | uniqueId: block.uniqueId, 15 | blockType: block.blockType, 16 | namespace: block.namespace, 17 | comments: block.comments, 18 | data: undefined, 19 | outputTextureOptions: block instanceof ShaderBlock ? block.outputTextureOptions : undefined, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/core/src/serialization/v1/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./defaultBlockSerializer.js"; 2 | export * from "./smartFilterSerialization.types.js"; 3 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildTools/buildShaders.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds all .glsl files under . 3 | * @param shaderPath - The path to the shaders to watch 4 | * @param importPath - The path to import the converted shaders 5 | * @example node buildShaders.js 6 | */ 7 | 8 | import { convertShaders } from "./convertShaders.js"; 9 | 10 | const externalArguments = process.argv.slice(2); 11 | if (externalArguments.length >= 2 && externalArguments[0] && externalArguments[1]) { 12 | convertShaders(externalArguments[0], externalArguments[1]); 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildTools/convertShaders.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import { Logger } from "@babylonjs/core/Misc/logger.js"; 4 | import { convertGlslIntoShaderProgram } from "./convertGlslIntoShaderProgram.js"; 5 | import { convertGlslIntoBlock } from "./convertGlslIntoBlock.js"; 6 | 7 | /** 8 | * Converts all GLSL files in a path into blocks for use in the build system. 9 | * @param shaderPath - The path to the .glsl files to convert. 10 | * @param importPath - The path to import the ShaderProgram type from. 11 | */ 12 | export function convertShaders(shaderPath: string, importPath: string) { 13 | // Get all files in the path 14 | const allFiles = fs.readdirSync(shaderPath, { withFileTypes: true, recursive: true }); 15 | 16 | // Find all shaders (files with .fragment.glsl or .block.glsl extensions) 17 | const shaderFiles = allFiles.filter( 18 | (file) => file.isFile() && (file.name.endsWith(".fragment.glsl") || file.name.endsWith(".block.glsl")) 19 | ); 20 | 21 | // Convert all shaders 22 | for (const shaderFile of shaderFiles) { 23 | const fullPathAndFileName = path.join(shaderFile.path, shaderFile.name); 24 | convertShader(fullPathAndFileName, importPath); 25 | } 26 | } 27 | 28 | /** 29 | * Converts a single GLSL file into a block class or a ShaderProgram for use in the build system. 30 | * @param fullPathAndFileName - The full path and file name of the .glsl file to convert. 31 | * @param importPath - The path to import the ShaderProgram type from. 32 | */ 33 | export function convertShader(fullPathAndFileName: string, importPath: string): void { 34 | Logger.Log(`\nProcessing shader: ${fullPathAndFileName}`); 35 | 36 | if (fullPathAndFileName.endsWith(".fragment.glsl")) { 37 | Logger.Log("Generating a .ts file that exports a ShaderProgram."); 38 | convertGlslIntoShaderProgram(fullPathAndFileName, importPath); 39 | } else if (fullPathAndFileName.endsWith(".block.glsl")) { 40 | Logger.Log("Generating a .ts file that exports the block as a class."); 41 | convertGlslIntoBlock(fullPathAndFileName, importPath); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildTools/shaderCode.types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Describes a shader function. 3 | */ 4 | export type ShaderFunction = { 5 | /** 6 | * The name of the function. 7 | */ 8 | name: string; 9 | 10 | /** 11 | * The code of the function. 12 | */ 13 | code: string; 14 | 15 | /** 16 | * The parameters of the function. 17 | */ 18 | params?: string; 19 | }; 20 | 21 | /** 22 | * Describes a shader code. 23 | */ 24 | export type ShaderCode = { 25 | /** 26 | * The declaration of the const variables. 27 | */ 28 | const?: string; 29 | 30 | /** 31 | * The declaration of the uniform variables. 32 | */ 33 | uniform?: string; 34 | 35 | /** 36 | * The declaration of the uniform variables that should be common for all ShaderBlock instances using this shader code. 37 | */ 38 | uniformSingle?: string; 39 | 40 | /** 41 | * The name of the main function. 42 | */ 43 | mainFunctionName: string; 44 | 45 | /** 46 | * The name of the input texture which is passed through if the block is disabled. 47 | */ 48 | mainInputTexture?: string; 49 | 50 | /** 51 | * The list of functions used in the shader. 52 | */ 53 | functions: ShaderFunction[]; 54 | 55 | /** 56 | * The declaration of define statements. 57 | */ 58 | defines?: string[]; 59 | }; 60 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildTools/versionUp.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as fs from "fs"; 3 | import { exec, type ExecException } from "child_process"; 4 | import { compareVersions, determineVersion, getNpmVersion, type VersionType } from "./determineVersion.js"; 5 | import type { Nullable } from "@babylonjs/core/types.js"; 6 | 7 | const alpha = process.argv.includes("--alpha"); 8 | const packageText = fs.readFileSync("package.json"); 9 | const packageJSON = JSON.parse(packageText.toString()); 10 | 11 | const packageName = packageJSON.name; 12 | console.log("Processing package:", packageName); 13 | console.log("Alpha flag:", alpha); 14 | console.log("Current package.json version:", packageJSON.version); 15 | 16 | /** 17 | * Queries the NPM registry for the specified version type 18 | * @param versionType - The type of version to query 19 | * @param callback - The callback to call with the NPM version 20 | */ 21 | function queryNpmFeed(versionType: VersionType, callback: (npmVersion: Nullable) => void) { 22 | exec(`npm view ${packageName} dist-tags.${versionType}`, (err: Nullable, stdout) => { 23 | let npmVersion = getNpmVersion(versionType, err, stdout); 24 | if (npmVersion !== null) { 25 | npmVersion = npmVersion.trim(); 26 | console.log(`NPM Registry ${versionType} version:`, npmVersion); 27 | } 28 | callback(npmVersion); 29 | }); 30 | } 31 | 32 | queryNpmFeed("preview", (npmPreviewVersion) => { 33 | queryNpmFeed("latest", (npmLatestVersion) => { 34 | let highestNpmVersion: Nullable = npmLatestVersion; 35 | if (npmPreviewVersion && (!highestNpmVersion || compareVersions(npmPreviewVersion, highestNpmVersion) === 1)) { 36 | highestNpmVersion = npmPreviewVersion; 37 | } 38 | 39 | console.log("Highest NPM Registry version:", highestNpmVersion); 40 | 41 | const versionToUse = determineVersion(highestNpmVersion, packageJSON.version, alpha); 42 | 43 | console.log("Version to use:", versionToUse); 44 | 45 | // Update package.json if needed 46 | if (packageJSON.version !== versionToUse) { 47 | packageJSON.version = versionToUse; 48 | fs.writeFileSync("package.json", JSON.stringify(packageJSON, null, 4)); 49 | console.log("Version updated in package.json"); 50 | } else { 51 | console.log("No need to update package.json"); 52 | } 53 | 54 | // Write out to version.ts 55 | const versionTsText = `/** 56 | * The version of the SmartFilter core. During publish, this file is overwritten by versionUp.ts with the same version that is used for the NPM publish. 57 | */ 58 | export const SmartFilterCoreVersion = "${versionToUse}";\n`; 59 | fs.writeFileSync("src/version.ts", versionTsText); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/core/src/utils/buildTools/watchShaders.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** 3 | * Watches all .glsl files under and rebuilds them when changed. 4 | * @param shaderPath - The path to the shaders to watch 5 | * @param importPath - The path to import the converted shaders 6 | * @example node watchShaders.js 7 | */ 8 | 9 | import { watch } from "chokidar"; 10 | import { extname } from "path"; 11 | import { convertShader } from "./convertShaders.js"; 12 | 13 | const externalArguments = process.argv.slice(2); 14 | if (externalArguments.length >= 2 && externalArguments[0] && externalArguments[1]) { 15 | const shaderPath = externalArguments[0]; 16 | const importPath = externalArguments[1]; 17 | 18 | watch(shaderPath).on("all", (event, file) => { 19 | // Only process file changes and added files 20 | if (event !== "change" && event !== "add") { 21 | return; 22 | } 23 | 24 | // Only process .glsl files 25 | if (extname(file) !== ".glsl") { 26 | return; 27 | } 28 | 29 | console.log(`Change detected. Starting conversion...`); 30 | 31 | // Wrap in try-catch to prevent the watcher from crashing 32 | // if the new shader changes are invalid 33 | try { 34 | convertShader(file, importPath); 35 | console.log(`Successfully updated shader ${file}`); 36 | } catch (error) { 37 | console.error(`Failed to convert shader ${file}: ${error}`); 38 | } 39 | 40 | console.log(`Watching for changes in ${shaderPath}...`); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./textureLoaders.js"; 2 | export { type ShaderProgram } from "./shaderCodeUtils.js"; 3 | -------------------------------------------------------------------------------- /packages/core/src/utils/renderTargetUtils.ts: -------------------------------------------------------------------------------- 1 | import { createCommand } from "../command/command.js"; 2 | import type { BaseBlock } from "../blockFoundation/baseBlock.js"; 3 | import type { ShaderRuntime } from "../runtime/shaderRuntime"; 4 | import type { InternalSmartFilterRuntime } from "../runtime/smartFilterRuntime"; 5 | import type { OutputBlock } from "../blockFoundation/outputBlock.js"; 6 | 7 | /** 8 | * Registers the final command of the command queue - the one that draws to either the canvas or 9 | * renderTargetTexture. 10 | * @param outputBlock - The output block. 11 | * @param runtime - The smart filter runtime to use. 12 | * @param commandOwner - The owner of the command. 13 | * @param shaderBlockRuntime - The shader block runtime to use. 14 | */ 15 | export function registerFinalRenderCommand( 16 | outputBlock: OutputBlock, 17 | runtime: InternalSmartFilterRuntime, 18 | commandOwner: BaseBlock, 19 | shaderBlockRuntime: ShaderRuntime 20 | ): void { 21 | const commandOwnerBlockType = commandOwner.blockType; 22 | if (outputBlock.renderTargetWrapper) { 23 | runtime.registerCommand( 24 | createCommand(`${commandOwnerBlockType}.renderToFinalTexture`, commandOwner, () => { 25 | shaderBlockRuntime.renderToTargetWrapper(outputBlock); 26 | }) 27 | ); 28 | } else { 29 | runtime.registerCommand( 30 | createCommand(`${commandOwnerBlockType}.renderToCanvas`, commandOwner, () => { 31 | shaderBlockRuntime.renderToCanvas(); 32 | }) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/utils/textureLoaders.ts: -------------------------------------------------------------------------------- 1 | import { ThinTexture } from "@babylonjs/core/Materials/Textures/thinTexture.js"; 2 | import { type ThinEngine } from "@babylonjs/core/Engines/thinEngine.js"; 3 | import type { Nullable } from "@babylonjs/core/types"; 4 | 5 | /** 6 | * Helper that takes in a URL to an image and returns a ThinTexture 7 | * @param engine - defines the engine to use to create the texture 8 | * @param url - defines a value which contains one of the following: 9 | * * A conventional http URL, e.g. 'http://...' or 'file://...' 10 | * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...' 11 | * @param flipY - Indicates if the Y axis should be flipped 12 | * @param samplingMode - The sampling mode to use 13 | * @param forcedExtension - defines the extension to use to pick the right loader 14 | * @returns A ThinTexture of the image 15 | */ 16 | export function createImageTexture( 17 | engine: ThinEngine, 18 | url: string, 19 | flipY: Nullable = null, 20 | samplingMode: number | undefined = undefined, 21 | forcedExtension: string | null = null 22 | ): ThinTexture { 23 | const internalTexture = engine.createTexture( 24 | url, 25 | true, 26 | flipY ?? true, 27 | null, 28 | samplingMode, 29 | null, 30 | null, 31 | null, 32 | null, 33 | null, 34 | forcedExtension 35 | ); 36 | return new ThinTexture(internalTexture); 37 | } 38 | 39 | /* 40 | Future util ideas: 41 | HtmlElementTexture 42 | WebCamTexture 43 | */ 44 | -------------------------------------------------------------------------------- /packages/core/src/utils/textureUtils.ts: -------------------------------------------------------------------------------- 1 | import type { TextureSize } from "@babylonjs/core/Materials/Textures/textureCreationOptions"; 2 | import type { OutputTextureOptions } from "../blockFoundation/textureOptions"; 3 | import type { SmartFilter } from "../smartFilter"; 4 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 5 | 6 | /** 7 | * Determines the output texture size for a given shader block 8 | * @param smartFilter - The smart filter to use 9 | * @param engine - The engine to use 10 | * @param textureOptions - The texture options to use 11 | * @returns - The output texture size 12 | */ 13 | export function getBlockOutputTextureSize( 14 | smartFilter: SmartFilter, 15 | engine: ThinEngine, 16 | textureOptions: OutputTextureOptions 17 | ): TextureSize { 18 | let outputWidth: number; 19 | let outputHeight: number; 20 | const renderTargetWrapper = smartFilter.outputBlock.renderTargetWrapper; 21 | if (renderTargetWrapper) { 22 | outputWidth = renderTargetWrapper.width; 23 | outputHeight = renderTargetWrapper.height; 24 | } else { 25 | outputWidth = engine.getRenderWidth(true); 26 | outputHeight = engine.getRenderHeight(true); 27 | } 28 | return { 29 | width: Math.floor(outputWidth * textureOptions.ratio), 30 | height: Math.floor(outputHeight * textureOptions.ratio), 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/utils/uniqueIdGenerator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper class used to generate IDs unique to the current session 3 | */ 4 | export class UniqueIdGenerator { 5 | /** 6 | * The next unique ID to be returned 7 | */ 8 | public static _NextUniqueId = 1; 9 | 10 | /** 11 | * Gets a unique (relatively to the current session) Id 12 | */ 13 | public static get UniqueId() { 14 | const result = this._NextUniqueId; 15 | this._NextUniqueId++; 16 | return result; 17 | } 18 | 19 | /** 20 | * Ensures future generated IDs are greater than the specified value 21 | * @param minimum - The minimum value that future generated IDs should be greater than 22 | */ 23 | public static EnsureIdsGreaterThan(minimum: number): void { 24 | if (this._NextUniqueId <= minimum) { 25 | this._NextUniqueId = minimum + 1; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The version of the SmartFilter core. During publish, this file is overwritten by versionUp.ts with the same version that is used for the NPM publish. 3 | */ 4 | export const SmartFilterCoreVersion = "locally built"; 5 | -------------------------------------------------------------------------------- /packages/core/test/unit/importCustomBlockDefinition.test.js: -------------------------------------------------------------------------------- 1 | import { importCustomBlockDefinition } from "../../dist/serialization/importCustomBlockDefinition.js"; 2 | import { CustomShaderBlock } from "../../dist/blockFoundation/customShaderBlock.js"; 3 | import { SmartFilter } from "../../dist/smartFilter.js"; 4 | import { InputBlock } from "../../dist/blockFoundation/inputBlock.js"; 5 | import { ConnectionPointType } from "../../dist/connection/connectionPointType.js"; 6 | import { SmartFilterOptimizer } from "../../dist/optimization/smartFilterOptimizer.js"; 7 | 8 | const annotatedFragmentGeneral = ` 9 | // { "smartFilterBlockType": "GeneralBlock", "namespace": "UnitTests" } 10 | uniform sampler2D input; // main 11 | uniform float intensity; 12 | 13 | vec4 apply2(vec2 vUV){ // main 14 | vec4 color = texture2D(input, vUV); 15 | return color * intensity; 16 | } 17 | `; 18 | 19 | describe("importCustomBlockDefinition", () => { 20 | describe("a block is parsed after a block where the string ends exactly at the end of the function definition", () => { 21 | const annotatedFragmentEndOfFunctionDefinitionIsEndOfString = ` 22 | // { "smartFilterBlockType": "BlockWhereFunctionEndsAtEndOfString", "namespace": "UnitTests" } 23 | uniform sampler2D input; // main 24 | uniform float amount; 25 | 26 | vec4 apply1(vec2 vUV){ // main 27 | vec4 color = texture2D(input, vUV); 28 | return color + amount; 29 | }`; 30 | 31 | const customBlockDefinition1 = importCustomBlockDefinition( 32 | annotatedFragmentEndOfFunctionDefinitionIsEndOfString 33 | ); 34 | const customBlockDefinition2 = importCustomBlockDefinition(annotatedFragmentGeneral); 35 | let smartFilter; 36 | 37 | beforeEach(() => { 38 | smartFilter = new SmartFilter("test"); 39 | const customBlock1 = CustomShaderBlock.Create(smartFilter, "block1", customBlockDefinition1); 40 | const customBlock2 = CustomShaderBlock.Create(smartFilter, "block2", customBlockDefinition2); 41 | 42 | const inputTexture = new InputBlock(smartFilter, "inputTexture", ConnectionPointType.Texture, null); 43 | const inputFloat = new InputBlock(smartFilter, "inputFloat", ConnectionPointType.Float, 1.0); 44 | 45 | inputTexture.output.connectTo(customBlock1.findInput("input")); 46 | inputFloat.output.connectTo(customBlock1.findInput("amount")); 47 | 48 | customBlock1.output.connectTo(customBlock2.findInput("input")); 49 | inputFloat.output.connectTo(customBlock2.findInput("intensity")); 50 | 51 | customBlock2.output.connectTo(smartFilter.output); 52 | }); 53 | 54 | it("a smart filter using those blocks can be optimized without error", () => { 55 | const optimizer = new SmartFilterOptimizer(smartFilter); 56 | expect(optimizer.optimize()).not.toBeNull(); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.build.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "./src", 6 | "outDir": "./dist", 7 | "composite": true 8 | }, 9 | 10 | "include": ["./src/**/*.ts", "./src/**/*.tsx"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.buildTools.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "./src/utils/buildTools", 6 | "outDir": "./buildTools", 7 | "composite": true 8 | }, 9 | 10 | "include": ["./src/utils/buildTools/**/*.ts"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "../", 6 | "outDir": "./dist", 7 | "composite": true 8 | }, 9 | 10 | "include": ["../**/*.ts", "../**/*.tsx"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/demo/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/smart-filters-demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "Demo usage of Smart Filters, as well as dev inner loop for working on the core.", 6 | "keywords": [ 7 | "video", 8 | "composition", 9 | "3D", 10 | "2D", 11 | "javascript", 12 | "html5", 13 | "webgl", 14 | "webgl2", 15 | "webgpu", 16 | "babylon" 17 | ], 18 | "license": "MIT", 19 | "scripts": { 20 | "build": "webpack --env=prod", 21 | "clean": "rimraf .temp && rimraf www/scripts", 22 | "start": "concurrently \"npx webpack-dev-server --open\" \"npm run watch:shaders -w @babylonjs/smart-filters-blocks\"", 23 | "start:dev": "npx webpack-dev-server", 24 | "analyze": "webpack --profile --json > www/scripts/stats.json && npx webpack-bundle-analyzer www/scripts/stats.json" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^17.0.30", 28 | "@types/react-dom": "^17.0.10", 29 | "@fortawesome/fontawesome-svg-core": "^6.1.0", 30 | "@fortawesome/free-solid-svg-icons": "^6.1.0", 31 | "@fortawesome/react-fontawesome": "^0.1.18", 32 | "react": "^17.0.2", 33 | "react-dom": "^17.0.2", 34 | "css-loader": "^7.1.0", 35 | "file-loader": "^6.2.0", 36 | "sass": "^1.85.0", 37 | "sass-loader": "^16.0.5", 38 | "source-map-loader": "^3.0.0", 39 | "style-loader": "^3.3.0", 40 | "ts-loader": "^9.4.1", 41 | "url-loader": "^4.1.1", 42 | "webpack": "^5.94.0", 43 | "webpack-cli": "^6.0.1", 44 | "webpack-dev-server": "^5.2.1", 45 | "webpack-bundle-analyzer": "^4.7.0" 46 | } 47 | } -------------------------------------------------------------------------------- /packages/demo/readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | ## Demo Project 4 | 5 | This is the main demo application of the repository, and is also used as the dev inner loop for working on the core and block library. It shows how you could integrate Smart Filters into your own application. 6 | 7 | See the full documentation at [doc.babylonjs.com](https://doc.babylonjs.com/features/featuresDeepDive/smartFilters/) 8 | 9 | ## Customizing 10 | 11 | All customization of blocks and smart filters in the demo is done within the configuration folder. 12 | -------------------------------------------------------------------------------- /packages/demo/src/backgroundOption.ts: -------------------------------------------------------------------------------- 1 | import type { Nullable } from "@babylonjs/core/types"; 2 | 3 | const BackgroundStorageKey = "Background"; 4 | const DefaultBackground = "grid"; 5 | 6 | export function hookupBackgroundOption(): void { 7 | const contentDiv = document.getElementById("demoContent")! as unknown as HTMLDivElement; 8 | const backgroundSelect = document.getElementById("backgroundSelect") as HTMLSelectElement; 9 | 10 | const currentBackground = localStorage.getItem(BackgroundStorageKey) || DefaultBackground; 11 | applyBackground(contentDiv, currentBackground); 12 | backgroundSelect.value = currentBackground; 13 | 14 | backgroundSelect.addEventListener("change", () => { 15 | localStorage.setItem(BackgroundStorageKey, backgroundSelect.value); 16 | applyBackground(contentDiv, backgroundSelect.value); 17 | }); 18 | } 19 | 20 | function applyBackground(element: HTMLDivElement, background: Nullable): void { 21 | if (background === null || background === "grid") { 22 | element.classList.add("gridBackground"); 23 | element.style.backgroundColor = "unset"; 24 | } else { 25 | element.classList.remove("gridBackground"); 26 | element.style.backgroundColor = background; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/blockFactory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 3 | import type { SmartFilter, ISerializedBlockV1, BaseBlock, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 4 | import type { Nullable } from "@babylonjs/core/types"; 5 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 6 | 7 | /** 8 | * Creates instances of blocks upon request 9 | * @param smartFilter - The SmartFilter the block will belong to 10 | * @param engine - The ThinEngine to use 11 | * @param serializedBlock - The serialized block to create 12 | * @param smartFilterDeserializer - The deserializer to use 13 | * @param builtInBlockRegistrations - The built-in block editor registrations 14 | * @returns The created block or null if the block type is not recognized 15 | */ 16 | export async function blockFactory( 17 | smartFilter: SmartFilter, 18 | engine: ThinEngine, 19 | serializedBlock: ISerializedBlockV1, 20 | smartFilterDeserializer: SmartFilterDeserializer, 21 | builtInBlockRegistrations: IBlockRegistration[] 22 | ): Promise> { 23 | let newBlock: Nullable = null; 24 | 25 | const registration = builtInBlockRegistrations.find( 26 | (registration) => registration.blockType === serializedBlock.blockType 27 | ); 28 | if (registration && registration.factory) { 29 | newBlock = await registration.factory(smartFilter, engine, smartFilterDeserializer, serializedBlock); 30 | } 31 | 32 | return newBlock; 33 | } 34 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/smartFilters.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import type { SmartFilterManifest } from "../smartFilterLoader"; 3 | import { HardCodedSmartFilterNames } from "./smartFilters/hardCoded/hardCodedSmartFilterNames"; 4 | 5 | /** 6 | * The manifests describing all of the Smart Filters than can be loaded in the app's UI. 7 | * Note: these are dynamically loaded so that the blocks aren't loaded unless they're needed. 8 | */ 9 | export const smartFilterManifests: SmartFilterManifest[] = [ 10 | { 11 | type: "HardCoded", 12 | name: HardCodedSmartFilterNames.simpleLogo, 13 | createSmartFilter: async (engine: ThinEngine) => { 14 | const module = await import(/* webpackChunkName: "simpleLogo" */ "./smartFilters/hardCoded/simpleLogo"); 15 | return module.createSimpleLogoSmartFilter(engine); 16 | }, 17 | }, 18 | { 19 | type: "HardCoded", 20 | name: HardCodedSmartFilterNames.simpleWebcam, 21 | createSmartFilter: async () => { 22 | const module = await import(/* webpackChunkName: "simpleWebcam" */ "./smartFilters/hardCoded/simpleWebcam"); 23 | return module.createSimpleWebcamSmartFilter(); 24 | }, 25 | }, 26 | { 27 | type: "HardCoded", 28 | name: HardCodedSmartFilterNames.simplePhotoEdit, 29 | createSmartFilter: async (engine: ThinEngine) => { 30 | const module = await import( 31 | /* webpackChunkName: "simplePhotoEdit" */ "./smartFilters/hardCoded/simplePhotoEdit" 32 | ); 33 | return module.createSimplePhotoEditSmartFilter(engine); 34 | }, 35 | }, 36 | { 37 | type: "Serialized", 38 | name: "Serialized Simple Logo", 39 | getSmartFilterJson: async () => { 40 | return await import( 41 | /* webpackChunkName: "serializedSimpleLogo" */ "./smartFilters/serialized/serializedSimpleLogo.json" 42 | ); 43 | }, 44 | }, 45 | ]; 46 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/smartFilters/hardCoded/hardCodedSmartFilterNames.ts: -------------------------------------------------------------------------------- 1 | export const HardCodedSmartFilterNames = { 2 | simpleLogo: "Simple Logo", 3 | simpleWebcam: "Simple Webcam", 4 | simplePhotoEdit: "Simple Photo Edit", 5 | }; 6 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/smartFilters/hardCoded/simpleLogo.ts: -------------------------------------------------------------------------------- 1 | import { BlackAndWhiteBlock, PixelateBlock, PremultiplyAlphaBlock } from "@babylonjs/smart-filters-blocks"; 2 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 3 | import { 4 | ConnectionPointType, 5 | InputBlock, 6 | SmartFilter, 7 | createImageTexture, 8 | createStrongRef, 9 | } from "@babylonjs/smart-filters"; 10 | import { HardCodedSmartFilterNames } from "./hardCodedSmartFilterNames"; 11 | 12 | export function createSimpleLogoSmartFilter(engine: ThinEngine): SmartFilter { 13 | const smartFilter = new SmartFilter(HardCodedSmartFilterNames.simpleLogo); 14 | const logoTexture = createImageTexture(engine, "/assets/logo.png"); 15 | const logoInput = new InputBlock(smartFilter, "logo", ConnectionPointType.Texture, createStrongRef(logoTexture)); 16 | const blackAndWhite = new BlackAndWhiteBlock(smartFilter, "blackAndWhite"); 17 | const pixelate = new PixelateBlock(smartFilter, "pixelate"); 18 | const pixelateIntensity = new InputBlock(smartFilter, "intensity", ConnectionPointType.Float, 0.4); 19 | const premultiplyAlpha = new PremultiplyAlphaBlock(smartFilter, "premultiplyAlpha"); 20 | 21 | logoInput.output.connectTo(blackAndWhite.input); 22 | blackAndWhite.output.connectTo(pixelate.input); 23 | pixelateIntensity.output.connectTo(pixelate.intensity); 24 | pixelate.output.connectTo(premultiplyAlpha.input); 25 | premultiplyAlpha.output.connectTo(smartFilter.output); 26 | 27 | return smartFilter; 28 | } 29 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/smartFilters/hardCoded/simplePhotoEdit.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import { ConnectionPointType, InputBlock, SmartFilter, createImageTexture } from "@babylonjs/smart-filters"; 3 | import { ExposureBlock, ContrastBlock, DesaturateBlock } from "@babylonjs/smart-filters-blocks"; 4 | import { HardCodedSmartFilterNames } from "./hardCodedSmartFilterNames"; 5 | 6 | export function createSimplePhotoEditSmartFilter(engine: ThinEngine): SmartFilter { 7 | const smartFilter = new SmartFilter(HardCodedSmartFilterNames.simplePhotoEdit); 8 | 9 | const photoTexture = createImageTexture(engine, "/assets/kittens.jpg"); 10 | 11 | const exposureBlock = new ExposureBlock(smartFilter, "exposure"); 12 | const contrastBlock = new ContrastBlock(smartFilter, "contrast"); 13 | const desaturateBlock = new DesaturateBlock(smartFilter, "desaturate"); 14 | 15 | const photoInput = new InputBlock(smartFilter, "photo", ConnectionPointType.Texture, photoTexture); 16 | const exposureDisabled = new InputBlock(smartFilter, "exposureDisabled", ConnectionPointType.Boolean, false); 17 | const contrastDisabled = new InputBlock(smartFilter, "contrastDisabled", ConnectionPointType.Boolean, false); 18 | const desaturateDisabled = new InputBlock(smartFilter, "desaturateDisabled", ConnectionPointType.Boolean, false); 19 | 20 | const exposureAmount = new InputBlock(smartFilter, "exposureAmount", ConnectionPointType.Float, 1.04); 21 | const contrastIntensity = new InputBlock(smartFilter, "contrastIntensity", ConnectionPointType.Float, 0.54); 22 | const desaturateIntensity = new InputBlock(smartFilter, "desaturateIntensity", ConnectionPointType.Float, 1.09); 23 | 24 | photoInput.output.connectTo(exposureBlock.input); 25 | 26 | exposureDisabled.output.connectTo(exposureBlock.disabled); 27 | exposureAmount.output.connectTo(exposureBlock.amount); 28 | exposureBlock.output.connectTo(contrastBlock.input); 29 | 30 | contrastDisabled.output.connectTo(contrastBlock.disabled); 31 | contrastIntensity.output.connectTo(contrastBlock.intensity); 32 | contrastBlock.output.connectTo(desaturateBlock.input); 33 | 34 | desaturateDisabled.output.connectTo(desaturateBlock.disabled); 35 | desaturateIntensity.output.connectTo(desaturateBlock.intensity); 36 | desaturateBlock.output.connectTo(smartFilter.output); 37 | 38 | return smartFilter; 39 | } 40 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/smartFilters/hardCoded/simpleWebcam.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionPointType, InputBlock, SmartFilter } from "@babylonjs/smart-filters"; 2 | import { HardCodedSmartFilterNames } from "./hardCodedSmartFilterNames"; 3 | import { WebCamInputBlock } from "@babylonjs/smart-filters-editor-control"; 4 | import { BlackAndWhiteBlock, PixelateBlock } from "@babylonjs/smart-filters-blocks"; 5 | 6 | export function createSimpleWebcamSmartFilter(): SmartFilter { 7 | const smartFilter = new SmartFilter(HardCodedSmartFilterNames.simpleWebcam); 8 | const webcamInput = new WebCamInputBlock(smartFilter); 9 | const blackAndWhite = new BlackAndWhiteBlock(smartFilter, "blackAndWhite"); 10 | const pixelate = new PixelateBlock(smartFilter, "pixelate"); 11 | const pixelateIntensity = new InputBlock(smartFilter, "intensity", ConnectionPointType.Float, 0.4); 12 | 13 | webcamInput.output.connectTo(blackAndWhite.input); 14 | blackAndWhite.output.connectTo(pixelate.input); 15 | pixelateIntensity.output.connectTo(pixelate.intensity); 16 | pixelate.output.connectTo(smartFilter.output); 17 | 18 | return smartFilter; 19 | } 20 | -------------------------------------------------------------------------------- /packages/demo/src/configuration/texturePresets.ts: -------------------------------------------------------------------------------- 1 | import type { TexturePreset } from "@babylonjs/smart-filters-editor-control"; 2 | 3 | /** 4 | * Texture presets are used to provide a list of assets that can be used 5 | * easily in the editor. 6 | * 7 | * You can either a base64 encoded image or a URL to an image. 8 | * 9 | * For a URL to an image, you can add the assets to packages/demo/www/assets then add them to this list. 10 | * 11 | */ 12 | export const texturePresets: TexturePreset[] = [ 13 | { 14 | name: "Babylon.js Logo", 15 | url: "/assets/logo.png", 16 | }, 17 | { 18 | name: "Kittens", 19 | url: "/assets/kittens.jpg", 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /packages/demo/src/helpers/createThinEngine.ts: -------------------------------------------------------------------------------- 1 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | 3 | export function createThinEngine(canvas: HTMLCanvasElement) { 4 | const antialias = false; 5 | return new ThinEngine( 6 | canvas, 7 | antialias, 8 | { 9 | stencil: false, 10 | depth: false, 11 | antialias, 12 | audioEngine: false, 13 | // Important to allow skip frame and tiled optimizations 14 | preserveDrawingBuffer: false, 15 | // Useful during debug to simulate WebGL1 devices (Safari) 16 | // disableWebGL2Support: true, 17 | }, 18 | false 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/demo/src/helpers/launchEditor.ts: -------------------------------------------------------------------------------- 1 | import { logCommands, type SmartFilterDeserializer, type SmartFilter } from "@babylonjs/smart-filters"; 2 | import { 3 | editorBlockRegistrations, 4 | getBlockEditorRegistration, 5 | SmartFilterEditorControl, 6 | } from "@babylonjs/smart-filters-editor-control"; 7 | import { texturePresets } from "../configuration/texturePresets"; 8 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 9 | import type { SmartFilterRenderer } from "../smartFilterRenderer"; 10 | import { builtInBlockRegistrations } from "@babylonjs/smart-filters-blocks"; 11 | 12 | /** 13 | * Launches the editor - in a separate file so it can be dynamically imported 14 | * @param currentSmartFilter - The smart filter to edit 15 | * @param engine - The engine to use 16 | * @param renderer - The renderer to use 17 | */ 18 | export function launchEditor( 19 | currentSmartFilter: SmartFilter, 20 | engine: ThinEngine, 21 | renderer: SmartFilterRenderer, 22 | errorHandler: (message: string) => void, 23 | closeError: () => void, 24 | smartFilterDeserializer: SmartFilterDeserializer 25 | ) { 26 | if (!currentSmartFilter) { 27 | return; 28 | } 29 | 30 | // Set up block registration 31 | const allBlockRegistrations = [...editorBlockRegistrations, ...builtInBlockRegistrations]; 32 | const blockRegistration = getBlockEditorRegistration(smartFilterDeserializer, allBlockRegistrations, false); 33 | 34 | // Function to rebuild the runtime 35 | function rebuildRuntime() { 36 | renderer 37 | .rebuildRuntime() 38 | .then(closeError) 39 | .catch((err: unknown) => { 40 | errorHandler(`Could not start rendering\n${err}`); 41 | }); 42 | } 43 | 44 | // Display the editor 45 | SmartFilterEditorControl.Show({ 46 | engine, 47 | blockEditorRegistration: blockRegistration, 48 | filter: currentSmartFilter, 49 | rebuildRuntime, 50 | reloadAssets: () => { 51 | renderer.reloadAssets().catch((err: unknown) => { 52 | errorHandler(`Could not reload assets:\n${err}`); 53 | }); 54 | }, 55 | texturePresets, 56 | beforeRenderObservable: renderer.beforeRenderObservable, 57 | }); 58 | 59 | if (renderer.runtime) { 60 | // Display debug info in the console 61 | logCommands(renderer.runtime.commandBuffer); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/demo/src/textureRenderHelper.ts: -------------------------------------------------------------------------------- 1 | import { ThinRenderTargetTexture } from "@babylonjs/core/Materials/Textures/thinRenderTargetTexture.js"; 2 | import { ConnectionPointType, InputBlock, RenderTargetGenerator, SmartFilter } from "@babylonjs/smart-filters"; 3 | import type { SmartFilterRenderer } from "./smartFilterRenderer"; 4 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 5 | 6 | /** 7 | * Helper class which makes it easy to render a texture to a Canvas, using a trivial Smart Filter graph. 8 | * This is used to test having Smart Filters render to a texture instead of a Canvas. The texture 9 | * another Smart Filter renders to can be rendered to the Canvas for display using this helper. 10 | */ 11 | export class TextureRenderHelper { 12 | private _started = false; 13 | private readonly _smartFilter: SmartFilter; 14 | private readonly _renderer: SmartFilterRenderer; 15 | private readonly _engine: ThinEngine; 16 | 17 | /** 18 | * The texture to be drawn to the Canvas. This can be used as the target output texture of 19 | * another Smart Filter graph to test the output of that graph. 20 | */ 21 | public readonly renderTargetTexture: ThinRenderTargetTexture; 22 | 23 | public constructor(engine: ThinEngine, renderer: SmartFilterRenderer) { 24 | // Create target texture 25 | // We are only rendering full screen post process without depth or stencil information 26 | const setup = { 27 | generateDepthBuffer: false, 28 | generateStencilBuffer: false, 29 | generateMipMaps: false, 30 | samplingMode: 2, // Babylon Constants.TEXTURE_LINEAR_LINEAR, 31 | }; 32 | const width = engine.getRenderWidth(true); 33 | const height = engine.getRenderHeight(true); 34 | this.renderTargetTexture = new ThinRenderTargetTexture(engine, { width, height }, setup); 35 | // Babylon Constants.TEXTURE_CLAMP_ADDRESSMODE; NPOT Friendly 36 | this.renderTargetTexture.wrapU = 0; 37 | this.renderTargetTexture.wrapV = 0; 38 | 39 | // Create Smart Filter to render the texture to the canvas 40 | this._smartFilter = new SmartFilter("TextureRenderHelper"); 41 | const inputBlock = new InputBlock( 42 | this._smartFilter, 43 | "inputTexture", 44 | ConnectionPointType.Texture, 45 | this.renderTargetTexture 46 | ); 47 | inputBlock.output.connectTo(this._smartFilter.output); 48 | 49 | // Save params for later 50 | this._engine = engine; 51 | this._renderer = renderer; 52 | } 53 | 54 | public async startAsync(): Promise { 55 | if (this._started) { 56 | return; 57 | } 58 | this._started = true; 59 | 60 | const rtg = new RenderTargetGenerator(false); 61 | const runtime = await this._smartFilter.createRuntimeAsync(this._engine, rtg); 62 | 63 | this._renderer.afterRenderObservable.add(() => { 64 | runtime.render(); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "../", 6 | "outDir": "./.temp", 7 | "composite": true, 8 | "resolveJsonModule": true 9 | }, 10 | 11 | "include": [ 12 | "./src/**/*.ts", 13 | "../core/**/*.ts", 14 | "../core/**/*.tsx", 15 | "../editor/**/*.ts", 16 | "../editor/**/*.tsx", 17 | "../blocks/**/*.ts", 18 | "./src/configuration/smartFilters/serialized/*.json" 19 | ], 20 | "exclude": ["**/node_modules", "**/.temp", "../browserExtension/**"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/demo/www/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/demo/www/assets/favicon.ico -------------------------------------------------------------------------------- /packages/demo/www/assets/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/demo/www/assets/frame.png -------------------------------------------------------------------------------- /packages/demo/www/assets/kittens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/demo/www/assets/kittens.jpg -------------------------------------------------------------------------------- /packages/demo/www/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/demo/www/assets/logo.png -------------------------------------------------------------------------------- /packages/demo/www/assets/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/demo/www/assets/title.png -------------------------------------------------------------------------------- /packages/editor/build/copyAssets.js: -------------------------------------------------------------------------------- 1 | import copy from "recursive-copy"; 2 | 3 | var options = { 4 | overwrite: true, 5 | // expand: true, 6 | // dot: true, 7 | // junk: true, 8 | // filter: [ 9 | // '**/*', 10 | // '!.htpasswd' 11 | // ], 12 | // rename: function(filePath) { 13 | // return filePath + '.orig'; 14 | // }, 15 | // transform: function(src, dest, stats) { 16 | // if (path.extname(src) !== '.txt') { return null; } 17 | // return through(function(chunk, enc, done) { 18 | // var output = chunk.toString().toUpperCase(); 19 | // done(null, output); 20 | // }); 21 | // } 22 | }; 23 | 24 | copy('./src/assets', './dist/assets', options) -------------------------------------------------------------------------------- /packages/editor/build/preparePublish.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import * as fs from "fs"; 3 | 4 | const corePackageText = fs.readFileSync("../core/package.json"); 5 | const corePackageJSON = JSON.parse(corePackageText.toString()); 6 | 7 | const editorPackageText = fs.readFileSync("package.json"); 8 | const editorPackageJSON = JSON.parse(editorPackageText.toString()); 9 | 10 | console.log("Setting editor package version to match core package version:", corePackageJSON.version); 11 | editorPackageJSON.version = corePackageJSON.version; 12 | 13 | console.log("Adding dependency on core package to editor package"); 14 | if (!editorPackageJSON.dependencies) { 15 | editorPackageJSON.dependencies = {}; 16 | } 17 | editorPackageJSON.dependencies["@babylonjs/smart-filters"] = corePackageJSON.version; 18 | 19 | console.log("Saving changes to editor package.json"); 20 | fs.writeFileSync("package.json", JSON.stringify(editorPackageJSON, null, 4)); 21 | -------------------------------------------------------------------------------- /packages/editor/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/smart-filters-editor-control", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "The editor control used in Smart Filters applications.", 6 | "keywords": [ 7 | "video", 8 | "composition", 9 | "3D", 10 | "2D", 11 | "javascript", 12 | "html5", 13 | "webgl", 14 | "webgl2", 15 | "webgpu", 16 | "babylon" 17 | ], 18 | "license": "MIT", 19 | "readme": "README.md", 20 | "main": "dist/index", 21 | "module": "dist/index", 22 | "esnext": "dist/index", 23 | "types": "dist/index", 24 | "type": "module", 25 | "sideEffects": true, 26 | "files": [ 27 | "dist", 28 | "src", 29 | "license.md", 30 | "readme.md" 31 | ], 32 | "scripts": { 33 | "clean": "rimraf dist && rimraf tsconfig.build.tsbuildinfo", 34 | "assets": "node build/copyAssets.js", 35 | "build": "npm run assets && npm run build:editorControl", 36 | "build:editorControl": "tsc -p ./tsconfig.build.json", 37 | "test": "echo \"Error: no test specified\" && exit 1", 38 | "preparePublish": "node build/preparePublish.js" 39 | }, 40 | "devDependencies": { 41 | "recursive-copy": "^2.0.13" 42 | }, 43 | "peerDependencies": { 44 | "@babylonjs/core": "^7.47.3 || ^8.0.1", 45 | "@babylonjs/shared-ui-components": "^7.47.3 || ^8.0.1", 46 | "@fortawesome/fontawesome-svg-core": "^6.1.0", 47 | "@fortawesome/free-solid-svg-icons": "^6.1.0", 48 | "@fortawesome/react-fontawesome": "^0.1.18", 49 | "react": "^17.0.2", 50 | "react-dom": "^17.0.2" 51 | } 52 | } -------------------------------------------------------------------------------- /packages/editor/readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | ## Smart Filters Editor Control 4 | 5 | The package contains the visual editor control used in the demo app and the Smart Filters Editor application. 6 | 7 | See the full documentation at [doc.babylonjs.com](https://doc.babylonjs.com/features/featuresDeepDive/smartFilters/) 8 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_fluent_add_circle_24_regular 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_fluent_copy_24_regular 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ic_fluent_delete_24_regular 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/downArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/editor/src/assets/imgs/logo.png -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/popOut.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/square.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/editor/src/assets/imgs/triangle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/editor/src/assets/styles/components/log.scss: -------------------------------------------------------------------------------- 1 | #sfe-log-console { 2 | background: #050505; 3 | height: 120px; 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 10px; 7 | width: 100%; 8 | overflow: hidden; 9 | overflow-y: auto; 10 | grid-row: 2; 11 | grid-column: 3; 12 | 13 | .log { 14 | color: white; 15 | font-size: 14px; 16 | font-family: "Courier New", Courier, monospace; 17 | } 18 | 19 | .error { 20 | color: red; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/editor/src/assets/styles/graphSystem/blockNodeData.module.scss: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none !important; 3 | } 4 | -------------------------------------------------------------------------------- /packages/editor/src/assets/styles/graphSystem/display/common.module.scss: -------------------------------------------------------------------------------- 1 | .texture-block { 2 | grid-row: 2; 3 | height: 140px; 4 | width: 140px; 5 | overflow: hidden; 6 | border-bottom-left-radius: 7px; 7 | border: black 4px solid; 8 | border-left: 0px; 9 | border-bottom: 0px; 10 | 11 | img { 12 | width: 100%; 13 | height: 100%; 14 | pointer-events: none; 15 | 16 | &.empty { 17 | display: none; 18 | } 19 | } 20 | } 21 | 22 | .empty { 23 | display: none; 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/src/assets/styles/graphSystem/display/inputDisplayManager.module.scss: -------------------------------------------------------------------------------- 1 | .input-block { 2 | grid-row: 2; 3 | min-height: 34px; 4 | text-align: center; 5 | font-size: 18px; 6 | font-weight: bold; 7 | margin: 0 10px 5px; 8 | display: grid; 9 | align-content: center; 10 | 11 | &.small-font { 12 | font-size: 17px; 13 | } 14 | } 15 | 16 | .input-block img { 17 | height: 100%; 18 | width: 100%; 19 | } 20 | 21 | .constant { 22 | border-color: #464348; 23 | background: #464348; 24 | } 25 | 26 | .inspector { 27 | border-color: #66491b; 28 | background: #66491b; 29 | } 30 | -------------------------------------------------------------------------------- /packages/editor/src/assets/styles/graphSystem/display/outputDisplayManager.module.scss: -------------------------------------------------------------------------------- 1 | .output-block { 2 | min-height: 0px; 3 | height: 5px; 4 | } 5 | -------------------------------------------------------------------------------- /packages/editor/src/blockTools.ts: -------------------------------------------------------------------------------- 1 | import type { SmartFilter, AnyInputBlock } from "@babylonjs/smart-filters"; 2 | import { ConnectionPointType } from "@babylonjs/smart-filters"; 3 | 4 | export class BlockTools { 5 | public static GetColorFromConnectionNodeType(type: ConnectionPointType) { 6 | let color = "#880000"; 7 | switch (type) { 8 | case ConnectionPointType.Boolean: 9 | color = "#51b0e5"; 10 | break; 11 | case ConnectionPointType.Float: 12 | color = "#cb9e27"; 13 | break; 14 | case ConnectionPointType.Color3: 15 | color = "#b786cb"; 16 | break; 17 | case ConnectionPointType.Color4: 18 | color = "#be5126"; 19 | break; 20 | case ConnectionPointType.Texture: 21 | color = "#f28e0a"; 22 | break; 23 | } 24 | 25 | return color; 26 | } 27 | 28 | public static GetConnectionNodeTypeFromString(blockType: string) { 29 | switch (blockType) { 30 | case "Float": 31 | return ConnectionPointType.Float; 32 | case "Texture": 33 | return ConnectionPointType.Texture; 34 | case "Color3": 35 | return ConnectionPointType.Color3; 36 | case "Color4": 37 | return ConnectionPointType.Color4; 38 | case "Vector2": 39 | return ConnectionPointType.Vector2; 40 | case "WebCam": 41 | return ConnectionPointType.Texture; 42 | case "Boolean": 43 | return ConnectionPointType.Boolean; 44 | } 45 | 46 | // TODO AutoDetect... 47 | return ConnectionPointType.Float; 48 | } 49 | 50 | public static GetStringFromConnectionNodeType(type: ConnectionPointType) { 51 | switch (type) { 52 | case ConnectionPointType.Float: 53 | return "Float"; 54 | case ConnectionPointType.Color3: 55 | return "Color3"; 56 | case ConnectionPointType.Color4: 57 | return "Color4"; 58 | case ConnectionPointType.Texture: 59 | return "Texture"; 60 | case ConnectionPointType.Vector2: 61 | return "Vector2"; 62 | case ConnectionPointType.Boolean: 63 | return "Boolean"; 64 | } 65 | 66 | return ""; 67 | } 68 | 69 | /** 70 | * Gets the list of all input blocks attached to the Smart Filter. 71 | * @returns The list of input blocks 72 | */ 73 | public static GetInputBlocks(smartFilter: SmartFilter): AnyInputBlock[] { 74 | const blocks: AnyInputBlock[] = []; 75 | for (const block of smartFilter.attachedBlocks) { 76 | if (block.isInput) { 77 | blocks.push(block as AnyInputBlock); 78 | } 79 | } 80 | 81 | return blocks; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/editor/src/components/log/logComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as react from "react"; 2 | import type { GlobalState } from "../../globalState"; 3 | 4 | import "../../assets/styles/components/log.scss"; 5 | 6 | interface ILogComponentProps { 7 | globalState: GlobalState; 8 | } 9 | 10 | export class LogEntry { 11 | public time = new Date(); 12 | 13 | constructor( 14 | public message: string, 15 | public isError: boolean 16 | ) {} 17 | } 18 | 19 | export class LogComponent extends react.Component { 20 | private _logConsoleRef: React.RefObject; 21 | 22 | constructor(props: ILogComponentProps) { 23 | super(props); 24 | 25 | this.state = { logs: [] }; 26 | this._logConsoleRef = react.createRef(); 27 | } 28 | 29 | override componentDidMount() { 30 | this.props.globalState.onLogRequiredObservable.add((log) => { 31 | const currentLogs = this.state.logs; 32 | currentLogs.push(log); 33 | 34 | this.setState({ logs: currentLogs }); 35 | }); 36 | } 37 | 38 | override componentDidUpdate() { 39 | if (!this._logConsoleRef.current) { 40 | return; 41 | } 42 | 43 | this._logConsoleRef.current.scrollTop = this._logConsoleRef.current.scrollHeight; 44 | } 45 | 46 | override render() { 47 | return ( 48 |
49 | {this.state.logs.map((l, i) => { 50 | return ( 51 |
52 | {l.time.getHours() + 53 | ":" + 54 | l.time.getMinutes().toLocaleString(undefined, { minimumIntegerDigits: 2 }) + 55 | ":" + 56 | l.time.getSeconds().toLocaleString(undefined, { minimumIntegerDigits: 2 }) + 57 | ": " + 58 | l.message} 59 |
60 | ); 61 | })} 62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/editor/src/components/propertyTab/properties/color3PropertyTabComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as react from "react"; 2 | import { Color3LineComponent } from "@babylonjs/shared-ui-components/lines/color3LineComponent.js"; 3 | import type { ConnectionPointType, InputBlock } from "@babylonjs/smart-filters"; 4 | import { Color3 } from "@babylonjs/core/Maths/math.color.js"; 5 | import type { StateManager } from "@babylonjs/shared-ui-components/nodeGraphSystem/stateManager"; 6 | 7 | interface IColor3PropertyTabComponentProps { 8 | stateManager: StateManager; 9 | inputBlock: InputBlock; 10 | } 11 | 12 | export class Color3PropertyTabComponent extends react.Component { 13 | override render() { 14 | const target = this.props.inputBlock.runtimeValue.value; 15 | const foo = { 16 | color: new Color3(target.r, target.g, target.b), 17 | }; 18 | return ( 19 | { 25 | target.r = foo.color.r; 26 | target.g = foo.color.g; 27 | target.b = foo.color.b; 28 | this.props.stateManager.onUpdateRequiredObservable.notifyObservers(this.props.inputBlock); 29 | }} 30 | > 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/editor/src/components/propertyTab/properties/color4PropertyTabComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as react from "react"; 2 | import { Color4LineComponent } from "@babylonjs/shared-ui-components/lines/color4LineComponent.js"; 3 | import type { ConnectionPointType, InputBlock } from "@babylonjs/smart-filters"; 4 | import { Color4 } from "@babylonjs/core/Maths/math.color.js"; 5 | import type { StateManager } from "@babylonjs/shared-ui-components/nodeGraphSystem/stateManager"; 6 | 7 | interface IColor4PropertyTabComponentProps { 8 | stateManager: StateManager; 9 | inputBlock: InputBlock; 10 | } 11 | 12 | export class Color4PropertyTabComponent extends react.Component { 13 | override render() { 14 | const target = this.props.inputBlock.runtimeValue.value; 15 | const foo = { 16 | color: new Color4(target.r, target.g, target.b, target.a), 17 | }; 18 | return ( 19 | { 25 | target.r = foo.color.r; 26 | target.g = foo.color.g; 27 | target.b = foo.color.b; 28 | target.a = foo.color.a; 29 | this.props.stateManager.onUpdateRequiredObservable.notifyObservers(this.props.inputBlock); 30 | }} 31 | > 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/editor/src/components/propertyTab/properties/vector2PropertyTabComponent.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import type { ConnectionPointType, InputBlock } from "@babylonjs/smart-filters"; 3 | import type { StateManager } from "@babylonjs/shared-ui-components/nodeGraphSystem/stateManager.js"; 4 | import { Vector2LineComponent } from "@babylonjs/shared-ui-components/lines/vector2LineComponent.js"; 5 | import type { LockObject } from "@babylonjs/shared-ui-components/tabs/propertyGrids/lockObject.js"; 6 | import { Vector2 } from "@babylonjs/core/Maths/math.vector.js"; 7 | 8 | export interface Vector2PropertyTabComponentProps { 9 | stateManager: StateManager; 10 | inputBlock: InputBlock; 11 | lockObject: LockObject; 12 | } 13 | 14 | /** 15 | * The property tab component for InputBlock of type ConnectionPointType.Vector2. 16 | */ 17 | export class Vector2PropertyTabComponent extends Component { 18 | override render() { 19 | const value = this.props.inputBlock.runtimeValue.value; 20 | const target = { 21 | value: new Vector2(value.x, value.y), 22 | }; 23 | return ( 24 | { 31 | value.x = target.value.x; 32 | value.y = target.value.y; 33 | this.props.stateManager.onUpdateRequiredObservable.notifyObservers(this.props.inputBlock); 34 | }} 35 | > 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/blockEditorRegistration.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import type { Nullable } from "@babylonjs/core/types"; 3 | import type { BaseBlock, SmartFilter } from "@babylonjs/smart-filters"; 4 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 5 | 6 | /** 7 | * All of the configuration needed for the editor to work with a set of blocks. 8 | */ 9 | export type BlockEditorRegistration = { 10 | /** 11 | * Some blocks must appear only once in the graph (e.g. OutputBlock) - this function returns true if the block 12 | * should be unique in the graph. 13 | * @param block - The block to check 14 | * @returns true if the block should be unique in the graph 15 | */ 16 | getIsUniqueBlock: (block: BaseBlock) => boolean; 17 | 18 | /** 19 | * Given a block's type and namespace, this function should return a new instance of that block with default values, 20 | * or null if the block name is not recognized. 21 | * @param blockType - The type of the block to create 22 | * @param namespace - The namespace of the block to create 23 | * @param smartFilter - The Smart Filter to create the block for 24 | * @param engine - The engine to use for creating blocks 25 | * @returns A new instance of the block, or null if the block name is not recognized 26 | */ 27 | getBlock( 28 | blockType: string, 29 | namespace: Nullable, 30 | smartFilter: SmartFilter, 31 | engine: ThinEngine 32 | ): Promise>; 33 | 34 | /** 35 | * An object that contains all of the blocks to display, organized by category. 36 | */ 37 | allBlocks: { [key: string]: IBlockRegistration[] }; 38 | 39 | /** 40 | * Optional override of the InputDisplayManager to provide custom display for particular blocks if desired. 41 | */ 42 | inputDisplayManager?: any; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/constants.ts: -------------------------------------------------------------------------------- 1 | export const CustomBlocksNamespace = "Custom_Blocks"; 2 | export const OutputBlockName = "OutputBlock"; 3 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/customInputDisplayManager.ts: -------------------------------------------------------------------------------- 1 | import type { INodeData } from "@babylonjs/shared-ui-components/nodeGraphSystem/interfaces/nodeData"; 2 | import { ConnectionPointType, type AnyInputBlock } from "@babylonjs/smart-filters"; 3 | import { InputDisplayManager } from "../graphSystem/display/inputDisplayManager.js"; 4 | import { WebCamInputBlockName } from "./editorBlocks/blockNames.js"; 5 | import type { WebCamInputBlock } from "./editorBlocks/webCamInputBlock/webCamInputBlock.js"; 6 | 7 | /** 8 | * Optional override of the InputDisplayManager to provide custom display for particular blocks if desired. 9 | */ 10 | export class CustomInputDisplayManager extends InputDisplayManager { 11 | /** 12 | * Returns preview content for custom input blocks, or null if no custom content is needed. 13 | * @param nodeData - The node data to display 14 | * @param contentArea - The content area to update 15 | * @returns 16 | */ 17 | public override updatePreviewContent(nodeData: INodeData, contentArea: HTMLDivElement): void { 18 | super.updatePreviewContent(nodeData, contentArea); 19 | 20 | let value = ""; 21 | const inputBlock = nodeData.data as AnyInputBlock; 22 | 23 | if (inputBlock.type === ConnectionPointType.Texture && inputBlock.name === WebCamInputBlockName) { 24 | const webCamInputBlock = inputBlock as WebCamInputBlock; 25 | value = webCamInputBlock.webcamSource?.label ?? "Default"; 26 | contentArea.innerHTML = value; 27 | } else { 28 | return super.updatePreviewContent(nodeData, contentArea); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/editorBlocks/blockNames.ts: -------------------------------------------------------------------------------- 1 | export const WebCamInputBlockName = "WebCam"; 2 | export const TimeInputBlockName = "Time"; 3 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/editorBlocks/editorBlockRegistrations.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionPointType, InputBlock, type SmartFilter } from "@babylonjs/smart-filters"; 2 | import { TimeInputBlockName, WebCamInputBlockName } from "./blockNames.js"; 3 | import { type IBlockRegistration, inputsNamespace } from "@babylonjs/smart-filters-blocks"; 4 | 5 | /** 6 | * The block registrations for special blocks for ease of use in the editor. 7 | */ 8 | export const editorBlockRegistrations: IBlockRegistration[] = [ 9 | { 10 | blockType: WebCamInputBlockName, 11 | namespace: inputsNamespace, 12 | isInput: true, 13 | tooltip: "Supplies a texture from a webcam", 14 | factory: async (smartFilter: SmartFilter) => { 15 | const module = await import(/* webpackChunkName: "webCamBlock" */ "./webCamInputBlock/webCamInputBlock.js"); 16 | return new module.WebCamInputBlock(smartFilter); 17 | }, 18 | }, 19 | { 20 | blockType: TimeInputBlockName, 21 | namespace: inputsNamespace, 22 | isInput: true, 23 | tooltip: "Supplies a float value representing the current time", 24 | factory: (smartFilter: SmartFilter) => { 25 | const inputBlock = new InputBlock(smartFilter, "Time", ConnectionPointType.Float, 0.0); 26 | inputBlock.editorData = { 27 | animationType: "time", 28 | valueDeltaPerMs: 0.001, 29 | min: null, 30 | max: null, 31 | }; 32 | return Promise.resolve(inputBlock); 33 | }, 34 | }, 35 | ]; 36 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/editorBlocks/inputBlockDeserializer.ts: -------------------------------------------------------------------------------- 1 | import type { SmartFilter, ISerializedBlockV1, BaseBlock } from "@babylonjs/smart-filters"; 2 | import type { Nullable } from "@babylonjs/core/types"; 3 | import { WebCamInputBlockName } from "./blockNames.js"; 4 | 5 | /** 6 | * Custom input block deserializer to provide special behavior for input blocks in this library. 7 | * 8 | * @param smartFilter - The smart filter to create the block for 9 | * @param serializedBlock - The serialized block to create 10 | * @returns - The instantiated block, or null if the block type is not registered 11 | */ 12 | export async function inputBlockDeserializer( 13 | smartFilter: SmartFilter, 14 | serializedBlock: ISerializedBlockV1 15 | ): Promise> { 16 | if (serializedBlock.name === WebCamInputBlockName) { 17 | const module = await import(/* webpackChunkName: "webCamBlock" */ "./webCamInputBlock/webCamInputBlock.js"); 18 | return new module.WebCamInputBlock(smartFilter); 19 | } 20 | return null; 21 | } 22 | -------------------------------------------------------------------------------- /packages/editor/src/configuration/editorBlocks/webCamInputBlock/webCamRuntime.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine.js"; 2 | import { type ConnectionPointType, type IDisposable, type RuntimeData, Logger } from "@babylonjs/smart-filters"; 3 | import type { Nullable } from "@babylonjs/core/types"; 4 | import { WebCamSession } from "./webCamSession.js"; 5 | 6 | /** 7 | * Manages the runtime of WebCam input block, hooking it up to a texture, responding to changes in source, 8 | * and disposing of it when done. 9 | */ 10 | export class WebCamRuntime implements IDisposable { 11 | private readonly _engine: ThinEngine; 12 | private readonly _textureOutput: RuntimeData; 13 | private _currentSession: WebCamSession | undefined; 14 | 15 | /** 16 | * The device ID of the webcam to use. An empty string means default webcam. 17 | * Null means it hasn't been set yet. 18 | */ 19 | private _deviceId: Nullable = null; 20 | 21 | /** 22 | * The device ID of the webcam to use. An empty string means default webcam. 23 | * Null means it hasn't been set yet. 24 | */ 25 | public get deviceId(): Nullable { 26 | return this._deviceId; 27 | } 28 | 29 | /** 30 | * The device ID of the webcam to use. An empty string means default webcam. 31 | */ 32 | public set deviceId(value: string) { 33 | if (this._deviceId !== value) { 34 | this._deviceId = value; 35 | this._refresh(); 36 | } 37 | } 38 | 39 | /** 40 | * Creates a new WebCamRuntime instance. 41 | * @param engine - The engine to use 42 | * @param textureOutput - The output texture 43 | */ 44 | constructor(engine: ThinEngine, textureOutput: RuntimeData) { 45 | this._engine = engine; 46 | this._textureOutput = textureOutput; 47 | } 48 | 49 | private _refresh(): void { 50 | if (this._currentSession) { 51 | this._currentSession.dispose(); 52 | this._currentSession = undefined; 53 | } 54 | 55 | if (this._deviceId !== null) { 56 | this._currentSession = new WebCamSession(this._engine, this._textureOutput, this._deviceId); 57 | this._currentSession.load().catch((e: unknown) => { 58 | Logger.Error(`Failed to load webcam ${e}`); 59 | }); 60 | } 61 | } 62 | 63 | /** 64 | * Disposes the webcam runtime. 65 | */ 66 | public dispose(): void { 67 | if (this._currentSession) { 68 | this._currentSession.dispose(); 69 | this._currentSession = undefined; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/editor/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const OnlyShowCustomBlocksDefaultValue = false; 2 | -------------------------------------------------------------------------------- /packages/editor/src/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: string; 3 | export default content; 4 | } 5 | 6 | declare module "*.module.scss" { 7 | const content: Record; 8 | export = content; 9 | } 10 | -------------------------------------------------------------------------------- /packages/editor/src/graphSystem/display/outputDisplayManager.ts: -------------------------------------------------------------------------------- 1 | import type { IDisplayManager } from "@babylonjs/shared-ui-components/nodeGraphSystem/interfaces/displayManager"; 2 | import type { INodeData } from "@babylonjs/shared-ui-components/nodeGraphSystem/interfaces/nodeData"; 3 | 4 | export class OutputDisplayManager implements IDisplayManager { 5 | public getHeaderClass() { 6 | return ""; 7 | } 8 | 9 | public shouldDisplayPortLabels(): boolean { 10 | return true; 11 | } 12 | 13 | public getHeaderText(nodeData: INodeData): string { 14 | return nodeData.data.name; 15 | } 16 | 17 | public getBackgroundColor(): string { 18 | return "rgb(106, 44, 131)"; 19 | } 20 | 21 | public updatePreviewContent(_nodeData: INodeData, contentArea: HTMLDivElement): void { 22 | contentArea.classList.add("output-block"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/editor/src/graphSystem/getEditorData.ts: -------------------------------------------------------------------------------- 1 | import type { ConnectionPointType, InputBlock, InputBlockEditorData } from "@babylonjs/smart-filters"; 2 | 3 | /** 4 | * Gets the InputBlockEditorData for a Texture InputBlock, and if it's missing, 5 | * reads the values from the actual texture, and falls back to defaults if necessary. 6 | * It sets the editor data on the input block for future use. 7 | * @param inputBlock - The input block to get the editor data for 8 | * @returns The editor data for the input block 9 | */ 10 | export function getTextureInputBlockEditorData( 11 | inputBlock: InputBlock 12 | ): InputBlockEditorData { 13 | if (inputBlock.editorData === null) { 14 | const internalTexture = inputBlock.runtimeValue.value?.getInternalTexture(); 15 | inputBlock.editorData = { 16 | url: internalTexture?.url ?? null, 17 | urlTypeHint: null, 18 | anisotropicFilteringLevel: internalTexture?.anisotropicFilteringLevel ?? null, 19 | flipY: internalTexture?.invertY ?? true, 20 | forcedExtension: internalTexture?._extension ?? null, 21 | }; 22 | } 23 | 24 | // Apply defaults 25 | inputBlock.editorData.flipY = inputBlock.editorData.flipY ?? true; 26 | 27 | return inputBlock.editorData; 28 | } 29 | 30 | /** 31 | * Gets the InputBlockEditorData for a Float InputBlock, and if it's missing 32 | * anything, falls back to defaults. 33 | * @param inputBlock - The input block to get the editor data for 34 | * @returns The editor data for the input block 35 | */ 36 | export function getFloatInputBlockEditorData( 37 | inputBlock: InputBlock 38 | ): InputBlockEditorData { 39 | if (inputBlock.editorData === null) { 40 | inputBlock.editorData = { 41 | animationType: null, 42 | valueDeltaPerMs: null, 43 | min: null, 44 | max: null, 45 | }; 46 | } 47 | 48 | return inputBlock.editorData; 49 | } 50 | -------------------------------------------------------------------------------- /packages/editor/src/graphSystem/registerElbowSupport.ts: -------------------------------------------------------------------------------- 1 | import type { StateManager } from "@babylonjs/shared-ui-components/nodeGraphSystem/stateManager"; 2 | 3 | export const RegisterElbowSupport = (stateManager: StateManager) => { 4 | stateManager.isElbowConnectionAllowed = () => { 5 | return false; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/editor/src/graphSystem/registerToDisplayLedger.ts: -------------------------------------------------------------------------------- 1 | import { OutputBlockName } from "../configuration/constants.js"; 2 | import type { GlobalState } from "../globalState.js"; 3 | import { InputDisplayManager } from "./display/inputDisplayManager.js"; 4 | import { OutputDisplayManager } from "./display/outputDisplayManager.js"; 5 | import { DisplayLedger } from "@babylonjs/shared-ui-components/nodeGraphSystem/displayLedger.js"; 6 | 7 | export const RegisterToDisplayManagers = (globalState: GlobalState) => { 8 | DisplayLedger.RegisteredControls["InputBlock"] = 9 | globalState.blockEditorRegistration?.inputDisplayManager || InputDisplayManager; 10 | DisplayLedger.RegisteredControls[OutputBlockName] = OutputDisplayManager; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/editor/src/graphSystem/registerToPropertyLedger.ts: -------------------------------------------------------------------------------- 1 | import { PropertyLedger } from "@babylonjs/shared-ui-components/nodeGraphSystem/propertyLedger.js"; 2 | import { GenericPropertyComponent } from "./properties/genericNodePropertyComponent.js"; 3 | import { InputPropertyComponent } from "./properties/inputNodePropertyComponent.js"; 4 | // import { TexturePropertyTabComponent } from "./properties/texturePropertyTabComponent"; 5 | 6 | export const RegisterToPropertyTabManagers = () => { 7 | PropertyLedger.DefaultControl = GenericPropertyComponent; 8 | PropertyLedger.RegisteredControls["InputBlock"] = InputPropertyComponent; 9 | // PropertyLedger.RegisteredControls["TextureBlock"] = TexturePropertyTabComponent; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/editor/src/graphSystem/registerToTypeLedger.ts: -------------------------------------------------------------------------------- 1 | import { TypeLedger } from "@babylonjs/shared-ui-components/nodeGraphSystem/typeLedger.js"; 2 | import { BlockNodeData } from "./blockNodeData.js"; 3 | import { ConnectionPointPortData } from "./connectionPointPortData.js"; 4 | 5 | export const RegisterTypeLedger = () => { 6 | TypeLedger.PortDataBuilder = (data, nodeContainer) => { 7 | return new ConnectionPointPortData(data.portData.data, nodeContainer); 8 | }; 9 | 10 | TypeLedger.NodeDataBuilder = (data, nodeContainer) => { 11 | return new BlockNodeData(data, nodeContainer); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/editor/src/helpers/blockKeyConverters.ts: -------------------------------------------------------------------------------- 1 | import type { Nullable } from "@babylonjs/core/types"; 2 | 3 | /** 4 | * The editor uses a single string to uniquely identify a block type, but Smart Filter blocks use 5 | * a namespace and block type. This function converts a block type and namespace to the string used 6 | * by the editor. 7 | * @param blockType - The block type 8 | * @param namespace - The namespace of the block 9 | * @returns - The block name for the editor 10 | */ 11 | export function getBlockKey(blockType: string, namespace: Nullable) { 12 | if (namespace === null) { 13 | return blockType; 14 | } 15 | return `[${namespace}].[${blockType}]`; 16 | } 17 | 18 | /** 19 | * The editor uses a single string to uniquely identify a block type, but Smart Filter blocks use 20 | * a namespace and block type. This function converts the block key used by the editor to the block 21 | * type and namespace used by Smart Filter blocks. 22 | * @param blockKey - The block key used by the editor 23 | * @returns - The block type and namespace 24 | */ 25 | export function decodeBlockKey(blockKey: string): { 26 | blockType: string; 27 | namespace: Nullable; 28 | } { 29 | if (blockKey.indexOf("].[") === -1) { 30 | return { blockType: blockKey, namespace: null }; 31 | } 32 | 33 | const [namespace, blockType] = blockKey.slice(1, -1).split("].["); 34 | 35 | if (!blockType) { 36 | throw new Error(`Invalid block name: ${blockKey}`); 37 | } 38 | 39 | return { blockType, namespace: namespace || null }; 40 | } 41 | -------------------------------------------------------------------------------- /packages/editor/src/helpers/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This function will debounce calls to functions. 3 | * 4 | * @param callback - callback to call. 5 | * @param time - time time to wait between calls in ms. 6 | * @returns a function that will call the callback after the time has passed. 7 | */ 8 | export function debounce(callback: (...args: any[]) => void, time: number) { 9 | let timerId: any; 10 | return function (...args: any[]) { 11 | clearTimeout(timerId); 12 | timerId = setTimeout(() => callback(...args), time); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /packages/editor/src/helpers/observableProperty.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "@babylonjs/core/Misc/observable.js"; 2 | 3 | /** 4 | * An Observable that doesn't allow you to notify observers. 5 | */ 6 | export type ReadOnlyObservable = Omit, "notifyObserver" | "notifyObservers">; 7 | 8 | /** 9 | * Represents a property that can be observed for changes. The setter of the value property 10 | * will notify observers of the onChangedObservable about the change. 11 | */ 12 | export class ObservableProperty { 13 | private _value: T; 14 | private _onChangedObservable: Observable = new Observable(); 15 | 16 | public get value(): T { 17 | return this._value; 18 | } 19 | 20 | public set value(newValue: T) { 21 | if (this._value !== newValue) { 22 | this._value = newValue; 23 | this._onChangedObservable.notifyObservers(this._value); 24 | } 25 | } 26 | public readonly onChangedObservable: ReadOnlyObservable; 27 | 28 | public constructor(value: T) { 29 | this._value = value; 30 | this.onChangedObservable = this._onChangedObservable; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/editor/src/helpers/registerAnimations.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionPointType, type SmartFilter, type InputBlock } from "@babylonjs/smart-filters"; 2 | import { WebCamInputBlock } from "../configuration/editorBlocks/webCamInputBlock/webCamInputBlock.js"; 3 | import type { Observable } from "@babylonjs/core/Misc/observable.js"; 4 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine.js"; 5 | 6 | /** 7 | * Registers animations for the Smart Filter Editor specific to the editor blocks, such as the time and webcam blocks. 8 | * @param smartFilter - The Smart Filter to register animations for 9 | * @param engine - The engine to use 10 | * @param beforeRenderObservable - The before render observable to register animations to 11 | * @returns A function to unregister the animations 12 | */ 13 | export function registerAnimations( 14 | smartFilter: SmartFilter, 15 | engine: ThinEngine, 16 | beforeRenderObservable: Observable 17 | ): () => void { 18 | const disposeWork: (() => void)[] = []; 19 | 20 | for (const block of smartFilter.attachedBlocks) { 21 | if (block.getClassName() === "InputBlock" && (block as any).type === ConnectionPointType.Float) { 22 | const inputBlock = block as InputBlock; 23 | if (inputBlock.editorData?.animationType === "time") { 24 | let deltaPerMs = 0; 25 | let currentTime = performance.now(); 26 | let lastTime = performance.now(); 27 | 28 | const observer = beforeRenderObservable.add(() => { 29 | currentTime = performance.now(); 30 | deltaPerMs = inputBlock.editorData?.valueDeltaPerMs ?? 0.001; 31 | inputBlock.runtimeValue.value += (currentTime - lastTime) * deltaPerMs; 32 | lastTime = currentTime; 33 | }); 34 | 35 | disposeWork.push(() => { 36 | beforeRenderObservable.remove(observer); 37 | }); 38 | } 39 | } else if (block instanceof WebCamInputBlock) { 40 | const webCamRuntime = block.initializeWebCamRuntime(engine); 41 | disposeWork.push(() => { 42 | webCamRuntime.dispose(); 43 | }); 44 | } 45 | } 46 | 47 | return () => { 48 | for (const disposeWorkToDo of disposeWork) { 49 | disposeWorkToDo(); 50 | } 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /packages/editor/src/helpers/serializationTools.ts: -------------------------------------------------------------------------------- 1 | import type { SmartFilter } from "@babylonjs/smart-filters"; 2 | import type { GlobalState } from "../globalState"; 3 | import type { GraphCanvasComponent } from "@babylonjs/shared-ui-components/nodeGraphSystem/graphCanvas"; 4 | 5 | /** 6 | * Sets the SmartFilter's stored editor data (block locations, canvas position, zoom) using the current graph canvas state. 7 | * @param smartFilter - Target SmartFilter to update 8 | * @param globalState - State of the editor 9 | * @param graphCanvas - Graph canvas to pull data from 10 | */ 11 | export function setEditorData(smartFilter: SmartFilter, globalState: GlobalState, graphCanvas: GraphCanvasComponent) { 12 | smartFilter.editorData = { 13 | locations: [], 14 | x: graphCanvas.x, 15 | y: graphCanvas.y, 16 | zoom: graphCanvas.zoom, 17 | }; 18 | 19 | for (const block of smartFilter.attachedBlocks) { 20 | const node = globalState.onGetNodeFromBlock(block); 21 | if (node) { 22 | smartFilter.editorData.locations.push({ 23 | blockId: block.uniqueId, 24 | x: node.x, 25 | y: node.y, 26 | isCollapsed: node.isCollapsed, 27 | }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/editor/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./smartFilterEditorControl.js"; 2 | export * from "./graphSystem/properties/genericNodePropertyComponent.js"; 3 | export * from "./sharedComponents/lineContainerComponent.js"; 4 | export * from "./globalState.js"; 5 | export * from "./graphSystem/display/inputDisplayManager.js"; 6 | export { createDefaultValue } from "./graphSystem/registerDefaultInput.js"; 7 | 8 | export type { TexturePreset } from "./globalState.js"; 9 | export * from "./helpers/serializationTools.js"; 10 | export * from "./helpers/blockKeyConverters.js"; 11 | export { LogEntry } from "./components/log/logComponent.js"; 12 | export * from "./helpers/textureAssetCache.js"; 13 | export * from "./configuration/getBlockEditorRegistration.js"; 14 | export * from "./configuration/blockEditorRegistration.js"; 15 | export * from "./configuration/editorBlocks/editorBlockRegistrations.js"; 16 | export * from "./configuration/editorBlocks/inputBlockDeserializer.js"; 17 | export * from "./configuration/editorBlocks/webCamInputBlock/webCamInputBlock.js"; 18 | export * from "./helpers/registerAnimations.js"; 19 | export * from "./configuration/constants.js"; 20 | export * from "./helpers/observableProperty.js"; 21 | -------------------------------------------------------------------------------- /packages/editor/src/initializePreview.ts: -------------------------------------------------------------------------------- 1 | import { ThinEngine } from "@babylonjs/core/Engines/thinEngine.js"; 2 | 3 | export function initializePreview(canvas: HTMLCanvasElement, forceWebGL1: boolean): ThinEngine { 4 | const antialias = false; 5 | const engine = new ThinEngine( 6 | canvas, 7 | antialias, 8 | { 9 | stencil: false, 10 | depth: false, 11 | antialias, 12 | audioEngine: false, 13 | // Important to allow skip frame and tiled optimizations 14 | preserveDrawingBuffer: false, 15 | premultipliedAlpha: false, 16 | disableWebGL2Support: forceWebGL1, 17 | }, 18 | false 19 | ); 20 | engine.getCaps().parallelShaderCompile = undefined; 21 | return engine; 22 | } 23 | -------------------------------------------------------------------------------- /packages/editor/src/portal.tsx: -------------------------------------------------------------------------------- 1 | import * as react from "react"; 2 | import type { GlobalState } from "./globalState"; 3 | import * as reactDOM from "react-dom"; 4 | 5 | interface IPortalProps { 6 | globalState: GlobalState; 7 | } 8 | 9 | export class Portal extends react.Component { 10 | override render() { 11 | return reactDOM.createPortal(this.props.children, this.props.globalState.hostElement); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/editor/src/sharedComponents/draggableBlockLineComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as react from "react"; 2 | import { getBlockKey } from "../helpers/blockKeyConverters.js"; 3 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 4 | 5 | export interface IDraggableLineWithButtonComponent { 6 | block: IBlockRegistration; 7 | 8 | iconImage?: any; 9 | 10 | onIconClick?: () => void; 11 | 12 | iconTitle?: string; 13 | } 14 | 15 | export class DraggableBlockLineComponent extends react.Component { 16 | constructor(props: IDraggableLineWithButtonComponent) { 17 | super(props); 18 | } 19 | 20 | override render() { 21 | return ( 22 |
{ 27 | event.dataTransfer.setData( 28 | "babylonjs-smartfilter-node", 29 | getBlockKey(this.props.block.blockType, this.props.block.namespace) 30 | ); 31 | }} 32 | > 33 | {this.props.block.blockType.replace("Block", "")} 34 | {this.props.iconImage && this.props.iconTitle && this.props.onIconClick && ( 35 |
{ 38 | if (this.props.onIconClick) { 39 | this.props.onIconClick(); 40 | } 41 | }} 42 | title={this.props.iconTitle} 43 | > 44 | 45 |
46 | )} 47 |
48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/editor/src/sharedComponents/dynamicOptionsLineComponent.tsx: -------------------------------------------------------------------------------- 1 | import { Component, createRef } from "react"; 2 | import type { Observable, Observer } from "@babylonjs/core/Misc/observable"; 3 | import type { IInspectableOptions } from "@babylonjs/core/Misc/iInspectable"; 4 | import type { Nullable } from "@babylonjs/core/types"; 5 | import { type IOptionsLineProps, OptionsLine } from "@babylonjs/shared-ui-components/lines/optionsLineComponent.js"; 6 | 7 | /** 8 | * Props for normal OptionsLine, with options replaced by optionsObservable 9 | */ 10 | interface IDynamicOptionsLineProps extends Omit { 11 | optionsObservable: Observable; 12 | } 13 | 14 | /** 15 | * State that tracks the final, rendered option list 16 | */ 17 | interface IDynamicOptionsLineState { 18 | options: IInspectableOptions[]; 19 | } 20 | 21 | /** 22 | * A wrapper of OptionsLineComponent that supports options obtained via Observable. 23 | * Using an Observable allows for dynamic updates to the options list. 24 | */ 25 | export class DynamicOptionsLine extends Component { 26 | private _observer: Nullable>; 27 | private _optionsLineRef: React.RefObject; 28 | 29 | constructor(props: IDynamicOptionsLineProps) { 30 | super(props); 31 | this.state = { 32 | options: [], 33 | }; 34 | this._observer = null; 35 | this._optionsLineRef = createRef(); 36 | } 37 | 38 | onOptionsChanged(value: IInspectableOptions[]) { 39 | this.setState({ options: value }); 40 | } 41 | 42 | override componentDidUpdate() { 43 | // OptionsLine component does not update on prop changes, since it uses its 44 | // own shouldUpdateMethod. Hopefully we can change this later. 45 | if (this._optionsLineRef.current) { 46 | this._optionsLineRef.current.forceUpdate(); 47 | } 48 | } 49 | 50 | override componentDidMount(): void { 51 | this._observer = this.props.optionsObservable.add(this.onOptionsChanged.bind(this)); 52 | } 53 | 54 | override componentWillUnmount(): void { 55 | if (this._observer) { 56 | this.props.optionsObservable.remove(this._observer); 57 | this._observer = null; 58 | } 59 | } 60 | 61 | override render() { 62 | // Exclude optionsObservable from the props passed to OptionsLine 63 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 64 | const { optionsObservable, ...rest } = this.props; 65 | return ; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/editor/src/sharedComponents/fileButtonLineComponent.tsx: -------------------------------------------------------------------------------- 1 | import { Component, type RefObject, createRef } from "react"; 2 | 3 | interface IFileButtonLineComponentProps { 4 | label: string; 5 | onClick: (file: File) => void; 6 | accept: string; 7 | uploadName?: string; 8 | } 9 | 10 | export class FileButtonLineComponent extends Component { 11 | private _uploadRef: RefObject; 12 | 13 | constructor(props: IFileButtonLineComponentProps) { 14 | super(props); 15 | 16 | this._uploadRef = createRef(); 17 | } 18 | 19 | onChange(evt: any) { 20 | const files: File[] = evt.target.files; 21 | if (files && files.length && files[0]) { 22 | this.props.onClick(files[0]); 23 | } 24 | 25 | evt.target.value = ""; 26 | } 27 | 28 | override render() { 29 | return ( 30 |
31 | 34 | this.onChange(evt)} 40 | /> 41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/editor/src/sharedComponents/floatSliderComponent.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { FloatLineComponent } from "@babylonjs/shared-ui-components/lines/floatLineComponent.js"; 3 | import { SliderLineComponent } from "@babylonjs/shared-ui-components/lines/sliderLineComponent.js"; 4 | import type { Nullable } from "@babylonjs/core/types.js"; 5 | 6 | export interface FloatSliderComponentProps { 7 | lockObject: any; 8 | label: string; 9 | target: any; 10 | propertyName: string; 11 | min: Nullable; 12 | max: Nullable; 13 | onChange: () => void; 14 | forceSliderOff?: boolean; 15 | } 16 | 17 | /** 18 | * A simple float slider component 19 | */ 20 | export class FloatSliderComponent extends Component { 21 | constructor(props: FloatSliderComponentProps) { 22 | super(props); 23 | this.state = { displaySlider: false }; 24 | } 25 | 26 | /** 27 | * Trigger update when props change 28 | * @param prevProps - The previous props 29 | */ 30 | override componentDidUpdate(prevProps: FloatSliderComponentProps) { 31 | if (prevProps !== this.props) { 32 | this.forceUpdate(); 33 | } 34 | } 35 | 36 | override render() { 37 | const canUseSlider = 38 | !this.props.forceSliderOff && 39 | this.props.min !== null && 40 | this.props.max !== null && 41 | this.props.min < this.props.max; 42 | 43 | // Ensure the value is within the min/max range 44 | if (canUseSlider) { 45 | this.props.target[this.props.propertyName] = Math.max( 46 | this.props.min, 47 | Math.min(this.props.max, this.props.target[this.props.propertyName]) 48 | ); 49 | } 50 | 51 | return ( 52 | <> 53 | {!canUseSlider && ( 54 | { 60 | this.props.onChange(); 61 | }} 62 | > 63 | )} 64 | {canUseSlider && ( 65 | { 74 | this.props.onChange(); 75 | }} 76 | > 77 | )} 78 | 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/editor/src/sharedComponents/lineContainerComponent.tsx: -------------------------------------------------------------------------------- 1 | import { DataStorage } from "@babylonjs/core/Misc/dataStorage.js"; 2 | import * as react from "react"; 3 | import downArrow from "../assets/imgs/downArrow.svg"; 4 | 5 | interface ILineContainerComponentProps { 6 | title: string; 7 | children: any[] | any; 8 | closed?: boolean; 9 | } 10 | 11 | export class LineContainerComponent extends react.Component { 12 | constructor(props: ILineContainerComponentProps) { 13 | super(props); 14 | 15 | const initialState = DataStorage.ReadBoolean(this.props.title, !this.props.closed); 16 | 17 | this.state = { isExpanded: initialState }; 18 | } 19 | 20 | switchExpandedState(): void { 21 | const newState = !this.state.isExpanded; 22 | 23 | DataStorage.WriteBoolean(this.props.title, newState); 24 | 25 | this.setState({ isExpanded: newState }); 26 | } 27 | 28 | renderHeader() { 29 | const className = this.state.isExpanded ? "collapse" : "collapse closed"; 30 | 31 | return ( 32 |
this.switchExpandedState()}> 33 |
{this.props.title}
34 |
35 | 36 |
37 |
38 | ); 39 | } 40 | 41 | override render() { 42 | if (!this.state.isExpanded) { 43 | return ( 44 |
45 |
{this.renderHeader()}
46 |
47 | ); 48 | } 49 | 50 | return ( 51 |
52 |
53 | {this.renderHeader()} 54 |
{this.props.children}
55 |
56 |
57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/editor/src/sharedComponents/lineWithFileButtonComponent.tsx: -------------------------------------------------------------------------------- 1 | import { DataStorage } from "@babylonjs/core/Misc/dataStorage.js"; 2 | import * as react from "react"; 3 | 4 | interface ILineWithFileButtonComponentProps { 5 | title: string; 6 | closed?: boolean; 7 | label: string; 8 | iconImage: any; 9 | onIconClick: (file: File) => void; 10 | accept: string; 11 | uploadName?: string; 12 | allowMultiple?: boolean; 13 | } 14 | 15 | export class LineWithFileButtonComponent extends react.Component< 16 | ILineWithFileButtonComponentProps, 17 | { isExpanded: boolean } 18 | > { 19 | private _uploadRef: react.RefObject; 20 | constructor(props: ILineWithFileButtonComponentProps) { 21 | super(props); 22 | 23 | const initialState = DataStorage.ReadBoolean(this.props.title, !this.props.closed); 24 | this.state = { isExpanded: initialState }; 25 | this._uploadRef = react.createRef(); 26 | } 27 | 28 | onChange(evt: any) { 29 | const files: File[] = evt.target.files; 30 | if (files) { 31 | for (const file of files) { 32 | this.props.onIconClick(file); 33 | } 34 | } 35 | evt.target.value = ""; 36 | } 37 | 38 | switchExpandedState(): void { 39 | const newState = !this.state.isExpanded; 40 | DataStorage.WriteBoolean(this.props.title, newState); 41 | this.setState({ isExpanded: newState }); 42 | } 43 | 44 | override render() { 45 | return ( 46 |
47 | {this.props.label} 48 |
49 | icon 50 |
51 |
52 |
65 |
66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.build.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "./src", 6 | "outDir": "./dist", 7 | "composite": true 8 | }, 9 | 10 | "include": ["./src/**/*.ts", "./src/**/*.tsx"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "../", 6 | "outDir": "./dist", 7 | "composite": true 8 | }, 9 | 10 | "include": ["./src/**/*.ts", "./src/**/*.tsx", "../core/**/*.ts", "../core/**/*.tsx", "../blocks/**/*.ts"], 11 | "exclude": ["**/node_modules", "**/dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/sfe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Smart Filter Editor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/sfe/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Babylon.js 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 | -------------------------------------------------------------------------------- /packages/sfe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@babylonjs/smart-filters-editor", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "The Smart Filters Editor tool is a web-based tool that allows you to create and edit Smart Filters for Babylon.js.", 6 | "keywords": [ 7 | "video", 8 | "composition", 9 | "3D", 10 | "2D", 11 | "javascript", 12 | "html5", 13 | "webgl", 14 | "webgl2", 15 | "webgpu", 16 | "babylon" 17 | ], 18 | "license": "MIT", 19 | "scripts": { 20 | "build": "webpack --env=prod", 21 | "clean": "rimraf .temp && rimraf www/scripts", 22 | "start": "concurrently \"npx webpack-dev-server --open\" \"npm run watch:shaders -w @babylonjs/smart-filters-blocks\"", 23 | "start:dev": "npx webpack-dev-server", 24 | "analyze": "webpack --profile --json > www/scripts/stats.json && npx webpack-bundle-analyzer www/scripts/stats.json" 25 | }, 26 | "devDependencies": { 27 | "@fortawesome/fontawesome-svg-core": "^6.1.0", 28 | "@fortawesome/free-solid-svg-icons": "^6.1.0", 29 | "@fortawesome/react-fontawesome": "^0.1.18", 30 | "@types/react": "^17.0.30", 31 | "@types/react-dom": "^17.0.10", 32 | "css-loader": "^7.1.0", 33 | "file-loader": "^6.2.0", 34 | "react": "^17.0.2", 35 | "react-dom": "^17.0.2", 36 | "sass": "^1.85.1", 37 | "sass-loader": "^16.0.5", 38 | "source-map-loader": "^3.0.0", 39 | "style-loader": "^3.3.0", 40 | "ts-loader": "^9.4.1", 41 | "url-loader": "^4.1.1", 42 | "webpack": "^5.94.0", 43 | "webpack-bundle-analyzer": "^4.7.0", 44 | "webpack-cli": "^6.0.1", 45 | "webpack-dev-server": "^5.2.1" 46 | } 47 | } -------------------------------------------------------------------------------- /packages/sfe/readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | ## Smart Filters Editor 4 | 5 | The package contains the visual editor application for Smart Filters and is deployed to https://sfe.babylonjs.com 6 | 7 | It can be run locally with `npm run start:sfe` at the root of the repo. 8 | 9 | See the full documentation at [doc.babylonjs.com](https://doc.babylonjs.com/features/featuresDeepDive/smartFilters/) 10 | -------------------------------------------------------------------------------- /packages/sfe/src/blockRegistration/addCustomBlockToBlockEditorRegistration.ts: -------------------------------------------------------------------------------- 1 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 2 | import { CustomBlocksNamespace, type BlockEditorRegistration } from "@babylonjs/smart-filters-editor-control"; 3 | 4 | /** 5 | * Adds a custom block to the block editor registration. 6 | * @param blockEditorRegistration - The block editor registration to add the custom block to 7 | * @param blockRegistration - The block registration to add 8 | */ 9 | export function addCustomBlockToBlockEditorRegistration( 10 | blockEditorRegistration: BlockEditorRegistration, 11 | blockRegistration: IBlockRegistration 12 | ) { 13 | const namespace = blockRegistration.namespace || CustomBlocksNamespace; 14 | if (!blockEditorRegistration.allBlocks[namespace]) { 15 | blockEditorRegistration.allBlocks[namespace] = []; 16 | } 17 | blockEditorRegistration.allBlocks[namespace]!.push(blockRegistration); 18 | } 19 | -------------------------------------------------------------------------------- /packages/sfe/src/blockRegistration/blockFactory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import type { SmartFilter, ISerializedBlockV1, BaseBlock, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 3 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 4 | import type { Nullable } from "@babylonjs/core/types"; 5 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 6 | import type { CustomBlockManager } from "../customBlockManager"; 7 | 8 | /** 9 | * Creates instances of blocks upon request 10 | * @param smartFilter - The SmartFilter the block will belong to 11 | * @param engine - The ThinEngine to use 12 | * @param serializedBlock - The serialized block to create 13 | * @param customBlockManager - The manager for custom blocks 14 | * @param smartFilterDeserializer - The deserializer to use 15 | * @param builtInBlockRegistrations - The built-in block registrations 16 | * @returns The created block or null if the block type is not recognized 17 | */ 18 | export async function blockFactory( 19 | smartFilter: SmartFilter, 20 | engine: ThinEngine, 21 | serializedBlock: ISerializedBlockV1, 22 | customBlockManager: CustomBlockManager, 23 | smartFilterDeserializer: SmartFilterDeserializer, 24 | builtInBlockRegistrations: IBlockRegistration[] 25 | ): Promise> { 26 | let newBlock: Nullable = null; 27 | 28 | // See if it's in our list of hardcoded blocks 29 | // If the serialized block doesn't specify a namespace, find the first match by block type 30 | const registration = builtInBlockRegistrations.find( 31 | (registration) => 32 | registration.blockType === serializedBlock.blockType && 33 | (!serializedBlock.namespace || serializedBlock.namespace === registration.namespace) 34 | ); 35 | if (registration && registration.factory) { 36 | newBlock = await registration.factory(smartFilter, engine, smartFilterDeserializer, serializedBlock); 37 | } 38 | if (!newBlock) { 39 | // Check if it's a custom block 40 | newBlock = await customBlockManager.createBlockFromBlockTypeAndNamespace( 41 | smartFilter, 42 | engine, 43 | serializedBlock.blockType, 44 | serializedBlock.namespace, 45 | serializedBlock.name, 46 | smartFilterDeserializer 47 | ); 48 | } 49 | 50 | return newBlock; 51 | } 52 | -------------------------------------------------------------------------------- /packages/sfe/src/blockRegistration/generateCustomBlockEditorRegistrations.ts: -------------------------------------------------------------------------------- 1 | import type { CustomBlockManager } from "../customBlockManager"; 2 | import type { SerializedBlockDefinition, SmartFilter, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 3 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 4 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 5 | import { CustomBlocksNamespace } from "@babylonjs/smart-filters-editor-control"; 6 | 7 | /** 8 | * Generates the block registrations for custom blocks. 9 | * @param customBlockManager - The custom block manager. 10 | * @param smartFilterDeserializer - The Smart Filter deserializer. 11 | * @param customBlockDefinitions - The custom block definitions. 12 | * @returns - The block registrations. 13 | */ 14 | export function generateCustomBlockRegistrations( 15 | customBlockManager: CustomBlockManager, 16 | smartFilterDeserializer: SmartFilterDeserializer, 17 | customBlockDefinitions: SerializedBlockDefinition[] 18 | ): IBlockRegistration[] { 19 | const blockRegistrations: IBlockRegistration[] = []; 20 | 21 | if (customBlockDefinitions.length > 0) { 22 | for (const customBlockDefinition of customBlockDefinitions) { 23 | blockRegistrations.push( 24 | createBlockRegistration(customBlockManager, customBlockDefinition, smartFilterDeserializer) 25 | ); 26 | } 27 | } 28 | 29 | return blockRegistrations; 30 | } 31 | 32 | /** 33 | * Creates a block registration for a custom block 34 | * @param customBlockManager - The custom block manager. 35 | * @param blockDefinition - The serialized block definition. 36 | * @param deserializer - The Smart Filter deserializer. 37 | * @returns - The block registration. 38 | */ 39 | export function createBlockRegistration( 40 | customBlockManager: CustomBlockManager, 41 | blockDefinition: SerializedBlockDefinition, 42 | deserializer: SmartFilterDeserializer 43 | ): IBlockRegistration { 44 | return { 45 | blockType: blockDefinition.blockType, 46 | namespace: blockDefinition.namespace || CustomBlocksNamespace, 47 | factory: (smartFilter: SmartFilter, engine: ThinEngine) => { 48 | return customBlockManager.createBlockFromBlockDefinition( 49 | smartFilter, 50 | engine, 51 | blockDefinition, 52 | null, // use default name 53 | deserializer 54 | ); 55 | }, 56 | tooltip: blockDefinition.blockType, 57 | isCustom: true, 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /packages/sfe/src/blockRegistration/removeCustomBlockFromBlockEditorRegistration.ts: -------------------------------------------------------------------------------- 1 | import type { IBlockRegistration } from "@babylonjs/smart-filters-blocks"; 2 | import type { BlockEditorRegistration } from "@babylonjs/smart-filters-editor-control"; 3 | 4 | /** 5 | * Removes a custom block from the block editor registration. 6 | * @param blockEditorRegistration - The block editor registration to remove the custom block from 7 | * @param allBlockRegistrations - The list of all block registrations 8 | * @param blockType - The type of the block to remove 9 | * @param namespace - The namespace of the block to remove 10 | */ 11 | export function removeCustomBlockFromBlockEditorRegistration( 12 | blockEditorRegistration: BlockEditorRegistration, 13 | allBlockRegistrations: IBlockRegistration[], 14 | blockType: string, 15 | namespace: string 16 | ) { 17 | const customBlockList = blockEditorRegistration.allBlocks[namespace]; 18 | if (customBlockList) { 19 | const index = customBlockList.findIndex((b) => b.blockType === blockType); 20 | if (index !== -1) { 21 | customBlockList.splice(index, 1); 22 | } 23 | } 24 | 25 | const index = allBlockRegistrations.findIndex((b) => b.blockType === blockType && b.namespace === namespace); 26 | if (index !== -1) { 27 | allBlockRegistrations.splice(index, 1); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/sfe/src/defaultSmartFilter.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionPointType, InputBlock, SmartFilter } from "@babylonjs/smart-filters"; 2 | import { PixelateBlock } from "@babylonjs/smart-filters-blocks"; 3 | 4 | /** 5 | * Creates a new instance of the default Smart Filter for the Smart Filter Editor 6 | * @returns The default Smart Filter 7 | */ 8 | export function createDefaultSmartFilter(): SmartFilter { 9 | const smartFilter = new SmartFilter("Default"); 10 | 11 | const pixelateBlock = new PixelateBlock(smartFilter, "Pixelate"); 12 | 13 | const textureInputBlock = new InputBlock(smartFilter, "Texture", ConnectionPointType.Texture, null); 14 | textureInputBlock.editorData = { 15 | url: "/assets/logo.png", 16 | urlTypeHint: "image", 17 | flipY: true, 18 | anisotropicFilteringLevel: null, 19 | forcedExtension: null, 20 | }; 21 | 22 | const intensityInputBlock = new InputBlock(smartFilter, "Intensity", ConnectionPointType.Float, 0.5); 23 | intensityInputBlock.editorData = { 24 | animationType: null, 25 | valueDeltaPerMs: null, 26 | min: 0.0, 27 | max: 1.0, 28 | }; 29 | 30 | textureInputBlock.output.connectTo(pixelateBlock.input); 31 | intensityInputBlock.output.connectTo(pixelateBlock.intensity); 32 | pixelateBlock.output.connectTo(smartFilter.output); 33 | 34 | return smartFilter; 35 | } 36 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The URL of the Smart Filter snippet server 3 | */ 4 | export const SnippetUrl = "https://snippet.babylonjs.com"; 5 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/copySmartFilter.ts: -------------------------------------------------------------------------------- 1 | import type { SmartFilter } from "@babylonjs/smart-filters"; 2 | import { serializeSmartFilter } from "./serializeSmartFilter.js"; 3 | 4 | /** 5 | * Copies the Smart Filter to the clipboard as a JSON file. 6 | * @param smartFilter - The Smart Filter to copy 7 | */ 8 | export async function copySmartFilter(smartFilter: SmartFilter): Promise { 9 | serializeSmartFilter(smartFilter).then((serializedSmartFilter) => { 10 | navigator.clipboard.writeText(serializedSmartFilter); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/downloadSmartFilter.ts: -------------------------------------------------------------------------------- 1 | import { StringTools } from "@babylonjs/shared-ui-components/stringTools.js"; 2 | import type { SmartFilter } from "@babylonjs/smart-filters"; 3 | import { serializeSmartFilter } from "./serializeSmartFilter.js"; 4 | 5 | /** 6 | * Initiates the download of a Smart Filter as a JSON file. 7 | * @param smartFilter - The Smart Filter to download 8 | */ 9 | export async function downloadSmartFilter(smartFilter: SmartFilter): Promise { 10 | const serializedSmartFilter = await serializeSmartFilter(smartFilter); 11 | 12 | StringTools.DownloadAsFile(document, serializedSmartFilter, smartFilter.name + ".json"); 13 | } 14 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/hashFunctions.ts: -------------------------------------------------------------------------------- 1 | // Constants 2 | const DELIMITER = "#"; 3 | const SAFARI_DELIMITER = "%23"; 4 | const DELIMITER_REGEX = new RegExp(`(?:${DELIMITER}|${SAFARI_DELIMITER})`); 5 | 6 | /** 7 | * Extracts the snippet info from the URL hash. 8 | * @returns The snippet token and version from the URL hash as an array. 9 | */ 10 | export function getSnippet() { 11 | const [snippetToken, version] = location.hash.substring(1).split(DELIMITER_REGEX); 12 | return [snippetToken, version]; 13 | } 14 | 15 | /** 16 | * Set the snippet info in the URL hash. 17 | * @param snippetToken - Snippet token to set 18 | * @param version - Version of the snippet to set 19 | * @param triggerHashChangeEvent - Whether to trigger a hash change event 20 | */ 21 | export function setSnippet(snippetToken: string, version: string | undefined, triggerHashChangeEvent: boolean = true) { 22 | let newHash = snippetToken; 23 | if (version && version != "0") { 24 | newHash += DELIMITER + version; 25 | } 26 | 27 | if (triggerHashChangeEvent) { 28 | location.hash = newHash; 29 | } else { 30 | history.replaceState(null, "", window.location.pathname + DELIMITER + newHash); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/loadSmartFilterFromFile.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import { ReadFile } from "@babylonjs/core/Misc/fileTools.js"; 3 | import type { SmartFilter, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 4 | 5 | /** 6 | * Loads a Smart Filter from the provided file. 7 | * @param smartFilterDeserializer - SmartFilterDeserializer to use 8 | * @param engine - ThinEngine to use 9 | * @param file - File object to load from 10 | * @returns Promise that resolves with the loaded Smart Filter 11 | */ 12 | export async function loadSmartFilterFromFile( 13 | smartFilterDeserializer: SmartFilterDeserializer, 14 | engine: ThinEngine, 15 | file: File 16 | ): Promise { 17 | const data = await new Promise((resolve, reject) => { 18 | ReadFile( 19 | file, 20 | (data) => resolve(data), 21 | undefined, 22 | false, 23 | (error) => reject(error) 24 | ); 25 | }); 26 | return smartFilterDeserializer.deserialize(engine, JSON.parse(data)); 27 | } 28 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/loadSmartFilterFromSnippetServer.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import type { SmartFilter, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 3 | import { SnippetUrl } from "./constants.js"; 4 | 5 | /** 6 | * Loads a SmartFilter from the provided file. 7 | * @param smartFilterDeserializer - SmartFilterDeserializer to use 8 | * @param engine - ThinEngine to use 9 | * @param snippetToken - Snippet token to load from 10 | * @param version - Optional version to load 11 | * @returns Promise that resolves with the loaded SmartFilter 12 | */ 13 | export async function loadSmartFilterFromSnippetServer( 14 | smartFilterDeserializer: SmartFilterDeserializer, 15 | engine: ThinEngine, 16 | snippetToken: string, 17 | version: string | undefined 18 | ): Promise { 19 | const response = await fetch(`${SnippetUrl}/${snippetToken}/${version || ""}`); 20 | 21 | if (!response.ok) { 22 | throw new Error(`Could not fetch snippet ${snippetToken}. Response was: ${response.status}`); 23 | } 24 | 25 | const data = await response.json(); 26 | const snippet = JSON.parse(data.jsonPayload); 27 | const serializedSmartFilter = JSON.parse(snippet.smartFilter); 28 | 29 | return smartFilterDeserializer.deserialize(engine, serializedSmartFilter); 30 | } 31 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/loadStartingSmartFilter.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import type { Observable } from "@babylonjs/core/Misc/observable"; 3 | import type { SmartFilter, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 4 | import { createDefaultSmartFilter } from "../defaultSmartFilter.js"; 5 | import type { Nullable } from "@babylonjs/core/types"; 6 | import { getSnippet, setSnippet } from "./hashFunctions.js"; 7 | import { loadSmartFilterFromSnippetServer } from "./loadSmartFilterFromSnippetServer.js"; 8 | import { LogEntry } from "@babylonjs/smart-filters-editor-control"; 9 | 10 | /** 11 | * Loads the starting Smart Filter for this session, consulting the URL first, and if 12 | * there isn't a snippet on the URL, loads a default Smart Filter. 13 | * 14 | * @param smartFilterDeserializer - SmartFilterDeserializer to use 15 | * @param engine - ThinEngine to use 16 | * @param onLogRequiredObservable - Observable that will be called when a log is required 17 | * @returns Promise that resolves with the loaded Smart Filter 18 | */ 19 | export async function loadStartingSmartFilter( 20 | smartFilterDeserializer: SmartFilterDeserializer, 21 | engine: ThinEngine, 22 | onLogRequiredObservable: Observable 23 | ): Promise { 24 | const smartFilterFromUrl = await loadFromUrl(smartFilterDeserializer, engine, onLogRequiredObservable); 25 | if (smartFilterFromUrl) { 26 | return smartFilterFromUrl; 27 | } 28 | 29 | onLogRequiredObservable.notifyObservers(new LogEntry("Loaded default Smart Filter", false)); 30 | return createDefaultSmartFilter(); 31 | } 32 | 33 | /** 34 | * Checks the hash for a snippet token and loads the Smart Filter if one is found. 35 | * Otherwise, loads the last in-repo Smart Filter or the default. 36 | * @param smartFilterDeserializer - SmartFilterDeserializer to use 37 | * @param engine - ThinEngine to use 38 | * @param onLogRequiredObservable - Observable that will be called when a log is required 39 | * @returns Promise that resolves with the loaded Smart Filter, or null if no Smart Filter was loaded 40 | */ 41 | export async function loadFromUrl( 42 | smartFilterDeserializer: SmartFilterDeserializer, 43 | engine: ThinEngine, 44 | onLogRequiredObservable: Observable 45 | ): Promise> { 46 | const [snippetToken, version] = getSnippet(); 47 | 48 | if (snippetToken) { 49 | try { 50 | // Reset hash with our formatting to keep it looking consistent 51 | setSnippet(snippetToken, version, false); 52 | const smartFilter = await loadSmartFilterFromSnippetServer( 53 | smartFilterDeserializer, 54 | engine, 55 | snippetToken, 56 | version 57 | ); 58 | onLogRequiredObservable.notifyObservers(new LogEntry("Loaded Smart Filter from unique URL", false)); 59 | return smartFilter; 60 | } catch (err) { 61 | onLogRequiredObservable.notifyObservers( 62 | new LogEntry(`Could not load Smart Filter from snippet server:\n${err}`, true) 63 | ); 64 | } 65 | } 66 | return null; 67 | } 68 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/pasteSmartFilter.ts: -------------------------------------------------------------------------------- 1 | import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine"; 2 | import type { SmartFilter, SmartFilterDeserializer } from "@babylonjs/smart-filters"; 3 | import type { Nullable } from "@babylonjs/core/types"; 4 | 5 | /** 6 | * Pastes a Smart Filter from clipboard. 7 | * @param smartFilterDeserializer - SmartFilterDeserializer to use 8 | * @param engine - ThinEngine to use 9 | * @returns Promise that resolves with the pasted Smart Filter 10 | */ 11 | export async function pasteSmartFilter( 12 | smartFilterDeserializer: SmartFilterDeserializer, 13 | engine: ThinEngine 14 | ): Promise> { 15 | try { 16 | const clipboardText = await navigator.clipboard.readText(); 17 | return smartFilterDeserializer.deserialize(engine, JSON.parse(clipboardText)); 18 | } catch (error) { 19 | throw new Error(`Failed to paste Smart Filter: ${error instanceof Error ? error.message : String(error)}`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/saveToSnipperServer.ts: -------------------------------------------------------------------------------- 1 | import { type SmartFilter } from "@babylonjs/smart-filters"; 2 | import { getSnippet, setSnippet } from "./hashFunctions.js"; 3 | import { SnippetUrl } from "./constants.js"; 4 | import { serializeSmartFilter } from "./serializeSmartFilter.js"; 5 | 6 | /** 7 | * Saves the provided Smart Filter to the snippet server 8 | * @param smartFilter - Smart Filter to save 9 | */ 10 | export async function saveToSnippetServer(smartFilter: SmartFilter): Promise { 11 | const smartFilterJson = await serializeSmartFilter(smartFilter); 12 | 13 | const dataToSend = { 14 | payload: JSON.stringify({ 15 | smartFilter: smartFilterJson, 16 | }), 17 | name: "", 18 | description: "", 19 | tags: "", 20 | }; 21 | 22 | const [snippetToken] = getSnippet(); 23 | 24 | const response = await fetch(`${SnippetUrl}/${snippetToken || ""}`, { 25 | method: "POST", 26 | headers: { 27 | // eslint-disable-next-line @typescript-eslint/naming-convention 28 | "Content-Type": "application/json", 29 | }, 30 | body: JSON.stringify(dataToSend), 31 | }); 32 | 33 | if (!response.ok) { 34 | throw new Error(`Could not save snippet: ${response.statusText}`); 35 | } 36 | 37 | const snippet = await response.json(); 38 | 39 | // Update the location in the address bar 40 | setSnippet(snippet.id, snippet.version, false); 41 | } 42 | -------------------------------------------------------------------------------- /packages/sfe/src/smartFilterLoadSave/serializeSmartFilter.ts: -------------------------------------------------------------------------------- 1 | import { SmartFilterSerializer, type SmartFilter } from "@babylonjs/smart-filters"; 2 | 3 | /** 4 | * Serializes the provided Smart Filter to a JSON string. 5 | * @param smartFilter - The Smart Filter to serialize 6 | * @returns The serialized Smart Filter 7 | */ 8 | export async function serializeSmartFilter(smartFilter: SmartFilter): Promise { 9 | const serializerModule = await import( 10 | /* webpackChunkName: "serializers" */ "@babylonjs/smart-filters-blocks/src/registration/blockSerializers.js" 11 | ); 12 | const serializer = new SmartFilterSerializer( 13 | serializerModule.blocksUsingDefaultSerialization, 14 | serializerModule.additionalBlockSerializers 15 | ); 16 | 17 | return JSON.stringify(serializer.serialize(smartFilter), null, 2); 18 | } 19 | -------------------------------------------------------------------------------- /packages/sfe/src/texturePresets.ts: -------------------------------------------------------------------------------- 1 | import type { TexturePreset } from "@babylonjs/smart-filters-editor-control"; 2 | 3 | /** 4 | * Texture presets are used to provide a list of assets that can be used 5 | * easily in the editor. 6 | * 7 | * You can either a base64 encoded image or a URL to an image. 8 | * 9 | * For a URL to an image, you can add the assets to packages/demo/www/assets then add them to this list. 10 | * 11 | */ 12 | export const texturePresets: TexturePreset[] = [ 13 | { 14 | name: "Babylon.js Logo", 15 | url: "/assets/logo.png", 16 | }, 17 | { 18 | name: "Kittens", 19 | url: "/assets/kittens.jpg", 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /packages/sfe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.common.json", 3 | 4 | "compilerOptions": { 5 | "rootDir": "../", 6 | "outDir": "./.temp", 7 | "composite": true, 8 | "resolveJsonModule": true 9 | }, 10 | 11 | "include": [ 12 | "./src/**/*.ts", 13 | "../core/**/*.ts", 14 | "../core/**/*.tsx", 15 | "../editor/**/*.ts", 16 | "../editor/**/*.tsx", 17 | "../blocks/**/*.ts" 18 | ], 19 | "exclude": ["**/node_modules", "**/.temp", "../browserExtension/**"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/sfe/www/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/sfe/www/assets/favicon.ico -------------------------------------------------------------------------------- /packages/sfe/www/assets/kittens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/sfe/www/assets/kittens.jpg -------------------------------------------------------------------------------- /packages/sfe/www/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BabylonJS/SmartFilters/14be287039ecb0b7bced1a2659507ed904d6a175/packages/sfe/www/assets/logo.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Babylon.js Smart Filters 2 | 3 | Smart Filters is a graph based system for applying GPU accelerated effects to videos or still images with a built-in optimizer. 4 | 5 | See the full documentation at [doc.babylonjs.com](https://doc.babylonjs.com/features/featuresDeepDive/smartFilters/) 6 | 7 | ## Structure 8 | 9 | These are the packages in this monorepo: 10 | 11 | ### core 12 | 13 | This is the main package for Smart Filters. It implements the notion of a `SmartFilter` which is a graph of blocks (all inheriting from `BaseBlock`) linked to each other through `ConnectionPoint`s. 14 | 15 | More info can be found in the dedicated [readme](./packages/core/readme.md). 16 | 17 | ### demo 18 | 19 | Entry point of the demo application integrating Smart Filters. This demo is also used as the dev inner loop for working on the Core. 20 | 21 | More info can be found in the dedicated [readme](./packages/demo/readme.md). 22 | 23 | ### editor 24 | 25 | This package contains the graphical editor control used by the demo and the Smart Filter Editor. 26 | 27 | More info can be found in the dedicated [readme](./packages/editor/readme.md). 28 | 29 | ### sfe 30 | 31 | This package contains the visual editor application deployed to https://sfe.babylonjs.com. 32 | 33 | More info can be found in the dedicated [readme](./packages/sfe/readme.md). 34 | 35 | ## Running Locally 36 | 37 | After cloning the repo, running locally during development is as simple as: 38 | 39 | ``` 40 | npm install 41 | npm start 42 | ``` 43 | 44 | The local Smart Filter demo will run at http://localhost:8080 45 | 46 | If you want to run the Smart Filter Editor application locally, start with: 47 | 48 | ``` 49 | npm install 50 | npm run start:sfe 51 | ``` 52 | 53 | The local build of the Smart Filter Editor will run at http://localhost:8081 54 | 55 | In both cases, the code is watched for changes and will automatically incrementally build and reload the webpage. 56 | 57 | For VSCode users, if you have installed the Chrome Debugging extension, you can start debugging within VSCode by using the appropriate launch menu. 58 | 59 | ## Additional Build Commands 60 | 61 | The monorepo is based on npm workspace and typescript composite projects. All the packages are trying to be of type "module" without side effects for simple consumption. You can find additional commands of the repo below. 62 | 63 | The following command will run all the test projects in the repo: 64 | 65 | ``` 66 | npm run test 67 | ``` 68 | 69 | A full production build is similar: 70 | 71 | ``` 72 | npm run build 73 | ``` 74 | 75 | Linting and formatting can be tested with: 76 | 77 | ``` 78 | npm run lint:check 79 | ``` 80 | -------------------------------------------------------------------------------- /tsconfig.common.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "moduleResolution": "node", 5 | "module": "esNext", 6 | "target": "es2018", 7 | "sourceMap": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "types": ["jest", "node"], 11 | "lib": ["dom", "es2015"], 12 | "jsx": "react-jsx", 13 | 14 | "removeComments": false, 15 | 16 | "strict": true, 17 | "alwaysStrict": true, 18 | 19 | "experimentalDecorators": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noImplicitOverride": true, 22 | "noPropertyAccessFromIndexSignature": true, 23 | "noUncheckedIndexedAccess": true, 24 | "noUnusedParameters": true, 25 | "noImplicitAny": true, 26 | "noImplicitReturns": true, 27 | "noImplicitThis": true, 28 | "noUnusedLocals": true, 29 | "verbatimModuleSyntax": true, 30 | "strictNullChecks": true, 31 | "strictFunctionTypes": true, 32 | "strictBindCallApply": true, 33 | "strictPropertyInitialization": true, 34 | 35 | "forceConsistentCasingInFileNames": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.common.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.common.build.json", 3 | "compilerOptions": { 4 | "baseUrl": "packages", 5 | 6 | "paths": { 7 | "@babylonjs/smart-filters": ["core/src"], 8 | "@babylonjs/smart-filters-blocks": ["blocks/src"], 9 | "@babylonjs/smart-filters-editor-control": ["editor/src"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.common.build.json", 3 | "references": [ 4 | { "path": "./packages/core/tsconfig.build.json" }, 5 | { "path": "./packages/blocks/tsconfig.build.json" }, 6 | { "path": "./packages/editor/tsconfig.build.json" } 7 | ], 8 | "files": [] 9 | } 10 | --------------------------------------------------------------------------------